Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add structural test vectors for transaction formats #85

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

str4d
Copy link
Contributor

@str4d str4d commented Mar 11, 2022

Closes #84.

str4d added 3 commits March 11, 2022 14:14
We weren't generating negative value balances, which meant we missed
bugs where value balances were being serialized as u64 instead of i64.

The change to make some value balances negative causes some ZIP 243 and
ZIP 244 test vector sighashes to change (as expected).
The test vectors include explicit values for simple fields like anchors
and signatures, and lengths of variable-length composite fields like
Sapling Spends and Orchard Actions. As the v5 transaction format always
ends with either a simple field (`bindingSigOrchard`) or a zero-length
composite field (`nActionsOrchard`), these test vectors are sufficient
to ensure correct parsing of the entire transaction.
The test vectors include explicit values for simple fields like pubkeys
and signatures, and lengths of variable-length composite fields like
Sapling Spends and Sprout JSDescriptions.
@r3ld3v r3ld3v requested review from nuttycom and daira March 11, 2022 15:40
@@ -19,6 +19,7 @@ tv_scripts=(
sapling_note_encryption
sapling_signatures
sapling_zip32
transaction_v5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is transaction_legacy not included?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like that's in the third commit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LarryRuane it should be included here as well.

if valueBalanceRand & 0x8000000000000000:
self.valueBalanceOrchard = (valueBalanceRand % (MAX_MONEY + 1)) - MAX_MONEY
else:
self.valueBalanceOrchard = valueBalanceRand % (MAX_MONEY + 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three times and you factor out:

def signed_amount(rand):
    valueRand = rand.u64()
    if valueRand & 0x8000000000000000:
        return (valueRand % (MAX_MONEY + 1)) - MAX_MONEY
    else:
        return valueRand % (MAX_MONEY + 1)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe?

return (valueRand % (MAX_MONEY + 1)) - (MAX_MONEY if valueRand & 0x8000000000000000 else 0)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like @daira's suggestion here: it's explicit as to the switch that you're making at the top level.

test_vectors = []
while len(test_vectors) < 30:
# Generate transactions with versions prior to ZIP 225.
tx = LegacyTransaction(rand, rand.u8() % NU5_TX_VERSION)
Copy link
Contributor

@daira daira Mar 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 was never a valid version number. I see that line 327 above will replace a version other than 3 (Overwinter) and 4 (Sapling) with a random version:

            self.nVersion = rand.u32() & ((1 << 31) - 1)

but I think that versions 1 and 2 (as version numbers that were actually used) should have high probability of occurrence, and 0 should be excluded. One option is to keep this line as it is, and only replace 0 with a random number in [1, 231) in the LegacyTransaction constructor.

if valueBalanceRand & 0x8000000000000000:
self.valueBalanceOrchard = (valueBalanceRand % (MAX_MONEY + 1)) - MAX_MONEY
else:
self.valueBalanceOrchard = valueBalanceRand % (MAX_MONEY + 1)
self.anchorOrchard = PallasBase(leos2ip(rand.b(32)))
self.proofsOrchard = rand.b(rand.u8() + 32) # Proof will always contain at least one element
self.bindingSigOrchard = RedPallasSignature(rand)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below here: version_bytes is confusingly named because it doesn't return bytes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, ZIP 225 has been updated to specify that all fields are little-endian, and so that TODO on line 504 can be removed.

Copy link
Contributor

@daira daira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK with comments.

Copy link
Collaborator

@LarryRuane LarryRuane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concept ACK

if valueBalanceRand & 0x8000000000000000:
self.valueBalanceOrchard = (valueBalanceRand % (MAX_MONEY + 1)) - MAX_MONEY
else:
self.valueBalanceOrchard = valueBalanceRand % (MAX_MONEY + 1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe?

return (valueRand % (MAX_MONEY + 1)) - (MAX_MONEY if valueRand & 0x8000000000000000 else 0)

@@ -19,6 +19,7 @@ tv_scripts=(
sapling_note_encryption
sapling_signatures
sapling_zip32
transaction_v5
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like that's in the third commit.

@LarryRuane
Copy link
Collaborator

I'm trying to use the transaction test vectors here to test lightwalletd parsing. Maybe I'm interpreting things wrongly, but it seems like the nConsensusBranchId values are wrong in the broken-out fields. Immediately after the occurrences of 5, 648488714,, which are version and the nVersionGroupId and are both correct, there are many different values: 1938782813, 626621063, 1719046631, ... Shouldn't those all be 3268858036 (=0xc2d6d0b4)?

@str4d
Copy link
Contributor Author

str4d commented Apr 6, 2022

@LarryRuane These are structural test vectors, and structurally the nConsensusBranchId field may contain any value. If we forced it to always be set to the NU5 consensus branch ID, then we'd never be able to use v5 transactions in any subsequent NU.

Incidentally, this is something I plan to change about the Rust transaction parser, which currently applies the additional restriction that nConsensusBranchId must be some known value (not necessarily the NU5 branch ID, but one we've hard-coded). That restriction is a consensus rule, but it's not actually something the parser should concern itself with (and it made running a private NU5 testnet harder).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add transaction parser test vectors
4 participants