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

Updating test vectors for Action Groups #24

Open
wants to merge 8 commits into
base: zsa1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

80 changes: 79 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,88 @@
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_v6_digest(tx):
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
digest = blake2b(digest_size=32, person=b'ZTxIdOrchardHash')

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

return digest.digest()


def orchard_v6_auth_digest(tx):
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
digest = blake2b(digest_size=32, person=b'ZTxAuthOrchaHash')

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

return digest.digest()


def orchard_v6_action_groups_auth_digest(tx):
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
digest = blake2b(digest_size=32, person=b'ZTxAuthOrcAGHash')

if len(tx.vActionGroupsOrchard) > 0:
for grp in tx.vActionGroupsOrchard:
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
digest.update(grp.proofsOrchard)
for desc in grp.vActionsOrchard:
digest.update(bytes(desc.spendAuthSig))
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved

return digest.digest()

def orchard_v6_action_groups_digest(tx):
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActGHash')

if len(tx.vActionGroupsOrchard) > 0:
for grp in tx.vActionGroupsOrchard:
digest.update(orchard_v6_actions_compact_digest(grp))
digest.update(orchard_v6_actions_memos_digest(grp))
digest.update(orchard_v6_actions_noncompact_digest(grp))
digest.update(struct.pack('<B', grp.flagsOrchard))
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
digest.update(bytes(grp.anchorOrchard))
digest.update(struct.pack('<I', grp.nAGExpiryHeight))

return digest.digest()


def orchard_v6_actions_compact_digest(grp):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActCHash')
for desc in grp.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_v6_actions_memos_digest(grp):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActMHash')
for desc in grp.vActionsOrchard:
digest.update(desc.encCiphertext[84:596])

return digest.digest()


def orchard_v6_actions_noncompact_digest(grp):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActNHash')
for desc in grp.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)
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
ret += bytes(self.bindingSigOrchard)

return ret
Expand Down
51 changes: 41 additions & 10 deletions zcash_test_vectors/transaction_v6.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,38 @@ 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))
self.flagsOrchard = rand.u8()
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
# Three flag bits are defined, we set enableZSA to true.
self.flagsOrchard = (self.flagsOrchard & 7) | 4
if is_coinbase:
# set enableSpendsOrchard = 0
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
self.flagsOrchard &= 2
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
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:
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
ret += bytes(desc) # Excludes spendAuthSig
vivek-arte marked this conversation as resolved.
Show resolved Hide resolved
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 +127,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 +177,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 +192,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_v6_digest, \
orchard_v6_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_v6_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_v6_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_v6_digest(tx))
digest.update(issuance_digest(tx))
else:
digest.update(orchard_digest(tx))

return digest.digest()

Expand Down
Loading