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.
This commit is contained in:
Jack Grigg 2022-01-05 21:03:56 +00:00
parent 29872dcaa0
commit bda224b133
2 changed files with 87 additions and 44 deletions

View File

@ -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<u8>' 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)

View File

@ -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('<I', tx.nConsensusBranchId),
)
digest.update(header_digest(tx))
digest.update(transparent_sig_digest(tx, nHashType, txin))
digest.update(transparent_sig_digest(tx, t_inputs, 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)
def transparent_sig_digest(tx, t_inputs, nHashType, txin):
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))
if len(tx.vin) + len(tx.vout) > 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('<Q', x.amount))
return digest.digest()
else:
return blake2b(digest_size=32, person=b'ZTxTrAmountsHash').digest()
def script_codes_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'ZTxTrScriptsHash')
for x in t_inputs:
digest.update(bytes(x.scriptCode))
return digest.digest()
else:
return blake2b(digest_size=32, person=b'ZTxTrScriptsHash').digest()
def sequence_sig_digest(tx, nHashType):
# if the SIGHASH_ANYONECANPAY flag is not set:
if not (nHashType & SIGHASH_ANYONECANPAY):
@ -285,10 +304,11 @@ def outputs_sig_digest(tx, nHashType, txin):
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))
if txin is not None:
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()
@ -312,37 +332,42 @@ def main():
txid = txid_digest(tx)
auth = auth_digest(tx)
# Generate amounts and scriptCodes for each transparent input.
t_inputs = [TransparentInput(nIn, rand) for nIn in range(len(tx.vin))]
# If there are any transparent inputs, derive a corresponding transparent sighash.
if len(tx.vin) > 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<u8>', 'bitcoin_flavoured': False}),
('txid', '[u8; 32]'),
('auth_digest', '[u8; 32]'),
('amounts', '[i64; %d]' % len(t_inputs)),
('script_codes', {
'rust_type': '[Vec<u8>; %d]' % len(t_inputs),
'bitcoin_flavoured': False,
}),
('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>>',
('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<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),