Add NSM transaction test vectors

This commit is contained in:
Tomek Piotrowski 2024-08-06 09:11:38 +02:00 committed by Paul Dann
parent db9c9b9519
commit 8cfdb3ed6f
3 changed files with 196 additions and 3 deletions

View File

@ -44,6 +44,7 @@ unified_incoming_viewing_keys = "zcash_test_vectors.unified_incoming_viewing_key
zip_0143 = "zcash_test_vectors.zip_0143:main"
zip_0243 = "zcash_test_vectors.zip_0243:main"
zip_0244 = "zcash_test_vectors.zip_0244:main"
zip_nsm = "zcash_test_vectors.zip_nsm:main"
# Transparent test vectors
bip_0032 = "zcash_test_vectors.transparent.bip_0032:main"

View File

@ -26,6 +26,9 @@ SAPLING_TX_VERSION = 4
NU5_VERSION_GROUP_ID = 0x26A7270A
NU5_TX_VERSION = 5
ZFUTURE_VERSION_GROUP_ID = 0xFFFFFFFF
ZFUTURE_TX_VERSION = 0x0000FFFF
# Sapling note magic values, copied from src/zcash/Zcash.h
NOTEENCRYPTION_AUTH_BYTES = 16
ZC_NOTEPLAINTEXT_LEADING = 1
@ -413,7 +416,7 @@ class LegacyTransaction(object):
class TransactionV5(object):
def __init__(self, rand, consensus_branch_id):
def __init__(self, rand, consensus_branch_id, version_group_id):
# Decide which transaction parts will be generated.
flip_coins = rand.u8()
have_transparent_in = (flip_coins >> 0) % 2
@ -423,7 +426,7 @@ class TransactionV5(object):
is_coinbase = (not have_transparent_in) and (flip_coins >> 4) % 2
# Common Transaction Fields
self.nVersionGroupId = NU5_VERSION_GROUP_ID
self.nVersionGroupId = version_group_id
self.nConsensusBranchId = consensus_branch_id
self.nLockTime = rand.u32()
self.nExpiryHeight = rand.u32() % TX_EXPIRY_HEIGHT_THRESHOLD
@ -549,12 +552,29 @@ class TransactionV5(object):
return ret
class TransactionNSM(TransactionV5):
def __init__(self, rand, consensus_branch_id, version_group_id):
super().__init__(rand, consensus_branch_id, version_group_id)
self.burnAmount = rand.u64() % (MAX_MONEY + 1)
def version_bytes(self):
return ZFUTURE_TX_VERSION | (1 << 31)
def __bytes__(self):
ret = super().__bytes__()
ret += struct.pack('<Q', self.burnAmount)
return ret
class Transaction(object):
def __init__(self, rand, version, consensus_branch_id=None):
if version == NU5_TX_VERSION:
assert consensus_branch_id is not None
self.inner = TransactionV5(rand, consensus_branch_id)
self.inner = TransactionV5(rand, consensus_branch_id, NU5_VERSION_GROUP_ID)
elif version == ZFUTURE_TX_VERSION:
assert consensus_branch_id is not None
self.inner = TransactionNSM(rand, consensus_branch_id, ZFUTURE_VERSION_GROUP_ID)
else:
self.inner = LegacyTransaction(rand, version)

