summaryrefslogtreecommitdiff
path: root/bip-0158/gentestvectors.go
diff options
context:
space:
mode:
Diffstat (limited to 'bip-0158/gentestvectors.go')
-rw-r--r--bip-0158/gentestvectors.go368
1 files changed, 368 insertions, 0 deletions
diff --git a/bip-0158/gentestvectors.go b/bip-0158/gentestvectors.go
new file mode 100644
index 0000000..f6560cb
--- /dev/null
+++ b/bip-0158/gentestvectors.go
@@ -0,0 +1,368 @@
+// This program connects to your local btcd and generates test vectors for
+// 5 blocks and collision space sizes of 1-32 bits. Change the RPC cert path
+// and credentials to run on your system. The program assumes you're running
+// a btcd with cfilter support, which mainline btcd doesn't have; in order to
+// circumvent this assumption, comment out the if block that checks for
+// filter size of DefaultP.
+
+package main
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path"
+
+ "github.com/roasbeef/btcd/chaincfg"
+ "github.com/roasbeef/btcd/chaincfg/chainhash"
+ "github.com/roasbeef/btcd/rpcclient"
+ "github.com/roasbeef/btcd/wire"
+ "github.com/roasbeef/btcutil/gcs"
+ "github.com/roasbeef/btcutil/gcs/builder"
+)
+
+func main() {
+ err := os.Mkdir("gcstestvectors", os.ModeDir|0755)
+ if err != nil { // Don't overwrite existing output if any
+ fmt.Println("Couldn't create directory: ", err)
+ return
+ }
+ files := make([]*os.File, 33)
+ prevBasicHeaders := make([]chainhash.Hash, 33)
+ prevExtHeaders := make([]chainhash.Hash, 33)
+ for i := 1; i <= 32; i++ { // Min 1 bit of collision space, max 32
+ var blockBuf bytes.Buffer
+ fName := fmt.Sprintf("gcstestvectors/testnet-%02d.csv", i)
+ file, err := os.Create(fName)
+ if err != nil {
+ fmt.Println("Error creating CSV file: ", err.Error())
+ return
+ }
+ _, err = file.WriteString("Block Height,Block Hash,Block,Previous Basic Header,Previous Ext Header,Basic Filter,Ext Filter,Basic Header,Ext Header\n")
+ if err != nil {
+ fmt.Println("Error writing to CSV file: ", err.Error())
+ return
+ }
+ files[i] = file
+ basicFilter, err := buildBasicFilter(
+ chaincfg.TestNet3Params.GenesisBlock, uint8(i))
+ if err != nil {
+ fmt.Println("Error generating basic filter: ", err.Error())
+ return
+ }
+ prevBasicHeaders[i], err = builder.MakeHeaderForFilter(basicFilter,
+ chaincfg.TestNet3Params.GenesisBlock.Header.PrevBlock)
+ if err != nil {
+ fmt.Println("Error generating header for filter: ", err.Error())
+ return
+ }
+ if basicFilter == nil {
+ basicFilter = &gcs.Filter{}
+ }
+ extFilter, err := buildExtFilter(
+ chaincfg.TestNet3Params.GenesisBlock, uint8(i))
+ if err != nil {
+ fmt.Println("Error generating ext filter: ", err.Error())
+ return
+ }
+ prevExtHeaders[i], err = builder.MakeHeaderForFilter(extFilter,
+ chaincfg.TestNet3Params.GenesisBlock.Header.PrevBlock)
+ if err != nil {
+ fmt.Println("Error generating header for filter: ", err.Error())
+ return
+ }
+ if extFilter == nil {
+ extFilter = &gcs.Filter{}
+ }
+ err = chaincfg.TestNet3Params.GenesisBlock.Serialize(&blockBuf)
+ if err != nil {
+ fmt.Println("Error serializing block to buffer: ", err.Error())
+ return
+ }
+ bfBytes, err := basicFilter.NBytes()
+ if err != nil {
+ fmt.Println("Couldn't get NBytes(): ", err)
+ return
+ }
+ efBytes, err := extFilter.NBytes()
+ if err != nil {
+ fmt.Println("Couldn't get NBytes(): ", err)
+ return
+ }
+ err = writeCSVRow(
+ file,
+ 0, // Height
+ *chaincfg.TestNet3Params.GenesisHash,
+ blockBuf.Bytes(),
+ chaincfg.TestNet3Params.GenesisBlock.Header.PrevBlock,
+ chaincfg.TestNet3Params.GenesisBlock.Header.PrevBlock,
+ bfBytes,
+ efBytes,
+ prevBasicHeaders[i],
+ prevExtHeaders[i],
+ )
+ if err != nil {
+ fmt.Println("Error writing to CSV file: ", err.Error())
+ return
+ }
+ }
+ cert, err := ioutil.ReadFile(
+ path.Join(os.Getenv("HOME"), "/.btcd/rpc.cert"))
+ if err != nil {
+ fmt.Println("Couldn't read RPC cert: ", err.Error())
+ return
+ }
+ conf := rpcclient.ConnConfig{
+ Host: "127.0.0.1:18334",
+ Endpoint: "ws",
+ User: "kek",
+ Pass: "kek",
+ Certificates: cert,
+ }
+ client, err := rpcclient.New(&conf, nil)
+ if err != nil {
+ fmt.Println("Couldn't create a new client: ", err.Error())
+ return
+ }
+ for height := 1; height < 988000; height++ {
+ fmt.Printf("Height: %d\n", height)
+ blockHash, err := client.GetBlockHash(int64(height))
+ if err != nil {
+ fmt.Println("Couldn't get block hash: ", err.Error())
+ return
+ }
+ block, err := client.GetBlock(blockHash)
+ if err != nil {
+ fmt.Println("Couldn't get block hash: ", err.Error())
+ return
+ }
+ var blockBuf bytes.Buffer
+ err = block.Serialize(&blockBuf)
+ if err != nil {
+ fmt.Println("Error serializing block to buffer: ", err.Error())
+ return
+ }
+ blockBytes := blockBuf.Bytes()
+ for i := 1; i <= 32; i++ {
+ basicFilter, err := buildBasicFilter(block, uint8(i))
+ if err != nil {
+ fmt.Println("Error generating basic filter: ", err.Error())
+ return
+ }
+ basicHeader, err := builder.MakeHeaderForFilter(basicFilter,
+ prevBasicHeaders[i])
+ if err != nil {
+ fmt.Println("Error generating header for filter: ", err.Error())
+ return
+ }
+ if basicFilter == nil {
+ basicFilter = &gcs.Filter{}
+ }
+ extFilter, err := buildExtFilter(block, uint8(i))
+ if err != nil {
+ fmt.Println("Error generating ext filter: ", err.Error())
+ return
+ }
+ extHeader, err := builder.MakeHeaderForFilter(extFilter,
+ prevExtHeaders[i])
+ if err != nil {
+ fmt.Println("Error generating header for filter: ", err.Error())
+ return
+ }
+ if extFilter == nil {
+ extFilter = &gcs.Filter{}
+ }
+ if i == builder.DefaultP { // This is the default filter size so we can check against the server's info
+ filter, err := client.GetCFilter(blockHash, wire.GCSFilterRegular)
+ if err != nil {
+ fmt.Println("Error getting basic filter: ", err.Error())
+ return
+ }
+ nBytes, err := basicFilter.NBytes()
+ if err != nil {
+ fmt.Println("Couldn't get NBytes(): ", err)
+ return
+ }
+ if !bytes.Equal(filter.Data, nBytes) {
+ // Don't error on empty filters
+ fmt.Println("Basic filter doesn't match!\n", filter.Data, "\n", nBytes)
+ return
+ }
+ filter, err = client.GetCFilter(blockHash, wire.GCSFilterExtended)
+ if err != nil {
+ fmt.Println("Error getting extended filter: ", err.Error())
+ return
+ }
+ nBytes, err = extFilter.NBytes()
+ if err != nil {
+ fmt.Println("Couldn't get NBytes(): ", err)
+ return
+ }
+ if !bytes.Equal(filter.Data, nBytes) {
+ fmt.Println("Extended filter doesn't match!")
+ return
+ }
+ header, err := client.GetCFilterHeader(blockHash, wire.GCSFilterRegular)
+ if err != nil {
+ fmt.Println("Error getting basic header: ", err.Error())
+ return
+ }
+ if !bytes.Equal(header.PrevFilterHeader[:], basicHeader[:]) {
+ fmt.Println("Basic header doesn't match!")
+ return
+ }
+ header, err = client.GetCFilterHeader(blockHash, wire.GCSFilterExtended)
+ if err != nil {
+ fmt.Println("Error getting extended header: ", err.Error())
+ return
+ }
+ if !bytes.Equal(header.PrevFilterHeader[:], extHeader[:]) {
+ fmt.Println("Extended header doesn't match!")
+ return
+ }
+ fmt.Println("Verified against server")
+ }
+ switch height {
+ case 1, 2, 3, 926485, 987876: // Blocks for test cases
+ var bfBytes []byte
+ var efBytes []byte
+ if basicFilter.N() > 0 {
+ bfBytes, err = basicFilter.NBytes()
+ if err != nil {
+ fmt.Println("Couldn't get NBytes(): ", err)
+ return
+ }
+ }
+ if extFilter.N() > 0 { // Exclude special case for block 987876
+ efBytes, err = extFilter.NBytes()
+ if err != nil {
+ fmt.Println("Couldn't get NBytes(): ", err)
+ return
+ }
+ }
+ writeCSVRow(
+ files[i],
+ height,
+ *blockHash,
+ blockBytes,
+ prevBasicHeaders[i],
+ prevExtHeaders[i],
+ bfBytes,
+ efBytes,
+ basicHeader,
+ extHeader)
+ }
+ prevBasicHeaders[i] = basicHeader
+ prevExtHeaders[i] = extHeader
+ }
+ }
+}
+
+// writeCSVRow writes a test vector to a CSV file.
+func writeCSVRow(file *os.File, height int, blockHash chainhash.Hash,
+ blockBytes []byte, prevBasicHeader, prevExtHeader chainhash.Hash,
+ basicFilter, extFilter []byte, basicHeader, extHeader chainhash.Hash) error {
+ row := fmt.Sprintf("%d,%s,%s,%s,%s,%s,%s,%s,%s\n",
+ height,
+ blockHash.String(),
+ hex.EncodeToString(blockBytes),
+ prevBasicHeader.String(),
+ prevExtHeader.String(),
+ hex.EncodeToString(basicFilter),
+ hex.EncodeToString(extFilter),
+ basicHeader.String(),
+ extHeader.String(),
+ )
+ _, err := file.WriteString(row)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// buildBasicFilter builds a basic GCS filter from a block. A basic GCS filter
+// will contain all the previous outpoints spent within a block, as well as the
+// data pushes within all the outputs created within a block. p is specified as
+// an argument in order to create test vectors with various values for p.
+func buildBasicFilter(block *wire.MsgBlock, p uint8) (*gcs.Filter, error) {
+ blockHash := block.BlockHash()
+ b := builder.WithKeyHashP(&blockHash, p)
+
+ // If the filter had an issue with the specified key, then we force it
+ // to bubble up here by calling the Key() function.
+ _, err := b.Key()
+ if err != nil {
+ return nil, err
+ }
+
+ // In order to build a basic filter, we'll range over the entire block,
+ // adding the outpoint data as well as the data pushes within the
+ // pkScript.
+ for i, tx := range block.Transactions {
+ // First we'll compute the bash of the transaction and add that
+ // directly to the filter.
+ txHash := tx.TxHash()
+ b.AddHash(&txHash)
+
+ // Skip the inputs for the coinbase transaction
+ if i != 0 {
+ // Each each txin, we'll add a serialized version of
+ // the txid:index to the filters data slices.
+ for _, txIn := range tx.TxIn {
+ b.AddOutPoint(txIn.PreviousOutPoint)
+ }
+ }
+
+ // For each output in a transaction, we'll add each of the
+ // individual data pushes within the script.
+ for _, txOut := range tx.TxOut {
+ b.AddEntry(txOut.PkScript)
+ }
+ }
+
+ return b.Build()
+}
+
+// buildExtFilter builds an extended GCS filter from a block. An extended
+// filter supplements a regular basic filter by include all the _witness_ data
+// found within a block. This includes all the data pushes within any signature
+// scripts as well as each element of an input's witness stack. Additionally,
+// the _hashes_ of each transaction are also inserted into the filter. p is
+// specified as an argument in order to create test vectors with various values
+// for p.
+func buildExtFilter(block *wire.MsgBlock, p uint8) (*gcs.Filter, error) {
+ blockHash := block.BlockHash()
+ b := builder.WithKeyHashP(&blockHash, p)
+
+ // If the filter had an issue with the specified key, then we force it
+ // to bubble up here by calling the Key() function.
+ _, err := b.Key()
+ if err != nil {
+ return nil, err
+ }
+
+ // In order to build an extended filter, we add the hash of each
+ // transaction as well as each piece of witness data included in both
+ // the sigScript and the witness stack of an input.
+ for i, tx := range block.Transactions {
+ // Skip the inputs for the coinbase transaction
+ if i != 0 {
+ // Next, for each input, we'll add the sigScript (if
+ // it's present), and also the witness stack (if it's
+ // present)
+ for _, txIn := range tx.TxIn {
+ if txIn.SignatureScript != nil {
+ b.AddScript(txIn.SignatureScript)
+ }
+
+ if len(txIn.Witness) != 0 {
+ b.AddWitness(txIn.Witness)
+ }
+ }
+ }
+ }
+
+ return b.Build()
+}