Merge pull request #20 from zcash-hackworks/tx-v5

Test vectors for ZIP 225 and ZIP 244
This commit is contained in:
str4d 2021-06-04 18:13:27 +01:00 committed by GitHub
commit cd50b88703
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 573 additions and 21 deletions

View File

@ -1,8 +1,17 @@
#!/usr/bin/env python3
import struct
from orchard_pallas import (
Fp as PallasBase,
Scalar as PallasScalar,
)
from orchard_sinsemilla import group_hash as pallas_group_hash
from sapling_generators import find_group_hash, SPENDING_KEY_BASE
from sapling_jubjub import Fq, Point
from sapling_jubjub import (
Fq,
Point,
Fr as JubjubScalar,
)
from utils import leos2ip
from zc_utils import write_compact_size
@ -15,6 +24,9 @@ OVERWINTER_TX_VERSION = 3
SAPLING_VERSION_GROUP_ID = 0x892F2085
SAPLING_TX_VERSION = 4
NU5_VERSION_GROUP_ID = 0x26A7270A
NU5_TX_VERSION = 5
# Sapling note magic values, copied from src/zcash/Zcash.h
NOTEENCRYPTION_AUTH_BYTES = 16
ZC_NOTEPLAINTEXT_LEADING = 1
@ -75,14 +87,43 @@ class GrothProof(object):
self.g_C
)
class SpendDescription(object):
class RedJubjubSignature(object):
def __init__(self, rand):
self.R = find_group_hash(b'TVRandPt', rand.b(32))
self.S = JubjubScalar(leos2ip(rand.b(32)))
def __bytes__(self):
return (
bytes(self.R) +
bytes(self.S)
)
class RedPallasSignature(object):
def __init__(self, rand):
self.R = pallas_group_hash(b'TVRandPt', rand.b(32))
self.S = PallasScalar(leos2ip(rand.b(32)))
def __bytes__(self):
return (
bytes(self.R) +
bytes(self.S)
)
class SpendDescription(object):
def __init__(self, rand, anchor=None):
self.cv = find_group_hash(b'TVRandPt', rand.b(32))
self.anchor = Fq(leos2ip(rand.b(32)))
self.anchor = Fq(leos2ip(rand.b(32))) if anchor is None else anchor
self.nullifier = rand.b(32)
self.rk = Point.rand(rand)
self.proof = GrothProof(rand)
self.spendAuthSig = rand.b(64) # Invalid
self.spendAuthSig = rand.b(64) if anchor is None else RedJubjubSignature(rand) # Invalid
def bytes_v5(self):
return (
bytes(self.cv) +
self.nullifier +
bytes(self.rk)
)
def __bytes__(self):
return (
@ -103,16 +144,43 @@ class OutputDescription(object):
self.outCipherText = rand.b(ZC_SAPLING_OUTCIPHERTEXT_SIZE)
self.proof = GrothProof(rand)
def __bytes__(self):
def bytes_v5(self):
return (
bytes(self.cv) +
bytes(self.cmu) +
bytes(self.ephemeralKey) +
self.encCiphertext +
self.outCipherText +
self.outCipherText
)
def __bytes__(self):
return (
self.bytes_v5() +
bytes(self.proof)
)
class OrchardActionDescription(object):
def __init__(self, rand):
self.cv = pallas_group_hash(b'TVRandPt', rand.b(32))
self.nullifier = PallasBase(leos2ip(rand.b(32)))
self.rk = pallas_group_hash(b'TVRandPt', rand.b(32))
self.cmx = PallasBase(leos2ip(rand.b(32)))
self.ephemeralKey = pallas_group_hash(b'TVRandPt', rand.b(32))
self.encCiphertext = rand.b(ZC_SAPLING_ENCCIPHERTEXT_SIZE)
self.outCiphertext = rand.b(ZC_SAPLING_OUTCIPHERTEXT_SIZE)
self.spendAuthSig = RedPallasSignature(rand)
def __bytes__(self):
return (
bytes(self.cv) +
bytes(self.nullifier) +
bytes(self.rk) +
bytes(self.cmx) +
bytes(self.ephemeralKey) +
self.encCiphertext +
self.outCiphertext
)
class JoinSplit(object):
def __init__(self, rand, fUseGroth = False):
self.vpub_old = 0
@ -197,7 +265,7 @@ class TxOut(object):
return struct.pack('<Q', self.nValue) + bytes(self.scriptPubKey)
class Transaction(object):
class LegacyTransaction(object):
def __init__(self, rand, version):
if version == OVERWINTER_TX_VERSION:
self.fOverwintered = True
@ -243,12 +311,12 @@ class Transaction(object):
if self.nVersion >= SAPLING_TX_VERSION:
self.bindingSig = rand.b(64) # Invalid
def header(self):
def version_bytes(self):
return self.nVersion | (1 << 31 if self.fOverwintered else 0)
def __bytes__(self):
ret = b''
ret += struct.pack('<I', self.header())
ret += struct.pack('<I', self.version_bytes())
if self.fOverwintered:
ret += struct.pack('<I', self.nVersionGroupId)
@ -295,3 +363,138 @@ class Transaction(object):
ret += self.bindingSig
return ret
class TransactionV5(object):
def __init__(self, rand, consensus_branch_id):
# Decide which transaction parts will be generated.
flip_coins = rand.u8()
have_transparent_in = (flip_coins >> 0) % 2
have_transparent_out = (flip_coins >> 1) % 2
have_sapling = (flip_coins >> 2) % 2
have_orchard = (flip_coins >> 3) % 2
# Common Transaction Fields
self.nVersionGroupId = NU5_VERSION_GROUP_ID
self.nConsensusBranchId = consensus_branch_id
self.nLockTime = rand.u32()
self.nExpiryHeight = rand.u32() % TX_EXPIRY_HEIGHT_THRESHOLD
# Transparent Transaction Fields
self.vin = []
self.vout = []
if have_transparent_in:
for _ in range((rand.u8() % 3) + 1):
self.vin.append(TxIn(rand))
if have_transparent_out:
for _ in range((rand.u8() % 3) + 1):
self.vout.append(TxOut(rand))
# Sapling Transaction Fields
self.vSpendsSapling = []
self.vOutputsSapling = []
if have_sapling:
self.anchorSapling = Fq(leos2ip(rand.b(32)))
for _ in range(rand.u8() % 3):
self.vSpendsSapling.append(SpendDescription(rand, self.anchorSapling))
for _ in range(rand.u8() % 3):
self.vOutputsSapling.append(OutputDescription(rand))
self.valueBalanceSapling = rand.u64() % (MAX_MONEY + 1)
self.bindingSigSapling = RedJubjubSignature(rand)
else:
# If valueBalanceSapling is not present in the serialized transaction, then
# v^balanceSapling is defined to be 0.
self.valueBalanceSapling = 0
# Orchard Transaction Fields
self.vActionsOrchard = []
if have_orchard:
for _ in range(rand.u8() % 5):
self.vActionsOrchard.append(OrchardActionDescription(rand))
self.flagsOrchard = rand.u8() & 3 # Only two flag bits are currently defined.
self.valueBalanceOrchard = rand.u64() % (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)
else:
# If valueBalanceOrchard is not present in the serialized transaction, then
# v^balanceOrchard is defined to be 0.
self.valueBalanceOrchard = 0
def version_bytes(self):
return NU5_TX_VERSION | (1 << 31)
# TODO: Update ZIP 225 to document endianness
def __bytes__(self):
ret = b''
# Common Transaction Fields
ret += struct.pack('<I', self.version_bytes())
ret += struct.pack('<I', self.nVersionGroupId)
ret += struct.pack('<I', self.nConsensusBranchId)
ret += struct.pack('<I', self.nLockTime)
ret += struct.pack('<I', self.nExpiryHeight)
# Transparent Transaction Fields
ret += write_compact_size(len(self.vin))
for x in self.vin:
ret += bytes(x)
ret += write_compact_size(len(self.vout))
for x in self.vout:
ret += bytes(x)
# Sapling Transaction Fields
hasSapling = len(self.vSpendsSapling) + len(self.vOutputsSapling) > 0
ret += write_compact_size(len(self.vSpendsSapling))
for desc in self.vSpendsSapling:
ret += desc.bytes_v5()
ret += write_compact_size(len(self.vOutputsSapling))
for desc in self.vOutputsSapling:
ret += desc.bytes_v5()
if hasSapling:
ret += struct.pack('<Q', self.valueBalanceSapling)
if len(self.vSpendsSapling) > 0:
ret += bytes(self.anchorSapling)
# 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.vSpendsSapling: # vSpendProofsSapling
ret += bytes(desc.proof)
for desc in self.vSpendsSapling: # vSpendAuthSigsSapling
ret += bytes(desc.spendAuthSig)
for desc in self.vOutputsSapling: # vOutputProofsSapling
ret += bytes(desc.proof)
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)
ret += bytes(self.bindingSigOrchard)
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)
else:
self.inner = LegacyTransaction(rand, version)
def __getattr__(self, item):
return getattr(self.inner, item)
def __bytes__(self):
return bytes(self.inner)

