summaryrefslogtreecommitdiff
path: root/bip-0158
diff options
context:
space:
mode:
Diffstat (limited to 'bip-0158')
-rw-r--r--bip-0158/gentestvectors.go368
-rw-r--r--bip-0158/testnet-20.csv7
2 files changed, 375 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()
+}
diff --git a/bip-0158/testnet-20.csv b/bip-0158/testnet-20.csv
new file mode 100644
index 0000000..606e40d
--- /dev/null
+++ b/bip-0158/testnet-20.csv
@@ -0,0 +1,7 @@
+Block Height,Block Hash,Block,Previous Basic Header,Previous Ext Header,Basic Filter,Ext Filter,Basic Header,Ext Header
+0,000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943,0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,0285c7cdbe33a0,00,c0589c7f567cffaf7bc0c9f6ad61710b78d3c1afef5d65a2a08e8a753173aa54,753e0d1c28585269ab770b166ca2cd1b32f9bc918750547941ed4849d5a80ba8
+1,00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206,0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000,c0589c7f567cffaf7bc0c9f6ad61710b78d3c1afef5d65a2a08e8a753173aa54,753e0d1c28585269ab770b166ca2cd1b32f9bc918750547941ed4849d5a80ba8,026929d09bee00,,81e4f3e934488be62758f0b88037aa558262da3190ca018329997a319a0f8b5b,31b674ab635e074717329dabdb25d3cb0e14cb2526000cc2cedac7b5f2595110
+2,000000006c02c8ea6e4ff69651f7fcde348fb9d557a06e6957b65552002a7820,0100000006128e87be8b1b4dea47a7247d5528d2702c96826c7a648497e773b800000000e241352e3bec0a95a6217e10c3abb54adfa05abb12c126695595580fb92e222032e7494dffff001d00d235340101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0432e7494d010e062f503253482fffffffff0100f2052a010000002321038a7f6ef1c8ca0c588aa53fa860128077c9e6c11e6830f4d7ee4e763a56b7718fac00000000,81e4f3e934488be62758f0b88037aa558262da3190ca018329997a319a0f8b5b,31b674ab635e074717329dabdb25d3cb0e14cb2526000cc2cedac7b5f2595110,0278fc41168ec0,,ec48f9f8a625bd8adb2d2684867a05baafebf935553e0b78b386da98179dcf49,0dd53b407c3f242f1838e39e2fc0cfb89cdca27de07ec230568a08d1872f9e01
+3,000000008b896e272758da5297bcd98fdc6d97c9b765ecec401e286dc1fdbe10,0100000020782a005255b657696ea057d5b98f34defcf75196f64f6eeac8026c0000000041ba5afc532aae03151b8aa87b65e1594f97504a768e010c98c0add79216247186e7494dffff001d058dc2b60101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0486e7494d0151062f503253482fffffffff0100f2052a01000000232103f6d9ff4c12959445ca5549c811683bf9c88e637b222dd2e0311154c4c85cf423ac00000000,ec48f9f8a625bd8adb2d2684867a05baafebf935553e0b78b386da98179dcf49,0dd53b407c3f242f1838e39e2fc0cfb89cdca27de07ec230568a08d1872f9e01,022ce4b3256540,,060b7e1be150cc3cabd9e96b00af217132b387f31fd2bd9adfb0c7f5a09a3356,5b2a59bc476d52b45de1398ee34ed10d0cccd2a4b19ba502a456c7356b35be0d
+926485,000000000000015d6077a411a8f5cc95caf775ccf11c54e27df75ce58d187313,0000002060bbab0edbf3ef8a49608ee326f8fd75c473b7e3982095e2d100000000000000c30134f8c9b6d2470488d7a67a888f6fa12f8692e0c3411fbfb92f0f68f67eedae03ca57ef13021acc22dc4105010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff2f0315230e0004ae03ca57043e3d1e1d0c8796bf579aef0c0000000000122f4e696e6a61506f6f6c2f5345475749542fffffffff038427a112000000001976a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac0000000000000000266a24aa21a9ed5c748e121c0fe146d973a4ac26fa4a68b0549d46ee22d25f50a5e46fe1b377ee00000000000000002952534b424c4f434b3acd16772ad61a3c5f00287480b720f6035d5e54c9efc71be94bb5e3727f10909001200000000000000000000000000000000000000000000000000000000000000000000000000100000000010145310e878941a1b2bc2d33797ee4d89d95eaaf2e13488063a2aa9a74490f510a0100000023220020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1ffffffff01002d3101000000001976a9143ebc40e411ed3c76f86711507ab952300890397288ac0400473044022001dd489a5d4e2fbd8a3ade27177f6b49296ba7695c40dbbe650ea83f106415fd02200b23a0602d8ff1bdf79dee118205fc7e9b40672bf31563e5741feb53fb86388501483045022100f88f040e90cc5dc6c6189d04718376ac19ed996bf9e4a3c29c3718d90ffd27180220761711f16c9e3a44f71aab55cbc0634907a1fa8bb635d971a9a01d368727bea10169522103b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb2103dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba621033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be153ae00000000010000000120925534261de4dcebb1ed5ab1b62bfe7a3ef968fb111dc2c910adfebc6e3bdf010000006b483045022100f50198f5ae66211a4f485190abe4dc7accdabe3bc214ebc9ea7069b97097d46e0220316a70a03014887086e335fc1b48358d46cd6bdc9af3b57c109c94af76fc915101210316cff587a01a2736d5e12e53551b18d73780b83c3bfb4fcf209c869b11b6415effffffff0220a10700000000001976a91450333046115eaa0ac9e0216565f945070e44573988ac2e7cd01a000000001976a914c01a7ca16b47be50cbdbc60724f701d52d75156688ac00000000010000000203a25f58630d7a1ea52550365fd2156683f56daf6ca73a4b4bbd097e66516322010000006a47304402204efc3d70e4ca3049c2a425025edf22d5ca355f9ec899dbfbbeeb2268533a0f2b02204780d3739653035af4814ea52e1396d021953f948c29754edd0ee537364603dc012103f7a897e4dbecab2264b21917f90664ea8256189ea725d28740cf7ba5d85b5763ffffffff03a25f58630d7a1ea52550365fd2156683f56daf6ca73a4b4bbd097e66516322000000006a47304402202d96defdc5b4af71d6ba28c9a6042c2d5ee7bc6de565d4db84ef517445626e03022022da80320e9e489c8f41b74833dfb6a54a4eb5087cdb46eb663eef0b25caa526012103f7a897e4dbecab2264b21917f90664ea8256189ea725d28740cf7ba5d85b5763ffffffff0200e1f5050000000017a914b7e6f7ff8658b2d1fb107e3d7be7af4742e6b1b3876f88fc00000000001976a914913bcc2be49cb534c20474c4dee1e9c4c317e7eb88ac0000000001000000043ffd60d3818431c495b89be84afac205d5d1ed663009291c560758bbd0a66df5010000006b483045022100f344607de9df42049688dcae8ff1db34c0c7cd25ec05516e30d2bc8f12ac9b2f022060b648f6a21745ea6d9782e17bcc4277b5808326488a1f40d41e125879723d3a012103f7a897e4dbecab2264b21917f90664ea8256189ea725d28740cf7ba5d85b5763ffffffffa5379401cce30f84731ef1ba65ce27edf2cc7ce57704507ebe8714aa16a96b92010000006a473044022020c37a63bf4d7f564c2192528709b6a38ab8271bd96898c6c2e335e5208661580220435c6f1ad4d9305d2c0a818b2feb5e45d443f2f162c0f61953a14d097fd07064012103f7a897e4dbecab2264b21917f90664ea8256189ea725d28740cf7ba5d85b5763ffffffff70e731e193235ff12c3184510895731a099112ffca4b00246c60003c40f843ce000000006a473044022053760f74c29a879e30a17b5f03a5bb057a5751a39f86fa6ecdedc36a1b7db04c022041d41c9b95f00d2d10a0373322a9025dba66c942196bc9d8adeb0e12d3024728012103f7a897e4dbecab2264b21917f90664ea8256189ea725d28740cf7ba5d85b5763ffffffff66b7a71b3e50379c8e85fc18fe3f1a408fc985f257036c34702ba205cef09f6f000000006a4730440220499bf9e2db3db6e930228d0661395f65431acae466634d098612fd80b08459ee022040e069fc9e3c60009f521cef54c38aadbd1251aee37940e6018aadb10f194d6a012103f7a897e4dbecab2264b21917f90664ea8256189ea725d28740cf7ba5d85b5763ffffffff0200e1f5050000000017a9148fc37ad460fdfbd2b44fe446f6e3071a4f64faa6878f447f0b000000001976a914913bcc2be49cb534c20474c4dee1e9c4c317e7eb88ac00000000,5c169b91332e661a4ebf684d6dffcf64aa139e1e736096ce462609b2e0783c55,5f3cd111e10ed28b7c2bc138fe4bc62e5df1cdc292c010b78c84e5111a5daaa6,16040c63f7ddea293f2d9c13690c0ba7b2910228c38b0fe542ce525021e49b598ada05f83bb9c37c711a02b1850265991c34c4fea6261d22a4b84596c0,0e6651beff00ee7a3be424a90e98450727b304558434c8d53781d469131ad21d399376c151ca28,c8f83ffbc9781c2bd4b7e3c055e888b00d3e2fea14e93b3c4f3adee86b063374,61f8ee615258981089be9f98337f53f44b14cc1532aa469a348fdee547117b80
+987876,0000000000000c00901f2049055e2a437c819d79a3d54fd63e6af796cd7b8a79,000000202694f74969fdb542090e95a56bc8aa2d646e27033850e32f1c5f000000000000f7e53676b3f12d5beb524ed617f2d25f5a93b5f4f52c1ba2678260d72712f8dd0a6dfe5740257e1a4b1768960101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1603e4120ff9c30a1c216900002f424d4920546573742fffffff0001205fa012000000001e76a914c486de584a735ec2f22da7cd9681614681f92173d83d0aa68688ac00000000,37bf9e681888b3cd204ca4e0c995aad68cd0ecb86bdf19dd0fa2e72dbabcda28,c4c5051dd741c11840ef3f11fb4f372a16bb5aac1dc66576e89e8e6835d667e0,021016dc7a6a20,,156e9bf3ec5be367f0a829858e9ee182cc3a6531bedced491a52fcfed841c6cb,cab50aab93410cb150fd761f2067a909d35c8a9c0114578efd9590e2d381ee02