From bda224b13399b9db4df6340deb299b5ac89beb96 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 5 Jan 2022 21:03:56 +0000 Subject: [PATCH] ZIP 244: Add new commitments to transparent inputs Signatures for shielded inputs also commit to these new commitments, meaning that for mixed transactions, shielded signatures are no longer equivalent to signing the txid. This property remains for fully shielded transactions. --- tv_output.py | 29 ++++++++++++--- zip_0244.py | 102 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 87 insertions(+), 44 deletions(-) diff --git a/tv_output.py b/tv_output.py index 304e50d..552fccd 100644 --- a/tv_output.py +++ b/tv_output.py @@ -27,11 +27,17 @@ def tv_value_json(value, bitcoin_flavoured): if isinstance(value, Some): value = value.thing - if type(value) == bytes: - if bitcoin_flavoured and len(value) == 32: - value = value[::-1] - value = hexlify(value).decode() - return value + def bitcoinify(value): + if type(value) == bytes: + if bitcoin_flavoured and len(value) == 32: + value = value[::-1] + value = hexlify(value).decode() + return value + + if type(value) == list: + return [bitcoinify(v) for v in value] + else: + return bitcoinify(value) def tv_json(filename, parts, vectors, bitcoin_flavoured): if type(vectors) == type({}): @@ -148,11 +154,22 @@ def tv_part_rust(name, value, config, indent=3): name, )) for item in value: - if type(item) == bytes: + if 'Vec' in config['rust_type']: + print('''%svec![ + %s%s +%s],''' % ( + ' ' * (indent + 1), + ' ' * (indent + 1), + chunk(hexlify(item)), + ' ' * (indent + 1), + )) + elif type(item) == bytes: print('''%s[%s],''' % ( ' ' * (indent + 1), chunk(hexlify(item)), )) + elif type(item) == int: + print('%s%d,' % (' ' * (indent + 1), item)) elif type(item) == list: print('''%s[''' % ( ' ' * (indent + 1) diff --git a/zip_0244.py b/zip_0244.py index 2a3be29..0573f7b 100755 --- a/zip_0244.py +++ b/zip_0244.py @@ -221,35 +221,34 @@ def auth_digest(tx): # Signatures class TransparentInput(object): - def __init__(self, tx, rand): + def __init__(self, nIn, rand): + self.nIn = nIn self.scriptCode = Script(rand) - self.nIn = rand.u8() % len(tx.vin) self.amount = rand.u64() % (MAX_MONEY + 1) -def signature_digest(tx, nHashType, txin): +def signature_digest(tx, t_inputs, nHashType, txin): digest = blake2b( digest_size=32, person=b'ZcashTxHash_' + struct.pack(' 0: + digest.update(prevouts_sig_digest(tx, nHashType)) + digest.update(amounts_sig_digest(t_inputs, nHashType)) + digest.update(script_codes_sig_digest(t_inputs, 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() @@ -260,6 +259,26 @@ def prevouts_sig_digest(tx, nHashType): else: return blake2b(digest_size=32, person=b'ZTxIdPrevoutHash').digest() +def amounts_sig_digest(t_inputs, nHashType): + # If the SIGHASH_ANYONECANPAY flag is not set: + if not (nHashType & SIGHASH_ANYONECANPAY): + digest = blake2b(digest_size=32, person=b'ZTxTrAmountsHash') + for x in t_inputs: + digest.update(struct.pack(' 0: - txin = TransparentInput(tx, rand) + if len(t_inputs) > 0: + txin = rand.a(t_inputs) else: txin = None - sighash_all = signature_digest(tx, SIGHASH_ALL, txin) - other_sighashes = None if txin is None else [ - signature_digest(tx, nHashType, txin) + 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, ] - ] + } test_vectors.append({ 'tx': bytes(tx), 'txid': txid, 'auth_digest': auth, + 'amounts': [x.amount for x in t_inputs], + 'script_codes': [x.scriptCode.raw() for x in t_inputs], '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], + '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( @@ -352,19 +377,20 @@ def main(): ('tx', {'rust_type': 'Vec', 'bitcoin_flavoured': False}), ('txid', '[u8; 32]'), ('auth_digest', '[u8; 32]'), + ('amounts', '[i64; %d]' % len(t_inputs)), + ('script_codes', { + 'rust_type': '[Vec; %d]' % len(t_inputs), + 'bitcoin_flavoured': False, + }), ('transparent_input', { 'rust_type': 'Option', 'rust_fmt': lambda x: None if x is None else Some(x), }), - ('script_code', { - 'rust_type': 'Option>', + ('sighash_shielded', '[u8; 32]'), + ('sighash_all', { + 'rust_type': 'Option<[u8; 32]>', 'rust_fmt': lambda x: None if x is None else Some(x), }), - ('amount', { - 'rust_type': 'Option', - '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),