Add tests to check auth data commitments committing to Orchard actions.

In the process of adding these tests, I also found and fixed a number
of type errors in Orchard auth data root computation.

Fixes #5223
This commit is contained in:
Kris Nuttycombe 2022-05-09 20:17:23 -06:00
parent ac1f6799c2
commit 1b8a9af946
4 changed files with 61 additions and 27 deletions

View File

@ -10,6 +10,7 @@ from test_framework.util import (
assert_equal,
CANOPY_BRANCH_ID,
NU5_BRANCH_ID,
get_coinbase_address,
hex_str_to_bytes,
nuparams,
nustr,
@ -18,7 +19,6 @@ from test_framework.util import (
)
from test_framework.mininode import (
CTransaction,
uint256_from_str,
)
from test_framework.blocktools import (
create_block
@ -49,7 +49,13 @@ class GetBlockTemplateTest(BitcoinTestFramework):
node = self.node
# sprout to transparent (v4)
recipients = [{"address": self.transparent_addr, "amount": Decimal('0.1')}]
myopid = node.z_sendmany(self.sprout_addr, recipients)
myopid = node.z_sendmany(self.sprout_addr, recipients, 1)
wait_and_assert_operationid_status(node, myopid)
def add_nu5_v5_tx_to_mempool(self):
node = self.node
recipients = [{"address": self.unified_addr, "amount": Decimal('9.99999')}]
myopid = node.z_sendmany(get_coinbase_address(node), recipients, 1, Decimal('0.00001'), 'AllowRevealedSenders')
wait_and_assert_operationid_status(node, myopid)
def add_transparent_tx_to_mempool(self):
@ -97,6 +103,7 @@ class GetBlockTemplateTest(BitcoinTestFramework):
tx = CTransaction()
tx.deserialize(f)
tx.calc_sha256()
assert_equal(tx.hash, gbt_tx['hash'])
assert_equal(tx.auth_digest_hex, node.getrawtransaction(tx.hash, 1)['authdigest'])
block.vtx.append(tx)
block.hashMerkleRoot = int(gbt['defaultroots']['merkleroot'], 16)
@ -104,7 +111,8 @@ class GetBlockTemplateTest(BitcoinTestFramework):
assert_equal(len(block.vtx), len(gbt['transactions']) + 1, "number of transactions")
assert_equal(block.hashPrevBlock, int(gbt['previousblockhash'], 16), "prevhash")
if nu5_active:
assert_equal(uint256_from_str(block.calc_auth_data_root()), int(gbt['defaultroots']['authdataroot'], 16))
block.hashAuthDataRoot = int(gbt['defaultroots']['authdataroot'], 16)
assert_equal(block.hashAuthDataRoot, block.calc_auth_data_root(), "authdataroot")
else:
assert 'authdataroot' not in gbt['defaultroots']
block.solve()
@ -131,6 +139,8 @@ class GetBlockTemplateTest(BitcoinTestFramework):
wait_and_assert_operationid_status(node, myopid)
self.transparent_addr = node.getnewaddress()
account = node.z_getnewaccount()['account']
self.unified_addr = node.z_getaddressforaccount(account)['address']
node.generate(15)
# at height 120, NU5 is not active
@ -176,13 +186,29 @@ class GetBlockTemplateTest(BitcoinTestFramework):
self.add_transparent_tx_to_mempool()
self.gbt_submitblock(True)
# Adding both v4 and v5 to cover legacy auth digest.
# Adding both v4 and v5 to cover legacy auth digest (without full auth digest subtree).
print("- both v4 and v5 transactions (plus coinbase)")
self.add_nu5_v4_tx_to_mempool()
self.add_transparent_tx_to_mempool()
self.gbt_submitblock(True)
# Adding both v4 and v5 to cover legacy auth digest (with full auth digest subtree).
print("- both v4 and v5 transactions (plus coinbase)")
self.add_nu5_v4_tx_to_mempool()
self.add_transparent_tx_to_mempool()
self.add_transparent_tx_to_mempool()
self.gbt_submitblock(True)
print("- block with 6 Orchard transactions (plus coinbase)")
for i in range(0, 6):
print(str(node.z_getbalance(self.transparent_addr)))
self.add_nu5_v5_tx_to_mempool()
self.gbt_submitblock(True)
print("- block with 7 Orchard transactions (plus coinbase)")
for i in range(0, 7):
self.add_nu5_v5_tx_to_mempool()
self.gbt_submitblock(True)
if __name__ == '__main__':
GetBlockTemplateTest().main()

View File

@ -28,6 +28,7 @@ def create_block(hashprev, coinbase, nTime=None, nBits=None, hashFinalSaplingRoo
block.nBits = nBits
block.vtx.append(coinbase)
block.hashMerkleRoot = block.calc_merkle_root()
block.hashAuthDataRoot = block.calc_auth_data_root()
block.calc_sha256()
return block

View File

@ -174,21 +174,25 @@ def deser_vector(f, c):
return r
def ser_vector(l):
def ser_vector(elems):
r = b""
if len(l) < 253:
r = struct.pack("B", len(l))
elif len(l) < 0x10000:
r = struct.pack("<BH", 253, len(l))
elif len(l) < 0x100000000:
r = struct.pack("<BI", 254, len(l))
else:
r = struct.pack("<BQ", 255, len(l))
for i in l:
r += i.serialize()
r += ser_compact_size(len(elems))
for elem in elems:
r += elem.serialize()
return r
def ser_compact_size(l):
if l < 253:
return struct.pack("B", l)
elif l < 0x10000:
return struct.pack("<BH", 253, l)
elif l < 0x100000000:
return struct.pack("<BI", 254, l)
else:
return struct.pack("<BQ", 255, l)
def deser_uint256_vector(f):
nit = struct.unpack("<B", f.read(1))[0]
if nit == 253:
@ -473,7 +477,7 @@ class OrchardBundle(object):
def deserialize(self, f):
self.actions = deser_vector(f, OrchardAction)
if len(self.actions) > 0:
flags = f.read(1)
flags = struct.unpack("B", f.read(1))[0]
self.enableSpends = (flags & ORCHARD_FLAGS_ENABLE_SPENDS) != 0
self.enableOutputs = (flags & ORCHARD_FLAGS_ENABLE_OUTPUTS) != 0
self.valueBalance = struct.unpack("<q", f.read(8))[0]
@ -489,20 +493,23 @@ class OrchardBundle(object):
r = b""
r += ser_vector(self.actions)
if len(self.actions) > 0:
flags = 0 ^ (
ORCHARD_FLAGS_ENABLE_SPENDS if self.enableSpends else 0
) ^ (
ORCHARD_FLAGS_ENABLE_OUTPUTS if self.enableOutputs else 0
)
r += struct.pack("B", flags)
r += struct.pack("B", self.flags())
r += struct.pack("<q", self.valueBalance)
r += ser_uint256(self.anchor)
r += ser_vector(self.proofs)
r += ser_compact_size(len(self.proofs))
r += bytes(self.proofs)
for i in range(len(self.actions)):
r += self.actions[i].spendAuthSig.serialize()
r += self.bindingSig.serialize()
return r
def flags(self):
return 0 ^ (
ORCHARD_FLAGS_ENABLE_SPENDS if self.enableSpends else 0
) ^ (
ORCHARD_FLAGS_ENABLE_OUTPUTS if self.enableOutputs else 0
)
def __repr__(self):
return "OrchardBundle(actions=%r, enableSpends=%s, enableOutputs=%s, valueBalance=%i, proofs=%r, spendAuthSigs=%r, bindingSig=%r)" \
% (
@ -1285,7 +1292,7 @@ class CBlock(CBlockHeader):
digest.update(hashes[i+1])
newhashes.append(digest.digest())
hashes = newhashes
return hashes[0]
return uint256_from_str(hashes[0])
def is_valid(self, n=48, k=5):
# H(I||...

View File

@ -138,9 +138,9 @@ def orchard_digest(orchardBundle):
digest.update(orchard_actions_compact_digest(orchardBundle))
digest.update(orchard_actions_memos_digest(orchardBundle))
digest.update(orchard_actions_noncompact_digest(orchardBundle))
digest.update(struct.pack('<B', orchardBundle.flags()))
digest.update(struct.pack('B', orchardBundle.flags()))
digest.update(struct.pack('<q', orchardBundle.valueBalance))
digest.update(bytes(orchardBundle.anchor))
digest.update(ser_uint256(orchardBundle.anchor))
return digest.digest()
@ -148,7 +148,7 @@ def orchard_auth_digest(orchardBundle):
digest = blake2b(digest_size=32, person=b'ZTxAuthOrchaHash')
if len(orchardBundle.actions) > 0:
digest.update(orchardBundle.proofs)
digest.update(bytes(orchardBundle.proofs))
for desc in orchardBundle.actions:
digest.update(desc.spendAuthSig.serialize())
digest.update(orchardBundle.bindingSig.serialize())