diff options
Diffstat (limited to 'bip-0158/gentestvectors.go')
-rw-r--r-- | bip-0158/gentestvectors.go | 368 |
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() +} |