172
zcash_test_vectors/zip_nsm.py Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env python3
import sys; assert sys.version_info[0] >= 3, "Python 3 required."
from hashlib import blake2b
import struct
from .transaction import (
ZFUTURE_VERSION_GROUP_ID,
TransactionNSM,
)
from .output import render_args, render_tv, Some
from .rand import Rand
from .zip_0143 import (
SIGHASH_ALL,
SIGHASH_ANYONECANPAY,
SIGHASH_NONE,
SIGHASH_SINGLE,
)
from .zip_0244 import *
def auth_digest(tx):
digest = blake2b(
digest_size=32,
person=b'ZTxAuthHash_' + struct.pack('<I', tx.nConsensusBranchId),
)
digest.update(transparent_scripts_digest(tx))
digest.update(sapling_auth_digest(tx))
digest.update(orchard_auth_digest(tx))
digest.update(burn_amount_digest(tx))
return digest.digest()
# NSM
def burn_amount_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxBurnAmnt_Hash')
digest.update(struct.pack('<Q', tx.burnAmount))
return digest.digest()
def txid_digest(tx):
digest = blake2b(
digest_size=32,
person=b'ZcashTxHash_' + struct.pack('<I', tx.nConsensusBranchId),
)
digest.update(header_digest(tx))
digest.update(transparent_digest(tx))
digest.update(sapling_digest(tx))
digest.update(orchard_digest(tx))
digest.update(burn_amount_digest(tx))
return digest.digest()
def signature_digest(tx, t_inputs, nHashType, txin):
digest = blake2b(
digest_size=32,
person=b'ZcashTxHash_' + struct.pack('<I', tx.nConsensusBranchId),
)
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))
digest.update(burn_amount_digest(tx))
return digest.digest()
def txin_sig_digest(tx, txin):
digest = blake2b(digest_size=32, person=b'Zcash___TxInHash')
if txin is not None:
digest.update(bytes(tx.vin[txin.nIn].prevout))
digest.update(struct.pack('<Q', txin.amount))
digest.update(bytes(txin.scriptPubKey))
digest.update(struct.pack('<I', tx.vin[txin.nIn].nSequence))
return digest.digest()
def main():
args = render_args()
from random import Random
rng = Random(0xB7D6_0F44)
def randbytes(l):
ret = []
while len(ret) < l:
ret.append(rng.randrange(0, 256))
return bytes(ret)
rand = Rand(randbytes)
consensusBranchId = 0xFFFF_FFFF # ZFUTURE
test_vectors = []
for _ in range(10):
tx = TransactionNSM(rand, consensusBranchId, ZFUTURE_VERSION_GROUP_ID)
txid = txid_digest(tx)
auth = auth_digest(tx)
# Generate amounts and scriptCodes for each non-dummy transparent input.
if tx.is_coinbase():
t_inputs = []
else:
t_inputs = [TransparentInput(nIn, rand) for nIn in range(len(tx.vin))]
# If there are any non-dummy transparent inputs, derive a corresponding transparent sighash.
if len(t_inputs) > 0:
txin = rand.a(t_inputs)
else:
txin = None
sighash_shielded = signature_digest(tx, t_inputs, SIGHASH_ALL, None)
other_sighashes = {
nHashType: None if txin is None else signature_digest(tx, t_inputs, nHashType, txin)
for nHashType in ([
SIGHASH_ALL,
SIGHASH_NONE,
SIGHASH_SINGLE,
SIGHASH_ALL | SIGHASH_ANYONECANPAY,
SIGHASH_NONE | SIGHASH_ANYONECANPAY,
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,
] if txin is None or txin.nIn < len(tx.vout) else [
SIGHASH_ALL,
SIGHASH_NONE,
SIGHASH_ALL | SIGHASH_ANYONECANPAY,
SIGHASH_NONE | SIGHASH_ANYONECANPAY,
])
}
test_vectors.append({
'tx': bytes(tx),
'txid': txid,
'auth_digest': auth,
'amounts': [x.amount for x in t_inputs],
'burn_amount': tx.burnAmount,
'script_pubkeys': [x.scriptPubKey.raw() for x in t_inputs],
'transparent_input': None if txin is None else txin.nIn,
'sighash_shielded': sighash_shielded,
'sighash_all': other_sighashes.get(SIGHASH_ALL),
'sighash_none': other_sighashes.get(SIGHASH_NONE),
'sighash_single': other_sighashes.get(SIGHASH_SINGLE),
'sighash_all_anyone': other_sighashes.get(SIGHASH_ALL | SIGHASH_ANYONECANPAY),
'sighash_none_anyone': other_sighashes.get(SIGHASH_NONE | SIGHASH_ANYONECANPAY),
'sighash_single_anyone': other_sighashes.get(SIGHASH_SINGLE | SIGHASH_ANYONECANPAY),
})
render_tv(
args,
'zip_nsm',
(
('tx', {'rust_type': 'Vec<u8>', 'bitcoin_flavoured': False}),
('txid', '[u8; 32]'),
('auth_digest', '[u8; 32]'),
('amounts', 'Vec<i64>'),
('burn_amount', 'u64'),
('script_pubkeys', {'rust_type': 'Vec<Vec<u8>>', 'bitcoin_flavoured': False}),
('transparent_input', 'Option<u32>'),
('sighash_shielded', '[u8; 32]'),
('sighash_all', 'Option<[u8; 32]>'),
('sighash_none', 'Option<[u8; 32]>'),
('sighash_single', 'Option<[u8; 32]>'),
('sighash_all_anyone', 'Option<[u8; 32]>'),
('sighash_none_anyone', 'Option<[u8; 32]>'),
('sighash_single_anyone', 'Option<[u8; 32]>'),
),
test_vectors,
)
if __name__ == '__main__':
main()