From 29872dcaa001d1cb491128d83dfcba8a36e0263a Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 5 Jan 2022 16:52:46 +0000 Subject: [PATCH 1/4] ZIP 244: Change semantics of sequence_sig_digest --- zip_0244.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/zip_0244.py b/zip_0244.py index 82411a9..2a3be29 100755 --- a/zip_0244.py +++ b/zip_0244.py @@ -261,13 +261,8 @@ def prevouts_sig_digest(tx, nHashType): 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 - ): + # if the SIGHASH_ANYONECANPAY flag is not set: + if not (nHashType & SIGHASH_ANYONECANPAY): return getHashSequence(tx, b'ZTxIdSequencHash') else: return blake2b(digest_size=32, person=b'ZTxIdSequencHash').digest() From bda224b13399b9db4df6340deb299b5ac89beb96 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 5 Jan 2022 21:03:56 +0000 Subject: [PATCH 2/4] 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), From fe666c2b946fb201cfcb56b1b4f1e6ab9319cc1c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 6 Jan 2022 16:51:32 +0000 Subject: [PATCH 3/4] ZIP 244: Add hash_type to transparent_sig_digest --- zip_0244.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/zip_0244.py b/zip_0244.py index 0573f7b..cdc48f6 100755 --- a/zip_0244.py +++ b/zip_0244.py @@ -243,6 +243,7 @@ def transparent_sig_digest(tx, t_inputs, nHashType, txin): digest = blake2b(digest_size=32, person=b'ZTxIdTranspaHash') if len(tx.vin) + len(tx.vout) > 0: + digest.update(hash_type(tx, nHashType, txin)) digest.update(prevouts_sig_digest(tx, nHashType)) digest.update(amounts_sig_digest(t_inputs, nHashType)) digest.update(script_codes_sig_digest(t_inputs, nHashType)) @@ -252,6 +253,23 @@ def transparent_sig_digest(tx, t_inputs, nHashType, txin): return digest.digest() +def hash_type(tx, nHashType, txin): + if txin is None: + # Sapling Spend or Orchard Action + assert nHashType == SIGHASH_ALL + else: + # Transparent input + assert nHashType in [ + SIGHASH_ALL, + SIGHASH_NONE, + SIGHASH_SINGLE, + SIGHASH_ALL | SIGHASH_ANYONECANPAY, + SIGHASH_NONE | SIGHASH_ANYONECANPAY, + SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, + ] + assert (nHashType & 0x1f) != SIGHASH_SINGLE or 0 <= txin.nIn and txin.nIn < len(tx.vout) + return struct.pack('B', nHashType) + def prevouts_sig_digest(tx, nHashType): # If the SIGHASH_ANYONECANPAY flag is not set: if not (nHashType & SIGHASH_ANYONECANPAY): @@ -344,14 +362,19 @@ def main(): 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 [ + 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({ From 9a666e4a29077fda0951e14b3ab274d4d882a855 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 6 Jan 2022 17:01:11 +0000 Subject: [PATCH 4/4] ZIP 244: Reverse order of value and script_code in txin_sig_digest --- zip_0244.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zip_0244.py b/zip_0244.py index cdc48f6..1159e32 100755 --- a/zip_0244.py +++ b/zip_0244.py @@ -324,8 +324,8 @@ 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(bytes(txin.scriptCode)) digest.update(struct.pack('