Skip to content

Commit

Permalink
parse V5 (nu5) transactions
Browse files Browse the repository at this point in the history
todo:
- add relevant V5 fields to CompactBlock (instead of skipping over)
- add V5 test vectors to unit tests
  • Loading branch information
Larry Ruane committed Oct 7, 2021
1 parent 9263e7f commit c6ae3df
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 58 deletions.
136 changes: 102 additions & 34 deletions parser/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,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
Expand Down Expand Up @@ -101,14 +102,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")
}

Expand All @@ -120,11 +121,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")
}

Expand All @@ -148,7 +149,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) {
Expand All @@ -171,7 +172,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")
}

Expand Down Expand Up @@ -339,12 +340,26 @@ func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) {

tx.fOverwintered = (header >> 31) == 1
tx.version = header & 0x7FFFFFFF
if tx.version >= 5 && !tx.fOverwintered {
return nil, errors.New("version 5 tx requires fOverwintered")
}

if tx.version >= 3 {
if !s.ReadUint32(&tx.nVersionGroupID) {
return nil, errors.New("could not read nVersionGroupId")
}
}
if tx.version >= 5 {
if !s.ReadUint32(&tx.consensusBranchID) {
return nil, errors.New("could not read nVersionGroupId")
}
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 txInCount int
if !s.ReadCompactSize(&txInCount) {
Expand Down Expand Up @@ -384,23 +399,27 @@ func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) {
}
}

if !s.ReadUint32(&tx.nLockTime) {
return nil, errors.New("could not read nLockTime")
}
if tx.version <= 4 {
if !s.ReadUint32(&tx.nLockTime) {
return nil, errors.New("could not read nLockTime")
}

if tx.fOverwintered {
if !s.ReadUint32(&tx.nExpiryHeight) {
return nil, errors.New("could not read nExpiryHeight")
if 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) {
if tx.version >= 4 && tx.version <= 4 {
if !s.ReadInt64(&tx.valueBalanceSapling) {
return nil, errors.New("could not read valueBalance")
}
}

if tx.version >= 4 {
if !s.ReadCompactSize(&spendCount) {
return nil, errors.New("could not read nShieldedSpend")
}
Expand All @@ -409,7 +428,7 @@ func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) {
tx.shieldedSpends = make([]*spend, spendCount)
for i := 0; i < spendCount; i++ {
newSpend := &spend{}
s, err = newSpend.ParseFromSlice([]byte(s))
s, err = newSpend.ParseFromSlice([]byte(s), tx.version)
if err != nil {
return nil, errors.Wrap(err, "while parsing shielded Spend")
}
Expand All @@ -425,16 +444,33 @@ func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) {
tx.shieldedOutputs = make([]*output, outputCount)
for i := 0; i < outputCount; i++ {
newOutput := &output{}
s, err = newOutput.ParseFromSlice([]byte(s))
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 tx.version >= 5 {
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 tx.version >= 2 {
if tx.version >= 2 && tx.version <= 4 {
var joinSplitCount int
if !s.ReadCompactSize(&joinSplitCount) {
return nil, errors.New("could not read nJoinSplit")
Expand All @@ -461,9 +497,41 @@ func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) {
}
}

if tx.version >= 4 && (spendCount+outputCount > 0) {
if !s.ReadBytes(&tx.bindingSig, 64) {
return nil, errors.New("could not read bindingSig")
if tx.version >= 4 {
if spendCount+outputCount > 0 && !s.ReadBytes(&tx.bindingSigSapling, 64) {
return nil, errors.New("could not read bindingSigSapling")
}
}
if tx.version >= 5 {
var actionsCount, proofsCount int
if !s.ReadCompactSize(&actionsCount) {
return nil, errors.New("could not read nActionsOrchard")
}
if !s.Skip(820 * actionsCount) {
return nil, errors.New("could not read vActionsOrchard")
}
if actionsCount > 0 {
if !s.Skip(1) {
return nil, errors.New("could not read flagsOrchard")
}
if !s.Skip(8) {
return nil, errors.New("could not read valueBalanceOrchard")
}
if !s.Skip(32) {
return nil, errors.New("could not read anchorOrchard")
}
if !s.ReadCompactSize(&proofsCount) {
return nil, errors.New("could not read sizeProofsOrchard")
}
if !s.Skip(proofsCount) {
return nil, errors.New("could not read proofsOrchard")
}
if !s.Skip(64 * actionsCount) {
return nil, errors.New("could not read vSpendAuthSigsOrchard")
}
if !s.Skip(64) {
return nil, errors.New("could not read bindingSigOrchard")
}
}
}

Expand Down
48 changes: 24 additions & 24 deletions parser/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ type txTestVector struct {
joinSplitPubKey, joinSplitSig string

// Sapling-only
valueBalance string // encoded int64
spends []spendTestVector
outputs []outputTestVector
bindingSig string
valueBalanceSapling string // encoded int64
spends []spendTestVector
outputs []outputTestVector
bindingSigSapling string
}

// https://github.com/zcash/zips/blob/master/zip-0143.rst
Expand Down Expand Up @@ -520,9 +520,9 @@ var zip243tests = []txTestVector{
{"e7719811893e0000", "095200ac6551ac636565"},
{"b2835a0805750200", "025151"},
},
nLockTime: "481cdd86",
nExpiryHeight: "b3cc4318",
valueBalance: "442117623ceb0500",
nLockTime: "481cdd86",
nExpiryHeight: "b3cc4318",
valueBalanceSapling: "442117623ceb0500",
spends: []spendTestVector{
{
cv: "1b3d1a027c2c40590958b7eb13d742a997738c46a458965baf276ba92f272c72",
Expand Down Expand Up @@ -609,9 +609,9 @@ var zip243tests = []txTestVector{
},
},
},
joinSplitPubKey: "f4599f5aa5dfbb45da60cdceab7eefde89be63f3f7c0d2324847cce1405def7c",
joinSplitSig: "469b0e272494e5df54f568656cb9c8818d92b72b8bc34db7bb3112487e746eefe4e808bbb287d99bf07d00dabededc5e5f074ffeae0cba7da3a516c173be1c51",
bindingSig: "3323e119f635e8209a074b216b7023fadc2d25949c90037e71e3e550726d210a2c688342e52440635e9cc14afe10102621a9c9accb782e9e4a5fa87f0a956f5b",
joinSplitPubKey: "f4599f5aa5dfbb45da60cdceab7eefde89be63f3f7c0d2324847cce1405def7c",
joinSplitSig: "469b0e272494e5df54f568656cb9c8818d92b72b8bc34db7bb3112487e746eefe4e808bbb287d99bf07d00dabededc5e5f074ffeae0cba7da3a516c173be1c51",
bindingSigSapling: "3323e119f635e8209a074b216b7023fadc2d25949c90037e71e3e550726d210a2c688342e52440635e9cc14afe10102621a9c9accb782e9e4a5fa87f0a956f5b",
},
// Test vector 2
{
Expand All @@ -626,9 +626,9 @@ var zip243tests = []txTestVector{
{"e91cb65a63b70100", "09516a6a656aac636565"},
{"5cc7c9aae5bd0300", "02636a"},
},
nLockTime: "675cb83e",
nExpiryHeight: "43e29c17",
valueBalance: "44b8b5b99ce30500",
nLockTime: "675cb83e",
nExpiryHeight: "43e29c17",
valueBalanceSapling: "44b8b5b99ce30500",
spends: []spendTestVector{
{
cv: "b0f5b874a6ecabe6c56ee58b67d02f5d47db8cc3458435d5088d69b2240c28f3",
Expand Down Expand Up @@ -656,11 +656,11 @@ var zip243tests = []txTestVector{
spendAuthSig: "9c8a091ffdc75b7ecfdc7c12995a5e37ce3488bd29f8629d68f696492448dd526697476dc061346ebe3f677217ff9c60efce943af28dfd3f9e59692598a6047c",
},
},
outputs: nil,
vJoinSplits: nil,
joinSplitPubKey: "",
joinSplitSig: "",
bindingSig: "c01400f1ab5730eac0ae8d5843d5051c376240172af218d7a1ecfe65b4f75100638983c14de4974755dade8018c9b8f4543fb095961513e67c61dbc59c607f9b",
outputs: nil,
vJoinSplits: nil,
joinSplitPubKey: "",
joinSplitSig: "",
bindingSigSapling: "c01400f1ab5730eac0ae8d5843d5051c376240172af218d7a1ecfe65b4f75100638983c14de4974755dade8018c9b8f4543fb095961513e67c61dbc59c607f9b",
},
}

Expand Down Expand Up @@ -747,10 +747,10 @@ func TestSaplingTransactionParser(t *testing.T) {
}

// Begin Sapling-specific tests
testValueBalanceBytes, _ := hex.DecodeString(tt.valueBalance)
testValueBalanceBytes, _ := hex.DecodeString(tt.valueBalanceSapling)
testValueBalance := int64(binary.LittleEndian.Uint64(testValueBalanceBytes))
if testValueBalance != tx.valueBalance {
t.Errorf("Test %d: valueBalance mismatch %d %d", i, testValueBalance, tx.valueBalance)
if testValueBalance != tx.valueBalanceSapling {
t.Errorf("Test %d: valueBalance mismatch %d %d", i, testValueBalance, tx.valueBalanceSapling)
continue
}

Expand All @@ -762,9 +762,9 @@ func TestSaplingTransactionParser(t *testing.T) {
continue
}

testBinding, _ := hex.DecodeString(tt.bindingSig)
if !bytes.Equal(testBinding, tx.bindingSig) {
t.Errorf("Test %d: bindingSig %x %x", i, testBinding, tx.bindingSig)
testBinding, _ := hex.DecodeString(tt.bindingSigSapling)
if !bytes.Equal(testBinding, tx.bindingSigSapling) {
t.Errorf("Test %d: bindingSigSapling %x %x", i, testBinding, tx.bindingSigSapling)
continue
}

Expand Down

0 comments on commit c6ae3df

Please sign in to comment.