From ddf3781a60ecda546e8cdb4974a2556e0266b182 Mon Sep 17 00:00:00 2001 From: Larry Ruane Date: Thu, 7 Oct 2021 14:57:26 -0600 Subject: [PATCH] parse V5 (nu5) transactions TODO: - store, instead of just skip over, nu5 transaction fields - add relevant nu5 fields to CompactBlock - restore disabled V4 unit tests - add V5 test vectors to unit tests The reason most of the V4 transaction and block unit tests are removed is that they used V3 transactions, which lightwalletd never sees in production, since lightwalletd starts at Sapling activation (which has V4 transactions). So these tests were always wrong, in a way. This commit simplifies the parsing code by removing support for V3 (since it was never needed). The tests need to be updated to V4, but we'll do that in a later PR. --- parser/block_header_test.go | 2 +- parser/block_test.go | 196 -------- parser/transaction.go | 392 ++++++++++------ parser/transaction_test.go | 879 ------------------------------------ testdata/blocks | 8 +- 5 files changed, 254 insertions(+), 1223 deletions(-) diff --git a/parser/block_header_test.go b/parser/block_header_test.go index 0909da30..e0dbfc69 100644 --- a/parser/block_header_test.go +++ b/parser/block_header_test.go @@ -134,7 +134,7 @@ func TestBlockHeader(t *testing.T) { } // This is not necessarily true for anything but our current test cases. - for _, b := range hash[:4] { + for _, b := range hash[:1] { if b != 0 { t.Errorf("Hash lacked leading zeros: %x", hash) } diff --git a/parser/block_test.go b/parser/block_test.go index 99f63387..affb8c68 100644 --- a/parser/block_test.go +++ b/parser/block_test.go @@ -4,13 +4,11 @@ package parser import ( - "bufio" "bytes" "encoding/hex" "encoding/json" "fmt" "io/ioutil" - "os" "testing" "github.com/pkg/errors" @@ -18,200 +16,6 @@ import ( protobuf "github.com/golang/protobuf/proto" ) -func TestBlockParser(t *testing.T) { - // These (valid on testnet) correspond to the transactions in testdata/blocks; - // for each block, the hashes for the tx within that block. - var txhashes = [][]string{ - { - "81096ff101a4f01d25ffd34a446bee4368bd46c233a59ac0faf101e1861c6b22", - }, { - "921dc41bef3a0d887c615abac60a29979efc8b4bbd3d887caeb6bb93501bde8e", - }, { - "d8e4c336ffa69dacaa4e0b4eaf8e3ae46897f1930a573c10b53837a03318c980", - "4d5ccbfc6984680c481ff5ce145b8a93d59dfea90c150dfa45c938ab076ee5b2", - }, { - "df2b03619d441ce3d347e9278d87618e975079d0e235dfb3b3d8271510f707aa", - "8d2593edfc328fa637b4ac91c7d569ee922bb9a6fda7cea230e92deb3ae4b634", - }, - } - testBlocks, err := os.Open("../testdata/blocks") - if err != nil { - t.Fatal(err) - } - defer testBlocks.Close() - - scan := bufio.NewScanner(testBlocks) - for blockindex := 0; scan.Scan(); blockindex++ { - blockDataHex := scan.Text() - blockData, err := hex.DecodeString(blockDataHex) - if err != nil { - t.Error(err) - continue - } - - // This is just a sanity check of the test: - if int(blockData[1487]) != len(txhashes[blockindex]) { - t.Error("wrong number of transactions, test broken?") - } - - // Make a copy of just the transactions alone, which, - // for these blocks, start just beyond the header and - // the one-byte nTx value, which is offset 1488. - transactions := make([]byte, len(blockData[1488:])) - copy(transactions, blockData[1488:]) - - // Each iteration of this loop appends the block's original - // transactions, so we build an ever-larger block. The loop - // limit is arbitrary, but make sure we get into double-digit - // transaction counts (compact integer). - for i := 0; i < 264; i++ { - b := blockData - block := NewBlock() - b, err = block.ParseFromSlice(b) - if err != nil { - t.Error(errors.Wrap(err, fmt.Sprintf("parsing block %d", i))) - continue - } - if len(b) > 0 { - t.Error("Extra data remaining") - } - - // Some basic sanity checks - if block.hdr.Version != 4 { - t.Error("Read wrong version in a test block.") - break - } - if block.GetVersion() != 4 { - t.Error("Read wrong version in a test block.") - break - } - if block.GetTxCount() < 1 { - t.Error("No transactions in block") - break - } - if len(block.Transactions()) != block.GetTxCount() { - t.Error("Number of transactions mismatch") - break - } - if block.GetTxCount() != len(txhashes[blockindex])*(i+1) { - t.Error("Unexpected number of transactions") - } - if block.HasSaplingTransactions() { - t.Error("Unexpected Sapling tx") - break - } - for txindex, tx := range block.Transactions() { - if tx.HasSaplingElements() { - t.Error("Unexpected Sapling tx") - break - } - expectedHash := txhashes[blockindex][txindex%len(txhashes[blockindex])] - if hex.EncodeToString(tx.GetDisplayHash()) != expectedHash { - t.Error("incorrect tx hash") - } - } - // Keep appending the original transactions, which is unrealistic - // because the coinbase is being replicated, but it works; first do - // some surgery to the transaction count (see DarksideApplyStaged()). - for j := 0; j < len(txhashes[blockindex]); j++ { - nTxFirstByte := blockData[1487] - switch { - case nTxFirstByte < 252: - blockData[1487]++ - case nTxFirstByte == 252: - // incrementing to 253, requires "253" followed by 2-byte length, - // extend the block by two bytes, shift existing transaction bytes - blockData = append(blockData, 0, 0) - copy(blockData[1490:], blockData[1488:len(blockData)-2]) - blockData[1487] = 253 - blockData[1488] = 253 - blockData[1489] = 0 - case nTxFirstByte == 253: - blockData[1488]++ - if blockData[1488] == 0 { - // wrapped around - blockData[1489]++ - } - } - } - blockData = append(blockData, transactions...) - } - } -} - -func TestBlockParserFail(t *testing.T) { - testBlocks, err := os.Open("../testdata/badblocks") - if err != nil { - t.Fatal(err) - } - defer testBlocks.Close() - - scan := bufio.NewScanner(testBlocks) - - // the first "block" contains an illegal hex character - { - scan.Scan() - blockDataHex := scan.Text() - _, err := hex.DecodeString(blockDataHex) - if err == nil { - t.Error("unexpected success parsing illegal hex bad block") - } - } - for i := 0; scan.Scan(); i++ { - blockDataHex := scan.Text() - blockData, err := hex.DecodeString(blockDataHex) - if err != nil { - t.Error(err) - continue - } - - block := NewBlock() - blockData, err = block.ParseFromSlice(blockData) - if err == nil { - t.Error("unexpected success parsing bad block") - } - } -} - -// Checks on the first 20 blocks from mainnet genesis. -func TestGenesisBlockParser(t *testing.T) { - blockFile, err := os.Open("../testdata/mainnet_genesis") - if err != nil { - t.Fatal(err) - } - defer blockFile.Close() - - scan := bufio.NewScanner(blockFile) - for i := 0; scan.Scan(); i++ { - blockDataHex := scan.Text() - blockData, err := hex.DecodeString(blockDataHex) - if err != nil { - t.Error(err) - continue - } - - block := NewBlock() - blockData, err = block.ParseFromSlice(blockData) - if err != nil { - t.Error(err) - continue - } - if len(blockData) > 0 { - t.Error("Extra data remaining") - } - - // Some basic sanity checks - if block.hdr.Version != 4 { - t.Error("Read wrong version in genesis block.") - break - } - - if block.GetHeight() != i { - t.Errorf("Got wrong height for block %d: %d", i, block.GetHeight()) - } - } -} - func TestCompactBlocks(t *testing.T) { type compactTest struct { BlockHeight int `json:"block"` diff --git a/parser/transaction.go b/parser/transaction.go index add99d15..a93b34e6 100644 --- a/parser/transaction.go +++ b/parser/transaction.go @@ -7,6 +7,7 @@ package parser import ( "crypto/sha256" + "fmt" "github.com/pkg/errors" "github.com/zcash/lightwalletd/parser/internal/bytestring" @@ -14,20 +15,21 @@ import ( ) type rawTransaction struct { - fOverwintered bool - version uint32 - nVersionGroupID uint32 - transparentInputs []*txIn - transparentOutputs []*txOut - nLockTime uint32 - nExpiryHeight uint32 - valueBalance int64 - shieldedSpends []*spend - shieldedOutputs []*output - joinSplits []*joinSplit - joinSplitPubKey []byte - joinSplitSig []byte - bindingSig []byte + fOverwintered bool + version uint32 + nVersionGroupID uint32 + consensusBranchID uint32 + transparentInputs []*txIn + transparentOutputs []*txOut + nLockTime uint32 + nExpiryHeight uint32 + valueBalanceSapling int64 + shieldedSpends []*spend + shieldedOutputs []*output + joinSplits []*joinSplit + joinSplitPubKey []byte + joinSplitSig []byte + bindingSigSapling []byte } // Txin format as described in https://en.bitcoin.it/wiki/Transaction @@ -90,8 +92,45 @@ func (tx *txOut) ParseFromSlice(data []byte) ([]byte, error) { return []byte(s), nil } +// parse the transparent parts of the transaction +func (tx *Transaction) ParseTransparent(data []byte) ([]byte, error) { + s := bytestring.String(data) + var txInCount int + if !s.ReadCompactSize(&txInCount) { + return nil, errors.New("could not read tx_in_count") + } + var err error + // TODO: Duplicate/otherwise-too-many transactions are a possible DoS + // TODO: vector. At the moment we're assuming trusted input. + // See https://nvd.nist.gov/vuln/detail/CVE-2018-17144 for an example. + tx.transparentInputs = make([]*txIn, txInCount) + for i := 0; i < txInCount; i++ { + ti := &txIn{} + s, err = ti.ParseFromSlice([]byte(s)) + if err != nil { + return nil, errors.Wrap(err, "while parsing transparent input") + } + tx.transparentInputs[i] = ti + } + + var txOutCount int + if !s.ReadCompactSize(&txOutCount) { + return nil, errors.New("could not read tx_out_count") + } + tx.transparentOutputs = make([]*txOut, txOutCount) + for i := 0; i < txOutCount; i++ { + to := &txOut{} + s, err = to.ParseFromSlice([]byte(s)) + if err != nil { + return nil, errors.Wrap(err, "while parsing transparent output") + } + tx.transparentOutputs[i] = to + } + return []byte(s), nil +} + // spend is a Sapling Spend Description as described in 7.3 of the Zcash -// protocol spec. Total size is 384 bytes. +// protocol specification. type spend struct { cv []byte // 32 anchor []byte // 32 @@ -101,14 +140,14 @@ type spend struct { spendAuthSig []byte // 64 } -func (p *spend) ParseFromSlice(data []byte) ([]byte, error) { +func (p *spend) ParseFromSlice(data []byte, version uint32) ([]byte, error) { s := bytestring.String(data) if !s.ReadBytes(&p.cv, 32) { return nil, errors.New("could not read cv") } - if !s.ReadBytes(&p.anchor, 32) { + if version <= 4 && !s.ReadBytes(&p.anchor, 32) { return nil, errors.New("could not read anchor") } @@ -120,11 +159,11 @@ func (p *spend) ParseFromSlice(data []byte) ([]byte, error) { return nil, errors.New("could not read rk") } - if !s.ReadBytes(&p.zkproof, 192) { + if version <= 4 && !s.ReadBytes(&p.zkproof, 192) { return nil, errors.New("could not read zkproof") } - if !s.ReadBytes(&p.spendAuthSig, 64) { + if version <= 4 && !s.ReadBytes(&p.spendAuthSig, 64) { return nil, errors.New("could not read spendAuthSig") } @@ -138,7 +177,7 @@ func (p *spend) ToCompact() *walletrpc.CompactSpend { } // output is a Sapling Output Description as described in section 7.4 of the -// Zcash protocol spec. Total size is 948. +// Zcash protocol spec. type output struct { cv []byte // 32 cmu []byte // 32 @@ -148,7 +187,7 @@ type output struct { zkproof []byte // 192 } -func (p *output) ParseFromSlice(data []byte) ([]byte, error) { +func (p *output) ParseFromSlice(data []byte, version uint32) ([]byte, error) { s := bytestring.String(data) if !s.ReadBytes(&p.cv, 32) { @@ -171,7 +210,7 @@ func (p *output) ParseFromSlice(data []byte) ([]byte, error) { return nil, errors.New("could not read outCiphertext") } - if !s.ReadBytes(&p.zkproof, 192) { + if version <= 4 && !s.ReadBytes(&p.zkproof, 192) { return nil, errors.New("could not read zkproof") } @@ -188,7 +227,7 @@ func (p *output) ToCompact() *walletrpc.CompactOutput { // joinSplit is a JoinSplit description as described in 7.2 of the Zcash // protocol spec. Its exact contents differ by transaction version and network -// upgrade level. +// upgrade level. Only version 4 is supported, no need for proofPHGR13. type joinSplit struct { vpubOld uint64 vpubNew uint64 @@ -198,12 +237,8 @@ type joinSplit struct { ephemeralKey []byte // 32 randomSeed []byte // 32 vmacs [2][]byte // 64 [N_old][32]byte - proofPHGR13 []byte // 296 - proofGroth16 []byte // 192 + proofGroth16 []byte // 192 (version 4 only) encCiphertexts [2][]byte // 1202 [N_new][601]byte - - // not actually in the format, but needed for parsing - version uint32 } func (p *joinSplit) ParseFromSlice(data []byte) ([]byte, error) { @@ -247,16 +282,8 @@ func (p *joinSplit) ParseFromSlice(data []byte) ([]byte, error) { } } - if p.version == 2 || p.version == 3 { - if !s.ReadBytes(&p.proofPHGR13, 296) { - return nil, errors.New("could not read PHGR13 proof") - } - } else if p.version >= 4 { - if !s.ReadBytes(&p.proofGroth16, 192) { - return nil, errors.New("could not read Groth16 proof") - } - } else { - return nil, errors.New("unexpected transaction version") + if !s.ReadBytes(&p.proofGroth16, 192) { + return nil, errors.New("could not read Groth16 proof") } for i := 0; i < 2; i++ { @@ -325,148 +352,227 @@ func (tx *Transaction) ToCompact(index int) *walletrpc.CompactTx { return ctx } -// ParseFromSlice deserializes a single transaction from the given data. -func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) { +// parse version 4 transaction data after the nVersionGroupId field. +func (tx *Transaction) parseV4(data []byte) ([]byte, error) { s := bytestring.String(data) - - // declare here to prevent shadowing problems in cryptobyte assignments var err error + if tx.nVersionGroupID != 0x892F2085 { + return nil, errors.New(fmt.Sprintf("version group ID %x must be 0x892F2085", tx.nVersionGroupID)) + } + s, err = tx.ParseTransparent([]byte(s)) + if err != nil { + return nil, err + } + if !s.ReadUint32(&tx.nLockTime) { + return nil, errors.New("could not read nLockTime") + } - var header uint32 - if !s.ReadUint32(&header) { - return nil, errors.New("could not read header") + if !s.ReadUint32(&tx.nExpiryHeight) { + return nil, errors.New("could not read nExpiryHeight") } - tx.fOverwintered = (header >> 31) == 1 - tx.version = header & 0x7FFFFFFF + var spendCount, outputCount int - if tx.version >= 3 { - if !s.ReadUint32(&tx.nVersionGroupID) { - return nil, errors.New("could not read nVersionGroupId") + if !s.ReadInt64(&tx.valueBalanceSapling) { + return nil, errors.New("could not read valueBalance") + } + if !s.ReadCompactSize(&spendCount) { + return nil, errors.New("could not read nShieldedSpend") + } + tx.shieldedSpends = make([]*spend, spendCount) + for i := 0; i < spendCount; i++ { + newSpend := &spend{} + s, err = newSpend.ParseFromSlice([]byte(s), 4) + if err != nil { + return nil, errors.Wrap(err, "while parsing shielded Spend") } + tx.shieldedSpends[i] = newSpend } - - var txInCount int - if !s.ReadCompactSize(&txInCount) { - return nil, errors.New("could not read tx_in_count") + if !s.ReadCompactSize(&outputCount) { + return nil, errors.New("could not read nShieldedOutput") } - - // TODO: Duplicate/otherwise-too-many transactions are a possible DoS - // TODO: vector. At the moment we're assuming trusted input. - // See https://nvd.nist.gov/vuln/detail/CVE-2018-17144 for an example. - - if txInCount > 0 { - tx.transparentInputs = make([]*txIn, txInCount) - for i := 0; i < txInCount; i++ { - ti := &txIn{} - s, err = ti.ParseFromSlice([]byte(s)) - if err != nil { - return nil, errors.Wrap(err, "while parsing transparent input") - } - tx.transparentInputs[i] = ti + tx.shieldedOutputs = make([]*output, outputCount) + for i := 0; i < outputCount; i++ { + newOutput := &output{} + s, err = newOutput.ParseFromSlice([]byte(s), 4) + if err != nil { + return nil, errors.Wrap(err, "while parsing shielded Output") } + tx.shieldedOutputs[i] = newOutput } - - var txOutCount int - if !s.ReadCompactSize(&txOutCount) { - return nil, errors.New("could not read tx_out_count") + var joinSplitCount int + if !s.ReadCompactSize(&joinSplitCount) { + return nil, errors.New("could not read nJoinSplit") } - if txOutCount > 0 { - tx.transparentOutputs = make([]*txOut, txOutCount) - for i := 0; i < txOutCount; i++ { - to := &txOut{} - s, err = to.ParseFromSlice([]byte(s)) + tx.joinSplits = make([]*joinSplit, joinSplitCount) + if joinSplitCount > 0 { + for i := 0; i < joinSplitCount; i++ { + js := &joinSplit{} + s, err = js.ParseFromSlice([]byte(s)) if err != nil { - return nil, errors.Wrap(err, "while parsing transparent output") + return nil, errors.Wrap(err, "while parsing JoinSplit") } - tx.transparentOutputs[i] = to + tx.joinSplits[i] = js + } + + if !s.ReadBytes(&tx.joinSplitPubKey, 32) { + return nil, errors.New("could not read joinSplitPubKey") + } + + if !s.ReadBytes(&tx.joinSplitSig, 64) { + return nil, errors.New("could not read joinSplitSig") } } + if spendCount+outputCount > 0 && !s.ReadBytes(&tx.bindingSigSapling, 64) { + return nil, errors.New("could not read bindingSigSapling") + } + return s, nil +} +// parse version 5 transaction data after the nVersionGroupId field. +func (tx *Transaction) parseV5(data []byte) ([]byte, error) { + s := bytestring.String(data) + var err error + if !s.ReadUint32(&tx.consensusBranchID) { + return nil, errors.New("could not read nVersionGroupId") + } + if tx.nVersionGroupID != 0x26A7270A { + return nil, errors.New(fmt.Sprintf("version group ID %d must be 0x26A7270A", tx.nVersionGroupID)) + } + if tx.consensusBranchID != 0x37519621 { + return nil, errors.New("unknown consensusBranchID") + } if !s.ReadUint32(&tx.nLockTime) { return nil, errors.New("could not read nLockTime") } - - if tx.fOverwintered { - if !s.ReadUint32(&tx.nExpiryHeight) { - return nil, errors.New("could not read nExpiryHeight") - } + if !s.ReadUint32(&tx.nExpiryHeight) { + return nil, errors.New("could not read nExpiryHeight") + } + s, err = tx.ParseTransparent([]byte(s)) + if err != nil { + return nil, err } var spendCount, outputCount int - - if tx.version >= 4 { - if !s.ReadInt64(&tx.valueBalance) { - return nil, errors.New("could not read valueBalance") + if !s.ReadCompactSize(&spendCount) { + return nil, errors.New("could not read nShieldedSpend") + } + if spendCount >= (1 << 16) { + return nil, errors.New(fmt.Sprintf("spentCount (%d) must be less than 2^16", spendCount)) + } + tx.shieldedSpends = make([]*spend, spendCount) + for i := 0; i < spendCount; i++ { + newSpend := &spend{} + s, err = newSpend.ParseFromSlice([]byte(s), tx.version) + if err != nil { + return nil, errors.Wrap(err, "while parsing shielded Spend") } - - if !s.ReadCompactSize(&spendCount) { - return nil, errors.New("could not read nShieldedSpend") + tx.shieldedSpends[i] = newSpend + } + if !s.ReadCompactSize(&outputCount) { + return nil, errors.New("could not read nShieldedOutput") + } + if outputCount >= (1 << 16) { + return nil, errors.New(fmt.Sprintf("outputCount (%d) must be less than 2^16", outputCount)) + } + tx.shieldedOutputs = make([]*output, outputCount) + for i := 0; i < outputCount; i++ { + newOutput := &output{} + s, err = newOutput.ParseFromSlice([]byte(s), tx.version) + if err != nil { + return nil, errors.Wrap(err, "while parsing shielded Output") } - - if spendCount > 0 { - tx.shieldedSpends = make([]*spend, spendCount) - for i := 0; i < spendCount; i++ { - newSpend := &spend{} - s, err = newSpend.ParseFromSlice([]byte(s)) - if err != nil { - return nil, errors.Wrap(err, "while parsing shielded Spend") - } - tx.shieldedSpends[i] = newSpend - } + tx.shieldedOutputs[i] = newOutput + } + if spendCount+outputCount > 0 && !s.ReadInt64(&tx.valueBalanceSapling) { + return nil, errors.New("could not read valueBalance") + } + if spendCount > 0 && !s.Skip(32) { + return nil, errors.New("could not skip anchorSapling") + } + if !s.Skip(192 * spendCount) { + return nil, errors.New("could not skip vSpendProofsSapling") + } + if !s.Skip(64 * spendCount) { + return nil, errors.New("could not skip vSpendAuthSigsSapling") + } + if !s.Skip(192 * outputCount) { + return nil, errors.New("could not skip vOutputProofsSapling") + } + if spendCount+outputCount > 0 && !s.ReadBytes(&tx.bindingSigSapling, 64) { + return nil, errors.New("could not read bindingSigSapling") + } + var actionsCount int + if !s.ReadCompactSize(&actionsCount) { + return nil, errors.New("could not read nActionsOrchard") + } + if actionsCount >= (1 << 16) { + return nil, errors.New(fmt.Sprintf("actionsCount (%d) must be less than 2^16", actionsCount)) + } + if !s.Skip(820 * actionsCount) { + return nil, errors.New("could not skip vActionsOrchard") + } + if actionsCount > 0 { + if !s.Skip(1) { + return nil, errors.New("could not skip flagsOrchard") } - - if !s.ReadCompactSize(&outputCount) { - return nil, errors.New("could not read nShieldedOutput") + if !s.Skip(8) { + return nil, errors.New("could not skip valueBalanceOrchard") } - - if outputCount > 0 { - tx.shieldedOutputs = make([]*output, outputCount) - for i := 0; i < outputCount; i++ { - newOutput := &output{} - s, err = newOutput.ParseFromSlice([]byte(s)) - if err != nil { - return nil, errors.Wrap(err, "while parsing shielded Output") - } - tx.shieldedOutputs[i] = newOutput - } + if !s.Skip(32) { + return nil, errors.New("could not skip anchorOrchard") } - } - - if tx.version >= 2 { - var joinSplitCount int - if !s.ReadCompactSize(&joinSplitCount) { - return nil, errors.New("could not read nJoinSplit") + var proofsCount int + if !s.ReadCompactSize(&proofsCount) { + return nil, errors.New("could not read sizeProofsOrchard") } + if !s.Skip(proofsCount) { + return nil, errors.New("could not skip proofsOrchard") + } + if !s.Skip(64 * actionsCount) { + return nil, errors.New("could not skip vSpendAuthSigsOrchard") + } + if !s.Skip(64) { + return nil, errors.New("could not skip bindingSigOrchard") + } + } + return s, nil +} - if joinSplitCount > 0 { - tx.joinSplits = make([]*joinSplit, joinSplitCount) - for i := 0; i < joinSplitCount; i++ { - js := &joinSplit{version: tx.version} - s, err = js.ParseFromSlice([]byte(s)) - if err != nil { - return nil, errors.Wrap(err, "while parsing JoinSplit") - } - tx.joinSplits[i] = js - } +// ParseFromSlice deserializes a single transaction from the given data. +func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) { + s := bytestring.String(data) - if !s.ReadBytes(&tx.joinSplitPubKey, 32) { - return nil, errors.New("could not read joinSplitPubKey") - } + // declare here to prevent shadowing problems in cryptobyte assignments + var err error - if !s.ReadBytes(&tx.joinSplitSig, 64) { - return nil, errors.New("could not read joinSplitSig") - } - } + var header uint32 + if !s.ReadUint32(&header) { + return nil, errors.New("could not read header") } - if tx.version >= 4 && (spendCount+outputCount > 0) { - if !s.ReadBytes(&tx.bindingSig, 64) { - return nil, errors.New("could not read bindingSig") - } + tx.fOverwintered = (header >> 31) == 1 + if !tx.fOverwintered { + return nil, errors.New("fOverwinter flag must be set") + } + tx.version = header & 0x7FFFFFFF + if tx.version < 4 { + return nil, errors.New(fmt.Sprintf("version number %d must be greater or equal to 4", tx.version)) } + if !s.ReadUint32(&tx.nVersionGroupID) { + return nil, errors.New("could not read nVersionGroupId") + } + // parse the main part of the transaction + if tx.version <= 4 { + s, err = tx.parseV4([]byte(s)) + } else { + s, err = tx.parseV5([]byte(s)) + } + if err != nil { + return nil, err + } // TODO: implement rawBytes with MarshalBinary() instead txLen := len(data) - len(s) tx.rawBytes = data[:txLen] diff --git a/parser/transaction_test.go b/parser/transaction_test.go index 60671132..4b6a3726 100644 --- a/parser/transaction_test.go +++ b/parser/transaction_test.go @@ -2,882 +2,3 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php . package parser - -import ( - "bufio" - "bytes" - "encoding/binary" - "encoding/hex" - "os" - "strings" - "testing" - - "github.com/zcash/lightwalletd/parser/internal/bytestring" -) - -// "Human-readable" version of joinSplit struct defined in transaction.go. -// Remember to update this if the format ever changes. -type joinSplitTestVector struct { - vpubOld uint64 - vpubNew uint64 - anchor string // 32 - nullifiers []string // 64 [N_old][32]byte - commitments []string // 64 [N_new][32]byte - ephemeralKey string // 32 - randomSeed string // 32 - vmacs []string // 64 [N_old][32]byte - proofPHGR13 string // 296 - proofGroth16 string // 192 - encCiphertexts []string // 1202 [N_new][601]byte -} - -type spendTestVector struct { - cv string // 32 - anchor string // 32 - nullifier string // 32 - rk string // 32 - zkproof string // 192 - spendAuthSig string // 64 -} - -type outputTestVector struct { - cv string // 32 - cmu string // 32 - ephemeralKey string // 32 - encCiphertext string // 580 - outCiphertext string // 80 - zkproof string // 192 -} - -type txTestVector struct { - // Sprout and Sapling - txid, header, nVersionGroupID, nLockTime, nExpiryHeight string - vin, vout [][]string - vJoinSplits []joinSplitTestVector - joinSplitPubKey, joinSplitSig string - - // Sapling-only - valueBalance string // encoded int64 - spends []spendTestVector - outputs []outputTestVector - bindingSig string -} - -// https://github.com/zcash/zips/blob/master/zip-0143.rst -var zip143tests = []txTestVector{ - { - // Test vector 1 - txid: "f0b22277ac851b5f4df590fe6a128aad9d0ce8063235eb2b328c2dc6a23c1ec5", - header: "03000080", - nVersionGroupID: "7082c403", - nLockTime: "481cdd86", - nExpiryHeight: "b3cc4318", - vin: nil, - vout: [][]string{ - {"8f739811893e0000", "095200ac6551ac636565"}, - {"b1a45a0805750200", "025151"}, - }, - }, - { - // Test vector 2 - //raw: "we have some raw data for this tx, which this comment is too small to contain", - txid: "39fe585a56b005f568c3171d22afa916e946e2a8aff5971d58ee8a6fc1482059", - header: "03000080", - nVersionGroupID: "7082c403", - nLockTime: "97b0e4e4", - nExpiryHeight: "c705fc05", - vin: [][]string{ - {"4201cfb1cd8dbf69b8250c18ef41294ca97993db546c1fe01f7e9c8e36d6a5e2", "9d4e30a7", "03ac6a00", "98421c69"}, - {"378af1e40f64e125946f62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d", "62f5a8d7", "056363635353", "e8c7203d"}, - }, - vout: [][]string{ - {"6af786387ae60100", "080063656a63ac5200"}, - {"23752997f4ff0400", "0751510053536565"}, - }, - vJoinSplits: []joinSplitTestVector{ - { - vpubOld: uint64(0), - vpubNew: uint64(0), - anchor: "76495c222f7fba1e31defa3d5a57efc2e1e9b01a035587d5fb1a38e01d94903d", - nullifiers: []string{ - "3c3e0ad3360c1d3710acd20b183e31d49f25c9a138f49b1a537edcf04be34a98", - "51a7af9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dad", - }, - commitments: []string{ - "d64f5431e61ddf658d24ae67c22c8d1309131fc00fe7f235734276d38d47f1e1", - "91e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8", - }, - ephemeralKey: "fc1be4aac00ff2711ebd931de518856878f73476f21a482ec9378365c8f7393c", - randomSeed: "94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a0", - vmacs: []string{ - "08e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b21035965", - "55ed9494c6ac893c49723833ec8926c1039586a7afcf4a0d9c731e985d99589c", - }, - proofPHGR13: "03b838e8aaf745533ed9e8ae3a1cd074a51a20da8aba18d1dbebbc862ded42435e02476930d069896cff30eb414f727b89e001afa2fb8dc3436d75a4a6f26572504b0b2232ecb9f0c02411e52596bc5e90457e745939ffedbd12863ce71a02af117d417adb3d15cc54dcb1fce467500c6b8fb86b12b56da9c382857deecc40a98d5f2903395ee4762dd21afdbb5d47fa9a6dd984d567db2857b927b7fae2db587105415d0242789d38f50b8dbcc129cab3d17d19f3355bcf73cecb8cb8a5da01307152f13902a270572670dc82d39026c6cb4cd4b0f7f5aa2a4f5a5341ec5dd715406f2fdd2a02733f5f641c8c21862a1bafce2609d9eecfa158cfb5cd79f88008e315dc7d8388036c1782fd2795d18a763624c25fa959cc97489ce75745824b77868c53239cfbdf", - encCiphertexts: []string{ - "73caec65604037314faaceb56218c6bd30f8374ac13386793f21a9fb80ad03bc0cda4a44946c00e1b1a1df0e5b87b5bece477a709649e950060591394812951e1fe3895b8cc3d14d2cf6556df6ed4b4ddd3d9a69f53357d7767f4f5ccbdbc596631277f8fecd08cb056b95e3025b9792fff7f244fc716269b926d62e9596fa825c6bf21aff9e68625a192440ea06828123d97884806f15fa08da52754a1095e3ff1abd5ce4fddfccfc3a6128aef784a64610a89d1a7099216d0814d3a2d452431c32d411ac1cce82ad0229407bbc48985675e3f874a4533f1d63a84dfa3e0f460fe2f57e34fbc75423c3737f5b2a0615f5722db041a3ef66fa483afd3c2e19e59444a64add6df1d963f5dd5b5010d3d025f0287c4cf19c75f33d51ddddba5d657b43ee8da645443814cc7329f3e9b4e54c236c29af3923101756d9fa4bd0f7d2ddaacb6b0f86a2658e0a07a05ac5b950051cd24c47a88d13d659ba2a46ca1830816d09cd7646f76f716abec5de07fe9b523410806ea6f288f8736c23357c85f45791e1708029d9824d90704607f387a03e49bf9836574431345a7877efaa8a08e73081ef8d62cb780ab6883a50a0d470190dfba10a857f82842d3825b3d6da0573d316eb160dc0b716c48fbd467f75b780149ae8808f4e68f50c0536acddf6f1aeab016b6bc1ec144b4e553acfd670f77e755fc88e0677e31ba459b44e307768958fe3789d41c2b1ff434cb30e15914f01bc6bc2307b488d2556d7b7380ea4ffd712f6b02fe806b94569cd4059f396bf29b99d0a40e5e1711ca944f72d436a102fca4b97693da0b086fe9d2e7162470d02e0f05d4bec9512bf", - "b3f38327296efaa74328b118c27402c70c3a90b49ad4bbc68e37c0aa7d9b3fe17799d73b841e751713a02943905aae0803fd69442eb7681ec2a05600054e92eed555028f21b6a155268a2dd6640a69301a52a38d4d9f9f957ae35af7167118141ce4c9be0a6a492fe79f1581a155fa3a2b9dafd82e650b386ad3a08cb6b83131ac300b0846354a7eef9c410e4b62c47c5426907dfc6685c5c99b7141ac626ab4761fd3f41e728e1a28f89db89ffdeca364dd2f0f0739f0534556483199c71f189341ac9b78a269164206a0ea1ce73bfb2a942e7370b247c046f8e75ef8e3f8bd821cf577491864e20e6d08fd2e32b555c92c661f19588b72a89599710a88061253ca285b6304b37da2b5294f5cb354a894322848ccbdc7c2545b7da568afac87ffa005c312241c2d57f4b45d6419f0d2e2c5af33ae243785b325cdab95404fc7aed70525cddb41872cfcc214b13232edc78609753dbff930eb0dc156612b9cb434bc4b693392deb87c530435312edcedc6a961133338d786c4a3e103f60110a16b1337129704bf4754ff6ba9fbe65951e610620f71cda8fc877625f2c5bb04cbe1228b1e886f4050afd8fe94e97d2e9e85c6bb748c0042d3249abb1342bb0eebf62058bf3de080d94611a3750915b5dc6c0b3899d41222bace760ee9c8818ded599e34c56d7372af1eb86852f2a732104bdb750739de6c2c6e0f9eb7cb17f1942bfc9f4fd6ebb6b4cdd4da2bca26fac4578e9f543405acc7d86ff59158bd0cba3aef6f4a8472d144d99f8b8d1dedaa9077d4f01d4bb27bbe31d88fbefac3dcd4797563a26b1d61fcd9a464ab21ed550fe6fa09695ba0b2f10e", - }, - }, - { - vpubOld: uint64(0), - vpubNew: uint64(0), - anchor: "ea6468cc6e20a66f826e3d14c5006f0563887f5e1289be1b2004caca8d3f34d6", - nullifiers: []string{ - "e84bf59c1e04619a7c23a996941d889e4622a9b9b1d59d5e319094318cd405ba", - "27b7e2c084762d31453ec4549a4d97729d033460fcf89d6494f2ffd789e98082", - }, - commitments: []string{ - "ea5ce9534b3acd60fe49e37e4f666931677319ed89f85588741b3128901a93bd", - "78e4be0225a9e2692c77c969ed0176bdf9555948cbd5a332d045de6ba6bf4490", - }, - ephemeralKey: "adfe7444cd467a09075417fcc0062e49f008c51ad4227439c1b4476ccd8e9786", - randomSeed: "2dab7be1e8d399c05ef27c6e22ee273e15786e394c8f1be31682a30147963ac8", - vmacs: []string{ - "da8d41d804258426a3f70289b8ad19d8de13be4eebe3bd4c8a6f55d6e0c373d4", - "56851879f5fbc282db9e134806bff71e11bc33ab75dd6ca067fb73a043b646a7", - }, - proofPHGR13: "0339cab4928386786d2f24141ee120fdc34d6764eafc66880ee0204f53cc1167ed02b43a52dea3ca7cff8ef35cd8e6d7c111a68ef44bcd0c1513ad47ca61c659cc5d0a5b440f6b9f59aff66879bb6688fd2859362b182f207b3175961f6411a493bffd048e7d0d87d82fe6f990a2b0a25f5aa0111a6e68f37bf6f3ac2d26b84686e569038d99c1383597fad81193c4c1b16e6a90e2d507cdfe6fbdaa86163e9cf5de310003ca7e8da047b090db9f37952fbfee76af61668190bd52ed490e677b515d0143840307219c7c0ee7fc7bfc79f325644e4df4c0d7db08e9f0bd024943c705abff899403a605cfbc7ed746a7d3f7c37d9e8bdc433b7d79e08a12f738a8f0dbddfef2f26502f3e47d1b0fd11e6a13311fb799c79c641d9da43b33e7ad012e28255398789262", - encCiphertexts: []string{ - "275f1175be8462c01491c4d842406d0ec4282c9526174a09878fe8fdde33a29604e5e5e7b2a025d6650b97dbb52befb59b1d30a57433b0a351474444099daa371046613260cf3354cfcdada663ece824ffd7e44393886a86165ddddf2b4c41773554c86995269408b11e6737a4c447586f69173446d8e48bf84cbc000a807899973eb93c5e819aad669413f8387933ad1584aa35e43f4ecd1e2d0407c0b1b89920ffdfdb9bea51ac95b557af71b89f903f5d9848f14fcbeb1837570f544d6359eb23faf38a0822da36ce426c4a2fbeffeb0a8a2e297a9d19ba15024590e3329d9fa9261f9938a4032dd34606c9cf9f3dd33e576f05cd1dd6811c6298757d77d9e810abdb226afcaa4346a6560f8932b3181fd355d5d391976183f8d99388839632d6354f666d09d3e5629ea19737388613d38a34fd0f6e50ee5a0cc9677177f50028c141378187bd2819403fc534f80076e9380cb4964d3b6b45819d3b8e9caf54f051852d671bf8c1ffde2d1510756418cb4810936aa57e6965d6fb656a760b7f19adf96c173488552193b147ee58858033dac7cd0eb204c06490bbdedf5f7571acb2ebe76acef3f2a01ee987486dfe6c3f0a5e234c127258f97a28fb5d164a8176be946b8097d0e317287f33bf9c16f9a545409ce29b1f4273725fc0df02a04ebae178b3414fb0a82d50deb09fcf4e6ee9d180ff4f56ff3bc1d3601fc2dc90d814c3256f4967d3a8d64c83fea339c51f5a8e5801fbb97835581b602465dee04b5922c2761b54245bec0c9eef2db97d22b2b3556cc969fbb13d06509765a52b3fac54b93f421bf08e18d52ddd52cc1c8ca8adfaccab7e5cc2", - "f4573fbbf8239bb0b8aedbf8dad16282da5c9125dba1c059d0df8abf621078f02d6c4bc86d40845ac1d59710c45f07d585eb48b32fc0167ba256e73ca3b9311c62d109497957d8dbe10aa3e866b40c0baa2bc492c19ad1e6372d9622bf163fbffeaeee796a3cd9b6fbbfa4d792f34d7fd6e763cd5859dd26833d21d9bc5452bd19515dff9f4995b35bc0c1f876e6ad11f2452dc9ae85aec01fc56f8cbfda75a7727b75ebbd6bbffb43b63a3b1b671e40feb0db002974a3c3b1a788567231bf6399ff89236981149d423802d2341a3bedb9ddcbac1fe7b6435e1479c72e7089d029e7fbbaf3cf37e9b9a6b776791e4c5e6fda57e8d5f14c8c35a2d270846b9dbe005cda16af4408f3ab06a916eeeb9c9594b70424a4c1d171295b6763b22f47f80b53ccbb904bd68fd65fbd3fbdea1035e98c21a7dbc91a9b5bc7690f05ec317c97f8764eb48e911d428ec8d861b708e8298acb62155145155ae95f0a1d1501034753146e22d05f586d7f6b4fe12dad9a17f5db70b1db96b8d9a83edadc966c8a5466b61fc998c31f1070d9a5c9a6d268d304fe6b8fd3b4010348611abdcbd49fe4f85b623c7828c71382e1034ea67bc8ae97404b0c50b2a04f559e49950afcb0ef462a2ae024b0f0224dfd73684b88c7fbe92d02b68f759c4752663cd7b97a14943649305521326bde085630864629291bae25ff8822a14c4b666a9259ad0dc42a8290ac7bc7f53a16f379f758e5de750f04fd7cad47701c8597f97888bea6fa0bf2999956fbfd0ee68ec36e4688809ae231eb8bc4369f5fe1573f57e099d9c09901bf39caac48dc11956a8ae905ead86954547c448ae43d31", - }, - }, - }, - - joinSplitPubKey: "5e669c4242da565938f417bf43ce7b2b30b1cd4018388e1a910f0fc41fb0877a", - // This joinSplitSig is (intentionally) invalid random data. - joinSplitSig: "5925e466819d375b0a912d4fe843b76ef6f223f0f7c894f38f7ab780dfd75f669c8c06cffa43eb47565a50e3b1fa45ad61ce9a1c4727b7aaa53562f523e73952", - }, -} - -func TestSproutTransactionParser(t *testing.T) { - // The raw data are stored in a separate file because they're large enough - // to make the test table difficult to scroll through. They are in the same - // order as the test table above. If you update the test table without - // adding a line to the raw file, this test will panic due to index - // misalignment. - testData, err := os.Open("../testdata/zip143_raw_tx") - if err != nil { - t.Fatal(err) - } - defer testData.Close() - - // Parse the raw transactions file - rawTxData := [][]byte{} - scan := bufio.NewScanner(testData) - for scan.Scan() { - dataLine := scan.Text() - // Skip the comments - if strings.HasPrefix(dataLine, "#") { - continue - } - - txData, err := hex.DecodeString(dataLine) - if err != nil { - t.Fatal(err) - } - rawTxData = append(rawTxData, txData) - } - - for i, tt := range zip143tests { - tx := NewTransaction() - - rest, err := tx.ParseFromSlice(rawTxData[i]) - if err != nil { - t.Errorf("Test %d: %v", i, err) - continue - } - - if len(rest) != 0 { - t.Errorf("Test %d: did not consume entire buffer", i) - continue - } - - // Transaction metadata - if !subTestCommonBlockMeta(&tt, tx, t, i) { - continue - } - - // Transparent inputs and outputs - if !subTestTransparentInputs(tt.vin, tx.transparentInputs, t, i) { - continue - } - - if !subTestTransparentOutputs(tt.vout, tx.transparentOutputs, t, i) { - continue - } - - // JoinSplits - if !subTestJoinSplits(tt.vJoinSplits, tx.joinSplits, t, i) { - continue - } - - testJSPubKey, _ := hex.DecodeString(tt.joinSplitPubKey) - if !bytes.Equal(testJSPubKey, tx.joinSplitPubKey) { - t.Errorf("Test %d: jsPubKey mismatch %x %x", i, testJSPubKey, tx.joinSplitPubKey) - continue - } - - testJSSig, _ := hex.DecodeString(tt.joinSplitSig) - if !bytes.Equal(testJSSig, tx.joinSplitSig) { - t.Errorf("Test %d: jsSig mismatch %x %x", i, testJSSig, tx.joinSplitSig) - continue - } - if hex.EncodeToString(tx.GetDisplayHash()) != tt.txid { - t.Errorf("Test %d: incorrect txid", i) - } - } -} - -func subTestCommonBlockMeta(tt *txTestVector, tx *Transaction, t *testing.T, caseNum int) bool { - headerBytes, _ := hex.DecodeString(tt.header) - header := binary.LittleEndian.Uint32(headerBytes) - if (header >> 31) == 1 != tx.fOverwintered { - t.Errorf("Test %d: unexpected fOverwintered", caseNum) - return false - } - if (header & 0x7FFFFFFF) != tx.version { - t.Errorf("Test %d: unexpected tx version", caseNum) - return false - } - - versionGroupBytes, _ := hex.DecodeString(tt.nVersionGroupID) - versionGroup := binary.LittleEndian.Uint32(versionGroupBytes) - if versionGroup != tx.nVersionGroupID { - t.Errorf("Test %d: unexpected versionGroupId", caseNum) - return false - } - - lockTimeBytes, _ := hex.DecodeString(tt.nLockTime) - lockTime := binary.LittleEndian.Uint32(lockTimeBytes) - if lockTime != tx.nLockTime { - t.Errorf("Test %d: unexpected nLockTime", caseNum) - return false - } - - expiryHeightBytes, _ := hex.DecodeString(tt.nExpiryHeight) - expiryHeight := binary.LittleEndian.Uint32(expiryHeightBytes) - if expiryHeight != tx.nExpiryHeight { - t.Errorf("Test %d: unexpected nExpiryHeight", caseNum) - return false - } - - return true -} - -func subTestJoinSplits(testJoinSplits []joinSplitTestVector, txJoinSplits []*joinSplit, t *testing.T, caseNum int) bool { - if testJoinSplits == nil && txJoinSplits != nil { - t.Errorf("Test %d: non-zero joinSplits when expected empty vector", caseNum) - return false - } - if len(testJoinSplits) != len(txJoinSplits) { - t.Errorf("Test %d: joinSplit vector lengths mismatch", caseNum) - return false - } - - success := true - -JoinSplitLoop: - for idx, test := range testJoinSplits { - tx := txJoinSplits[idx] - - if test.vpubOld != tx.vpubOld { - t.Errorf("Test %d js %d: vpubOld %d %d", caseNum, idx, test.vpubOld, tx.vpubOld) - success = false - continue - } - if test.vpubNew != tx.vpubNew { - t.Errorf("Test %d js %d: vpubNew %d %d", caseNum, idx, test.vpubNew, tx.vpubNew) - success = false - continue - } - - anchor, _ := hex.DecodeString(test.anchor) - if !bytes.Equal(anchor, tx.anchor) { - t.Errorf("Test %d js %d: anchor %x %x", caseNum, idx, anchor, tx.anchor) - success = false - continue - } - - if len(test.nullifiers) != len(tx.nullifiers) { - t.Errorf("Test %d js %d: nf len mismatch %d %d", caseNum, idx, len(test.nullifiers), len(tx.nullifiers)) - success = false - continue - } - - for j := 0; j < len(test.nullifiers); j++ { - nf, _ := hex.DecodeString(test.nullifiers[j]) - if !bytes.Equal(nf, tx.nullifiers[j]) { - t.Errorf("Test %d js %d: nf mismatch %x %x", caseNum, idx, nf, tx.nullifiers[j]) - success = false - continue JoinSplitLoop - } - } - - if len(test.commitments) != len(tx.commitments) { - t.Errorf("Test %d js %d: cm len mismatch %d %d", caseNum, idx, len(test.commitments), len(tx.commitments)) - success = false - continue - } - - for j := 0; j < len(test.commitments); j++ { - cm, _ := hex.DecodeString(test.commitments[j]) - if !bytes.Equal(cm, tx.commitments[j]) { - t.Errorf("Test %d js %d: commit mismatch %x %x", caseNum, idx, cm, tx.commitments[j]) - success = false - continue JoinSplitLoop - } - } - - ephemeralKey, _ := hex.DecodeString(test.ephemeralKey) - if !bytes.Equal(ephemeralKey, tx.ephemeralKey) { - t.Errorf("Test %d js %d: ephemeralKey %x %x", caseNum, idx, ephemeralKey, tx.ephemeralKey) - success = false - continue - } - - randomSeed, _ := hex.DecodeString(test.randomSeed) - if !bytes.Equal(randomSeed, tx.randomSeed) { - t.Errorf("Test %d js %d: randomSeed %x %x", caseNum, idx, randomSeed, tx.randomSeed) - success = false - continue - } - - if len(test.vmacs) != len(tx.vmacs) { - t.Errorf("Test %d js %d: mac len mismatch %d %d", caseNum, idx, len(test.vmacs), len(tx.vmacs)) - success = false - continue - } - - for j := 0; j < len(test.vmacs); j++ { - mac, _ := hex.DecodeString(test.vmacs[j]) - if !bytes.Equal(mac, tx.vmacs[j]) { - t.Errorf("Test %d js %d: mac mismatch %x %x", caseNum, idx, mac, tx.vmacs[j]) - success = false - continue JoinSplitLoop - } - } - - // This should not be possible. - if tx.proofPHGR13 != nil && tx.proofGroth16 != nil { - t.Errorf("Test %d js %d: parsed tx had both PHGR and Groth proofs defined", caseNum, idx) - success = false - continue - } - - if test.proofPHGR13 != "" { - zkproof, _ := hex.DecodeString(test.proofPHGR13) - if !bytes.Equal(zkproof, tx.proofPHGR13) { - t.Errorf("Test %d js %d: zkproof %x %x", caseNum, idx, zkproof, tx.proofPHGR13) - success = false - continue - } - } - - if test.proofGroth16 != "" { - zkproof, _ := hex.DecodeString(test.proofGroth16) - if !bytes.Equal(zkproof, tx.proofGroth16) { - t.Errorf("Test %d js %d: zkproof %x %x", caseNum, idx, zkproof, tx.proofGroth16) - success = false - continue - } - } - - if len(test.encCiphertexts) != len(tx.encCiphertexts) { - t.Errorf("Test %d js %d: enc len mismatch %d %d", caseNum, idx, len(test.encCiphertexts), len(tx.encCiphertexts)) - success = false - continue - } - - for j := 0; j < len(test.encCiphertexts); j++ { - ct, _ := hex.DecodeString(test.encCiphertexts[j]) - if !bytes.Equal(ct, tx.encCiphertexts[j]) { - t.Errorf("Test %d js %d: ct mismatch %x %x", caseNum, idx, ct, tx.encCiphertexts[j]) - success = false - continue JoinSplitLoop - } - } - } - - return success -} - -func subTestTransparentInputs(testInputs [][]string, txInputs []*txIn, t *testing.T, caseNum int) bool { - if testInputs == nil && txInputs != nil { - t.Errorf("Test %d: non-zero vin when expected zero", caseNum) - return false - } - - if len(testInputs) != len(txInputs) { - t.Errorf("Test %d: vins have mismatched lengths", caseNum) - return false - } - - success := true - le := binary.LittleEndian - - // 4201cfb1cd8dbf69b8250c18ef41294ca97993db546c1fe01f7e9c8e36d6a5e2 9d4e30a7 03ac6a00 98421c69 - for idx, ti := range testInputs { - txInput := txInputs[idx] - - testPrevTxHash, _ := hex.DecodeString(ti[0]) - if eq := bytes.Equal(testPrevTxHash, txInput.PrevTxHash); !eq { - t.Errorf("Test %d tin %d: prevhash mismatch %x %x", caseNum, idx, testPrevTxHash, txInput.PrevTxHash) - success = false - continue - } - - testPrevTxOutIndexBytes, _ := hex.DecodeString(ti[1]) - testPrevTxOutIndex := le.Uint32(testPrevTxOutIndexBytes) - if testPrevTxOutIndex != txInput.PrevTxOutIndex { - t.Errorf("Test %d tin %d: prevout index mismatch %d %d", caseNum, idx, testPrevTxOutIndex, txInput.PrevTxOutIndex) - success = false - continue - } - - // Decode scriptSig and correctly consume own CompactSize field - testScriptSig, _ := hex.DecodeString(ti[2]) - ok := (*bytestring.String)(&testScriptSig).ReadCompactLengthPrefixed((*bytestring.String)(&testScriptSig)) - if !ok { - t.Errorf("Test %d, tin %d: couldn't strip size from script", caseNum, idx) - success = false - continue - } - - if eq := bytes.Equal(testScriptSig, txInput.ScriptSig); !eq { - t.Errorf("Test %d tin %d: scriptsig mismatch %x %x", caseNum, idx, testScriptSig, txInput.ScriptSig) - success = false - continue - } - - testSeqNumBytes, _ := hex.DecodeString(ti[3]) - testSeqNum := le.Uint32(testSeqNumBytes) - if testSeqNum != txInput.SequenceNumber { - t.Errorf("Test %d tin %d: seq mismatch %d %d", caseNum, idx, testSeqNum, txInput.SequenceNumber) - success = false - continue - } - } - return success -} - -func subTestTransparentOutputs(testOutputs [][]string, txOutputs []*txOut, t *testing.T, caseNum int) bool { - if testOutputs == nil && txOutputs != nil { - t.Errorf("Test %d: non-zero vout when expected zero", caseNum) - return false - } - - if len(testOutputs) != len(txOutputs) { - t.Errorf("Test %d: vout have mismatched lengths", caseNum) - return false - } - - success := true - le := binary.LittleEndian - - for idx, testOutput := range testOutputs { - txOutput := txOutputs[idx] - - // Parse tx out value from test - testValueBytes, _ := hex.DecodeString(testOutput[0]) - testValue := le.Uint64(testValueBytes) - - if testValue != txOutput.Value { - t.Errorf("Test %d, tout %d: value mismatch %d %d", caseNum, idx, testValue, txOutput.Value) - success = false - continue - } - - // Parse script from test - testScript, _ := hex.DecodeString(testOutput[1]) - // Correctly consume own CompactSize field - ok := (*bytestring.String)(&testScript).ReadCompactLengthPrefixed((*bytestring.String)(&testScript)) - if !ok { - t.Errorf("Test %d, tout %d: couldn't strip size from script", caseNum, idx) - success = false - continue - } - - if !bytes.Equal(testScript, txOutput.Script) { - t.Errorf("Test %d, tout %d: script mismatch %x %x", caseNum, idx, testScript, txOutput.Script) - success = false - continue - } - } - return success -} - -// https://github.com/zcash/zips/blob/master/zip-0243.rst -var zip243tests = []txTestVector{ - // Test vector 1 - { - txid: "5fc4867a1b8bd5ab709799adf322a85d10607e053726d5f5ab4b1c9ab897e6bc", - header: "04000080", - nVersionGroupID: "85202f89", - vin: nil, - vout: [][]string{ - {"e7719811893e0000", "095200ac6551ac636565"}, - {"b2835a0805750200", "025151"}, - }, - nLockTime: "481cdd86", - nExpiryHeight: "b3cc4318", - valueBalance: "442117623ceb0500", - spends: []spendTestVector{ - { - cv: "1b3d1a027c2c40590958b7eb13d742a997738c46a458965baf276ba92f272c72", - anchor: "1fe01f7e9c8e36d6a5e29d4e30a73594bf5098421c69378af1e40f64e125946f", - nullifier: "62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d62f5a8d7551db5fd9313", - rk: "e8c7203d996af7d477083756d59af80d06a745f44ab023752cb5b406ed8985e1", - zkproof: "8130ab33362697b0e4e4c763ccb8f676495c222f7fba1e31defa3d5a57efc2e1e9b01a035587d5fb1a38e01d94903d3c3e0ad3360c1d3710acd20b183e31d49f25c9a138f49b1a537edcf04be34a9851a7af9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dadd64f5431e61ddf658d24ae67c22c8d1309131fc00fe7f235734276d38d47f1e191e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878", - spendAuthSig: "f73476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a", - }, - { - cv: "15825b7acb4d6b57a61bc68f242b52e4fbf85cf1a09cc45b6d6bb3a391578f49", - anchor: "9486a7afd04a0d9c74c2995d96b4de37b36046a1ef6d190b916b1111c9288731", - nullifier: "1a20da8aba18d1dbebbc862ded42435e92476930d069896cff30eb414f727b89", - rk: "e001afa2fb8dc3436d75a4a6f26572504b192232ecb9f0c02411e52596bc5e90", - zkproof: "457e745939ffedbd12863ce71a02af117d417adb3d15cc54dcb1fce467500c6b8fb86b12b56da9c382857deecc40a98d5f2935395ee4762dd21afdbb5d47fa9a6dd984d567db2857b927b7fae2db587105415d4642789d38f50b8dbcc129cab3d17d19f3355bcf73cecb8cb8a5da01307152f13936a270572670dc82d39026c6cb4cd4b0f7f5aa2a4f5a5341ec5dd715406f2fdd2afa733f5f641c8c21862a1bafce2609d9eecfa158cfb5cd79f88008e315dc7d8388e76c1782fd2795d18a76", - spendAuthSig: "3624c25fa959cc97489ce75745824b77868c53239cfbdf73caec65604037314faaceb56218c6bd30f8374ac13386793f21a9fb80ad03bc0cda4a44946c00e1b1", - }, - { - cv: "02c78f11876b7065212183199fb5979ca77d2c24c738fe5145f02602053bb4c2", - anchor: "f6556df6ed4b4ddd3d9a69f53357d7767f4f5ccbdbc596631277f8fecd08cb05", - nullifier: "6b95e3025b9792fff7f244fc716269b926d62e9596fa825c6bf21aff9e68625a", - rk: "192440ea06828123d97884806f15fa08da52754a1095e3ff1abd5ce4fddfccfc", - zkproof: "3a6128aef784a64610a89d1a7099216d0814d3a2d452431c32d411ac1cce82ad0229407bbc48985675e3f874a4533f1d63a84dfa3e0f460fe2f57e34fbc75423c3737f5b2a0615f5722db041a3ef66fa483afd3c2e19e59444a64add6df1d963f5dd5b5010d3d025f0287c4cf19c75f33d51ddddba5d657b43ee8da645443814cc7329f3e9b4e54c236c29af3923101756d9fa4bd0f7d2ddaacb6b0f86a2658e0a07a05ac5b950051cd24c47a88d13d659ba2a46ca1830816d09cd7646f76f71", - spendAuthSig: "6abec5de07fe9b523410806ea6f288f8736c23357c85f45791e1708029d9824d90704607f387a03e49bf9836574431345a7877efaa8a08e73081ef8d62cb780a", - }, - }, - outputs: []outputTestVector{ - { - cv: "0fa3207ee2f0408097d563da1b2146819edf88d33e7753664fb71d122a6e3699", - cmu: "8fbd467f75b780149ae8808f4e68f50c0536acddf6f1aeab016b6bc1ec144b4e", - ephemeralKey: "59aeb77eef49d00e5fbb67101cdd41e6bc9cf641a52fca98be915f8440a410d7", - encCiphertext: "4cb30e15914f01bc6bc2307b488d2556d7b7380ea4ffd712f6b02fe806b94569cd4059f396bf29b99d0a40e5e1711ca944f72d436a102fca4b97693da0b086fe9d2e7162470d02e0f05d4bec9512bfb3f38327296efaa74328b118c27402c70c3a90b49ad4bbc68e37c0aa7d9b3fe17799d73b841e751713a02943905aae0803fd69442eb7681ec2a05600054e92eed555028f21b6a155268a2dd6640a69301a52a38d4d9f9f957ae35af7167118141ce4c9be0a6a492fe79f1581a155fa3a2b9dafd82e650b386ad3a08cb6b83131ac300b0846354a7eef9c410e4b62c47c5426907dfc6685c5c99b7141ac626ab4761fd3f41e728e1a28f89db89ffdeca364dd2f0f0739f0534556483199c71f189341ac9b78a269164206a0ea1ce73bfb2a942e7370b247c046f8e75ef8e3f8bd821cf577491864e20e6d08fd2e32b555c92c661f19588b72a89599710a88061253ca285b6304b37da2b5294f5cb354a894322848ccbdc7c2545b7da568afac87ffa005c312241c2d57f4b45d6419f0d2e2c5af33ae243785b325cdab95404fc7aed70525cddb41872cfcc214b13232edc78609753dbff930eb0dc156612b9cb434bc4b693392deb87c530435312edcedc6a961133338d786c4a3e103f60110a16b1337129704bf4754ff6ba9fbe65951e610620f71cda8fc877625f2c5bb04cbe1228b1e886f4050afd8fe94e97d2e9e85c6bb748c0042d3249abb1342bb0eebf62058bf3de080d94611a3750915b5dc6c0b3899d41222bace760ee9c8818ded599e34c56d7372af1eb86852f2a732104bdb750739", - outCiphertext: "de6c2c6e0f9eb7cb17f1942bfc9f4fd6ebb6b4cdd4da2bca26fac4578e9f543405acc7d86ff59158bd0cba3aef6f4a8472d144d99f8b8d1dedaa9077d4f01d4bb27bbe31d88fbefac3dcd4797563a26b", - zkproof: "1d61fcd9a464ab21ed550fe6fa09695ba0b2f10eea6468cc6e20a66f826e3d14c5006f0563887f5e1289be1b2004caca8d3f34d6e84bf59c1e04619a7c23a996941d889e4622a9b9b1d59d5e319094318cd405ba27b7e2c084762d31453ec4549a4d97729d033460fcf89d6494f2ffd789e98082ea5ce9534b3acd60fe49e37e4f666931677319ed89f85588741b3128901a93bd78e4be0225a9e2692c77c969ed0176bdf9555948cbd5a332d045de6ba6bf4490adfe7444cd467a09075417fc", - }, - }, - vJoinSplits: []joinSplitTestVector{ - { - vpubOld: uint64(0), - vpubNew: uint64(0), - anchor: "062e49f008c51ad4227439c1b4476ccd8e97862dab7be1e8d399c05ef27c6e22", - nullifiers: []string{ - "ee273e15786e394c8f1be31682a30147963ac8da8d41d804258426a3f70289b8", - "ad19d8de13be4eebe3bd4c8a6f55d6e0c373d456851879f5fbc282db9e134806", - }, - commitments: []string{ - "bff71e11bc33ab75dd6ca067fb73a043b646a7cf39cab4928386786d2f24141e", - "e120fdc34d6764eafc66880ee0204f53cc1167ed20b43a52dea3ca7cff8ef35c", - }, - ephemeralKey: "d8e6d7c111a68ef44bcd0c1513ad47ca61c659cc5d325b440f6b9f59aff66879", - randomSeed: "bb6688fd2859362b182f207b3175961f6411a493bffd048e7d0d87d82fe6f990", - vmacs: []string{ - "a2b0a25f5aa0111a6e68f37bf6f3ac2d26b84686e569d58d99c1383597fad811", - "93c4c1b16e6a90e2d507cdfe6fbdaa86163e9cf5de3100fbca7e8da047b090db", - }, - proofGroth16: "9f37952fbfee76af61668190bd52ed490e677b515d014384af07219c7c0ee7fc7bfc79f325644e4df4c0d7db08e9f0bd024943c705abff8994bfa605cfbc7ed746a7d3f7c37d9e8bdc433b7d79e08a12f738a8f0dbddfef2f2657ef3e47d1b0fd11e6a13311fb799c79c641d9da43b33e7ad012e28255398789262275f1175be8462c01491c4d842406d0ec4282c9526174a09878fe8fdde33a29604e5e5e7b2a025d6650b97dbb52befb59b1d30a57433b0a351474444099daa371046613260", - encCiphertexts: []string{ - "cf3354cfcdada663ece824ffd7e44393886a86165ddddf2b4c41773554c86995269408b11e6737a4c447586f69173446d8e48bf84cbc000a807899973eb93c5e819aad669413f8387933ad1584aa35e43f4ecd1e2d0407c0b1b89920ffdfdb9bea51ac95b557af71b89f903f5d9848f14fcbeb1837570f544d6359eb23faf38a0822da36ce426c4a2fbeffeb0a8a2e297a9d19ba15024590e3329d9fa9261f9938a4032dd34606c9cf9f3dd33e576f05cd1dd6811c6298757d77d9e810abdb226afcaa4346a6560f8932b3181fd355d5d391976183f8d99388839632d6354f666d09d3e5629ea19737388613d38a34fd0f6e50ee5a0cc9677177f50028c141378187bd2819403fc534f80076e9380cb4964d3b6b45819d3b8e9caf54f051852d671bf8c1ffde2d1510756418cb4810936aa57e6965d6fb656a760b7f19adf96c173488552193b147ee58858033dac7cd0eb204c06490bbdedf5f7571acb2ebe76acef3f2a01ee987486dfe6c3f0a5e234c127258f97a28fb5d164a8176be946b8097d0e317287f33bf9c16f9a545409ce29b1f4273725fc0df02a04ebae178b3414fb0a82d50deb09fcf4e6ee9d180ff4f56ff3bc1d3601fc2dc90d814c3256f4967d3a8d64c83fea339c51f5a8e5801fbb97835581b602465dee04b5922c2761b54245bec0c9eef2db97d22b2b3556cc969fbb13d06509765a52b3fac54b93f421bf08e18d52ddd52cc1c8ca8adfaccab7e5cc2f4573fbbf8239bb0b8aedbf8dad16282da5c9125dba1c059d0df8abf621078f02d6c4bc86d40845ac1d59710c45f07d585eb48b32fc0167ba256e73ca3b9311c62d1094979", - "57d8dbe10aa3e866b40c0baa2bc492c19ad1e6372d9622bf163fbffeaeee796a3cd9b6fbbfa4d792f34d7fd6e763cd5859dd26833d21d9bc5452bd19515dff9f4995b35bc0c1f876e6ad11f2452dc9ae85aec01fc56f8cbfda75a7727b75ebbd6bbffb43b63a3b1b671e40feb0db002974a3c3b1a788567231bf6399ff89236981149d423802d2341a3bedb9ddcbac1fe7b6435e1479c72e7089d029e7fbbaf3cf37e9b9a6b776791e4c5e6fda57e8d5f14c8c35a2d270846b9dbe005cda16af4408f3ab06a916eeeb9c9594b70424a4c1d171295b6763b22f47f80b53ccbb904bd68fd65fbd3fbdea1035e98c21a7dbc91a9b5bc7690f05ec317c97f8764eb48e911d428ec8d861b708e8298acb62155145155ae95f0a1d1501034753146e22d05f586d7f6b4fe12dad9a17f5db70b1db96b8d9a83edadc966c8a5466b61fc998c31f1070d9a5c9a6d268d304fe6b8fd3b4010348611abdcbd49fe4f85b623c7828c71382e1034ea67bc8ae97404b0c50b2a04f559e49950afcb0ef462a2ae024b0f0224dfd73684b88c7fbe92d02b68f759c4752663cd7b97a14943649305521326bde085630864629291bae25ff8822a14c4b666a9259ad0dc42a8290ac7bc7f53a16f379f758e5de750f04fd7cad47701c8597f97888bea6fa0bf2999956fbfd0ee68ec36e4688809ae231eb8bc4369f5fe1573f57e099d9c09901bf39caac48dc11956a8ae905ead86954547c448ae43d315e669c4242da565938f417bf43ce7b2b30b1cd4018388e1a910f0fc41fb0877a5925e466819d375b0a912d4fe843b76ef6f223f0f7c894f38f7ab780dfd75f669c8c06cffa", - }, - }, - { - vpubOld: uint64(0), - vpubNew: uint64(0), - anchor: "43eb47565a50e3b1fa45ad61ce9a1c4727b7aaa53562f523e73952bbf33d8a41", - nullifiers: []string{ - "04078ade3eaaa49699a69fdf1c5ac7732146ee5e1d6b6ca9b9180f964cc9d087", - "8ae1373524d7d510e58227df6de9d30d271867640177b0f1856e28d5c8afb095", - }, - commitments: []string{ - "ef6184fed651589022eeaea4c0ce1fa6f085092b04979489172b3ef8194a798d", - "f5724d6b05f1ae000013a08d612bca8a8c31443c10346dbf61de8475c0bbec51", - }, - ephemeralKey: "04b47556af3d514458e2321d146071789d2335934a680614e83562f82dfd405b", - randomSeed: "54a45eb32c165448d4d5d61ca2859585369f53f1a137e9e82b67b8fdaf01bda5", - vmacs: []string{ - "4a317311896ae10280a032440c420a421e944d1e952b70d5826cd3b08b7db963", - "0fe4fd5f22125de840fcc40b98038af11d55be25432597b4b65b9ec1c7a8bbfd", - }, - proofGroth16: "052cbf7e1c1785314934b262d5853754f1f17771cfb7503072655753fa3f54ecc587e9f83b581916092df26e63e18994cb0db91a0bbdc7b6119b32222adf5e61d8d8ae89dae4954b54813bb33f08d562ba513fee1b09c0fcd516055419474dd7fda038a89c84ea7b9468287f0eb0c10c4b132520194d3d8d5351fc10d09c15c8cc101aa1663bbf17b84111f38bb439f07353bdea3596d15e713e1e2e7d3f1c383135b47fa7f81f46df7a902a404699ec912f5656c35b85763e4de583aecaa1df", - encCiphertexts: []string{ - "d5d2677d9c8ffee877f63f40a5ca0d67f6e554124739f805af876aeede53aa8b0f8e5604a73c30cbd09dad963d6f8a5dcc40def40797342113ba206fae8ebe4f3bc3caf69259e462eff9ba8b3f4bfaa1300c26925a8729cd32915bfc966086f0d5560bbe32a598c22adfb48cef72ba5d4287c0cefbacfd8ce195b4963c34a94bba7a175dae4bbe3ef4863d53708915090f47a068e227433f9e49d3aa09e356d8d66d0c0121e91a3c4aa3f27fa1b63396e2b41db908fdab8b18cc7304e94e970568f9421c0dbbbaf84598d972b0534f48a5e52670436aaa776ed2482ad703430201e53443c36dcfd34a0cb6637876105e79bf3bd58ec148cb64970e3223a91f71dfcfd5a04b667fbaf3d4b3b908b9828820dfecdd753750b5f9d2216e56c615272f854464c0ca4b1e85aedd038292c4e1a57744ebba010b9ebfbb011bd6f0b78805025d27f3c17746bae116c15d9f471f0f6288a150647b2afe9df7cccf01f5cde5f04680bbfed87f6cf429fb27ad6babe791766611cf5bc20e48bef119259b9b8a0e39c3df28cb9582ea338601cdc481b32fb82adeebb3dade25d1a3df20c37e712506b5d996c49a9f0f30ddcb91fe9004e1e83294a6c9203d94e8dc2cbb449de4155032604e47997016b304fd437d8235045e255a19b743a0a9f2e336b44cae307bb3987bd3e4e777fbb34c0ab8cc3d67466c0a88dd4ccad18a07a8d1068df5b629e5718d0f6df5c957cf71bb00a5178f175caca944e635c5159f738e2402a2d21aa081e10e456afb00b9f62416c8b9c0f7228f510729e0be3f305313d77f7379dc2af24869c6c74ee4471498861d192f0ff0f508285dab6b", - "6a36ccf7d12256cc76b95503720ac672d08268d2cf7773b6ba2a5f664847bf707f2fc10c98f2f006ec22ccb5a8c8b7c40c7c2d49a6639b9f2ce33c25c04bc461e744dfa536b00d94baddf4f4d14044c695a33881477df124f0fcf206a9fb2e65e304cdbf0c4d2390170c130ab849c2f22b5cdd3921640c8cf1976ae1010b0dfd9cb2543e45f99749cc4d61f2e8aabfe98bd905fa39951b33ea769c45ab9531c57209862ad12fd76ba4807e65417b6cd12fa8ec916f013ebb8706a96effeda06c4be24b04846392e9d1e6930eae01fa21fbd700583fb598b92c8f4eb8a61aa6235db60f2841cf3a1c6ab54c67066844711d091eb931a1bd6281aedf2a0e8fab18817202a9be06402ed9cc720c16bfe881e4df4255e87afb7fc62f38116bbe03cd8a3cb11a27d568414782f47b1a44c97c680467694bc9709d32916c97e8006cbb07ba0e4180a3738038c374c4cce8f32959afb25f303f5815c4533124acf9d18940e77522ac5dc4b9570aae8f47b7f57fd8767bea1a24ae7bed65b4afdc8f1278c30e2db98fd172730ac6bbed4f1127cd32b04a95b205526cfcb4c4e1cc955175b3e8de1f5d81b18669692350aaa1a1d797617582e54d7a5b57a683b32fb1098062dad7b0c2eb518f6862e83db25e3dbaf7aed504de932acb99d735992ce62bae9ef893ff6acc0ffcf8e3483e146b9d49dd8c7835f43a37dca0787e3ec9f6605223d5ba7ae0ab9025b73bc03f7fac36c009a56d4d95d1e81d3b3ebca7e54cc1a12d127b57c8138976e791013b015f06a624f521b6ee04ec980893c7e5e01a336203594094f82833d7445fe2d09130f63511da54832de9136b39", - }, - }, - }, - joinSplitPubKey: "f4599f5aa5dfbb45da60cdceab7eefde89be63f3f7c0d2324847cce1405def7c", - joinSplitSig: "469b0e272494e5df54f568656cb9c8818d92b72b8bc34db7bb3112487e746eefe4e808bbb287d99bf07d00dabededc5e5f074ffeae0cba7da3a516c173be1c51", - bindingSig: "3323e119f635e8209a074b216b7023fadc2d25949c90037e71e3e550726d210a2c688342e52440635e9cc14afe10102621a9c9accb782e9e4a5fa87f0a956f5b", - }, - // Test vector 2 - { - txid: "6732cf8d67aac5b82a2a0f0217a7d4aa245b2adb0b97fd2d923dfc674415e221", - header: "04000080", - nVersionGroupID: "85202f89", - vin: [][]string{ - {"56e551406a7ee8355656a21e43e38ce129fdadb759eddfa08f00fc8e567cef93", "c6792d01", "0763656300ac63ac", "8df04245"}, - {"1a33590d3e8cf49b2627218f0c292fa66ada945fa55bb23548e33a83a562957a", "3149a993", "086a5352516a65006a", "78d97ce4"}, - }, - vout: [][]string{ - {"e91cb65a63b70100", "09516a6a656aac636565"}, - {"5cc7c9aae5bd0300", "02636a"}, - }, - nLockTime: "675cb83e", - nExpiryHeight: "43e29c17", - valueBalance: "44b8b5b99ce30500", - spends: []spendTestVector{ - { - cv: "b0f5b874a6ecabe6c56ee58b67d02f5d47db8cc3458435d5088d69b2240c28f3", - anchor: "71c012c415d2382a6eebc8b3db07ea1cbf28288daaa91538de4552eeeef72c24", - nullifier: "c85d83db20efad48be8996fb1bff591efff360fe1199056c56e5feec61a7b8b9", - rk: "f699d6012c2849232f329fef95c7af370098ffe4918e0ca1df47f275867b739e", - zkproof: "0a514d3209325e217045927b479c1ce2e5d54f25488cad1513e3f44a21266cfd841633327dee6cf810fbf7393e317d9e53d1be1d5ae7839b66b943b9ed18f2c530e975422332c3439cce49a29f2a336a4851263c5e9bd13d731109e844b7f8c392a5c1dcaa2ae5f50ff63fab9765e016702c35a67cd7364d3fab552fb349e35c15c50250453fd18f7b855992632e2c76c0fbf1ef963ea80e3223de3277bc559251725829ec03f213ba8955cab2822ff21a9b0a4904d668fcd77224bde3dd01f6", - spendAuthSig: "ffc4828f6b64230b35c6a049873494276ea1d7ed5e92cb4f90ba83a9e49601b194042f2900d99d312d7b70508cf176066d154dbe96ef9d4367e4c840e4a17b5e", - }, - { - - cv: "26bca7fdd7cc43201c56f468fadc42cff0d81a966417ad8f097ebf3b25879e55", - anchor: "c23e34da91c816d8d1790dfe34bdce040db1727af24d59ef78d3f4aac2b59822", - nullifier: "d6f12f24fd364496b3be0871ca3dd9625348a614b59bde45885649bae36de34d", - rk: "ef8fcec85343475d976ae1e9b27829ce2ac5efd0b399a8b448be6504294ee6b3", - zkproof: "c1c6a5342d7c01ae9d8ad3070c2b1a91573af5e0c5e4cbbf4acdc6b54c9272200d9970250c17c1036f06085c41858ed3a0c48150bc697e4a695fef335f7ad07e1a46dc767ff822db70e6669080b9816b2232c81a4c66cc586abfe1eaa8ca6cf41fc3c3e6c7b886fb6dac9f4822b4fc6fff9d0513d61a21c80a377671d135a668a0ae2bb934c82c4142da69d12ca7de9a7df706400ec79878d868e17e8f71ea31495af819a016cc419e07c501aa8309b2e6c85b79b2763733a37bbc0420d42537", - spendAuthSig: "b871b4294a65d3e055ff718dd9dc8c75e7e5b2efe442637371b7c48f6ee99e3ea38a4b0f2f67fc2b908cda657eae754e037e262e9a9f9bd7ec4267ed8e96930e", - }, - { - cv: "eb89a85980f97d7faaed78d8f38beb624b774c73a46ced614be219b3d94873b6", - anchor: "0df7fc90b579abf62037975edd6aacc442190a0ba55b15f81f86bade794ace2a", - nullifier: "9d9a816baf728a955b960b7701fa626687dc3c9cba646337b53e29816e9482dd", - rk: "f5578a8768aae477fce410ac2d5de6095861c111d7feb3e6bb4fbb5a54955495", - zkproof: "972798350a253f05f66c2ecfcbc0ed43f5ec2e6d8dba15a51254d97b1821107c07dd9a16ef8406f943e282b95d4b362530c913d6ba421df6027de5af1e4745d5868106954be6c1962780a2941072e95131b1679df0637625042c37d48ffb152e5ebc185c8a2b7d4385f1c95af937df78dfd8757fab434968b0b57c66574468f160b447ac8221e5060676a842a1c6b7172dd3340f764070ab1fe091c5c74c95a5dc043390723a4c127da14cdde1dc2675a62340b3e6afd0522a31de26e7d1ec3a", - spendAuthSig: "9c8a091ffdc75b7ecfdc7c12995a5e37ce3488bd29f8629d68f696492448dd526697476dc061346ebe3f677217ff9c60efce943af28dfd3f9e59692598a6047c", - }, - }, - outputs: nil, - vJoinSplits: nil, - joinSplitPubKey: "", - joinSplitSig: "", - bindingSig: "c01400f1ab5730eac0ae8d5843d5051c376240172af218d7a1ecfe65b4f75100638983c14de4974755dade8018c9b8f4543fb095961513e67c61dbc59c607f9b", - }, -} - -func TestSaplingTransactionParser(t *testing.T) { - testData, err := os.Open("../testdata/zip243_raw_tx") - if err != nil { - t.Fatal(err) - } - defer testData.Close() - - // Parse the raw transactions file - rawTxData := [][]byte{} - scan := bufio.NewScanner(testData) - for scan.Scan() { - dataLine := scan.Text() - // Skip the comments - if strings.HasPrefix(dataLine, "#") { - continue - } - - txData, err := hex.DecodeString(dataLine) - if err != nil { - t.Fatal(err) - } - rawTxData = append(rawTxData, txData) - } - - for i, tt := range zip243tests { - tx := NewTransaction() - - rest, err := tx.ParseFromSlice(rawTxData[i]) - if err != nil { - t.Errorf("Test %d: %v", i, err) - continue - } - - if len(rest) != 0 { - t.Errorf("Test %d: did not consume entire buffer", i) - continue - } - - // If the transaction is shorter than it should be, parsing - // should fail gracefully - for j := 0; j < len(rawTxData[i]); j++ { - _, err := tx.ParseFromSlice(rawTxData[i][0:j]) - if err == nil { - t.Errorf("Test %d: Parsing transaction unexpected succeeded", i) - break - } - if len(rest) > 0 { - t.Errorf("Test %d: Parsing transaction unexpected rest", i) - break - } - } - - // Transaction metadata - if !subTestCommonBlockMeta(&tt, tx, t, i) { - continue - } - - // Transparent inputs and outputs - if !subTestTransparentInputs(tt.vin, tx.transparentInputs, t, i) { - continue - } - if !subTestTransparentOutputs(tt.vout, tx.transparentOutputs, t, i) { - continue - } - - // JoinSplits - if !subTestJoinSplits(tt.vJoinSplits, tx.joinSplits, t, i) { - continue - } - - testJSPubKey, _ := hex.DecodeString(tt.joinSplitPubKey) - if !bytes.Equal(testJSPubKey, tx.joinSplitPubKey) { - t.Errorf("Test %d: jsPubKey mismatch %x %x", i, testJSPubKey, tx.joinSplitPubKey) - continue - } - - testJSSig, _ := hex.DecodeString(tt.joinSplitSig) - if !bytes.Equal(testJSSig, tx.joinSplitSig) { - t.Errorf("Test %d: jsSig mismatch %x %x", i, testJSSig, tx.joinSplitSig) - continue - } - - // Begin Sapling-specific tests - testValueBalanceBytes, _ := hex.DecodeString(tt.valueBalance) - testValueBalance := int64(binary.LittleEndian.Uint64(testValueBalanceBytes)) - if testValueBalance != tx.valueBalance { - t.Errorf("Test %d: valueBalance mismatch %d %d", i, testValueBalance, tx.valueBalance) - continue - } - - if !subTestShieldedSpends(tt.spends, tx.shieldedSpends, t, i) { - continue - } - - if !subTestShieldedOutputs(tt.outputs, tx.shieldedOutputs, t, i) { - continue - } - - testBinding, _ := hex.DecodeString(tt.bindingSig) - if !bytes.Equal(testBinding, tx.bindingSig) { - t.Errorf("Test %d: bindingSig %x %x", i, testBinding, tx.bindingSig) - continue - } - - if hex.EncodeToString(tx.GetDisplayHash()) != tt.txid { - t.Errorf("Test %d: incorrect txid", i) - } - // test caching - if hex.EncodeToString(tx.GetDisplayHash()) != tt.txid { - t.Errorf("Test %d: incorrect cached txid", i) - } - } -} - -func subTestShieldedSpends(testSpends []spendTestVector, txSpends []*spend, t *testing.T, caseNum int) bool { - if testSpends == nil && txSpends != nil { - t.Errorf("Test %d: non-zero Spends when expected empty vector", caseNum) - return false - } - if len(testSpends) != len(txSpends) { - t.Errorf("Test %d: Spend vector lengths mismatch", caseNum) - return false - } - - success := true - for j, tt := range testSpends { - tx := txSpends[j] - - testCV, _ := hex.DecodeString(tt.cv) - if !bytes.Equal(testCV, tx.cv) { - t.Errorf("Test %d spend %d: cv %x %x", caseNum, j, testCV, tx.cv) - success = false - continue - } - testAnchor, _ := hex.DecodeString(tt.anchor) - if !bytes.Equal(testAnchor, tx.anchor) { - t.Errorf("Test %d spend %d: anchor %x %x", caseNum, j, testAnchor, tx.anchor) - success = false - continue - } - testNullifier, _ := hex.DecodeString(tt.nullifier) - if !bytes.Equal(testNullifier, tx.nullifier) { - t.Errorf("Test %d spend %d: nullifier %x %x", caseNum, j, testNullifier, tx.nullifier) - success = false - continue - } - testrk, _ := hex.DecodeString(tt.rk) - if !bytes.Equal(testrk, tx.rk) { - t.Errorf("Test %d spend %d: rk %x %x", caseNum, j, testrk, tx.rk) - success = false - continue - } - testzkproof, _ := hex.DecodeString(tt.zkproof) - if !bytes.Equal(testzkproof, tx.zkproof) { - t.Errorf("Test %d spend %d: zkproof %x %x", caseNum, j, testzkproof, tx.zkproof) - success = false - continue - } - testspendAuthSig, _ := hex.DecodeString(tt.spendAuthSig) - if !bytes.Equal(testspendAuthSig, tx.spendAuthSig) { - t.Errorf("Test %d spend %d: spendAuthSig %x %x", caseNum, j, testspendAuthSig, tx.spendAuthSig) - success = false - continue - } - } - - return success -} - -func subTestShieldedOutputs(testOutputs []outputTestVector, txOutputs []*output, t *testing.T, caseNum int) bool { - if testOutputs == nil && txOutputs != nil { - t.Errorf("Test %d: non-zero Outputs when expected empty vector", caseNum) - return false - } - if len(testOutputs) != len(txOutputs) { - t.Errorf("Test %d: Output vector lengths mismatch", caseNum) - return false - } - - success := true - for j, tt := range testOutputs { - tx := txOutputs[j] - - testCV, _ := hex.DecodeString(tt.cv) - if !bytes.Equal(testCV, tx.cv) { - t.Errorf("Test %d output %d: cv %x %x", caseNum, j, testCV, tx.cv) - success = false - continue - } - testcmu, _ := hex.DecodeString(tt.cmu) - if !bytes.Equal(testcmu, tx.cmu) { - t.Errorf("Test %d output %d: cmu %x %x", caseNum, j, testcmu, tx.cmu) - success = false - continue - } - testEphemeralKey, _ := hex.DecodeString(tt.ephemeralKey) - if !bytes.Equal(testEphemeralKey, tx.ephemeralKey) { - t.Errorf("Test %d output %d: ephemeralKey %x %x", caseNum, j, testEphemeralKey, tx.ephemeralKey) - success = false - continue - } - testencCiphertext, _ := hex.DecodeString(tt.encCiphertext) - if !bytes.Equal(testencCiphertext, tx.encCiphertext) { - t.Errorf("Test %d output %d: encCiphertext %x %x", caseNum, j, testencCiphertext, tx.encCiphertext) - success = false - continue - } - testzkproof, _ := hex.DecodeString(tt.zkproof) - if !bytes.Equal(testzkproof, tx.zkproof) { - t.Errorf("Test %d output %d: zkproof %x %x", caseNum, j, testzkproof, tx.zkproof) - success = false - continue - } - } - - return success -} diff --git a/testdata/blocks b/testdata/blocks index e3af9b89..4c8f0682 100644 --- a/testdata/blocks +++ b/testdata/blocks @@ -1,4 +1,4 @@ -040000008a024cebb99e30ff83d5b9f50cc5303351923da95a8dc7fda3e0160900000000226b1c86e101f1fac09aa533c246bd6843ee6b444ad3ff251df0a401f16f0981000000000000000000000000000000000000000000000000000000000000000069cb7d5b0f1d0a1c0000000000000000004d6cdd939a4900000000000000000000000000310c50b3fd4005008f78a11b4b81126dec31cba6ede2e131ab376a8611237b04d20cd8bb4253f1e8721e0941970e0f58c206f09c8c86c70b2f73b3b5994571c389a90fadf6b812cebba5afe622b3e4aab20a88c805e1f3925e31300df11c09e3d4b7acebbfb156b75e213e36f65d88f023feae5d37ab2f13a1215299155b7d9a1f4cbb333212e8ef9959785adfcd2506d33ac1021207fc32584022080f4a7ba67157ea99f6fc645737a2a5c6f5d99700ff0c1a92f2845dba3b022d24de26691c0b56977b13e94d5f93ebe423d3d422a478f2e6d9f0b113b3fa0434fe432c173013b1a641e884f74922a3e6fc6cde4dedcd8c2458be13fb91f5285ffbf8a5dd207dad3b011b832444a85e27da43c1f94864ad7b066559d5210bf495565005f9297f6204ad7b4c395d5faa19c2530427ad4889555aefd272185f825564525f6d3e0bb62b18cce8ca120a6f636ee36f18612fede045bae81403752f8154e7c3bf4fef7134591379389155360cd15328c731c1ed3b57f439994b0350799e91f91f53e30ce9bed6ea9ce01b3f74f6041b739d85965d789b9c43ba65cf4f99808ef03c2ac46461ba0eedc05e77400815af9d8049027bb7fef6646142d86ef35adcca060e8a656c6c6cc811f65231b4ac531fa2083ddffbab26d19409cc357b89ed7da3d25476cb9d9a9c939c343d23fa35a09c52d593fb03f609dafcc28579fdd35e044168e33e747757bfdf5123080b6799d2527368f90de7f126304610765670214b6e9b6f497a491650db139129da19964461c368e2524aa1524248ed92561b3e94aec38d5ff4adcab5f73565dc7626477b1d56620ad1bb49000d1cd915a260c0913960c493edb9770d2fefae76dae63963e147331a51b1c66d5ff3ecf87f141306d575e60ca3b34fd26cb0b1d735dbbb1977db519a7a9d345ccc77121b688c7470975ee9dbfc489800f25a41d406ccbcbe1f01c629dfcbd59ea5dcd00334cc6af8718e08631c3a83d5e6395b4acc3afab48b145b73a064904176c30c2cd9a876ebab5f333b6ebd17c10e31ce6f009daad792c31fce13dd6d401120eb08d66348c13735ffc667e653ccf80bda54d0773473177433406a0c2001ec9f534c54e667c5a3cff3bca72625b5fee94a51dfc2b5b3fc73353b2b2f3a9e708932003e771dd21441e9cd075765e33ab5fe0db05b4b087bb04608d5fe5bd32f8272752cb1e59f856eab0e366a935a9651be4584f8886649c66bb6bff29ac82e0bf749c94e46cc6a42988196c15d3b2a6376185b990e653cbc8a77e56fd3e74378bcd54f4540afdf39650950fd18192198bd743708fcd948ec57aab07d71211157758ca83509cbc0094158b75bd840810fb266f91565df4f4013aaa4e414b3a56bf8442938189441ae24f8e2417b7bb729f9cf27a2ee5da291fed793afcf09a1c63f14af0dab7aa6b34b1be3d545356653f14a2d31173cf50fa6aed549933a5743cd46809bf6e64824111cb5032130536943f359ffe5cc7ad5b4dcce3f2d078f7ca751fb08f8a5d9ac3970cb9f2cf31ed1a713cec3e8bac8586ce10c74e215c3d691c58e176b39382da1fe4ff0edc7f23373f594ab00c987c772f92317575d4d199e79f5b95fe1986ff0dd4d4a339a797b3b366e737bf8befbf80fe58f0d5b6f35c831d00ebd0c47ddcec914cf879ca685c0724b9036f98abacd96102657148a55e6e7896f33d51daa97190a233a4d68278b51c2bd26dd23916c54e605b6578a8e3657359c36a9ee2e4c0f650a95a3f4b61c4133ddaa901154d20c807a6ab85211bda537f26a4fe4b53a4a64f5522d13eb75cd2bab7669ef47d1c7fd43416182bcde9370105383fa2c0c574d4895d2fea59889068e66f83c8f15118c7ce7adb41fd8de61c2afd7277d6ac31346aa01030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff0503e0ce0500ffffffff0200ca9a3b000000001976a914361683f47d7dfc6a8d17f8f7b9413ff1a27ec62988ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000 -0400000015bea7ff6272a28e0dd187b5160d8fae6f2ad6316d652fdd26b92204000000008ede1b5093bbb6ae7c883dbd4b8bfc9e97290ac6ba5a617c880d3aef1bc41d9200000000000000000000000000000000000000000000000000000000000000009bcb7d5ba51e0a1c0000000000000000002c476f1d2c08000000000000000000000000002c27173bfd4005001a40fe9c1795f9617212aa1ade76cd93f85db2e5254a8ffd5aab68218adb36065f6947477a423c26c719803ba9c01555a1d81ef1cbd4ab0e7a7c9c7aeba622c7c1614f16409f5b0f757a93d12923027fbe875e01d4003525150ba313f43148bf39e7836a3d5fae611de2de0eebd8a3d1b163e4ad7b3ad0bbc00a3f4b8d029ee0e23f6354cbc381711eec9df770f31149ac1e0eb55229d10d7ba106edb12834296cfd241f5a62a6014ac0726dc4543f05a88138ad2d7654b707aa799115886e467957d5f195fda7a0a0d7908e084b974a1e0565e222acc9670f4f7191b4052f84595457315647468f532773a0755b0b3c7649aad0ce1e9da9597819022f7c951ddf47978c5985bb3f4350b1b91f2eff7f149ba3992316712d0bb2a3b6fbb3dee54b516d22b21f60c23f87e3513d2ae3447191b55b17f3671f9e3629da0eb3b8261cb7eccc675761f95452435bf6b6f800a4e5a2deeee86b8ae3d026a11eaed463373c378b079a68b51eed25b596fd519a2530f9414684d71dfa427c461ab170137fcd343af2fef3122bb037df784e647b6386823340abffa60668eb7a5729fdda35d42602729b2fa822d11da41c304ce27530bc17e717fdd10a2f221cf793e4db842ee2f6f04b2a74d3945214ec0d1819005f0e42f68c8183e9a0a87011180ed87eb3205e7f01d7a760d95b874ae2d17e915b267cbd0e4e06e99982744e7473f74af2ff766730ee01f7528478116633f7d2eebd87d8e134429e4d29b92144fd0ffb1628add40f98c4df2856632055a3d2c70a2abf69851929b6aa5a9de2af2148ea564a7520f6d95e5724b40c2a3c9d39c995b2f3e131795f2299029c215b07cd14bce9e10fd05296a8a646302ff4c45e85a4bc3bec12fedc1d1f60da8f6aac22f746d43a11bd08be8e52153c60ab71061d35284872c427e3aaff0d8a3a82af016db8d42937f04be1ccb021e7152aa82a068b0a1b04cbe51b904ffc00f3bd46cedc3c58ba378355360e11982c9981c6a5ae7c6d21fa3b2e54b8e01d49a0f618df191ffbd3274b7ac3c5f066623a15c1e07eb64e1db18f37cf4d4452c441c63b8075ca220d0131a2d022da9305700b8e0d236ab4dfd17c2f453c3f17fd722925de3cdfac190bdec92340db74ad46ff693f79c92bad961d021e3a01dcf16445d66713e53bc37b89720c887088d26831e5a4a7011366eb3b6920df10a3316eb41dfbe9ef6f697bfb673be8bce2a64c16baa1ed1cce0a0bc32595bbc22f42ba8d2dbdba3f6097cf7b27cf4717d7187bab684e364a17c69e29c49ff3aab10d8f9d3002149c72d90cf115723ca3aee3c8b8014918896bf63a2746d7a486a496b274b21765dcdeb83b387252b876129b65ce91e613783f55aa1ca85c07a448b4eb36d95681c1851f7532a47e8056eacbd8c45d01b61dda489c744f267111294fef34a98c5391d6ff31491f974fdcbee1cfcd740c714abd9a616878d604076dc376afd53826da95c4b81ad0f991642a6bd3d518feec5a021e20a73be5924147203586c75b3fa9c51111119bd39061fdeedbc1a1a92aeafe834dff67071438f21b247a5713e66bf156ab70f04995ba70f4f0129da42ee3d5ca8da38c35c2b8d33d19e79618a0061ac8fdae6a58250725ab51dbda4a6541d7fe1cd31403ad8d6cfde6badbb85ff162bc1f08b109866f8aaa1688b2de068fa272a3a6371f3876101a37191dc9c308eff5bbe41512b7a749668f2a35f43e2dc67c91ed13e39bc4ce5783b795d1a16ba03886ca053dbd6a4006e0f5091e6955d7abdc154373d10d4d60ecee597c0a8d71dfbb2e3b4fa83554d459c0a68d655c9b28ff0cdd2ace655bdd8dc2322394a34255b1d5373dc39c310244fa0fa1c557d7caa3d029ad36966d0d7606e301030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff0503e1ce0500ffffffff0200ca9a3b000000001976a9146d17c0ca63e020d639f5fba181ca4f5da48b8f7088ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000 -040000003723935953489b860d1de1b8fc8240ced04625f0ffd23f16519b8e0700000000f066ce1a8f2b97d05dc15ee51e1eedac93ba6057a953eb81d3f856e9f4b7ae270000000000000000000000000000000000000000000000000000000000000000c8cb7d5b13520a1c00000000020ff5b32c230200000000000000000000000000000000000d5e39cefd4005003eed282b472cfdcb0540b4f4245ed479c5357ff11989ef70be9700eff5987318f727dafa0b467302aa016e5e956c5a5d23f0f9e4a5623b9dda30e0f1b3c47e8c3d151e686cabd4ac89fc627e5cded736d707780c3dc341370bf7af4cb291878a1c4236cc207f39f081ef26b50fe099bdb90c8a9462663d1aaf079fefa010c2a8c29211f08b8debf2f90e4afade29bc53d74253c3fbdf265759fee75e1ab46adc90f38bba5f73e80e518def9b61e8b714d6e6a1dd585539ebd1f5f2040e8e73498455bec4fd333747f96823c66fb859177d13f3ba05868918255bb022cc32dd0a099438fdb1b31b15e2de9acc341066d6b20e4c1d18f56397dfe97919a2eb988e114e3cc015c1c209d906e494c46b9dac34a99ad0595868b4f84a1474ff532202149ef12acc1db1f9307213f49efcb0a408fcc1cc6124e637adb22ee4368da8d0a78b696956e7e4c9feea5d2e198f0e00bdd16ca49956c32ff793e3ff6e635efe57788fbf11a8b2a771d1981aeed6462fdd74a6a9f5d813d86616923f5add19a41d507ba22b69a0caf4a608f8b7673bd605d7f2b022b3d8436448aeb34e491c12addc3911564ecf5de8b1355f1762dc402e37915c35cb69de2a9d575a765737ff92c653eed1eb871270589c2dff15f827ba45b19ab3d25da28fe3176895a8cc32a943391276625d991492ed315ab22a7192fbcc5f5eb01611640c96585ac4db590777a87444c3dadc653bc2bb279474ad76ac00d5972b92e026d45fdd0ed84b099a13d08c00eea952c5a2a78218e06ff989f85b52801d203f82d395310f67e80f4646f3f29c5b0c2f9c3ce31530b766c12f3253a6f3e2aed5b66efd9dcdb6f1de1e601a36b00fa2b7c4d6241e46afc746cfb73f1b2f19c60f81472bd15bdaff21afb0c44514915bfbdb5a20172209c4c9c281ba14950b07b22d097bc0cce5fa017ef344aec83bca60db20f4241ab3e1d29f7f200a24b801aa1e2c638d77ac13c0732da3561e3e1bcc6f2ac78fcf21e7f113b56d83c0343a1cb6d8b69eb12975891c1bb27885f1efc8c97a0af71176f716fe36b61262e23b48e7266f6551821948e4c44d48153df60a1a921faa5766e4a99239c2f36231bdadc93e749db715ff7e8ced9ef6cb28e7a5a8aa7603de56e73ea5fd1d79be8974a4c071ed1b969382da51d68b11f4722303d74f82849cb05d07c0c313d9a0d5eccbb853f85f0c7002c67eeb93737060180a3f557582d3849b73bf13807ff3f4065faacd7562487fc96a20fcf2cc892e16ad74b813caed4f72f11293243912c976a9f2035b099b9b9debc968aec54031bb9f4eb7815ddb3bffc128d6be34814fbec58b17c6dfab6c812a5eb3fd15d60fe3fa55128466cb80bae5579b71025e6a9c34b7f818eb11cdb029eff94fa465e419b9aef2e8679f12ce02b780aba2c17313bb375033beda50e90be5fd949002fbb85634d42598b62d21e346d13c99e4b6364354136f828f972a7b2fd8c4464defb82629ffdddea6355afcf76a5b6a1f797b2a46b51cee7f95f812d926d00436f98711c6f6a1e374a5f8a6f73f39b70150ae7315988b1a2c88174e9bad58c2e0f82476bb1c9ebd03079970441810e5c7c1b8d3a5b8eb21750f29eb92f348d5ccd2dd13fc68b063c754564f490e33ba1bc71b111eb14500d1dfcd883be26a8952aed912f4a999831b80a94d1f994c69558334e165513fbe129a9348922d6205d0bdb3fed3d0fec41a8f7dac2a46f2f9bdd837d6fa791bd25ea3bedf454b7fcf8907000a78d4e911bc798c6d261817400572cea0a26e9e2f5b16cd7129e92738be52b4832c2a072e2c7db3c2b889fa089b1284a3ae2ec654da817e15e0b26856be1caa1c7c0b3446158365a0992bc01fc67c19d5444ad8c6be044902030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff4903e2ce050004c8cb7d5b598c2ba5c7f0b4587e7d60e62e08985aee803a179dda187d2f0c5284df7da7482f4f76657277696e7465722f5361706c696e672f706f6f6c696e2e636f6d2fffffffff02d0eb9a3b000000001976a914de2757e612dee78a681ace9b8e623570dce776f588ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000030000807082c403012263eb14eca399ec05b64be85325e2c5ab93e5d70d7dc49b4e8b8dcb52ff0f7b680200006a47304402207ade36a404000f4d5cebed5c28e28c57a539b7985b66aa4c014e0c59a43fde3a02201bd61f0a998301241d83fc9370654e0acda7cc6602258b954af6bf06605dffaf0121026e88bf2ad6aa4fdc21b2636583184a589fcddb3c1ed40b4e3613cf61601fad54feffffff01bc131600000000001976a9142f592f81575c361e91397dccc60772b9335f432588accfce0500f4ce050000 -04000000c848f5a041f4406004243ecf645ef91f303f6766f5615e4cd2c7ab08000000002c46ffced042e210ec98759ee1bf5792b62c3a3c56f24667422350dcf3c84e070000000000000000000000000000000000000000000000000000000000000000d4cb7d5b7beb091cf18a4fa59e6d1400000000000000000000000000000000000000000038d0a613fd400500572cbab31dea47dc52529819b5b80cce7fa6ff4f0c1e8f09d50bee7b5012813826948828c3d5e6f14505a4f7ab54884cb7b8f1a49675b99f820d219567d72695fc14f74a26554ad3c346123852669bd29e50eb056cda5e176bd065ccbbf2663f3a12a11f7773a0847c09059fddb09a37908e1a8240f29dd6eaec78f92b1f67e2a1cd27c2b1d6f9c5c7d0c8545dedb6f949c93507838d4525606b73d176ed8ddfa2a20d93ba4d4303512f8fa4c96efcc084329bad15f9997edef03fc90edcd1e7f943c95b5c7123d66a4d0ec9012ab2578e1d9bbcb47bdf56e9eb72e3c82dcfb14ed9785c860522791bb3bde200f76db5e907555c27ef91489cc4410b4129e4aa08b800c00952b451cf3640c326b690e719d92cd2a87799d1dd63b36984c30a6582bdb1fc6d16b323c2ef33b23deaf2325763f8b1fe7b21dd7c801ffc7df2c4d68511aff3b2059e681bb1afaa19090d03592df27c452350d040718758902afcaee5fa48a00e9fffbd3ec4222973c2686fb3e7beee6c5ebe0ecc130b8981d00adfcd464832b625d7cd82adb99e02f71c4b3d9ec6ce5812cb5075cd70cffd967e73b9f08c04c97c728e02efa88b18f318423ce212fca69c05b743289eb18912bdbcde1bc5fb507286baccb1b8937438092c5418a86fe56bdf441637ac4a49e172f491f743a694bc0516e02ebb1e36ec3b4a460e5f45bf6d2c055924c396c9546f9839b696785fdc09f99ddc7e701a3f0f61b7cb90d6de292400b8423f45bdb6d11d921711cc0a5316d200c220f70e80cda935f6b911e34d1aed9769d40ab973a6aaf1bbbc33ecf57f60d93a490d5835583ca4b70de9218510784caaf2d28c1b3dfe1af1e47ea3c96ddd46e27343ea5b80d175c3ef69b5387d2bfae8d026c765d2f5ec7ec7dacdca63dfbae85974e5a57e636edd68754664b56b9421a009ed09d7007b72db75980945b9f8e0da56d03cdcd8cdb9ce8a6d21336a8ab946fdeb59472585584a85e59e31152f0a9e03da924877854e93d5f6fbcff21a4165bbc4b60c8a195bd46761499fe1a28eb464c0957203d337cd0781d1e385308017999260c8da0933ee3b751d3ce92015b3e67393dc98d883d70113d24f72c4b73a885f1cea47062e88aba3983f192c69dc10db68b95fb3e021363ba87ad938bf97077263a7aeca4f769dfd54370194582d31d7c632c60833fa9346814b26ce1d7c451d6fbd6c309de603780185f499b963a7310abc42450445a2f2840f25350d38f2140b3904fd636ff5016334e2c234d72eff0d8096588c71ffdcde3569b3914f02acbe1938957135acc6d10f2d5bf56885c177e6ca778f7ce431225fd535a15949fdfd2e8ab0c47fd8251e294c02cf98d91340afb5812950c46d9b8dadba902fc70ea73c2c26b5a4d0d59410da46018486d9ec5a0a9b89422190afb7e72b7150f97b359d70841170ae23bacf915f10a019c601f48017472dd2b7c7b89bf0128dfd4a01e450adf17071ded02a3ae13eb7f8fec54510955a305256b2be2d97994140f36222d5ba55ec0ea35749fc894289af4454e944c1dbb1d7d40f1e72276651c4e3542bfd9f145500d9295eab83c6fc92362875620cd0159b7b53ff530d66b25adde2187438045c5fc4b8bdaff93dfb65b85c71753846bfa91f6ac60c9db561423b6109feef932486ece2cdc3c539112318c79ecc7e08ddda73ce663c3d4e9b9a7c743955330f1ca702b8a5673f82328b5f637dfa3743bcbe07583bfecf731cab93abc8d4a5dbfcdbf3536db25f1fff0d71175b744d561eecd1a89fe0484d466d2c5a68d61abba2dd9e8786211482b2b50218eec505f94c67971242a67a93872681ac81e1f541ecbf95a8bd7f5d8c1e60d95a76138bab40da3455b5a36549920d7f4d0102030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff1a03e3ce05152f5669614254432f48656c6c6f20776f726c64212fffffffff0288f19a3b000000001976a914fb8a6a4c11cb216ce21f9f371dfc9271a469bd6d88ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000030000807082c403016f4029430669c709d041c579c8e9469446f1159064613e6cd47514691b93b8ff000000006a47304402204ff60fca23a36026d374850bc018e7c714de78d8560d4188fa247b1a4ee4dcee02206538b475265173915662465b4a8554a111003701371d553134047bf185c98ba20121029ba2dc5033c7c17f06274b85c0f8e51ad518f34235713ffee4705867162819fafeffffff0208672302000000001976a9140ed9a32475fa7214939a6705fd790a74976bab0388ac3e926c01000000001976a914d752bedc09529fb3719004407dd6b5f29000e3e688acd7ce0500f6ce050000 +0400000006785624481f381e68b4506ba5d6cc53ddd6dc876cbc97e45e3c7c4a626a19008c450aa9112b2e900eba30c2e3b8428c23fb2a30bb7fa34a853c97f02fb1a2200d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5aecdc295c172c191f870000790c975f8c9f0a77f12ef1e021c6045c3c9acb7c432c2aa7ed42ad0000fd4005005db35c547608bbf32bd1b90369e6e0bc1e275a0d064f092b235835cb6dfc9158c6146a0e5cfbdf2d2f04f71478f7d9212bdeeb938029a5cc217ebf1f517711dbc7cb35d084328b8f7613e878f69e27b2b40e8103292cb87845006943b761bbbe43d94cfbf971011617965a7de1e846a586ad81f2ad138eb0fd74729efc04720badc95a1f1383cc70e890db91c143591ab96526263ec6b8a6186568dda29b9015078d19e87cbb2c00b26edee3e4b227ad6fa090db2d290191ef1043e2037fc0bd225e6a1364d16223b0234311e8c5ff455a0cc2e0c1f84944dfab49864f57e5ec2a2b5e91df5229449ee399b4c721abab63c534602abdf64d37f60905bea71cd59be759bae5967f6f3e1bf732753d4d9c1c11dcb5b6949bc4f63a06a3b0db5c2deebab040c70fc55fc648bb2559fe2da2b4a4bd53ccbca7aaf03262cdf31c5bab90cdc151b686d3e5f8a29103b9346602b5394b245dc9655116d33c2ed9d31563bb74950d0927895a024c119d30d4f2c760c346b11108ab29db2931123aa36937cbd152756758e3d341ec40db1d1b539fcbefa2e76af9827f575025c58569fbec1f806607e03a6eabc228a900a18144e310ab608a930ee6a10d5e6fc7ecc9934fb42b51f0a6552e2a1ba1d627b51df2d27889e14b87f093452b79bce3e2a74398f4b748eda48495b30437bef13527a231ce724f24d5bcf4040cc77f56dab50ff30ce12039ca968eef3c5ab44b2cdd8f7fecb36ed39bc624807a4da49d5b2f53abeb1a027676fb500fb89d7092eef849f254dfd3a7325e4b810687b8567d7db8e796480836bbeab2efff44bb0421f4e3d5d1493bedc2c331f75b7ba931e93495dc379fa22404a2e60919fb2644ee4e5c3bc2025fb17540dce5dede2fdc4fc6b87494ae26ae89bbe954ee8458740601a665126129c9962052683661efcfffce8303211fe3bf0adac14936927ce2f4b30a22227c39b4128616dfbfcaeae8f4a3b30255d215d1b9ccd9bff2173b5a94ffa5473bc49097030e782d81c1dc4f8c4a25803c6dd9a9e775646084ed2bbbf7457386ece058032128bc87c7b7772be513f79176241de701580b5c04f88ee81e231583528b514f0c478382e60d5741bd0b5582c2ece2767931f538d050498886814c5b27541764bda9734d5c46f2b3c80f2649d40374e45c053c050e33e83ed578f6d5c19435b7f495261e7e522d5225eaead374497fed7c30e322d6acbf512751b58417120bf8d97fcfdaa5e3be842fc7beab85235ff7e9ee459b03ef2ae01ceff42e0621ddce403e65521505ed0b25fca1a0747263e44c72c5e1e95d85119a7956852c629fcb903b9589e768a6bcbec5358a5332368a6913ffb9b9880e8d3701c6a366bec102bab8c6d9e57b2287ede4774d69fe81af83dbd423c905f666170bfc03f360314edf3098fe2036634ad90f326a71faa5b70f245acbdf8f7d4481283819d8dae7823ac31bafef129b94822a4d94b8ea5773b7477269be9dbe3bc8c11886a2d2a8176658fe30e1ba1bdcf320873d1d2325096223af6e142818a2c370b8447db8e6e865fef2074596bc9e679b8c37c51e99c4f35ac8431303fac507193d1d162d309a3d8d6e57cf4cd429b34ce69f69ef2f1e728d7b9347051b46d4ba5a7e7f9d43963e960d0bbaf63eeb69396dc75b325f08e9243db0c678aa192eb3db32d9cf65a0a38bd5726e31380f2de23ea9a019d8496ab511df2d955978fcee6e331aeea5df0a1826639323180b87bbbf6a039e34f482a5e4eabc6d9a139c40e2840b40eb53dea3d549b54405ba7e1b392f308df2abfd6e97a3eb44f540b272a6d97c085be2142445ea27349bc7a5493397ceee4c8016a38457480f4de263cd2c2416296a2d86c19ded93cf431bfdd1e3010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e0ce050103ffffffff0200ca9a3b000000001976a914f4fbba801ad64c6539e64a1478392c57c9a5e7a688ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f570880148700000000000000000000000000000000000000 +04000000913b07faeb835a29bd3a1727876fe1c65aaeb10c7cde36ccd038b2b3445e0a001e1ecc75a33e350c7763ccf055402a3082f28209c643810a2ef17df4b14486cb0d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5a15dd295c21c8181f15000813d02ae5e889354eb9b7b62cca4f139ca6364fb63646ff32f2b4700000fd400500356a828c88fb46eaaa21d759e99109375d79b61557348f830c71fd4dabb126ae794d403af814bfd3fc01b76c2fc110525507f6728bec686ea9a432eda0ef1c8fc97d912923a9ab0d06f8aeef2ba272a3b7125e0e5079f131e4706d59c4f34a2775cfed3fca2cba4a23795351a38abfacf67ae7772e6fd14326efde46192105f2c3b0989a15aedfd22063529005723c8c8ea756ea876e0ae7846d4556e57fdb4b7ae1d2125456130653604eff1a6e632e20350a88b0fece49fa57079818d28c0064764979ed2e75766767a7ddce8578a23712274fac056571619b9ef4371f6924068fd5dcf25112da1ffe4255fe60b851e50b704eef0577967a867a0fbd7ed06f0d93f0f0ae31830ab5eaee5b20757e96233c21f96d6eb8f5b5ff03e88d7a31ced8037a52d94ce82fb719764841c06016ba82e29acb9d521cf9d74e1a1a90175c19ff365b763db1d3fbb6332499a5f90903d26af753812bca3eb2b142992bbd87140c3a0c29732d648c650e6bdfda63a56c6184570bf2bb4d700ba30d7800cecac92f67647bbb282d85228fedbdae1e1e3d56e9cb7743a58f02df45f658cb3b627f6bb311618d1697a217ede3f763d8fbf1fba96f697a62b321d397d492594b477029b23565f8ee80e3df1a74d81be2eaf203e3c9215c72c5fa6ec5f0d20cff3f151d7131ff6a3021f489f87819245d6af34f12dc5d1bd70d2a692be0da4827ed3f81a4b63ee58223bcb53474471a4bfcab99ca08d7dfc6c1cefda57f836d1e58731b4eccbea60c0606dc42945b26b17a3599be385b0b64519f6160adb65fd1da795cf27ea25e9884783834130be24ec10d549eb21ca32188f4221d3ee358f2894099930d165ca4d78985e6c13ceb9ea340ad5c116118c6a2d7851781e554a0e1b968540151e3715513f5476ff6618a5de0498e4f5632a6d5ffea8579ddd0be010b10204499c82540d521397b9a39a079acccaaae32add4d7991db22dcfcb04f86730cbef5b8abb0fa31137abab148457325eb161fd0ebe033094c324f6da261fc31476619753fe95c63a326fd14dbe6e7f83c501b77d4ce5602877b5164973dd55c04f7a891e6f3b423ee425b992d73bdf7e2735e4f52cbedceed9afde086f923208dc1abf1ddce86f9e7c467a8b0b79740510900bef0351faffdda005bd35e5a819b96c1622ca06399cc62f01d08d64c4236970e6c119380df5c48d11d57a8908e76b6d8ac3517f459ec83af139986abf135ea4be1886054eeb0087996aff4ace796b765175166f46af49d6ba10f8b5a455f7bc04ad82a53ac8d71529a6d0dd138642b68a518b61be13761b33bf60133cf97c9c2a0fab6cbfaf3601c0642b6d918b142caf1da2da75d52523739a62984f34f3016ed17cfe03522e71e332079920eb33c0b41de3c3f41e92c0382f7804195e251c0db4d2b6db83566cb4a767a9a85d4d674b226732bbe298ab5df3d5fe2239b82dca3fef254d1a7e3fa9c3545a23eb49136fcd21308280715c7d26601eb40fc49e025b33a6f676b16ae215bce5d6281e0bee2d779549ca7fe32811335e404c10e142ec50c5442afbb03ce0f179848765998ef1a3b60062fd187733919a34999d8bc5724d354421e699e25c39d416c74eb3e2c79229656b54af9a3cb0e71a52e8a9fefd08043f69ad8a8e536b0728e2a039ee80c999d4bd86340c97dea3cb2be33ff333e11dddf0ddd482d99844df08f7bc2ac351987b71e802aec4266b372f2a1e86780b89be04b444801e52c2a4cbf0cc2049e2b3d2c6180e7a28ba0e459de1ddbd016e0c3538f137dc98e2ee1729d5d5b0879073eb421183da7b55866ba8b79c911792a4a6b80b9ed15dfc4249d4c88cf229bc16491f5b8f1afcf272622bf782396aa55fe527a5a67d6327010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e1ce050103ffffffff0200ca9a3b000000001976a91403e105ea5dbb243cddada1ec31cebd92266cd22588ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f570880148700000000000000000000000000000000000000 +040000003893091e180992ed3ddbdca17b69bb32636fa24e139463cc3fcc390f72f00100152337b21d88dbabe7993aeda34e575edc0a10d8425ebf675ebc4a83b3ebc20c0d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5a39dd295c4699181f1e007d867f82f93d39b4107a1265900058b7565dd1b2945241a4ef63bc690000fd4005004b556303cc38ed6fccb33b93a81c0f8f05dd0e8006d64e34868ac9fe5c22863eff3928f6f2831b502400bed03b98c17215ca8a50612273461d43f857d192154f4ad5db69f067aa48d19503523a2f0602fbccbc01bceeeeb256d5690efa33a583efbf397481afaa5745976f397bd7c0cd270674f2de48ded99778fb0da11068c770ac303637c1ecb1b53e6f1be4f89130a7b45a49edcf081959f37121190d42d668b6bd5c1d353609479c1a598c2c43dd0661c9d51222bda25437f3fd1e952aea271a02c0f19a92f4a11faa20ccef67046f1a4036f38b299e73692e61aa67d526f1b7251da0914ac33d1cddd7a4bfeefada21c8f38ceedc1557d0710bbba0f4932c839177bac34abcf402e168303ed8021dc449b3ca0940448bff3217c0bc2730b19190c6811c420629a1878f6e7021171e9359002e4bc636a5322b8056733e3299bdf0c0f4545aba5372ca4a5b337300f3e8c43e89819a563df0e385229405479e1c12a50b57cc1b8e8e2f8b1db9b3aeca20724244f21c2997270e316e37528f7f302d95d9a2b71d7f0319fb4afc4cb25ea17f1a6fc3b7fbf68d71b65139fbee9997c404c2c76611cbb97750d7e5aae23d2ac26de83ec81d2774caf7279e40737f7b1502a4e8ad15f88a7bcc660a148b1c8c68fe4d891514a7a0d7b6b9e9e4561cf510a9c10bdd586fa57a9683afc9db809d93777c435e0780404f5e05a91ab84d6a63abd77e33e1881f2e341c6a7cae0b8d0507dd6bab56ba688cef5910ff18a816a3bb38fa5c6307eea5221569cdc90cde855083b12e189c39cc9052c328d1e654a3ea11371a6f5ad6ad07d396227464e23b4ae1e1aa1956567e000558473a29f34c53dcd8d4b8d3df0410d8af663de3f177565409f30a2ee236d6edbffc355609b4a60643f6f7db0b30a111d9269d6df96d1d487a6ae2d672ce37fbe0ef02ef64ea5793778f67af238fe45ff15ad1609bdead8fe39d445b2644254cf0acc5effc84ff8786fcbd041b7d3a42085d5c2f1cea86f2817d9b1e21c252e6f02404c7699ed8c6c14b979371d1a726451599fe312b10653fa69fc6c132e30bc299af2e92f9fb4495229f1e49ce927fadcbb19ebee2cb5ceae56981201d7b73168997bde9b287abb25d86d43562839a0b30be80f02c4ee3d2021afb990364264e78cf5b07097dfe860a047191ece343378f63d88136a6509c511f4b785b48187e1bde4f8fcabec22451c19eb9af4a6b743c289426cbc2b508bd4bf9ebb382fe131928292ed0d5113f3e86c3dd8656638f4c0ee5b500ef57d19aaa4e00ab17dcf7950954d017f648aa09bbd5a87aab8c9f42a01c810bc93790ed6791d3a2462a571320bae636632b2d3d77611c6cdff96afff421a534df351910f3ffe92eaae18fc6d4a50908be160ffc5dca626d2777625205d07f18eae60d9fe1b9e32f56fd3e92f98a1aa5c728130249a24cb47556d2c40f2029b17217807e3b3a11afbf8d981df73122d61199854f2414b9ec3f687a2a021fec118b38e6756e67aec56eb726fcab9b69ae09d77d3b55559c10d8afd242836bab39f8a93d1a5f16ba0e0c76565a2731a467af0a490a8e3baa3b3bcd36fba75c7d5c3985355dc38f2ff73f9728903f02274126e39301b1ee8599ccb9fd9955696f08f79a035f09d9ac570225ad05666a2900b56c97cfaa985f3a5814c46ad2a5d998c7f18be2dc1e23c540cd208b3a11124de8965dd8b645746cf351544d0371cc798ec6552d329980050c2a04812f66219677507591470d9a5d103d011b901f2a234395f363a59cc8d18561f7807f1d26e0fe5d2bd38365c174b2886758eeccb73f958c1550df9016863c87aa5a94daabb03dc34208bd682854ebaebe90954ddfc2a4974a02566c15fc5976d38c010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e2ce050101ffffffff0200ca9a3b000000001976a9141fb09ecb821dd7ce9cc8ee1ed54df0d1863f824188ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f570880148700000000000000000000000000000000000000 +04000000d91995f491c208a7a8c1d358417120e91451158db34ab4ef30a1c026e2d6070094eda3d052afd42b6d45e541a1d97184636bb767ccbb7d8f91584693a80233250d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5ab4dd295ca942181f1a0006ea1556b0c6c73177cd09700bb72e6b2457fed3174f1d69d4e6e3190000fd4005002f434e5adb994d86d3d45d4fc9584f3d70dba6ee34005bbd47169aa751d4c4d06bf9476a26cfb2fdc905c663c77186e370765413a5d0d25be15e4d50746c232c0fde2d09e01c5cf1e53b70db14c9d889fcd1ad128add562edc91339d81d1a4a78e3eed31d4d16d5b3209ae0571569d590114f68e85fd37b26b0c7724531d14a72bbacd02a309cf039521ce4d5537069326fd1fec07eda692ff09f941a8f8c0f543372ec8fa85c20416161dd719e3f4d3f0e663eeefc4da0feb31eace0e54f9df1fd5714aebb4d306636d0b61357e343e9c04c7f60402a9066bdf11b6817ced3f39e539ff98e4282b63ead466a6e15d5c85b76affa63a4e8e935f060a074f30cf9bcc89ef3872f80268ffb4cdc5aa67fb10b745e3b856fbb4b95d439fe8c8933b50eb5dc0091ec9c41c5ddc998b477c5302c0aede64eecf70b35c1f7d7983e1e052039dea18529752ea7e5ae67f4f51009908fd530968726e0fc9dd79d71022d2eb5749f1055e96fa98b35b79e7e32a92de750036c29bbaf38c0d30587b1f87653bcbb979142b658396d4cb7c2496566154d160ba44dbe97165f909c0c5c2e99077bb71041313603d852e22f0c560ab1f0738ddf74f105aa13e26ad8baa139f98f29ed71b85c80085f324f7db0b0a6e9e1d4584af6c592483254378654903adf6b4b70b77be24e817b053ac55016f29ac78a218095509e902936166f7923ceaabda692442ce1da6fd8e3a6b860cf8ecf7770ad2d77afbb0d15de5fc6084ff491a370baeb547171415e1be7f38e5edcc1283bc573f5522286f3d92208d568896dcf51c1566cdb583f1f0d87902e8fc27c6d1c43b1b3bb051cf568e426b551608cc1d27aa72fc9ff34f2479e609a8fc68a6899598f2d713065789f95983f78a3b746798bb19a989381ae0a531178d735c11e1cfa856b40d05b1f959115608e66800e95c067be6ebbd6ac008193ced38e24bc6db67a210d9a6ed0eddbbcdfb0f8439ab66cd4e27ca32f01a095212f36cd6b1c13c17c4ad3327c255b770dc66e03c797b1d39177365c08af59e723825596f024c7dba0a6a71657f84ab7ef5b345c11ad19191bd25d725cd1660d4e0e793c453bf9aac0494f4942f75481f577d0fe5860ffe6c776fbbb801bc6794c11c9d81ec91d32a2e162c69651c8deb52d2bd4bcdf9e0f18254549c041230266cadaf43cb09f113967492286faa3ce2c50a261ec8e847956ddce763e1df446b4164f2594bc40a1c6a756a451022381fd618bb432b9dbfbcb92d79865e3e9beee8a7277359188b6ef0956b59b47e023c06cd3f99c002fed0fb9a7cb63578a4ab4be39d24981caf13d1d4d580e4d48183aca7ae8a3230011b83d1097b33d5b7a3cf89f61d6702f5ec79e66fcd93e5bd4a5183189fae5123cb56791bcf64b9425e473cfdd2073b30e551703823d848e4adcf32673270b7d4b91511b38efd3a60937f2b90c7accaf7102e0b3c5e12c907b62acf098fe2abe7d073f4a4ba248623757adcd12f27ec2cedb749c7e280452b7f2cc085beceb495040cdb00ef434bc9ab78f8b0f79e8bb90cfaf7f8577743dd5a96c8264aa9b19ca5b7e46ed1a74b3d3fa1f02eb0164b62297b61e6bd951bea4562fdf8b3355ea95ad9a72ad0a35affe08f683bf77f781212cb53f9130b672e7e6a5ed3b31bf0a1089f2e694ed7875b9b5d11bc2c6ce1db7dc770d3b6a4e872a572285bfaff5c1f7a2202d8891b3ee2f6bbe4bd633ab700815c6ed9229e19b49856d72da66f865b9bcda32e5af81fd54c122c79ca1fd7a666ea4ef38b5a668b9532e675d8c0561aeba9c5f29a8f9cfc66918bda81e63ecf792705298699e526a10d4bdcc293e93b21513b8cb7dd486030145c81942a84038001873665f93d8ef80bf942f0020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e3ce050104ffffffff0208d99a3b000000001976a9142943b34e1bbdefc6f6a5c501264404eac228ba5b88ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f5708801487000000000000000000000000000000000000000400008085202f8901bd114c7474dc9bddc943f99e3cd8af97332b4c899fbdebc44f105fe6b760bf41010000006b483045022100b73c1baacf61e8b4bd915ee1c007a51366a7cc718c5469de9f035aec7f92fdda0220595ad4cadd2617be7ce65ca5aa10b6d86ae27fb86c960686d7e1c668469afe12012103f9e72f0713a4d4a980309a14a2ba563e0b1125ad067818e77553a1eefbfc5be7ffffffff02f0ba0400000000001976a914d78f41784821c3d9929fa56e85267eae0bb09ffd88ac486e1500000000001976a9144faeeb51bcd0b49f238b323e5f1c6c8bf11ae02a88ac00000000e6ce05000000000000000000000000