View File

@ -3,10 +3,10 @@ from pyblake2 import blake2b
import struct
from transaction import (
LegacyTransaction,
MAX_MONEY,
OVERWINTER_TX_VERSION,
Script,
Transaction,
)
from tv_output import render_args, render_tv, Some
from tv_rand import Rand
@ -19,20 +19,20 @@ SIGHASH_ANYONECANPAY = 0x80
NOT_AN_INPUT = -1 # For portability of the test vectors; replaced with None for Rust
def getHashPrevouts(tx):
digest = blake2b(digest_size=32, person=b'ZcashPrevoutHash')
def getHashPrevouts(tx, person=b'ZcashPrevoutHash'):
digest = blake2b(digest_size=32, person=person)
for x in tx.vin:
digest.update(bytes(x.prevout))
return digest.digest()
def getHashSequence(tx):
digest = blake2b(digest_size=32, person=b'ZcashSequencHash')
def getHashSequence(tx, person=b'ZcashSequencHash'):
digest = blake2b(digest_size=32, person=person)
for x in tx.vin:
digest.update(struct.pack('<I', x.nSequence))
return digest.digest()
def getHashOutputs(tx):
digest = blake2b(digest_size=32, person=b'ZcashOutputsHash')
def getHashOutputs(tx, person=b'ZcashOutputsHash'):
digest = blake2b(digest_size=32, person=person)
for x in tx.vout:
digest.update(bytes(x))
return digest.digest()
@ -76,7 +76,7 @@ def signature_hash(scriptCode, tx, nIn, nHashType, amount, consensusBranchId):
person=b'ZcashSigHash' + struct.pack('<I', consensusBranchId),
)
digest.update(struct.pack('<I', tx.header()))
digest.update(struct.pack('<I', tx.version_bytes()))
digest.update(struct.pack('<I', tx.nVersionGroupId))
digest.update(hashPrevouts)
digest.update(hashSequence)
@ -111,7 +111,7 @@ def main():
test_vectors = []
for i in range(10):
tx = Transaction(rand, OVERWINTER_TX_VERSION)
tx = LegacyTransaction(rand, OVERWINTER_TX_VERSION)
scriptCode = Script(rand)
nIn = rand.i8() % (len(tx.vin) + 1)
if nIn == len(tx.vin):

