1
0
Fork 0
mirror of https://github.com/bitcoin/bips.git synced 2025-03-06 20:54:21 +01:00
bitcoin-bips/bip-0158/gentestvectors.go

369 lines
11 KiB
Go
Raw Normal View History

// 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()
}