Skip to content

Commit

Permalink
Updating test vectors for Action Groups (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
vivek-arte authored Feb 6, 2025
1 parent 2c4e712 commit acb58a0
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 206 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/test_vectors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ jobs:

steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v5
with:
python-version: '3.9'

- name: Install gnome-keyring
run: sudo apt-get install gnome-keyring

- name: Install poetry
run: pip install --user poetry

- name: Set Python 3.9 as the version to use
run: poetry env use $(which python3.9)

- name: Install dependencies
run: poetry install --no-root

Expand Down
24 changes: 12 additions & 12 deletions test-vectors/json/orchard_zsa_digests.json

Large diffs are not rendered by default.

260 changes: 130 additions & 130 deletions test-vectors/rust/orchard_zsa_digests.rs

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions test-vectors/zcash/orchard_zsa_digests.json

Large diffs are not rendered by default.

81 changes: 80 additions & 1 deletion zcash_test_vectors/orchard_zsa/digests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,89 @@
from hashlib import blake2b
import struct

NU7_VERSION_GROUP_ID = 0x124A69F8
NU7_VERSION_GROUP_ID = 0x77777777
NU7_TX_VERSION = 6
NU7_TX_VERSION_BYTES = NU7_TX_VERSION | (1 << 31)


def orchard_zsa_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrchardHash')

if len(tx.vActionGroupsOrchard) > 0:
digest.update(orchard_zsa_action_groups_digest(tx))
digest.update(orchard_zsa_burn_digest(tx))
digest.update(struct.pack('<Q', tx.valueBalanceOrchard))

return digest.digest()


def orchard_zsa_action_groups_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActGHash')

if len(tx.vActionGroupsOrchard) > 0:
for ag in tx.vActionGroupsOrchard:
digest.update(orchard_zsa_actions_compact_digest(ag))
digest.update(orchard_zsa_actions_memos_digest(ag))
digest.update(orchard_zsa_actions_noncompact_digest(ag))
digest.update(struct.pack('<B', ag.flagsOrchard))
digest.update(bytes(ag.anchorOrchard))
digest.update(struct.pack('<I', ag.nAGExpiryHeight))

return digest.digest()


def orchard_zsa_auth_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxAuthOrchaHash')

if len(tx.vActionGroupsOrchard) > 0:
digest.update(orchard_zsa_action_groups_auth_digest(tx))
digest.update(bytes(tx.bindingSigOrchard))

return digest.digest()


def orchard_zsa_action_groups_auth_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxAuthOrcAGHash')

if len(tx.vActionGroupsOrchard) > 0:
for ag in tx.vActionGroupsOrchard:
digest.update(ag.proofsOrchard)
for desc in ag.vActionsOrchard:
digest.update(bytes(desc.spendAuthSig))

return digest.digest()


def orchard_zsa_actions_compact_digest(ag):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActCHash')
for desc in ag.vActionsOrchard:
digest.update(bytes(desc.nullifier))
digest.update(bytes(desc.cmx))
digest.update(bytes(desc.ephemeralKey))
digest.update(desc.encCiphertext[:84])

return digest.digest()


def orchard_zsa_actions_memos_digest(ag):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActMHash')
for desc in ag.vActionsOrchard:
digest.update(desc.encCiphertext[84:596])

return digest.digest()


def orchard_zsa_actions_noncompact_digest(ag):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActNHash')
for desc in ag.vActionsOrchard:
digest.update(bytes(desc.cv))
digest.update(bytes(desc.rk))
digest.update(desc.encCiphertext[596:])
digest.update(desc.outCiphertext)

return digest.digest()


def orchard_zsa_burn_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcBurnHash')

Expand Down
29 changes: 13 additions & 16 deletions zcash_test_vectors/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,6 @@ def __init__(self, rand, have_orchard=True):
self.valueBalanceSapling = 0

# Orchard Transaction Fields that are present in both V5 and V6
self.vActionsOrchard = []
if have_orchard:
self.valueBalanceOrchard = rand.u64() % (MAX_MONEY + 1)
self.anchorOrchard = PallasBase(leos2ip(rand.b(32)))
Expand Down Expand Up @@ -523,21 +522,6 @@ def to_bytes(self, version_bytes, nVersionGroupId, nConsensusBranchId):
if hasSapling:
ret += bytes(self.bindingSigSapling)

# Orchard Transaction Fields
ret += write_compact_size(len(self.vActionsOrchard))
if len(self.vActionsOrchard) > 0:
# Not explicitly gated in the protocol spec, but if the gate
# were inactive then these loops would be empty by definition.
for desc in self.vActionsOrchard:
ret += bytes(desc) # Excludes spendAuthSig
ret += struct.pack('B', self.flagsOrchard)
ret += struct.pack('<Q', self.valueBalanceOrchard)
ret += bytes(self.anchorOrchard)
ret += write_compact_size(len(self.proofsOrchard))
ret += self.proofsOrchard
for desc in self.vActionsOrchard:
ret += bytes(desc.spendAuthSig)

return ret

class TransactionV5(TransactionBase):
Expand All @@ -551,6 +535,7 @@ def __init__(self, rand, consensus_branch_id):
self.nConsensusBranchId = consensus_branch_id

# Orchard Transaction Fields
self.vActionsOrchard = []
if have_orchard:
for _ in range(rand.u8() % 5):
self.vActionsOrchard.append(OrchardActionDescription(rand))
Expand All @@ -571,7 +556,19 @@ def __bytes__(self):
ret += super().to_bytes(self.version_bytes(), self.nVersionGroupId, self.nConsensusBranchId)

# Orchard Transaction Fields
ret += write_compact_size(len(self.vActionsOrchard))
if len(self.vActionsOrchard) > 0:
# Not explicitly gated in the protocol spec, but if the gate
# were inactive then these loops would be empty by definition.
for desc in self.vActionsOrchard:
ret += bytes(desc) # Excludes spendAuthSig
ret += struct.pack('B', self.flagsOrchard)
ret += struct.pack('<Q', self.valueBalanceOrchard)
ret += bytes(self.anchorOrchard)
ret += write_compact_size(len(self.proofsOrchard))
ret += self.proofsOrchard
for desc in self.vActionsOrchard:
ret += bytes(desc.spendAuthSig)
ret += bytes(self.bindingSigOrchard)

return ret
Expand Down
52 changes: 42 additions & 10 deletions zcash_test_vectors/transaction_v6.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,39 @@ def __bytes__(self):
return ret


class ActionGroupDescription(object):
def __init__(self, rand, anchor_orchard, proofs_orchard, is_coinbase):
self.vActionsOrchard = []
# There must always be a non-zero number of Action Descriptions in an Action Group.
for _ in range(rand.u8() % 4 + 1):
self.vActionsOrchard.append(OrchardZSAActionDescription(rand))
# Three flag bits are defined, ensure only 3 bits used (mask with 0b00000111).
self.flagsOrchard = (rand.u8() & 7)
# Set the enableZSAs flag to true by OR with 0b00000100
self.flagsOrchard |= 4
if is_coinbase:
# set enableSpendsOrchard = 0
self.flagsOrchard &= 2
self.anchorOrchard = anchor_orchard
self.proofsOrchard = proofs_orchard
self.nAGExpiryHeight = 0

def __bytes__(self):
ret = b''
ret += write_compact_size(len(self.vActionsOrchard))
for desc in self.vActionsOrchard:
ret += bytes(desc) # Excludes spendAuthSig
ret += struct.pack('B', self.flagsOrchard)
ret += bytes(self.anchorOrchard)
ret += write_compact_size(len(self.proofsOrchard))
ret += self.proofsOrchard
ret += struct.pack('<I', self.nAGExpiryHeight)
for desc in self.vActionsOrchard:
ret += bytes(desc.spendAuthSig)

return ret


class TransactionV6(TransactionBase):
def __init__(self, rand, consensus_branch_id, have_orchard_zsa=True, have_burn=True, have_issuance=True):

Expand All @@ -95,15 +128,10 @@ def __init__(self, rand, consensus_branch_id, have_orchard_zsa=True, have_burn=T
self.nConsensusBranchId = consensus_branch_id

# OrchardZSA Transaction Fields
self.vActionGroupsOrchard = []
if have_orchard_zsa:
for _ in range(rand.u8() % 5):
self.vActionsOrchard.append(OrchardZSAActionDescription(rand))
self.flagsOrchard = rand.u8()
# Three flag bits are defined, we set enableZSA to true.
self.flagsOrchard = (self.flagsOrchard & 7) | 4
if self.is_coinbase():
# set enableSpendsOrchard = 0
self.flagsOrchard &= 2
# For NU7 we have a maximum of one Action Group.
self.vActionGroupsOrchard.append(ActionGroupDescription(rand, self.anchorOrchard, self.proofsOrchard, self.is_coinbase()))

# OrchardZSA Burn Fields
self.vAssetBurnOrchardZSA = []
Expand Down Expand Up @@ -150,7 +178,11 @@ def __bytes__(self):
ret += super().to_bytes(self.version_bytes(), self.nVersionGroupId, self.nConsensusBranchId)

# OrchardZSA Transaction Fields
if len(self.vActionsOrchard) > 0:
ret += write_compact_size(len(self.vActionGroupsOrchard))
if len(self.vActionGroupsOrchard) > 0:
for ag in self.vActionGroupsOrchard:
ret += bytes(ag)
ret += struct.pack('<Q', self.valueBalanceOrchard)
ret += self.orchard_zsa_burn_field_bytes()
ret += bytes(self.bindingSigOrchard)

Expand All @@ -161,7 +193,7 @@ def __bytes__(self):


def main():
consensus_branch_id = 0x77777777 # NU7
consensus_branch_id = 0x77190AD8 # NU7
rand = rand_gen()
test_vectors = []

Expand Down
39 changes: 14 additions & 25 deletions zcash_test_vectors/zip_0244.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
assert sys.version_info[0] >= 3, "Python 3 required."

from hashlib import blake2b
from collections import namedtuple
import struct

from .orchard_zsa.digests import NU7_TX_VERSION_BYTES, orchard_zsa_burn_digest, issuance_digest, issuance_auth_digest
from .orchard_zsa.digests import NU7_TX_VERSION_BYTES, issuance_digest, issuance_auth_digest, orchard_zsa_digest, \
orchard_zsa_auth_digest
from .transaction import (
MAX_MONEY,
Script,
Expand Down Expand Up @@ -139,8 +139,6 @@ def orchard_digest(tx):
digest.update(orchard_actions_compact_digest(tx))
digest.update(orchard_actions_memos_digest(tx))
digest.update(orchard_actions_noncompact_digest(tx))
if tx.version_bytes() == NU7_TX_VERSION_BYTES:
digest.update(orchard_zsa_burn_digest(tx))
digest.update(struct.pack('<B', tx.flagsOrchard))
digest.update(struct.pack('<Q', tx.valueBalanceOrchard))
digest.update(bytes(tx.anchorOrchard))
Expand All @@ -159,44 +157,29 @@ def orchard_auth_digest(tx):
return digest.digest()


# - helper for Actions functions
def ciphertext_offset(tx_version_bytes):
Offsets = namedtuple('Offsets', ['compact_end', 'memo_end'])
if tx_version_bytes == NU5_TX_VERSION_BYTES:
# Compact ends at 52, Memo ends at 564 for V5
return Offsets(compact_end=52, memo_end=564)
elif tx_version_bytes == NU7_TX_VERSION_BYTES:
# Compact ends at 84, Memo ends at 596 for V6
return Offsets(compact_end=84, memo_end=596)
else:
raise ValueError("Unsupported transaction version")

# - Actions

def orchard_actions_compact_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActCHash')
o = ciphertext_offset(tx.version_bytes())
for desc in tx.vActionsOrchard:
digest.update(bytes(desc.nullifier))
digest.update(bytes(desc.cmx))
digest.update(bytes(desc.ephemeralKey))
digest.update(desc.encCiphertext[:o.compact_end])
digest.update(desc.encCiphertext[:52])
return digest.digest()

def orchard_actions_memos_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActMHash')
o = ciphertext_offset(tx.version_bytes())
for desc in tx.vActionsOrchard:
digest.update(desc.encCiphertext[o.compact_end:o.memo_end])
digest.update(desc.encCiphertext[52:564])
return digest.digest()

def orchard_actions_noncompact_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActNHash')
o = ciphertext_offset(tx.version_bytes())
for desc in tx.vActionsOrchard:
digest.update(bytes(desc.cv))
digest.update(bytes(desc.rk))
digest.update(desc.encCiphertext[o.memo_end:])
digest.update(desc.encCiphertext[564:])
digest.update(desc.outCiphertext)
return digest.digest()

Expand All @@ -222,9 +205,11 @@ def txid_digest(tx):
digest.update(header_digest(tx))
digest.update(transparent_digest(tx))
digest.update(sapling_digest(tx))
digest.update(orchard_digest(tx))
if tx.version_bytes() == NU7_TX_VERSION_BYTES:
digest.update(orchard_zsa_digest(tx))
digest.update(issuance_digest(tx))
else:
digest.update(orchard_digest(tx))

return digest.digest()

Expand All @@ -238,9 +223,11 @@ def auth_digest(tx):

digest.update(transparent_scripts_digest(tx))
digest.update(sapling_auth_digest(tx))
digest.update(orchard_auth_digest(tx))
if tx.version_bytes() == NU7_TX_VERSION_BYTES:
digest.update(orchard_zsa_auth_digest(tx))
digest.update(issuance_auth_digest(tx))
else:
digest.update(orchard_auth_digest(tx))

return digest.digest()

Expand All @@ -261,9 +248,11 @@ def signature_digest(tx, t_inputs, nHashType, txin):
digest.update(header_digest(tx))
digest.update(transparent_sig_digest(tx, t_inputs, nHashType, txin))
digest.update(sapling_digest(tx))
digest.update(orchard_digest(tx))
if tx.version_bytes() == NU7_TX_VERSION_BYTES:
digest.update(orchard_zsa_digest(tx))
digest.update(issuance_digest(tx))
else:
digest.update(orchard_digest(tx))

return digest.digest()

Expand Down

0 comments on commit acb58a0

Please sign in to comment.