View File

@ -3,10 +3,10 @@ from pyblake2 import blake2b
import struct
from transaction import (
LegacyTransaction,
MAX_MONEY,
SAPLING_TX_VERSION,
Script,
Transaction,
)
from tv_output import render_args, render_tv, Some
from tv_rand import Rand
@ -80,7 +80,7 @@ def signature_hash(scriptCode, tx, nIn, nHashType, amount, consensusBranchId):
person=b'ZcashSigHash' + struct.pack('<I', consensusBranchId),
)
digest.update(struct.pack('<I', tx.header()))
digest.update(struct.pack('<I', tx.version_bytes()))
digest.update(struct.pack('<I', tx.nVersionGroupId))
digest.update(hashPrevouts)
digest.update(hashSequence)
@ -118,7 +118,7 @@ def main():
test_vectors = []
for _ in range(10):
tx = Transaction(rand, SAPLING_TX_VERSION)
tx = LegacyTransaction(rand, SAPLING_TX_VERSION)
scriptCode = Script(rand)
nIn = rand.i8() % (len(tx.vin) + 1)
if nIn == len(tx.vin):

349
zip_0244.py Normal file
View File

@ -0,0 +1,349 @@
#!/usr/bin/env python3
from pyblake2 import blake2b
import struct
from transaction import (
MAX_MONEY,
NU5_TX_VERSION,
Script,
TransactionV5,
)
from tv_output import render_args, render_tv, Some
from tv_rand import Rand
from zip_0143 import (
getHashOutputs,
getHashPrevouts,
getHashSequence,
SIGHASH_ALL,
SIGHASH_ANYONECANPAY,
SIGHASH_NONE,
SIGHASH_SINGLE,
)
# Transparent
def transparent_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdTranspaHash')
if len(tx.vin) + len(tx.vout) > 0:
digest.update(getHashPrevouts(tx, b'ZTxIdPrevoutHash'))
digest.update(getHashSequence(tx, b'ZTxIdSequencHash'))
digest.update(getHashOutputs(tx, b'ZTxIdOutputsHash'))
return digest.digest()
# Sapling
def sapling_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdSaplingHash')
if len(tx.vSpendsSapling) + len(tx.vOutputsSapling) > 0:
digest.update(sapling_spends_digest(tx))
digest.update(sapling_outputs_digest(tx))
digest.update(struct.pack('<Q', tx.valueBalanceSapling))
return digest.digest()
# - Spends
def sapling_spends_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdSSpendsHash')
if len(tx.vSpendsSapling) > 0:
digest.update(sapling_spends_compact_digest(tx))
digest.update(sapling_spends_noncompact_digest(tx))
return digest.digest()
def sapling_spends_compact_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdSSpendCHash')
for desc in tx.vSpendsSapling:
digest.update(desc.nullifier)
return digest.digest()
def sapling_spends_noncompact_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdSSpendNHash')
for desc in tx.vSpendsSapling:
digest.update(bytes(desc.cv))
digest.update(bytes(desc.anchor))
digest.update(bytes(desc.rk))
return digest.digest()
# - Outputs
def sapling_outputs_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdSOutputHash')
if len(tx.vOutputsSapling) > 0:
digest.update(sapling_outputs_compact_digest(tx))
digest.update(sapling_outputs_memos_digest(tx))
digest.update(sapling_outputs_noncompact_digest(tx))
return digest.digest()
def sapling_outputs_compact_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdSOutC__Hash')
for desc in tx.vOutputsSapling:
digest.update(bytes(desc.cmu))
digest.update(bytes(desc.ephemeralKey))
digest.update(desc.encCiphertext[:52])
return digest.digest()
def sapling_outputs_memos_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdSOutM__Hash')
for desc in tx.vOutputsSapling:
digest.update(desc.encCiphertext[52:564])
return digest.digest()
def sapling_outputs_noncompact_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdSOutN__Hash')
for desc in tx.vOutputsSapling:
digest.update(bytes(desc.cv))
digest.update(desc.encCiphertext[564:])
digest.update(desc.outCipherText)
return digest.digest()
# Orchard
def orchard_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrchardHash')
if len(tx.vActionsOrchard) > 0:
digest.update(orchard_actions_compact_digest(tx))
digest.update(orchard_actions_memos_digest(tx))
digest.update(orchard_actions_noncompact_digest(tx))
digest.update(struct.pack('<B', tx.flagsOrchard))
digest.update(struct.pack('<Q', tx.valueBalanceOrchard))
digest.update(bytes(tx.anchorOrchard))
return digest.digest()
# - Actions
def orchard_actions_compact_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActCHash')
for desc in tx.vActionsOrchard:
digest.update(bytes(desc.nullifier))
digest.update(bytes(desc.cmx))
digest.update(bytes(desc.ephemeralKey))
digest.update(desc.encCiphertext[:52])
return digest.digest()
def orchard_actions_memos_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActMHash')
for desc in tx.vActionsOrchard:
digest.update(desc.encCiphertext[52:564])
return digest.digest()
def orchard_actions_noncompact_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdOrcActNHash')
for desc in tx.vActionsOrchard:
digest.update(bytes(desc.cv))
digest.update(bytes(desc.rk))
digest.update(desc.encCiphertext[564:])
digest.update(desc.outCiphertext)
return digest.digest()
# Transaction
def header_digest(tx):
digest = blake2b(digest_size=32, person=b'ZTxIdHeadersHash')
digest.update(struct.pack('<I', tx.version_bytes()))
digest.update(struct.pack('<I', tx.nVersionGroupId))
digest.update(struct.pack('<I', tx.nConsensusBranchId))
digest.update(struct.pack('<I', tx.nLockTime))
digest.update(struct.pack('<I', tx.nExpiryHeight))
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))
return digest.digest()
# Signatures
class TransparentInput(object):
def __init__(self, tx, rand):
self.scriptCode = Script(rand)
self.nIn = rand.u8() % len(tx.vin)
self.amount = rand.u64() % (MAX_MONEY + 1)
def signature_digest(tx, 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, nHashType, txin))
digest.update(sapling_digest(tx))
digest.update(orchard_digest(tx))
return digest.digest()
def transparent_sig_digest(tx, nHashType, txin):
# Sapling Spend or Orchard Action
if txin is None:
return transparent_digest(tx)
digest = blake2b(digest_size=32, person=b'ZTxIdTranspaHash')
digest.update(prevouts_sig_digest(tx, nHashType))
digest.update(sequence_sig_digest(tx, nHashType))
digest.update(outputs_sig_digest(tx, nHashType, txin))
digest.update(txin_sig_digest(tx, txin))
return digest.digest()
def prevouts_sig_digest(tx, nHashType):
# If the SIGHASH_ANYONECANPAY flag is not set:
if not (nHashType & SIGHASH_ANYONECANPAY):
return getHashPrevouts(tx, b'ZTxIdPrevoutHash')
else:
return blake2b(digest_size=32, person=b'ZTxIdPrevoutHash').digest()
def sequence_sig_digest(tx, nHashType):
# if the SIGHASH_ANYONECANPAY flag is not set, and the sighash type is neither
# SIGHASH_SINGLE nor SIGHASH_NONE:
if (
(not (nHashType & SIGHASH_ANYONECANPAY)) and \
(nHashType & 0x1f) != SIGHASH_SINGLE and \
(nHashType & 0x1f) != SIGHASH_NONE
):
return getHashSequence(tx, b'ZTxIdSequencHash')
else:
return blake2b(digest_size=32, person=b'ZTxIdSequencHash').digest()
def outputs_sig_digest(tx, nHashType, txin):
# If the sighash type is neither SIGHASH_SINGLE nor SIGHASH_NONE:
if (nHashType & 0x1f) != SIGHASH_SINGLE and (nHashType & 0x1f) != SIGHASH_NONE:
return getHashOutputs(tx, b'ZTxIdOutputsHash')
# If the sighash type is SIGHASH_SINGLE and the signature hash is being computed for
# the transparent input at a particular index, and a transparent output appears in the
# transaction at that index:
elif (nHashType & 0x1f) == SIGHASH_SINGLE and 0 <= txin.nIn and txin.nIn < len(tx.vout):
digest = blake2b(digest_size=32, person=b'ZTxIdOutputsHash')
digest.update(bytes(tx.vout[txin.nIn]))
return digest.digest()
else:
return blake2b(digest_size=32, person=b'ZTxIdOutputsHash').digest()
def txin_sig_digest(tx, txin):
digest = blake2b(digest_size=32, person=b'Zcash___TxInHash')
digest.update(bytes(tx.vin[txin.nIn].prevout))
digest.update(bytes(txin.scriptCode))
digest.update(struct.pack('<Q', txin.amount))
digest.update(struct.pack('<I', tx.vin[txin.nIn].nSequence))
return digest.digest()
def main():
args = render_args()
from random import Random
rng = Random(0xabad533d)
def randbytes(l):
ret = []
while len(ret) < l:
ret.append(rng.randrange(0, 256))
return bytes(ret)
rand = Rand(randbytes)
consensusBranchId = 0xF919A198 # NU5
test_vectors = []
for _ in range(10):
tx = TransactionV5(rand, consensusBranchId)
txid = txid_digest(tx)
# If there are any transparent inputs, derive a corresponding transparent sighash.
if len(tx.vin) > 0:
txin = TransparentInput(tx, rand)
else:
txin = None
sighash_all = signature_digest(tx, SIGHASH_ALL, txin)
other_sighashes = None if txin is None else [
signature_digest(tx, nHashType, txin)
for nHashType in [
SIGHASH_NONE,
SIGHASH_SINGLE,
SIGHASH_ALL | SIGHASH_ANYONECANPAY,
SIGHASH_NONE | SIGHASH_ANYONECANPAY,
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,
]
]
test_vectors.append({
'tx': bytes(tx),
'txid': txid,
'transparent_input': None if txin is None else txin.nIn,
'script_code': None if txin is None else txin.scriptCode.raw(),
'amount': None if txin is None else txin.amount,
'sighash_all': sighash_all,
'sighash_none': None if txin is None else other_sighashes[0],
'sighash_single': None if txin is None else other_sighashes[1],
'sighash_all_anyone': None if txin is None else other_sighashes[2],
'sighash_none_anyone': None if txin is None else other_sighashes[3],
'sighash_single_anyone': None if txin is None else other_sighashes[4],
})
render_tv(
args,
'zip_0244',
(
('tx', {'rust_type': 'Vec<u8>', 'bitcoin_flavoured': False}),
('txid', '[u8; 32]'),
('transparent_input', {
'rust_type': 'Option<u32>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('script_code', {
'rust_type': 'Option<Vec<u8>>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('amount', {
'rust_type': 'Option<i64>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('sighash_all', '[u8; 32]'),
('sighash_none', {
'rust_type': 'Option<[u8; 32]>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('sighash_single', {
'rust_type': 'Option<[u8; 32]>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('sighash_all_anyone', {
'rust_type': 'Option<[u8; 32]>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('sighash_none_anyone', {
'rust_type': 'Option<[u8; 32]>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
('sighash_single_anyone', {
'rust_type': 'Option<[u8; 32]>',
'rust_fmt': lambda x: None if x is None else Some(x),
}),
),
test_vectors,
)
if __name__ == '__main__':
main()