diff --git a/parser/block_header_test.go b/parser/block_header_test.go index 0909da3..e0dbfc6 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 99f6338..affb8c6 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 add99d1..a93b34e 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,6 +352,194 @@ func (tx *Transaction) ToCompact(index int) *walletrpc.CompactTx { return ctx } +// parse version 4 transaction data after the nVersionGroupId field. +func (tx *Transaction) parseV4(data []byte) ([]byte, error) { + s := bytestring.String(data) + 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") + } + + if !s.ReadUint32(&tx.nExpiryHeight) { + return nil, errors.New("could not read nExpiryHeight") + } + + var spendCount, outputCount int + + 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 + } + if !s.ReadCompactSize(&outputCount) { + return nil, errors.New("could not read nShieldedOutput") + } + 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 joinSplitCount int + if !s.ReadCompactSize(&joinSplitCount) { + return nil, errors.New("could not read nJoinSplit") + } + + 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 JoinSplit") + } + 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 !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 !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") + } + 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") + } + 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.Skip(8) { + return nil, errors.New("could not skip valueBalanceOrchard") + } + if !s.Skip(32) { + return nil, errors.New("could not skip anchorOrchard") + } + 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 +} + // ParseFromSlice deserializes a single transaction from the given data. func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) { s := bytestring.String(data) @@ -338,135 +553,26 @@ func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) { } tx.fOverwintered = (header >> 31) == 1 + if !tx.fOverwintered { + return nil, errors.New("fOverwinter flag must be set") + } tx.version = header & 0x7FFFFFFF - - if tx.version >= 3 { - if !s.ReadUint32(&tx.nVersionGroupID) { - return nil, errors.New("could not read nVersionGroupId") - } + if tx.version < 4 { + return nil, errors.New(fmt.Sprintf("version number %d must be greater or equal to 4", tx.version)) } - var txInCount int - if !s.ReadCompactSize(&txInCount) { - return nil, errors.New("could not read tx_in_count") + if !s.ReadUint32(&tx.nVersionGroupID) { + return nil, errors.New("could not read nVersionGroupId") } - - // 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 - } + // parse the main part of the transaction + if tx.version <= 4 { + s, err = tx.parseV4([]byte(s)) + } else { + s, err = tx.parseV5([]byte(s)) } - - var txOutCount int - if !s.ReadCompactSize(&txOutCount) { - return nil, errors.New("could not read tx_out_count") + if err != nil { + return nil, err } - - if txOutCount > 0 { - 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 - } - } - - 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") - } - } - - 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 > 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 - } - } - - if !s.ReadCompactSize(&outputCount) { - return nil, errors.New("could not read nShieldedOutput") - } - - 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 tx.version >= 2 { - var joinSplitCount int - if !s.ReadCompactSize(&joinSplitCount) { - return nil, errors.New("could not read nJoinSplit") - } - - 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 - } - - 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 tx.version >= 4 && (spendCount+outputCount > 0) { - if !s.ReadBytes(&tx.bindingSig, 64) { - return nil, errors.New("could not read bindingSig") - } - } - // 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 6067113..4b6a372 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 e3af9b8..4c8f068 100644 --- a/testdata/blocks +++ b/testdata/blocks @@ -1,4 +1,4 @@ -040000008a024cebb99e30ff83d5b9f50cc5303351923da95a8dc7fda3e0160900000000226b1c86e101f1fac09aa533c246bd6843ee6b444ad3ff251df0a401f16f0981000000000000000000000000000000000000000000000000000000000000000069cb7d5b0f1d0a1c0000000000000000004d6cdd939a4900000000000000000000000000310c50b3fd4005008f78a11b4b81126dec31cba6ede2e131ab376a8611237b04d20cd8bb4253f1e8721e0941970e0f58c206f09c8c86c70b2f73b3b5994571c389a90fadf6b812cebba5afe622b3e4aab20a88c805e1f3925e31300df11c09e3d4b7acebbfb156b75e213e36f65d88f023feae5d37ab2f13a1215299155b7d9a1f4cbb333212e8ef9959785adfcd2506d33ac1021207fc32584022080f4a7ba67157ea99f6fc645737a2a5c6f5d99700ff0c1a92f2845dba3b022d24de26691c0b56977b13e94d5f93ebe423d3d422a478f2e6d9f0b113b3fa0434fe432c173013b1a641e884f74922a3e6fc6cde4dedcd8c2458be13fb91f5285ffbf8a5dd207dad3b011b832444a85e27da43c1f94864ad7b066559d5210bf495565005f9297f6204ad7b4c395d5faa19c2530427ad4889555aefd272185f825564525f6d3e0bb62b18cce8ca120a6f636ee36f18612fede045bae81403752f8154e7c3bf4fef7134591379389155360cd15328c731c1ed3b57f439994b0350799e91f91f53e30ce9bed6ea9ce01b3f74f6041b739d85965d789b9c43ba65cf4f99808ef03c2ac46461ba0eedc05e77400815af9d8049027bb7fef6646142d86ef35adcca060e8a656c6c6cc811f65231b4ac531fa2083ddffbab26d19409cc357b89ed7da3d25476cb9d9a9c939c343d23fa35a09c52d593fb03f609dafcc28579fdd35e044168e33e747757bfdf5123080b6799d2527368f90de7f126304610765670214b6e9b6f497a491650db139129da19964461c368e2524aa1524248ed92561b3e94aec38d5ff4adcab5f73565dc7626477b1d56620ad1bb49000d1cd915a260c0913960c493edb9770d2fefae76dae63963e147331a51b1c66d5ff3ecf87f141306d575e60ca3b34fd26cb0b1d735dbbb1977db519a7a9d345ccc77121b688c7470975ee9dbfc489800f25a41d406ccbcbe1f01c629dfcbd59ea5dcd00334cc6af8718e08631c3a83d5e6395b4acc3afab48b145b73a064904176c30c2cd9a876ebab5f333b6ebd17c10e31ce6f009daad792c31fce13dd6d401120eb08d66348c13735ffc667e653ccf80bda54d0773473177433406a0c2001ec9f534c54e667c5a3cff3bca72625b5fee94a51dfc2b5b3fc73353b2b2f3a9e708932003e771dd21441e9cd075765e33ab5fe0db05b4b087bb04608d5fe5bd32f8272752cb1e59f856eab0e366a935a9651be4584f8886649c66bb6bff29ac82e0bf749c94e46cc6a42988196c15d3b2a6376185b990e653cbc8a77e56fd3e74378bcd54f4540afdf39650950fd18192198bd743708fcd948ec57aab07d71211157758ca83509cbc0094158b75bd840810fb266f91565df4f4013aaa4e414b3a56bf8442938189441ae24f8e2417b7bb729f9cf27a2ee5da291fed793afcf09a1c63f14af0dab7aa6b34b1be3d545356653f14a2d31173cf50fa6aed549933a5743cd46809bf6e64824111cb5032130536943f359ffe5cc7ad5b4dcce3f2d078f7ca751fb08f8a5d9ac3970cb9f2cf31ed1a713cec3e8bac8586ce10c74e215c3d691c58e176b39382da1fe4ff0edc7f23373f594ab00c987c772f92317575d4d199e79f5b95fe1986ff0dd4d4a339a797b3b366e737bf8befbf80fe58f0d5b6f35c831d00ebd0c47ddcec914cf879ca685c0724b9036f98abacd96102657148a55e6e7896f33d51daa97190a233a4d68278b51c2bd26dd23916c54e605b6578a8e3657359c36a9ee2e4c0f650a95a3f4b61c4133ddaa901154d20c807a6ab85211bda537f26a4fe4b53a4a64f5522d13eb75cd2bab7669ef47d1c7fd43416182bcde9370105383fa2c0c574d4895d2fea59889068e66f83c8f15118c7ce7adb41fd8de61c2afd7277d6ac31346aa01030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff0503e0ce0500ffffffff0200ca9a3b000000001976a914361683f47d7dfc6a8d17f8f7b9413ff1a27ec62988ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000 -0400000015bea7ff6272a28e0dd187b5160d8fae6f2ad6316d652fdd26b92204000000008ede1b5093bbb6ae7c883dbd4b8bfc9e97290ac6ba5a617c880d3aef1bc41d9200000000000000000000000000000000000000000000000000000000000000009bcb7d5ba51e0a1c0000000000000000002c476f1d2c08000000000000000000000000002c27173bfd4005001a40fe9c1795f9617212aa1ade76cd93f85db2e5254a8ffd5aab68218adb36065f6947477a423c26c719803ba9c01555a1d81ef1cbd4ab0e7a7c9c7aeba622c7c1614f16409f5b0f757a93d12923027fbe875e01d4003525150ba313f43148bf39e7836a3d5fae611de2de0eebd8a3d1b163e4ad7b3ad0bbc00a3f4b8d029ee0e23f6354cbc381711eec9df770f31149ac1e0eb55229d10d7ba106edb12834296cfd241f5a62a6014ac0726dc4543f05a88138ad2d7654b707aa799115886e467957d5f195fda7a0a0d7908e084b974a1e0565e222acc9670f4f7191b4052f84595457315647468f532773a0755b0b3c7649aad0ce1e9da9597819022f7c951ddf47978c5985bb3f4350b1b91f2eff7f149ba3992316712d0bb2a3b6fbb3dee54b516d22b21f60c23f87e3513d2ae3447191b55b17f3671f9e3629da0eb3b8261cb7eccc675761f95452435bf6b6f800a4e5a2deeee86b8ae3d026a11eaed463373c378b079a68b51eed25b596fd519a2530f9414684d71dfa427c461ab170137fcd343af2fef3122bb037df784e647b6386823340abffa60668eb7a5729fdda35d42602729b2fa822d11da41c304ce27530bc17e717fdd10a2f221cf793e4db842ee2f6f04b2a74d3945214ec0d1819005f0e42f68c8183e9a0a87011180ed87eb3205e7f01d7a760d95b874ae2d17e915b267cbd0e4e06e99982744e7473f74af2ff766730ee01f7528478116633f7d2eebd87d8e134429e4d29b92144fd0ffb1628add40f98c4df2856632055a3d2c70a2abf69851929b6aa5a9de2af2148ea564a7520f6d95e5724b40c2a3c9d39c995b2f3e131795f2299029c215b07cd14bce9e10fd05296a8a646302ff4c45e85a4bc3bec12fedc1d1f60da8f6aac22f746d43a11bd08be8e52153c60ab71061d35284872c427e3aaff0d8a3a82af016db8d42937f04be1ccb021e7152aa82a068b0a1b04cbe51b904ffc00f3bd46cedc3c58ba378355360e11982c9981c6a5ae7c6d21fa3b2e54b8e01d49a0f618df191ffbd3274b7ac3c5f066623a15c1e07eb64e1db18f37cf4d4452c441c63b8075ca220d0131a2d022da9305700b8e0d236ab4dfd17c2f453c3f17fd722925de3cdfac190bdec92340db74ad46ff693f79c92bad961d021e3a01dcf16445d66713e53bc37b89720c887088d26831e5a4a7011366eb3b6920df10a3316eb41dfbe9ef6f697bfb673be8bce2a64c16baa1ed1cce0a0bc32595bbc22f42ba8d2dbdba3f6097cf7b27cf4717d7187bab684e364a17c69e29c49ff3aab10d8f9d3002149c72d90cf115723ca3aee3c8b8014918896bf63a2746d7a486a496b274b21765dcdeb83b387252b876129b65ce91e613783f55aa1ca85c07a448b4eb36d95681c1851f7532a47e8056eacbd8c45d01b61dda489c744f267111294fef34a98c5391d6ff31491f974fdcbee1cfcd740c714abd9a616878d604076dc376afd53826da95c4b81ad0f991642a6bd3d518feec5a021e20a73be5924147203586c75b3fa9c51111119bd39061fdeedbc1a1a92aeafe834dff67071438f21b247a5713e66bf156ab70f04995ba70f4f0129da42ee3d5ca8da38c35c2b8d33d19e79618a0061ac8fdae6a58250725ab51dbda4a6541d7fe1cd31403ad8d6cfde6badbb85ff162bc1f08b109866f8aaa1688b2de068fa272a3a6371f3876101a37191dc9c308eff5bbe41512b7a749668f2a35f43e2dc67c91ed13e39bc4ce5783b795d1a16ba03886ca053dbd6a4006e0f5091e6955d7abdc154373d10d4d60ecee597c0a8d71dfbb2e3b4fa83554d459c0a68d655c9b28ff0cdd2ace655bdd8dc2322394a34255b1d5373dc39c310244fa0fa1c557d7caa3d029ad36966d0d7606e301030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff0503e1ce0500ffffffff0200ca9a3b000000001976a9146d17c0ca63e020d639f5fba181ca4f5da48b8f7088ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000 -040000003723935953489b860d1de1b8fc8240ced04625f0ffd23f16519b8e0700000000f066ce1a8f2b97d05dc15ee51e1eedac93ba6057a953eb81d3f856e9f4b7ae270000000000000000000000000000000000000000000000000000000000000000c8cb7d5b13520a1c00000000020ff5b32c230200000000000000000000000000000000000d5e39cefd4005003eed282b472cfdcb0540b4f4245ed479c5357ff11989ef70be9700eff5987318f727dafa0b467302aa016e5e956c5a5d23f0f9e4a5623b9dda30e0f1b3c47e8c3d151e686cabd4ac89fc627e5cded736d707780c3dc341370bf7af4cb291878a1c4236cc207f39f081ef26b50fe099bdb90c8a9462663d1aaf079fefa010c2a8c29211f08b8debf2f90e4afade29bc53d74253c3fbdf265759fee75e1ab46adc90f38bba5f73e80e518def9b61e8b714d6e6a1dd585539ebd1f5f2040e8e73498455bec4fd333747f96823c66fb859177d13f3ba05868918255bb022cc32dd0a099438fdb1b31b15e2de9acc341066d6b20e4c1d18f56397dfe97919a2eb988e114e3cc015c1c209d906e494c46b9dac34a99ad0595868b4f84a1474ff532202149ef12acc1db1f9307213f49efcb0a408fcc1cc6124e637adb22ee4368da8d0a78b696956e7e4c9feea5d2e198f0e00bdd16ca49956c32ff793e3ff6e635efe57788fbf11a8b2a771d1981aeed6462fdd74a6a9f5d813d86616923f5add19a41d507ba22b69a0caf4a608f8b7673bd605d7f2b022b3d8436448aeb34e491c12addc3911564ecf5de8b1355f1762dc402e37915c35cb69de2a9d575a765737ff92c653eed1eb871270589c2dff15f827ba45b19ab3d25da28fe3176895a8cc32a943391276625d991492ed315ab22a7192fbcc5f5eb01611640c96585ac4db590777a87444c3dadc653bc2bb279474ad76ac00d5972b92e026d45fdd0ed84b099a13d08c00eea952c5a2a78218e06ff989f85b52801d203f82d395310f67e80f4646f3f29c5b0c2f9c3ce31530b766c12f3253a6f3e2aed5b66efd9dcdb6f1de1e601a36b00fa2b7c4d6241e46afc746cfb73f1b2f19c60f81472bd15bdaff21afb0c44514915bfbdb5a20172209c4c9c281ba14950b07b22d097bc0cce5fa017ef344aec83bca60db20f4241ab3e1d29f7f200a24b801aa1e2c638d77ac13c0732da3561e3e1bcc6f2ac78fcf21e7f113b56d83c0343a1cb6d8b69eb12975891c1bb27885f1efc8c97a0af71176f716fe36b61262e23b48e7266f6551821948e4c44d48153df60a1a921faa5766e4a99239c2f36231bdadc93e749db715ff7e8ced9ef6cb28e7a5a8aa7603de56e73ea5fd1d79be8974a4c071ed1b969382da51d68b11f4722303d74f82849cb05d07c0c313d9a0d5eccbb853f85f0c7002c67eeb93737060180a3f557582d3849b73bf13807ff3f4065faacd7562487fc96a20fcf2cc892e16ad74b813caed4f72f11293243912c976a9f2035b099b9b9debc968aec54031bb9f4eb7815ddb3bffc128d6be34814fbec58b17c6dfab6c812a5eb3fd15d60fe3fa55128466cb80bae5579b71025e6a9c34b7f818eb11cdb029eff94fa465e419b9aef2e8679f12ce02b780aba2c17313bb375033beda50e90be5fd949002fbb85634d42598b62d21e346d13c99e4b6364354136f828f972a7b2fd8c4464defb82629ffdddea6355afcf76a5b6a1f797b2a46b51cee7f95f812d926d00436f98711c6f6a1e374a5f8a6f73f39b70150ae7315988b1a2c88174e9bad58c2e0f82476bb1c9ebd03079970441810e5c7c1b8d3a5b8eb21750f29eb92f348d5ccd2dd13fc68b063c754564f490e33ba1bc71b111eb14500d1dfcd883be26a8952aed912f4a999831b80a94d1f994c69558334e165513fbe129a9348922d6205d0bdb3fed3d0fec41a8f7dac2a46f2f9bdd837d6fa791bd25ea3bedf454b7fcf8907000a78d4e911bc798c6d261817400572cea0a26e9e2f5b16cd7129e92738be52b4832c2a072e2c7db3c2b889fa089b1284a3ae2ec654da817e15e0b26856be1caa1c7c0b3446158365a0992bc01fc67c19d5444ad8c6be044902030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff4903e2ce050004c8cb7d5b598c2ba5c7f0b4587e7d60e62e08985aee803a179dda187d2f0c5284df7da7482f4f76657277696e7465722f5361706c696e672f706f6f6c696e2e636f6d2fffffffff02d0eb9a3b000000001976a914de2757e612dee78a681ace9b8e623570dce776f588ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000030000807082c403012263eb14eca399ec05b64be85325e2c5ab93e5d70d7dc49b4e8b8dcb52ff0f7b680200006a47304402207ade36a404000f4d5cebed5c28e28c57a539b7985b66aa4c014e0c59a43fde3a02201bd61f0a998301241d83fc9370654e0acda7cc6602258b954af6bf06605dffaf0121026e88bf2ad6aa4fdc21b2636583184a589fcddb3c1ed40b4e3613cf61601fad54feffffff01bc131600000000001976a9142f592f81575c361e91397dccc60772b9335f432588accfce0500f4ce050000 -04000000c848f5a041f4406004243ecf645ef91f303f6766f5615e4cd2c7ab08000000002c46ffced042e210ec98759ee1bf5792b62c3a3c56f24667422350dcf3c84e070000000000000000000000000000000000000000000000000000000000000000d4cb7d5b7beb091cf18a4fa59e6d1400000000000000000000000000000000000000000038d0a613fd400500572cbab31dea47dc52529819b5b80cce7fa6ff4f0c1e8f09d50bee7b5012813826948828c3d5e6f14505a4f7ab54884cb7b8f1a49675b99f820d219567d72695fc14f74a26554ad3c346123852669bd29e50eb056cda5e176bd065ccbbf2663f3a12a11f7773a0847c09059fddb09a37908e1a8240f29dd6eaec78f92b1f67e2a1cd27c2b1d6f9c5c7d0c8545dedb6f949c93507838d4525606b73d176ed8ddfa2a20d93ba4d4303512f8fa4c96efcc084329bad15f9997edef03fc90edcd1e7f943c95b5c7123d66a4d0ec9012ab2578e1d9bbcb47bdf56e9eb72e3c82dcfb14ed9785c860522791bb3bde200f76db5e907555c27ef91489cc4410b4129e4aa08b800c00952b451cf3640c326b690e719d92cd2a87799d1dd63b36984c30a6582bdb1fc6d16b323c2ef33b23deaf2325763f8b1fe7b21dd7c801ffc7df2c4d68511aff3b2059e681bb1afaa19090d03592df27c452350d040718758902afcaee5fa48a00e9fffbd3ec4222973c2686fb3e7beee6c5ebe0ecc130b8981d00adfcd464832b625d7cd82adb99e02f71c4b3d9ec6ce5812cb5075cd70cffd967e73b9f08c04c97c728e02efa88b18f318423ce212fca69c05b743289eb18912bdbcde1bc5fb507286baccb1b8937438092c5418a86fe56bdf441637ac4a49e172f491f743a694bc0516e02ebb1e36ec3b4a460e5f45bf6d2c055924c396c9546f9839b696785fdc09f99ddc7e701a3f0f61b7cb90d6de292400b8423f45bdb6d11d921711cc0a5316d200c220f70e80cda935f6b911e34d1aed9769d40ab973a6aaf1bbbc33ecf57f60d93a490d5835583ca4b70de9218510784caaf2d28c1b3dfe1af1e47ea3c96ddd46e27343ea5b80d175c3ef69b5387d2bfae8d026c765d2f5ec7ec7dacdca63dfbae85974e5a57e636edd68754664b56b9421a009ed09d7007b72db75980945b9f8e0da56d03cdcd8cdb9ce8a6d21336a8ab946fdeb59472585584a85e59e31152f0a9e03da924877854e93d5f6fbcff21a4165bbc4b60c8a195bd46761499fe1a28eb464c0957203d337cd0781d1e385308017999260c8da0933ee3b751d3ce92015b3e67393dc98d883d70113d24f72c4b73a885f1cea47062e88aba3983f192c69dc10db68b95fb3e021363ba87ad938bf97077263a7aeca4f769dfd54370194582d31d7c632c60833fa9346814b26ce1d7c451d6fbd6c309de603780185f499b963a7310abc42450445a2f2840f25350d38f2140b3904fd636ff5016334e2c234d72eff0d8096588c71ffdcde3569b3914f02acbe1938957135acc6d10f2d5bf56885c177e6ca778f7ce431225fd535a15949fdfd2e8ab0c47fd8251e294c02cf98d91340afb5812950c46d9b8dadba902fc70ea73c2c26b5a4d0d59410da46018486d9ec5a0a9b89422190afb7e72b7150f97b359d70841170ae23bacf915f10a019c601f48017472dd2b7c7b89bf0128dfd4a01e450adf17071ded02a3ae13eb7f8fec54510955a305256b2be2d97994140f36222d5ba55ec0ea35749fc894289af4454e944c1dbb1d7d40f1e72276651c4e3542bfd9f145500d9295eab83c6fc92362875620cd0159b7b53ff530d66b25adde2187438045c5fc4b8bdaff93dfb65b85c71753846bfa91f6ac60c9db561423b6109feef932486ece2cdc3c539112318c79ecc7e08ddda73ce663c3d4e9b9a7c743955330f1ca702b8a5673f82328b5f637dfa3743bcbe07583bfecf731cab93abc8d4a5dbfcdbf3536db25f1fff0d71175b744d561eecd1a89fe0484d466d2c5a68d61abba2dd9e8786211482b2b50218eec505f94c67971242a67a93872681ac81e1f541ecbf95a8bd7f5d8c1e60d95a76138bab40da3455b5a36549920d7f4d0102030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff1a03e3ce05152f5669614254432f48656c6c6f20776f726c64212fffffffff0288f19a3b000000001976a914fb8a6a4c11cb216ce21f9f371dfc9271a469bd6d88ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000030000807082c403016f4029430669c709d041c579c8e9469446f1159064613e6cd47514691b93b8ff000000006a47304402204ff60fca23a36026d374850bc018e7c714de78d8560d4188fa247b1a4ee4dcee02206538b475265173915662465b4a8554a111003701371d553134047bf185c98ba20121029ba2dc5033c7c17f06274b85c0f8e51ad518f34235713ffee4705867162819fafeffffff0208672302000000001976a9140ed9a32475fa7214939a6705fd790a74976bab0388ac3e926c01000000001976a914d752bedc09529fb3719004407dd6b5f29000e3e688acd7ce0500f6ce050000 +0400000006785624481f381e68b4506ba5d6cc53ddd6dc876cbc97e45e3c7c4a626a19008c450aa9112b2e900eba30c2e3b8428c23fb2a30bb7fa34a853c97f02fb1a2200d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5aecdc295c172c191f870000790c975f8c9f0a77f12ef1e021c6045c3c9acb7c432c2aa7ed42ad0000fd4005005db35c547608bbf32bd1b90369e6e0bc1e275a0d064f092b235835cb6dfc9158c6146a0e5cfbdf2d2f04f71478f7d9212bdeeb938029a5cc217ebf1f517711dbc7cb35d084328b8f7613e878f69e27b2b40e8103292cb87845006943b761bbbe43d94cfbf971011617965a7de1e846a586ad81f2ad138eb0fd74729efc04720badc95a1f1383cc70e890db91c143591ab96526263ec6b8a6186568dda29b9015078d19e87cbb2c00b26edee3e4b227ad6fa090db2d290191ef1043e2037fc0bd225e6a1364d16223b0234311e8c5ff455a0cc2e0c1f84944dfab49864f57e5ec2a2b5e91df5229449ee399b4c721abab63c534602abdf64d37f60905bea71cd59be759bae5967f6f3e1bf732753d4d9c1c11dcb5b6949bc4f63a06a3b0db5c2deebab040c70fc55fc648bb2559fe2da2b4a4bd53ccbca7aaf03262cdf31c5bab90cdc151b686d3e5f8a29103b9346602b5394b245dc9655116d33c2ed9d31563bb74950d0927895a024c119d30d4f2c760c346b11108ab29db2931123aa36937cbd152756758e3d341ec40db1d1b539fcbefa2e76af9827f575025c58569fbec1f806607e03a6eabc228a900a18144e310ab608a930ee6a10d5e6fc7ecc9934fb42b51f0a6552e2a1ba1d627b51df2d27889e14b87f093452b79bce3e2a74398f4b748eda48495b30437bef13527a231ce724f24d5bcf4040cc77f56dab50ff30ce12039ca968eef3c5ab44b2cdd8f7fecb36ed39bc624807a4da49d5b2f53abeb1a027676fb500fb89d7092eef849f254dfd3a7325e4b810687b8567d7db8e796480836bbeab2efff44bb0421f4e3d5d1493bedc2c331f75b7ba931e93495dc379fa22404a2e60919fb2644ee4e5c3bc2025fb17540dce5dede2fdc4fc6b87494ae26ae89bbe954ee8458740601a665126129c9962052683661efcfffce8303211fe3bf0adac14936927ce2f4b30a22227c39b4128616dfbfcaeae8f4a3b30255d215d1b9ccd9bff2173b5a94ffa5473bc49097030e782d81c1dc4f8c4a25803c6dd9a9e775646084ed2bbbf7457386ece058032128bc87c7b7772be513f79176241de701580b5c04f88ee81e231583528b514f0c478382e60d5741bd0b5582c2ece2767931f538d050498886814c5b27541764bda9734d5c46f2b3c80f2649d40374e45c053c050e33e83ed578f6d5c19435b7f495261e7e522d5225eaead374497fed7c30e322d6acbf512751b58417120bf8d97fcfdaa5e3be842fc7beab85235ff7e9ee459b03ef2ae01ceff42e0621ddce403e65521505ed0b25fca1a0747263e44c72c5e1e95d85119a7956852c629fcb903b9589e768a6bcbec5358a5332368a6913ffb9b9880e8d3701c6a366bec102bab8c6d9e57b2287ede4774d69fe81af83dbd423c905f666170bfc03f360314edf3098fe2036634ad90f326a71faa5b70f245acbdf8f7d4481283819d8dae7823ac31bafef129b94822a4d94b8ea5773b7477269be9dbe3bc8c11886a2d2a8176658fe30e1ba1bdcf320873d1d2325096223af6e142818a2c370b8447db8e6e865fef2074596bc9e679b8c37c51e99c4f35ac8431303fac507193d1d162d309a3d8d6e57cf4cd429b34ce69f69ef2f1e728d7b9347051b46d4ba5a7e7f9d43963e960d0bbaf63eeb69396dc75b325f08e9243db0c678aa192eb3db32d9cf65a0a38bd5726e31380f2de23ea9a019d8496ab511df2d955978fcee6e331aeea5df0a1826639323180b87bbbf6a039e34f482a5e4eabc6d9a139c40e2840b40eb53dea3d549b54405ba7e1b392f308df2abfd6e97a3eb44f540b272a6d97c085be2142445ea27349bc7a5493397ceee4c8016a38457480f4de263cd2c2416296a2d86c19ded93cf431bfdd1e3010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e0ce050103ffffffff0200ca9a3b000000001976a914f4fbba801ad64c6539e64a1478392c57c9a5e7a688ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f570880148700000000000000000000000000000000000000 +04000000913b07faeb835a29bd3a1727876fe1c65aaeb10c7cde36ccd038b2b3445e0a001e1ecc75a33e350c7763ccf055402a3082f28209c643810a2ef17df4b14486cb0d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5a15dd295c21c8181f15000813d02ae5e889354eb9b7b62cca4f139ca6364fb63646ff32f2b4700000fd400500356a828c88fb46eaaa21d759e99109375d79b61557348f830c71fd4dabb126ae794d403af814bfd3fc01b76c2fc110525507f6728bec686ea9a432eda0ef1c8fc97d912923a9ab0d06f8aeef2ba272a3b7125e0e5079f131e4706d59c4f34a2775cfed3fca2cba4a23795351a38abfacf67ae7772e6fd14326efde46192105f2c3b0989a15aedfd22063529005723c8c8ea756ea876e0ae7846d4556e57fdb4b7ae1d2125456130653604eff1a6e632e20350a88b0fece49fa57079818d28c0064764979ed2e75766767a7ddce8578a23712274fac056571619b9ef4371f6924068fd5dcf25112da1ffe4255fe60b851e50b704eef0577967a867a0fbd7ed06f0d93f0f0ae31830ab5eaee5b20757e96233c21f96d6eb8f5b5ff03e88d7a31ced8037a52d94ce82fb719764841c06016ba82e29acb9d521cf9d74e1a1a90175c19ff365b763db1d3fbb6332499a5f90903d26af753812bca3eb2b142992bbd87140c3a0c29732d648c650e6bdfda63a56c6184570bf2bb4d700ba30d7800cecac92f67647bbb282d85228fedbdae1e1e3d56e9cb7743a58f02df45f658cb3b627f6bb311618d1697a217ede3f763d8fbf1fba96f697a62b321d397d492594b477029b23565f8ee80e3df1a74d81be2eaf203e3c9215c72c5fa6ec5f0d20cff3f151d7131ff6a3021f489f87819245d6af34f12dc5d1bd70d2a692be0da4827ed3f81a4b63ee58223bcb53474471a4bfcab99ca08d7dfc6c1cefda57f836d1e58731b4eccbea60c0606dc42945b26b17a3599be385b0b64519f6160adb65fd1da795cf27ea25e9884783834130be24ec10d549eb21ca32188f4221d3ee358f2894099930d165ca4d78985e6c13ceb9ea340ad5c116118c6a2d7851781e554a0e1b968540151e3715513f5476ff6618a5de0498e4f5632a6d5ffea8579ddd0be010b10204499c82540d521397b9a39a079acccaaae32add4d7991db22dcfcb04f86730cbef5b8abb0fa31137abab148457325eb161fd0ebe033094c324f6da261fc31476619753fe95c63a326fd14dbe6e7f83c501b77d4ce5602877b5164973dd55c04f7a891e6f3b423ee425b992d73bdf7e2735e4f52cbedceed9afde086f923208dc1abf1ddce86f9e7c467a8b0b79740510900bef0351faffdda005bd35e5a819b96c1622ca06399cc62f01d08d64c4236970e6c119380df5c48d11d57a8908e76b6d8ac3517f459ec83af139986abf135ea4be1886054eeb0087996aff4ace796b765175166f46af49d6ba10f8b5a455f7bc04ad82a53ac8d71529a6d0dd138642b68a518b61be13761b33bf60133cf97c9c2a0fab6cbfaf3601c0642b6d918b142caf1da2da75d52523739a62984f34f3016ed17cfe03522e71e332079920eb33c0b41de3c3f41e92c0382f7804195e251c0db4d2b6db83566cb4a767a9a85d4d674b226732bbe298ab5df3d5fe2239b82dca3fef254d1a7e3fa9c3545a23eb49136fcd21308280715c7d26601eb40fc49e025b33a6f676b16ae215bce5d6281e0bee2d779549ca7fe32811335e404c10e142ec50c5442afbb03ce0f179848765998ef1a3b60062fd187733919a34999d8bc5724d354421e699e25c39d416c74eb3e2c79229656b54af9a3cb0e71a52e8a9fefd08043f69ad8a8e536b0728e2a039ee80c999d4bd86340c97dea3cb2be33ff333e11dddf0ddd482d99844df08f7bc2ac351987b71e802aec4266b372f2a1e86780b89be04b444801e52c2a4cbf0cc2049e2b3d2c6180e7a28ba0e459de1ddbd016e0c3538f137dc98e2ee1729d5d5b0879073eb421183da7b55866ba8b79c911792a4a6b80b9ed15dfc4249d4c88cf229bc16491f5b8f1afcf272622bf782396aa55fe527a5a67d6327010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e1ce050103ffffffff0200ca9a3b000000001976a91403e105ea5dbb243cddada1ec31cebd92266cd22588ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f570880148700000000000000000000000000000000000000 +040000003893091e180992ed3ddbdca17b69bb32636fa24e139463cc3fcc390f72f00100152337b21d88dbabe7993aeda34e575edc0a10d8425ebf675ebc4a83b3ebc20c0d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5a39dd295c4699181f1e007d867f82f93d39b4107a1265900058b7565dd1b2945241a4ef63bc690000fd4005004b556303cc38ed6fccb33b93a81c0f8f05dd0e8006d64e34868ac9fe5c22863eff3928f6f2831b502400bed03b98c17215ca8a50612273461d43f857d192154f4ad5db69f067aa48d19503523a2f0602fbccbc01bceeeeb256d5690efa33a583efbf397481afaa5745976f397bd7c0cd270674f2de48ded99778fb0da11068c770ac303637c1ecb1b53e6f1be4f89130a7b45a49edcf081959f37121190d42d668b6bd5c1d353609479c1a598c2c43dd0661c9d51222bda25437f3fd1e952aea271a02c0f19a92f4a11faa20ccef67046f1a4036f38b299e73692e61aa67d526f1b7251da0914ac33d1cddd7a4bfeefada21c8f38ceedc1557d0710bbba0f4932c839177bac34abcf402e168303ed8021dc449b3ca0940448bff3217c0bc2730b19190c6811c420629a1878f6e7021171e9359002e4bc636a5322b8056733e3299bdf0c0f4545aba5372ca4a5b337300f3e8c43e89819a563df0e385229405479e1c12a50b57cc1b8e8e2f8b1db9b3aeca20724244f21c2997270e316e37528f7f302d95d9a2b71d7f0319fb4afc4cb25ea17f1a6fc3b7fbf68d71b65139fbee9997c404c2c76611cbb97750d7e5aae23d2ac26de83ec81d2774caf7279e40737f7b1502a4e8ad15f88a7bcc660a148b1c8c68fe4d891514a7a0d7b6b9e9e4561cf510a9c10bdd586fa57a9683afc9db809d93777c435e0780404f5e05a91ab84d6a63abd77e33e1881f2e341c6a7cae0b8d0507dd6bab56ba688cef5910ff18a816a3bb38fa5c6307eea5221569cdc90cde855083b12e189c39cc9052c328d1e654a3ea11371a6f5ad6ad07d396227464e23b4ae1e1aa1956567e000558473a29f34c53dcd8d4b8d3df0410d8af663de3f177565409f30a2ee236d6edbffc355609b4a60643f6f7db0b30a111d9269d6df96d1d487a6ae2d672ce37fbe0ef02ef64ea5793778f67af238fe45ff15ad1609bdead8fe39d445b2644254cf0acc5effc84ff8786fcbd041b7d3a42085d5c2f1cea86f2817d9b1e21c252e6f02404c7699ed8c6c14b979371d1a726451599fe312b10653fa69fc6c132e30bc299af2e92f9fb4495229f1e49ce927fadcbb19ebee2cb5ceae56981201d7b73168997bde9b287abb25d86d43562839a0b30be80f02c4ee3d2021afb990364264e78cf5b07097dfe860a047191ece343378f63d88136a6509c511f4b785b48187e1bde4f8fcabec22451c19eb9af4a6b743c289426cbc2b508bd4bf9ebb382fe131928292ed0d5113f3e86c3dd8656638f4c0ee5b500ef57d19aaa4e00ab17dcf7950954d017f648aa09bbd5a87aab8c9f42a01c810bc93790ed6791d3a2462a571320bae636632b2d3d77611c6cdff96afff421a534df351910f3ffe92eaae18fc6d4a50908be160ffc5dca626d2777625205d07f18eae60d9fe1b9e32f56fd3e92f98a1aa5c728130249a24cb47556d2c40f2029b17217807e3b3a11afbf8d981df73122d61199854f2414b9ec3f687a2a021fec118b38e6756e67aec56eb726fcab9b69ae09d77d3b55559c10d8afd242836bab39f8a93d1a5f16ba0e0c76565a2731a467af0a490a8e3baa3b3bcd36fba75c7d5c3985355dc38f2ff73f9728903f02274126e39301b1ee8599ccb9fd9955696f08f79a035f09d9ac570225ad05666a2900b56c97cfaa985f3a5814c46ad2a5d998c7f18be2dc1e23c540cd208b3a11124de8965dd8b645746cf351544d0371cc798ec6552d329980050c2a04812f66219677507591470d9a5d103d011b901f2a234395f363a59cc8d18561f7807f1d26e0fe5d2bd38365c174b2886758eeccb73f958c1550df9016863c87aa5a94daabb03dc34208bd682854ebaebe90954ddfc2a4974a02566c15fc5976d38c010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e2ce050101ffffffff0200ca9a3b000000001976a9141fb09ecb821dd7ce9cc8ee1ed54df0d1863f824188ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f570880148700000000000000000000000000000000000000 +04000000d91995f491c208a7a8c1d358417120e91451158db34ab4ef30a1c026e2d6070094eda3d052afd42b6d45e541a1d97184636bb767ccbb7d8f91584693a80233250d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5ab4dd295ca942181f1a0006ea1556b0c6c73177cd09700bb72e6b2457fed3174f1d69d4e6e3190000fd4005002f434e5adb994d86d3d45d4fc9584f3d70dba6ee34005bbd47169aa751d4c4d06bf9476a26cfb2fdc905c663c77186e370765413a5d0d25be15e4d50746c232c0fde2d09e01c5cf1e53b70db14c9d889fcd1ad128add562edc91339d81d1a4a78e3eed31d4d16d5b3209ae0571569d590114f68e85fd37b26b0c7724531d14a72bbacd02a309cf039521ce4d5537069326fd1fec07eda692ff09f941a8f8c0f543372ec8fa85c20416161dd719e3f4d3f0e663eeefc4da0feb31eace0e54f9df1fd5714aebb4d306636d0b61357e343e9c04c7f60402a9066bdf11b6817ced3f39e539ff98e4282b63ead466a6e15d5c85b76affa63a4e8e935f060a074f30cf9bcc89ef3872f80268ffb4cdc5aa67fb10b745e3b856fbb4b95d439fe8c8933b50eb5dc0091ec9c41c5ddc998b477c5302c0aede64eecf70b35c1f7d7983e1e052039dea18529752ea7e5ae67f4f51009908fd530968726e0fc9dd79d71022d2eb5749f1055e96fa98b35b79e7e32a92de750036c29bbaf38c0d30587b1f87653bcbb979142b658396d4cb7c2496566154d160ba44dbe97165f909c0c5c2e99077bb71041313603d852e22f0c560ab1f0738ddf74f105aa13e26ad8baa139f98f29ed71b85c80085f324f7db0b0a6e9e1d4584af6c592483254378654903adf6b4b70b77be24e817b053ac55016f29ac78a218095509e902936166f7923ceaabda692442ce1da6fd8e3a6b860cf8ecf7770ad2d77afbb0d15de5fc6084ff491a370baeb547171415e1be7f38e5edcc1283bc573f5522286f3d92208d568896dcf51c1566cdb583f1f0d87902e8fc27c6d1c43b1b3bb051cf568e426b551608cc1d27aa72fc9ff34f2479e609a8fc68a6899598f2d713065789f95983f78a3b746798bb19a989381ae0a531178d735c11e1cfa856b40d05b1f959115608e66800e95c067be6ebbd6ac008193ced38e24bc6db67a210d9a6ed0eddbbcdfb0f8439ab66cd4e27ca32f01a095212f36cd6b1c13c17c4ad3327c255b770dc66e03c797b1d39177365c08af59e723825596f024c7dba0a6a71657f84ab7ef5b345c11ad19191bd25d725cd1660d4e0e793c453bf9aac0494f4942f75481f577d0fe5860ffe6c776fbbb801bc6794c11c9d81ec91d32a2e162c69651c8deb52d2bd4bcdf9e0f18254549c041230266cadaf43cb09f113967492286faa3ce2c50a261ec8e847956ddce763e1df446b4164f2594bc40a1c6a756a451022381fd618bb432b9dbfbcb92d79865e3e9beee8a7277359188b6ef0956b59b47e023c06cd3f99c002fed0fb9a7cb63578a4ab4be39d24981caf13d1d4d580e4d48183aca7ae8a3230011b83d1097b33d5b7a3cf89f61d6702f5ec79e66fcd93e5bd4a5183189fae5123cb56791bcf64b9425e473cfdd2073b30e551703823d848e4adcf32673270b7d4b91511b38efd3a60937f2b90c7accaf7102e0b3c5e12c907b62acf098fe2abe7d073f4a4ba248623757adcd12f27ec2cedb749c7e280452b7f2cc085beceb495040cdb00ef434bc9ab78f8b0f79e8bb90cfaf7f8577743dd5a96c8264aa9b19ca5b7e46ed1a74b3d3fa1f02eb0164b62297b61e6bd951bea4562fdf8b3355ea95ad9a72ad0a35affe08f683bf77f781212cb53f9130b672e7e6a5ed3b31bf0a1089f2e694ed7875b9b5d11bc2c6ce1db7dc770d3b6a4e872a572285bfaff5c1f7a2202d8891b3ee2f6bbe4bd633ab700815c6ed9229e19b49856d72da66f865b9bcda32e5af81fd54c122c79ca1fd7a666ea4ef38b5a668b9532e675d8c0561aeba9c5f29a8f9cfc66918bda81e63ecf792705298699e526a10d4bdcc293e93b21513b8cb7dd486030145c81942a84038001873665f93d8ef80bf942f0020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e3ce050104ffffffff0208d99a3b000000001976a9142943b34e1bbdefc6f6a5c501264404eac228ba5b88ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f5708801487000000000000000000000000000000000000000400008085202f8901bd114c7474dc9bddc943f99e3cd8af97332b4c899fbdebc44f105fe6b760bf41010000006b483045022100b73c1baacf61e8b4bd915ee1c007a51366a7cc718c5469de9f035aec7f92fdda0220595ad4cadd2617be7ce65ca5aa10b6d86ae27fb86c960686d7e1c668469afe12012103f9e72f0713a4d4a980309a14a2ba563e0b1125ad067818e77553a1eefbfc5be7ffffffff02f0ba0400000000001976a914d78f41784821c3d9929fa56e85267eae0bb09ffd88ac486e1500000000001976a9144faeeb51bcd0b49f238b323e5f1c6c8bf11ae02a88ac00000000e6ce05000000000000000000000000