parse V5 (nu5) transactions

TODO:
- store, instead of just skip over, nu5 transaction fields
- add relevant nu5 fields to CompactBlock
- restore disabled V4 unit tests
- add V5 test vectors to unit tests

The reason most of the V4 transaction and block unit tests are removed
is that they used V3 transactions, which lightwalletd never sees in
production, since lightwalletd starts at Sapling activation (which has
V4 transactions). So these tests were always wrong, in a way. This
commit simplifies the parsing code by removing support for V3 (since it
was never needed). The tests need to be updated to V4, but we'll do
that in a later PR.
This commit is contained in:
Larry Ruane 2021-10-07 14:57:26 -06:00 committed by Larry Ruane
parent 9263e7f111
commit ddf3781a60
5 changed files with 272 additions and 1241 deletions

View File

@ -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)
}

View File

@ -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"`

View File

@ -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]

View File

@ -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
}

8
testdata/blocks vendored
View File

@ -1,4 +1,4 @@
040000008a024cebb99e30ff83d5b9f50cc5303351923da95a8dc7fda3e0160900000000226b1c86e101f1fac09aa533c246bd6843ee6b444ad3ff251df0a401f16f0981000000000000000000000000000000000000000000000000000000000000000069cb7d5b0f1d0a1c0000000000000000004d6cdd939a4900000000000000000000000000310c50b3fd4005008f78a11b4b81126dec31cba6ede2e131ab376a8611237b04d20cd8bb4253f1e8721e0941970e0f58c206f09c8c86c70b2f73b3b5994571c389a90fadf6b812cebba5afe622b3e4aab20a88c805e1f3925e31300df11c09e3d4b7acebbfb156b75e213e36f65d88f023feae5d37ab2f13a1215299155b7d9a1f4cbb333212e8ef9959785adfcd2506d33ac1021207fc32584022080f4a7ba67157ea99f6fc645737a2a5c6f5d99700ff0c1a92f2845dba3b022d24de26691c0b56977b13e94d5f93ebe423d3d422a478f2e6d9f0b113b3fa0434fe432c173013b1a641e884f74922a3e6fc6cde4dedcd8c2458be13fb91f5285ffbf8a5dd207dad3b011b832444a85e27da43c1f94864ad7b066559d5210bf495565005f9297f6204ad7b4c395d5faa19c2530427ad4889555aefd272185f825564525f6d3e0bb62b18cce8ca120a6f636ee36f18612fede045bae81403752f8154e7c3bf4fef7134591379389155360cd15328c731c1ed3b57f439994b0350799e91f91f53e30ce9bed6ea9ce01b3f74f6041b739d85965d789b9c43ba65cf4f99808ef03c2ac46461ba0eedc05e77400815af9d8049027bb7fef6646142d86ef35adcca060e8a656c6c6cc811f65231b4ac531fa2083ddffbab26d19409cc357b89ed7da3d25476cb9d9a9c939c343d23fa35a09c52d593fb03f609dafcc28579fdd35e044168e33e747757bfdf5123080b6799d2527368f90de7f126304610765670214b6e9b6f497a491650db139129da19964461c368e2524aa1524248ed92561b3e94aec38d5ff4adcab5f73565dc7626477b1d56620ad1bb49000d1cd915a260c0913960c493edb9770d2fefae76dae63963e147331a51b1c66d5ff3ecf87f141306d575e60ca3b34fd26cb0b1d735dbbb1977db519a7a9d345ccc77121b688c7470975ee9dbfc489800f25a41d406ccbcbe1f01c629dfcbd59ea5dcd00334cc6af8718e08631c3a83d5e6395b4acc3afab48b145b73a064904176c30c2cd9a876ebab5f333b6ebd17c10e31ce6f009daad792c31fce13dd6d401120eb08d66348c13735ffc667e653ccf80bda54d0773473177433406a0c2001ec9f534c54e667c5a3cff3bca72625b5fee94a51dfc2b5b3fc73353b2b2f3a9e708932003e771dd21441e9cd075765e33ab5fe0db05b4b087bb04608d5fe5bd32f8272752cb1e59f856eab0e366a935a9651be4584f8886649c66bb6bff29ac82e0bf749c94e46cc6a42988196c15d3b2a6376185b990e653cbc8a77e56fd3e74378bcd54f4540afdf39650950fd18192198bd743708fcd948ec57aab07d71211157758ca83509cbc0094158b75bd840810fb266f91565df4f4013aaa4e414b3a56bf8442938189441ae24f8e2417b7bb729f9cf27a2ee5da291fed793afcf09a1c63f14af0dab7aa6b34b1be3d545356653f14a2d31173cf50fa6aed549933a5743cd46809bf6e64824111cb5032130536943f359ffe5cc7ad5b4dcce3f2d078f7ca751fb08f8a5d9ac3970cb9f2cf31ed1a713cec3e8bac8586ce10c74e215c3d691c58e176b39382da1fe4ff0edc7f23373f594ab00c987c772f92317575d4d199e79f5b95fe1986ff0dd4d4a339a797b3b366e737bf8befbf80fe58f0d5b6f35c831d00ebd0c47ddcec914cf879ca685c0724b9036f98abacd96102657148a55e6e7896f33d51daa97190a233a4d68278b51c2bd26dd23916c54e605b6578a8e3657359c36a9ee2e4c0f650a95a3f4b61c4133ddaa901154d20c807a6ab85211bda537f26a4fe4b53a4a64f5522d13eb75cd2bab7669ef47d1c7fd43416182bcde9370105383fa2c0c574d4895d2fea59889068e66f83c8f15118c7ce7adb41fd8de61c2afd7277d6ac31346aa01030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff0503e0ce0500ffffffff0200ca9a3b000000001976a914361683f47d7dfc6a8d17f8f7b9413ff1a27ec62988ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000
0400000015bea7ff6272a28e0dd187b5160d8fae6f2ad6316d652fdd26b92204000000008ede1b5093bbb6ae7c883dbd4b8bfc9e97290ac6ba5a617c880d3aef1bc41d9200000000000000000000000000000000000000000000000000000000000000009bcb7d5ba51e0a1c0000000000000000002c476f1d2c08000000000000000000000000002c27173bfd4005001a40fe9c1795f9617212aa1ade76cd93f85db2e5254a8ffd5aab68218adb36065f6947477a423c26c719803ba9c01555a1d81ef1cbd4ab0e7a7c9c7aeba622c7c1614f16409f5b0f757a93d12923027fbe875e01d4003525150ba313f43148bf39e7836a3d5fae611de2de0eebd8a3d1b163e4ad7b3ad0bbc00a3f4b8d029ee0e23f6354cbc381711eec9df770f31149ac1e0eb55229d10d7ba106edb12834296cfd241f5a62a6014ac0726dc4543f05a88138ad2d7654b707aa799115886e467957d5f195fda7a0a0d7908e084b974a1e0565e222acc9670f4f7191b4052f84595457315647468f532773a0755b0b3c7649aad0ce1e9da9597819022f7c951ddf47978c5985bb3f4350b1b91f2eff7f149ba3992316712d0bb2a3b6fbb3dee54b516d22b21f60c23f87e3513d2ae3447191b55b17f3671f9e3629da0eb3b8261cb7eccc675761f95452435bf6b6f800a4e5a2deeee86b8ae3d026a11eaed463373c378b079a68b51eed25b596fd519a2530f9414684d71dfa427c461ab170137fcd343af2fef3122bb037df784e647b6386823340abffa60668eb7a5729fdda35d42602729b2fa822d11da41c304ce27530bc17e717fdd10a2f221cf793e4db842ee2f6f04b2a74d3945214ec0d1819005f0e42f68c8183e9a0a87011180ed87eb3205e7f01d7a760d95b874ae2d17e915b267cbd0e4e06e99982744e7473f74af2ff766730ee01f7528478116633f7d2eebd87d8e134429e4d29b92144fd0ffb1628add40f98c4df2856632055a3d2c70a2abf69851929b6aa5a9de2af2148ea564a7520f6d95e5724b40c2a3c9d39c995b2f3e131795f2299029c215b07cd14bce9e10fd05296a8a646302ff4c45e85a4bc3bec12fedc1d1f60da8f6aac22f746d43a11bd08be8e52153c60ab71061d35284872c427e3aaff0d8a3a82af016db8d42937f04be1ccb021e7152aa82a068b0a1b04cbe51b904ffc00f3bd46cedc3c58ba378355360e11982c9981c6a5ae7c6d21fa3b2e54b8e01d49a0f618df191ffbd3274b7ac3c5f066623a15c1e07eb64e1db18f37cf4d4452c441c63b8075ca220d0131a2d022da9305700b8e0d236ab4dfd17c2f453c3f17fd722925de3cdfac190bdec92340db74ad46ff693f79c92bad961d021e3a01dcf16445d66713e53bc37b89720c887088d26831e5a4a7011366eb3b6920df10a3316eb41dfbe9ef6f697bfb673be8bce2a64c16baa1ed1cce0a0bc32595bbc22f42ba8d2dbdba3f6097cf7b27cf4717d7187bab684e364a17c69e29c49ff3aab10d8f9d3002149c72d90cf115723ca3aee3c8b8014918896bf63a2746d7a486a496b274b21765dcdeb83b387252b876129b65ce91e613783f55aa1ca85c07a448b4eb36d95681c1851f7532a47e8056eacbd8c45d01b61dda489c744f267111294fef34a98c5391d6ff31491f974fdcbee1cfcd740c714abd9a616878d604076dc376afd53826da95c4b81ad0f991642a6bd3d518feec5a021e20a73be5924147203586c75b3fa9c51111119bd39061fdeedbc1a1a92aeafe834dff67071438f21b247a5713e66bf156ab70f04995ba70f4f0129da42ee3d5ca8da38c35c2b8d33d19e79618a0061ac8fdae6a58250725ab51dbda4a6541d7fe1cd31403ad8d6cfde6badbb85ff162bc1f08b109866f8aaa1688b2de068fa272a3a6371f3876101a37191dc9c308eff5bbe41512b7a749668f2a35f43e2dc67c91ed13e39bc4ce5783b795d1a16ba03886ca053dbd6a4006e0f5091e6955d7abdc154373d10d4d60ecee597c0a8d71dfbb2e3b4fa83554d459c0a68d655c9b28ff0cdd2ace655bdd8dc2322394a34255b1d5373dc39c310244fa0fa1c557d7caa3d029ad36966d0d7606e301030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff0503e1ce0500ffffffff0200ca9a3b000000001976a9146d17c0ca63e020d639f5fba181ca4f5da48b8f7088ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000
040000003723935953489b860d1de1b8fc8240ced04625f0ffd23f16519b8e0700000000f066ce1a8f2b97d05dc15ee51e1eedac93ba6057a953eb81d3f856e9f4b7ae270000000000000000000000000000000000000000000000000000000000000000c8cb7d5b13520a1c00000000020ff5b32c230200000000000000000000000000000000000d5e39cefd4005003eed282b472cfdcb0540b4f4245ed479c5357ff11989ef70be9700eff5987318f727dafa0b467302aa016e5e956c5a5d23f0f9e4a5623b9dda30e0f1b3c47e8c3d151e686cabd4ac89fc627e5cded736d707780c3dc341370bf7af4cb291878a1c4236cc207f39f081ef26b50fe099bdb90c8a9462663d1aaf079fefa010c2a8c29211f08b8debf2f90e4afade29bc53d74253c3fbdf265759fee75e1ab46adc90f38bba5f73e80e518def9b61e8b714d6e6a1dd585539ebd1f5f2040e8e73498455bec4fd333747f96823c66fb859177d13f3ba05868918255bb022cc32dd0a099438fdb1b31b15e2de9acc341066d6b20e4c1d18f56397dfe97919a2eb988e114e3cc015c1c209d906e494c46b9dac34a99ad0595868b4f84a1474ff532202149ef12acc1db1f9307213f49efcb0a408fcc1cc6124e637adb22ee4368da8d0a78b696956e7e4c9feea5d2e198f0e00bdd16ca49956c32ff793e3ff6e635efe57788fbf11a8b2a771d1981aeed6462fdd74a6a9f5d813d86616923f5add19a41d507ba22b69a0caf4a608f8b7673bd605d7f2b022b3d8436448aeb34e491c12addc3911564ecf5de8b1355f1762dc402e37915c35cb69de2a9d575a765737ff92c653eed1eb871270589c2dff15f827ba45b19ab3d25da28fe3176895a8cc32a943391276625d991492ed315ab22a7192fbcc5f5eb01611640c96585ac4db590777a87444c3dadc653bc2bb279474ad76ac00d5972b92e026d45fdd0ed84b099a13d08c00eea952c5a2a78218e06ff989f85b52801d203f82d395310f67e80f4646f3f29c5b0c2f9c3ce31530b766c12f3253a6f3e2aed5b66efd9dcdb6f1de1e601a36b00fa2b7c4d6241e46afc746cfb73f1b2f19c60f81472bd15bdaff21afb0c44514915bfbdb5a20172209c4c9c281ba14950b07b22d097bc0cce5fa017ef344aec83bca60db20f4241ab3e1d29f7f200a24b801aa1e2c638d77ac13c0732da3561e3e1bcc6f2ac78fcf21e7f113b56d83c0343a1cb6d8b69eb12975891c1bb27885f1efc8c97a0af71176f716fe36b61262e23b48e7266f6551821948e4c44d48153df60a1a921faa5766e4a99239c2f36231bdadc93e749db715ff7e8ced9ef6cb28e7a5a8aa7603de56e73ea5fd1d79be8974a4c071ed1b969382da51d68b11f4722303d74f82849cb05d07c0c313d9a0d5eccbb853f85f0c7002c67eeb93737060180a3f557582d3849b73bf13807ff3f4065faacd7562487fc96a20fcf2cc892e16ad74b813caed4f72f11293243912c976a9f2035b099b9b9debc968aec54031bb9f4eb7815ddb3bffc128d6be34814fbec58b17c6dfab6c812a5eb3fd15d60fe3fa55128466cb80bae5579b71025e6a9c34b7f818eb11cdb029eff94fa465e419b9aef2e8679f12ce02b780aba2c17313bb375033beda50e90be5fd949002fbb85634d42598b62d21e346d13c99e4b6364354136f828f972a7b2fd8c4464defb82629ffdddea6355afcf76a5b6a1f797b2a46b51cee7f95f812d926d00436f98711c6f6a1e374a5f8a6f73f39b70150ae7315988b1a2c88174e9bad58c2e0f82476bb1c9ebd03079970441810e5c7c1b8d3a5b8eb21750f29eb92f348d5ccd2dd13fc68b063c754564f490e33ba1bc71b111eb14500d1dfcd883be26a8952aed912f4a999831b80a94d1f994c69558334e165513fbe129a9348922d6205d0bdb3fed3d0fec41a8f7dac2a46f2f9bdd837d6fa791bd25ea3bedf454b7fcf8907000a78d4e911bc798c6d261817400572cea0a26e9e2f5b16cd7129e92738be52b4832c2a072e2c7db3c2b889fa089b1284a3ae2ec654da817e15e0b26856be1caa1c7c0b3446158365a0992bc01fc67c19d5444ad8c6be044902030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff4903e2ce050004c8cb7d5b598c2ba5c7f0b4587e7d60e62e08985aee803a179dda187d2f0c5284df7da7482f4f76657277696e7465722f5361706c696e672f706f6f6c696e2e636f6d2fffffffff02d0eb9a3b000000001976a914de2757e612dee78a681ace9b8e623570dce776f588ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000030000807082c403012263eb14eca399ec05b64be85325e2c5ab93e5d70d7dc49b4e8b8dcb52ff0f7b680200006a47304402207ade36a404000f4d5cebed5c28e28c57a539b7985b66aa4c014e0c59a43fde3a02201bd61f0a998301241d83fc9370654e0acda7cc6602258b954af6bf06605dffaf0121026e88bf2ad6aa4fdc21b2636583184a589fcddb3c1ed40b4e3613cf61601fad54feffffff01bc131600000000001976a9142f592f81575c361e91397dccc60772b9335f432588accfce0500f4ce050000
04000000c848f5a041f4406004243ecf645ef91f303f6766f5615e4cd2c7ab08000000002c46ffced042e210ec98759ee1bf5792b62c3a3c56f24667422350dcf3c84e070000000000000000000000000000000000000000000000000000000000000000d4cb7d5b7beb091cf18a4fa59e6d1400000000000000000000000000000000000000000038d0a613fd400500572cbab31dea47dc52529819b5b80cce7fa6ff4f0c1e8f09d50bee7b5012813826948828c3d5e6f14505a4f7ab54884cb7b8f1a49675b99f820d219567d72695fc14f74a26554ad3c346123852669bd29e50eb056cda5e176bd065ccbbf2663f3a12a11f7773a0847c09059fddb09a37908e1a8240f29dd6eaec78f92b1f67e2a1cd27c2b1d6f9c5c7d0c8545dedb6f949c93507838d4525606b73d176ed8ddfa2a20d93ba4d4303512f8fa4c96efcc084329bad15f9997edef03fc90edcd1e7f943c95b5c7123d66a4d0ec9012ab2578e1d9bbcb47bdf56e9eb72e3c82dcfb14ed9785c860522791bb3bde200f76db5e907555c27ef91489cc4410b4129e4aa08b800c00952b451cf3640c326b690e719d92cd2a87799d1dd63b36984c30a6582bdb1fc6d16b323c2ef33b23deaf2325763f8b1fe7b21dd7c801ffc7df2c4d68511aff3b2059e681bb1afaa19090d03592df27c452350d040718758902afcaee5fa48a00e9fffbd3ec4222973c2686fb3e7beee6c5ebe0ecc130b8981d00adfcd464832b625d7cd82adb99e02f71c4b3d9ec6ce5812cb5075cd70cffd967e73b9f08c04c97c728e02efa88b18f318423ce212fca69c05b743289eb18912bdbcde1bc5fb507286baccb1b8937438092c5418a86fe56bdf441637ac4a49e172f491f743a694bc0516e02ebb1e36ec3b4a460e5f45bf6d2c055924c396c9546f9839b696785fdc09f99ddc7e701a3f0f61b7cb90d6de292400b8423f45bdb6d11d921711cc0a5316d200c220f70e80cda935f6b911e34d1aed9769d40ab973a6aaf1bbbc33ecf57f60d93a490d5835583ca4b70de9218510784caaf2d28c1b3dfe1af1e47ea3c96ddd46e27343ea5b80d175c3ef69b5387d2bfae8d026c765d2f5ec7ec7dacdca63dfbae85974e5a57e636edd68754664b56b9421a009ed09d7007b72db75980945b9f8e0da56d03cdcd8cdb9ce8a6d21336a8ab946fdeb59472585584a85e59e31152f0a9e03da924877854e93d5f6fbcff21a4165bbc4b60c8a195bd46761499fe1a28eb464c0957203d337cd0781d1e385308017999260c8da0933ee3b751d3ce92015b3e67393dc98d883d70113d24f72c4b73a885f1cea47062e88aba3983f192c69dc10db68b95fb3e021363ba87ad938bf97077263a7aeca4f769dfd54370194582d31d7c632c60833fa9346814b26ce1d7c451d6fbd6c309de603780185f499b963a7310abc42450445a2f2840f25350d38f2140b3904fd636ff5016334e2c234d72eff0d8096588c71ffdcde3569b3914f02acbe1938957135acc6d10f2d5bf56885c177e6ca778f7ce431225fd535a15949fdfd2e8ab0c47fd8251e294c02cf98d91340afb5812950c46d9b8dadba902fc70ea73c2c26b5a4d0d59410da46018486d9ec5a0a9b89422190afb7e72b7150f97b359d70841170ae23bacf915f10a019c601f48017472dd2b7c7b89bf0128dfd4a01e450adf17071ded02a3ae13eb7f8fec54510955a305256b2be2d97994140f36222d5ba55ec0ea35749fc894289af4454e944c1dbb1d7d40f1e72276651c4e3542bfd9f145500d9295eab83c6fc92362875620cd0159b7b53ff530d66b25adde2187438045c5fc4b8bdaff93dfb65b85c71753846bfa91f6ac60c9db561423b6109feef932486ece2cdc3c539112318c79ecc7e08ddda73ce663c3d4e9b9a7c743955330f1ca702b8a5673f82328b5f637dfa3743bcbe07583bfecf731cab93abc8d4a5dbfcdbf3536db25f1fff0d71175b744d561eecd1a89fe0484d466d2c5a68d61abba2dd9e8786211482b2b50218eec505f94c67971242a67a93872681ac81e1f541ecbf95a8bd7f5d8c1e60d95a76138bab40da3455b5a36549920d7f4d0102030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff1a03e3ce05152f5669614254432f48656c6c6f20776f726c64212fffffffff0288f19a3b000000001976a914fb8a6a4c11cb216ce21f9f371dfc9271a469bd6d88ac80b2e60e0000000017a91414c884180589f6d0fdc7c400181afb1bb10c9fe487000000000000000000030000807082c403016f4029430669c709d041c579c8e9469446f1159064613e6cd47514691b93b8ff000000006a47304402204ff60fca23a36026d374850bc018e7c714de78d8560d4188fa247b1a4ee4dcee02206538b475265173915662465b4a8554a111003701371d553134047bf185c98ba20121029ba2dc5033c7c17f06274b85c0f8e51ad518f34235713ffee4705867162819fafeffffff0208672302000000001976a9140ed9a32475fa7214939a6705fd790a74976bab0388ac3e926c01000000001976a914d752bedc09529fb3719004407dd6b5f29000e3e688acd7ce0500f6ce050000
0400000006785624481f381e68b4506ba5d6cc53ddd6dc876cbc97e45e3c7c4a626a19008c450aa9112b2e900eba30c2e3b8428c23fb2a30bb7fa34a853c97f02fb1a2200d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5aecdc295c172c191f870000790c975f8c9f0a77f12ef1e021c6045c3c9acb7c432c2aa7ed42ad0000fd4005005db35c547608bbf32bd1b90369e6e0bc1e275a0d064f092b235835cb6dfc9158c6146a0e5cfbdf2d2f04f71478f7d9212bdeeb938029a5cc217ebf1f517711dbc7cb35d084328b8f7613e878f69e27b2b40e8103292cb87845006943b761bbbe43d94cfbf971011617965a7de1e846a586ad81f2ad138eb0fd74729efc04720badc95a1f1383cc70e890db91c143591ab96526263ec6b8a6186568dda29b9015078d19e87cbb2c00b26edee3e4b227ad6fa090db2d290191ef1043e2037fc0bd225e6a1364d16223b0234311e8c5ff455a0cc2e0c1f84944dfab49864f57e5ec2a2b5e91df5229449ee399b4c721abab63c534602abdf64d37f60905bea71cd59be759bae5967f6f3e1bf732753d4d9c1c11dcb5b6949bc4f63a06a3b0db5c2deebab040c70fc55fc648bb2559fe2da2b4a4bd53ccbca7aaf03262cdf31c5bab90cdc151b686d3e5f8a29103b9346602b5394b245dc9655116d33c2ed9d31563bb74950d0927895a024c119d30d4f2c760c346b11108ab29db2931123aa36937cbd152756758e3d341ec40db1d1b539fcbefa2e76af9827f575025c58569fbec1f806607e03a6eabc228a900a18144e310ab608a930ee6a10d5e6fc7ecc9934fb42b51f0a6552e2a1ba1d627b51df2d27889e14b87f093452b79bce3e2a74398f4b748eda48495b30437bef13527a231ce724f24d5bcf4040cc77f56dab50ff30ce12039ca968eef3c5ab44b2cdd8f7fecb36ed39bc624807a4da49d5b2f53abeb1a027676fb500fb89d7092eef849f254dfd3a7325e4b810687b8567d7db8e796480836bbeab2efff44bb0421f4e3d5d1493bedc2c331f75b7ba931e93495dc379fa22404a2e60919fb2644ee4e5c3bc2025fb17540dce5dede2fdc4fc6b87494ae26ae89bbe954ee8458740601a665126129c9962052683661efcfffce8303211fe3bf0adac14936927ce2f4b30a22227c39b4128616dfbfcaeae8f4a3b30255d215d1b9ccd9bff2173b5a94ffa5473bc49097030e782d81c1dc4f8c4a25803c6dd9a9e775646084ed2bbbf7457386ece058032128bc87c7b7772be513f79176241de701580b5c04f88ee81e231583528b514f0c478382e60d5741bd0b5582c2ece2767931f538d050498886814c5b27541764bda9734d5c46f2b3c80f2649d40374e45c053c050e33e83ed578f6d5c19435b7f495261e7e522d5225eaead374497fed7c30e322d6acbf512751b58417120bf8d97fcfdaa5e3be842fc7beab85235ff7e9ee459b03ef2ae01ceff42e0621ddce403e65521505ed0b25fca1a0747263e44c72c5e1e95d85119a7956852c629fcb903b9589e768a6bcbec5358a5332368a6913ffb9b9880e8d3701c6a366bec102bab8c6d9e57b2287ede4774d69fe81af83dbd423c905f666170bfc03f360314edf3098fe2036634ad90f326a71faa5b70f245acbdf8f7d4481283819d8dae7823ac31bafef129b94822a4d94b8ea5773b7477269be9dbe3bc8c11886a2d2a8176658fe30e1ba1bdcf320873d1d2325096223af6e142818a2c370b8447db8e6e865fef2074596bc9e679b8c37c51e99c4f35ac8431303fac507193d1d162d309a3d8d6e57cf4cd429b34ce69f69ef2f1e728d7b9347051b46d4ba5a7e7f9d43963e960d0bbaf63eeb69396dc75b325f08e9243db0c678aa192eb3db32d9cf65a0a38bd5726e31380f2de23ea9a019d8496ab511df2d955978fcee6e331aeea5df0a1826639323180b87bbbf6a039e34f482a5e4eabc6d9a139c40e2840b40eb53dea3d549b54405ba7e1b392f308df2abfd6e97a3eb44f540b272a6d97c085be2142445ea27349bc7a5493397ceee4c8016a38457480f4de263cd2c2416296a2d86c19ded93cf431bfdd1e3010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e0ce050103ffffffff0200ca9a3b000000001976a914f4fbba801ad64c6539e64a1478392c57c9a5e7a688ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f570880148700000000000000000000000000000000000000
04000000913b07faeb835a29bd3a1727876fe1c65aaeb10c7cde36ccd038b2b3445e0a001e1ecc75a33e350c7763ccf055402a3082f28209c643810a2ef17df4b14486cb0d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5a15dd295c21c8181f15000813d02ae5e889354eb9b7b62cca4f139ca6364fb63646ff32f2b4700000fd400500356a828c88fb46eaaa21d759e99109375d79b61557348f830c71fd4dabb126ae794d403af814bfd3fc01b76c2fc110525507f6728bec686ea9a432eda0ef1c8fc97d912923a9ab0d06f8aeef2ba272a3b7125e0e5079f131e4706d59c4f34a2775cfed3fca2cba4a23795351a38abfacf67ae7772e6fd14326efde46192105f2c3b0989a15aedfd22063529005723c8c8ea756ea876e0ae7846d4556e57fdb4b7ae1d2125456130653604eff1a6e632e20350a88b0fece49fa57079818d28c0064764979ed2e75766767a7ddce8578a23712274fac056571619b9ef4371f6924068fd5dcf25112da1ffe4255fe60b851e50b704eef0577967a867a0fbd7ed06f0d93f0f0ae31830ab5eaee5b20757e96233c21f96d6eb8f5b5ff03e88d7a31ced8037a52d94ce82fb719764841c06016ba82e29acb9d521cf9d74e1a1a90175c19ff365b763db1d3fbb6332499a5f90903d26af753812bca3eb2b142992bbd87140c3a0c29732d648c650e6bdfda63a56c6184570bf2bb4d700ba30d7800cecac92f67647bbb282d85228fedbdae1e1e3d56e9cb7743a58f02df45f658cb3b627f6bb311618d1697a217ede3f763d8fbf1fba96f697a62b321d397d492594b477029b23565f8ee80e3df1a74d81be2eaf203e3c9215c72c5fa6ec5f0d20cff3f151d7131ff6a3021f489f87819245d6af34f12dc5d1bd70d2a692be0da4827ed3f81a4b63ee58223bcb53474471a4bfcab99ca08d7dfc6c1cefda57f836d1e58731b4eccbea60c0606dc42945b26b17a3599be385b0b64519f6160adb65fd1da795cf27ea25e9884783834130be24ec10d549eb21ca32188f4221d3ee358f2894099930d165ca4d78985e6c13ceb9ea340ad5c116118c6a2d7851781e554a0e1b968540151e3715513f5476ff6618a5de0498e4f5632a6d5ffea8579ddd0be010b10204499c82540d521397b9a39a079acccaaae32add4d7991db22dcfcb04f86730cbef5b8abb0fa31137abab148457325eb161fd0ebe033094c324f6da261fc31476619753fe95c63a326fd14dbe6e7f83c501b77d4ce5602877b5164973dd55c04f7a891e6f3b423ee425b992d73bdf7e2735e4f52cbedceed9afde086f923208dc1abf1ddce86f9e7c467a8b0b79740510900bef0351faffdda005bd35e5a819b96c1622ca06399cc62f01d08d64c4236970e6c119380df5c48d11d57a8908e76b6d8ac3517f459ec83af139986abf135ea4be1886054eeb0087996aff4ace796b765175166f46af49d6ba10f8b5a455f7bc04ad82a53ac8d71529a6d0dd138642b68a518b61be13761b33bf60133cf97c9c2a0fab6cbfaf3601c0642b6d918b142caf1da2da75d52523739a62984f34f3016ed17cfe03522e71e332079920eb33c0b41de3c3f41e92c0382f7804195e251c0db4d2b6db83566cb4a767a9a85d4d674b226732bbe298ab5df3d5fe2239b82dca3fef254d1a7e3fa9c3545a23eb49136fcd21308280715c7d26601eb40fc49e025b33a6f676b16ae215bce5d6281e0bee2d779549ca7fe32811335e404c10e142ec50c5442afbb03ce0f179848765998ef1a3b60062fd187733919a34999d8bc5724d354421e699e25c39d416c74eb3e2c79229656b54af9a3cb0e71a52e8a9fefd08043f69ad8a8e536b0728e2a039ee80c999d4bd86340c97dea3cb2be33ff333e11dddf0ddd482d99844df08f7bc2ac351987b71e802aec4266b372f2a1e86780b89be04b444801e52c2a4cbf0cc2049e2b3d2c6180e7a28ba0e459de1ddbd016e0c3538f137dc98e2ee1729d5d5b0879073eb421183da7b55866ba8b79c911792a4a6b80b9ed15dfc4249d4c88cf229bc16491f5b8f1afcf272622bf782396aa55fe527a5a67d6327010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e1ce050103ffffffff0200ca9a3b000000001976a91403e105ea5dbb243cddada1ec31cebd92266cd22588ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f570880148700000000000000000000000000000000000000
040000003893091e180992ed3ddbdca17b69bb32636fa24e139463cc3fcc390f72f00100152337b21d88dbabe7993aeda34e575edc0a10d8425ebf675ebc4a83b3ebc20c0d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5a39dd295c4699181f1e007d867f82f93d39b4107a1265900058b7565dd1b2945241a4ef63bc690000fd4005004b556303cc38ed6fccb33b93a81c0f8f05dd0e8006d64e34868ac9fe5c22863eff3928f6f2831b502400bed03b98c17215ca8a50612273461d43f857d192154f4ad5db69f067aa48d19503523a2f0602fbccbc01bceeeeb256d5690efa33a583efbf397481afaa5745976f397bd7c0cd270674f2de48ded99778fb0da11068c770ac303637c1ecb1b53e6f1be4f89130a7b45a49edcf081959f37121190d42d668b6bd5c1d353609479c1a598c2c43dd0661c9d51222bda25437f3fd1e952aea271a02c0f19a92f4a11faa20ccef67046f1a4036f38b299e73692e61aa67d526f1b7251da0914ac33d1cddd7a4bfeefada21c8f38ceedc1557d0710bbba0f4932c839177bac34abcf402e168303ed8021dc449b3ca0940448bff3217c0bc2730b19190c6811c420629a1878f6e7021171e9359002e4bc636a5322b8056733e3299bdf0c0f4545aba5372ca4a5b337300f3e8c43e89819a563df0e385229405479e1c12a50b57cc1b8e8e2f8b1db9b3aeca20724244f21c2997270e316e37528f7f302d95d9a2b71d7f0319fb4afc4cb25ea17f1a6fc3b7fbf68d71b65139fbee9997c404c2c76611cbb97750d7e5aae23d2ac26de83ec81d2774caf7279e40737f7b1502a4e8ad15f88a7bcc660a148b1c8c68fe4d891514a7a0d7b6b9e9e4561cf510a9c10bdd586fa57a9683afc9db809d93777c435e0780404f5e05a91ab84d6a63abd77e33e1881f2e341c6a7cae0b8d0507dd6bab56ba688cef5910ff18a816a3bb38fa5c6307eea5221569cdc90cde855083b12e189c39cc9052c328d1e654a3ea11371a6f5ad6ad07d396227464e23b4ae1e1aa1956567e000558473a29f34c53dcd8d4b8d3df0410d8af663de3f177565409f30a2ee236d6edbffc355609b4a60643f6f7db0b30a111d9269d6df96d1d487a6ae2d672ce37fbe0ef02ef64ea5793778f67af238fe45ff15ad1609bdead8fe39d445b2644254cf0acc5effc84ff8786fcbd041b7d3a42085d5c2f1cea86f2817d9b1e21c252e6f02404c7699ed8c6c14b979371d1a726451599fe312b10653fa69fc6c132e30bc299af2e92f9fb4495229f1e49ce927fadcbb19ebee2cb5ceae56981201d7b73168997bde9b287abb25d86d43562839a0b30be80f02c4ee3d2021afb990364264e78cf5b07097dfe860a047191ece343378f63d88136a6509c511f4b785b48187e1bde4f8fcabec22451c19eb9af4a6b743c289426cbc2b508bd4bf9ebb382fe131928292ed0d5113f3e86c3dd8656638f4c0ee5b500ef57d19aaa4e00ab17dcf7950954d017f648aa09bbd5a87aab8c9f42a01c810bc93790ed6791d3a2462a571320bae636632b2d3d77611c6cdff96afff421a534df351910f3ffe92eaae18fc6d4a50908be160ffc5dca626d2777625205d07f18eae60d9fe1b9e32f56fd3e92f98a1aa5c728130249a24cb47556d2c40f2029b17217807e3b3a11afbf8d981df73122d61199854f2414b9ec3f687a2a021fec118b38e6756e67aec56eb726fcab9b69ae09d77d3b55559c10d8afd242836bab39f8a93d1a5f16ba0e0c76565a2731a467af0a490a8e3baa3b3bcd36fba75c7d5c3985355dc38f2ff73f9728903f02274126e39301b1ee8599ccb9fd9955696f08f79a035f09d9ac570225ad05666a2900b56c97cfaa985f3a5814c46ad2a5d998c7f18be2dc1e23c540cd208b3a11124de8965dd8b645746cf351544d0371cc798ec6552d329980050c2a04812f66219677507591470d9a5d103d011b901f2a234395f363a59cc8d18561f7807f1d26e0fe5d2bd38365c174b2886758eeccb73f958c1550df9016863c87aa5a94daabb03dc34208bd682854ebaebe90954ddfc2a4974a02566c15fc5976d38c010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e2ce050101ffffffff0200ca9a3b000000001976a9141fb09ecb821dd7ce9cc8ee1ed54df0d1863f824188ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f570880148700000000000000000000000000000000000000
04000000d91995f491c208a7a8c1d358417120e91451158db34ab4ef30a1c026e2d6070094eda3d052afd42b6d45e541a1d97184636bb767ccbb7d8f91584693a80233250d86a7943df2dc92fb8b131b0698db24dd01a12d19696c20443f5f639df3ab5ab4dd295ca942181f1a0006ea1556b0c6c73177cd09700bb72e6b2457fed3174f1d69d4e6e3190000fd4005002f434e5adb994d86d3d45d4fc9584f3d70dba6ee34005bbd47169aa751d4c4d06bf9476a26cfb2fdc905c663c77186e370765413a5d0d25be15e4d50746c232c0fde2d09e01c5cf1e53b70db14c9d889fcd1ad128add562edc91339d81d1a4a78e3eed31d4d16d5b3209ae0571569d590114f68e85fd37b26b0c7724531d14a72bbacd02a309cf039521ce4d5537069326fd1fec07eda692ff09f941a8f8c0f543372ec8fa85c20416161dd719e3f4d3f0e663eeefc4da0feb31eace0e54f9df1fd5714aebb4d306636d0b61357e343e9c04c7f60402a9066bdf11b6817ced3f39e539ff98e4282b63ead466a6e15d5c85b76affa63a4e8e935f060a074f30cf9bcc89ef3872f80268ffb4cdc5aa67fb10b745e3b856fbb4b95d439fe8c8933b50eb5dc0091ec9c41c5ddc998b477c5302c0aede64eecf70b35c1f7d7983e1e052039dea18529752ea7e5ae67f4f51009908fd530968726e0fc9dd79d71022d2eb5749f1055e96fa98b35b79e7e32a92de750036c29bbaf38c0d30587b1f87653bcbb979142b658396d4cb7c2496566154d160ba44dbe97165f909c0c5c2e99077bb71041313603d852e22f0c560ab1f0738ddf74f105aa13e26ad8baa139f98f29ed71b85c80085f324f7db0b0a6e9e1d4584af6c592483254378654903adf6b4b70b77be24e817b053ac55016f29ac78a218095509e902936166f7923ceaabda692442ce1da6fd8e3a6b860cf8ecf7770ad2d77afbb0d15de5fc6084ff491a370baeb547171415e1be7f38e5edcc1283bc573f5522286f3d92208d568896dcf51c1566cdb583f1f0d87902e8fc27c6d1c43b1b3bb051cf568e426b551608cc1d27aa72fc9ff34f2479e609a8fc68a6899598f2d713065789f95983f78a3b746798bb19a989381ae0a531178d735c11e1cfa856b40d05b1f959115608e66800e95c067be6ebbd6ac008193ced38e24bc6db67a210d9a6ed0eddbbcdfb0f8439ab66cd4e27ca32f01a095212f36cd6b1c13c17c4ad3327c255b770dc66e03c797b1d39177365c08af59e723825596f024c7dba0a6a71657f84ab7ef5b345c11ad19191bd25d725cd1660d4e0e793c453bf9aac0494f4942f75481f577d0fe5860ffe6c776fbbb801bc6794c11c9d81ec91d32a2e162c69651c8deb52d2bd4bcdf9e0f18254549c041230266cadaf43cb09f113967492286faa3ce2c50a261ec8e847956ddce763e1df446b4164f2594bc40a1c6a756a451022381fd618bb432b9dbfbcb92d79865e3e9beee8a7277359188b6ef0956b59b47e023c06cd3f99c002fed0fb9a7cb63578a4ab4be39d24981caf13d1d4d580e4d48183aca7ae8a3230011b83d1097b33d5b7a3cf89f61d6702f5ec79e66fcd93e5bd4a5183189fae5123cb56791bcf64b9425e473cfdd2073b30e551703823d848e4adcf32673270b7d4b91511b38efd3a60937f2b90c7accaf7102e0b3c5e12c907b62acf098fe2abe7d073f4a4ba248623757adcd12f27ec2cedb749c7e280452b7f2cc085beceb495040cdb00ef434bc9ab78f8b0f79e8bb90cfaf7f8577743dd5a96c8264aa9b19ca5b7e46ed1a74b3d3fa1f02eb0164b62297b61e6bd951bea4562fdf8b3355ea95ad9a72ad0a35affe08f683bf77f781212cb53f9130b672e7e6a5ed3b31bf0a1089f2e694ed7875b9b5d11bc2c6ce1db7dc770d3b6a4e872a572285bfaff5c1f7a2202d8891b3ee2f6bbe4bd633ab700815c6ed9229e19b49856d72da66f865b9bcda32e5af81fd54c122c79ca1fd7a666ea4ef38b5a668b9532e675d8c0561aeba9c5f29a8f9cfc66918bda81e63ecf792705298699e526a10d4bdcc293e93b21513b8cb7dd486030145c81942a84038001873665f93d8ef80bf942f0020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e3ce050104ffffffff0208d99a3b000000001976a9142943b34e1bbdefc6f6a5c501264404eac228ba5b88ac80b2e60e0000000017a91466a04fe5f6f1b5bb1149e6cd0079167f5708801487000000000000000000000000000000000000000400008085202f8901bd114c7474dc9bddc943f99e3cd8af97332b4c899fbdebc44f105fe6b760bf41010000006b483045022100b73c1baacf61e8b4bd915ee1c007a51366a7cc718c5469de9f035aec7f92fdda0220595ad4cadd2617be7ce65ca5aa10b6d86ae27fb86c960686d7e1c668469afe12012103f9e72f0713a4d4a980309a14a2ba563e0b1125ad067818e77553a1eefbfc5be7ffffffff02f0ba0400000000001976a914d78f41784821c3d9929fa56e85267eae0bb09ffd88ac486e1500000000001976a9144faeeb51bcd0b49f238b323e5f1c6c8bf11ae02a88ac00000000e6ce05000000000000000000000000