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:
parent
ac1f6799c2
commit
1b8a9af946
|
@ -10,6 +10,7 @@ from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
CANOPY_BRANCH_ID,
|
CANOPY_BRANCH_ID,
|
||||||
NU5_BRANCH_ID,
|
NU5_BRANCH_ID,
|
||||||
|
get_coinbase_address,
|
||||||
hex_str_to_bytes,
|
hex_str_to_bytes,
|
||||||
nuparams,
|
nuparams,
|
||||||
nustr,
|
nustr,
|
||||||
|
@ -18,7 +19,6 @@ from test_framework.util import (
|
||||||
)
|
)
|
||||||
from test_framework.mininode import (
|
from test_framework.mininode import (
|
||||||
CTransaction,
|
CTransaction,
|
||||||
uint256_from_str,
|
|
||||||
)
|
)
|
||||||
from test_framework.blocktools import (
|
from test_framework.blocktools import (
|
||||||
create_block
|
create_block
|
||||||
|
@ -49,7 +49,13 @@ class GetBlockTemplateTest(BitcoinTestFramework):
|
||||||
node = self.node
|
node = self.node
|
||||||
# sprout to transparent (v4)
|
# sprout to transparent (v4)
|
||||||
recipients = [{"address": self.transparent_addr, "amount": Decimal('0.1')}]
|
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)
|
wait_and_assert_operationid_status(node, myopid)
|
||||||
|
|
||||||
def add_transparent_tx_to_mempool(self):
|
def add_transparent_tx_to_mempool(self):
|
||||||
|
@ -97,6 +103,7 @@ class GetBlockTemplateTest(BitcoinTestFramework):
|
||||||
tx = CTransaction()
|
tx = CTransaction()
|
||||||
tx.deserialize(f)
|
tx.deserialize(f)
|
||||||
tx.calc_sha256()
|
tx.calc_sha256()
|
||||||
|
assert_equal(tx.hash, gbt_tx['hash'])
|
||||||
assert_equal(tx.auth_digest_hex, node.getrawtransaction(tx.hash, 1)['authdigest'])
|
assert_equal(tx.auth_digest_hex, node.getrawtransaction(tx.hash, 1)['authdigest'])
|
||||||
block.vtx.append(tx)
|
block.vtx.append(tx)
|
||||||
block.hashMerkleRoot = int(gbt['defaultroots']['merkleroot'], 16)
|
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(len(block.vtx), len(gbt['transactions']) + 1, "number of transactions")
|
||||||
assert_equal(block.hashPrevBlock, int(gbt['previousblockhash'], 16), "prevhash")
|
assert_equal(block.hashPrevBlock, int(gbt['previousblockhash'], 16), "prevhash")
|
||||||
if nu5_active:
|
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:
|
else:
|
||||||
assert 'authdataroot' not in gbt['defaultroots']
|
assert 'authdataroot' not in gbt['defaultroots']
|
||||||
block.solve()
|
block.solve()
|
||||||
|
@ -131,6 +139,8 @@ class GetBlockTemplateTest(BitcoinTestFramework):
|
||||||
wait_and_assert_operationid_status(node, myopid)
|
wait_and_assert_operationid_status(node, myopid)
|
||||||
|
|
||||||
self.transparent_addr = node.getnewaddress()
|
self.transparent_addr = node.getnewaddress()
|
||||||
|
account = node.z_getnewaccount()['account']
|
||||||
|
self.unified_addr = node.z_getaddressforaccount(account)['address']
|
||||||
node.generate(15)
|
node.generate(15)
|
||||||
|
|
||||||
# at height 120, NU5 is not active
|
# at height 120, NU5 is not active
|
||||||
|
@ -176,13 +186,29 @@ class GetBlockTemplateTest(BitcoinTestFramework):
|
||||||
self.add_transparent_tx_to_mempool()
|
self.add_transparent_tx_to_mempool()
|
||||||
self.gbt_submitblock(True)
|
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)")
|
print("- both v4 and v5 transactions (plus coinbase)")
|
||||||
self.add_nu5_v4_tx_to_mempool()
|
self.add_nu5_v4_tx_to_mempool()
|
||||||
self.add_transparent_tx_to_mempool()
|
self.add_transparent_tx_to_mempool()
|
||||||
self.add_transparent_tx_to_mempool()
|
self.add_transparent_tx_to_mempool()
|
||||||
self.gbt_submitblock(True)
|
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__':
|
if __name__ == '__main__':
|
||||||
GetBlockTemplateTest().main()
|
GetBlockTemplateTest().main()
|
||||||
|
|
|
@ -28,6 +28,7 @@ def create_block(hashprev, coinbase, nTime=None, nBits=None, hashFinalSaplingRoo
|
||||||
block.nBits = nBits
|
block.nBits = nBits
|
||||||
block.vtx.append(coinbase)
|
block.vtx.append(coinbase)
|
||||||
block.hashMerkleRoot = block.calc_merkle_root()
|
block.hashMerkleRoot = block.calc_merkle_root()
|
||||||
|
block.hashAuthDataRoot = block.calc_auth_data_root()
|
||||||
block.calc_sha256()
|
block.calc_sha256()
|
||||||
return block
|
return block
|
||||||
|
|
||||||
|
|
|
@ -174,21 +174,25 @@ def deser_vector(f, c):
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def ser_vector(l):
|
def ser_vector(elems):
|
||||||
r = b""
|
r = b""
|
||||||
if len(l) < 253:
|
r += ser_compact_size(len(elems))
|
||||||
r = struct.pack("B", len(l))
|
for elem in elems:
|
||||||
elif len(l) < 0x10000:
|
r += elem.serialize()
|
||||||
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()
|
|
||||||
return r
|
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):
|
def deser_uint256_vector(f):
|
||||||
nit = struct.unpack("<B", f.read(1))[0]
|
nit = struct.unpack("<B", f.read(1))[0]
|
||||||
if nit == 253:
|
if nit == 253:
|
||||||
|
@ -473,7 +477,7 @@ class OrchardBundle(object):
|
||||||
def deserialize(self, f):
|
def deserialize(self, f):
|
||||||
self.actions = deser_vector(f, OrchardAction)
|
self.actions = deser_vector(f, OrchardAction)
|
||||||
if len(self.actions) > 0:
|
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.enableSpends = (flags & ORCHARD_FLAGS_ENABLE_SPENDS) != 0
|
||||||
self.enableOutputs = (flags & ORCHARD_FLAGS_ENABLE_OUTPUTS) != 0
|
self.enableOutputs = (flags & ORCHARD_FLAGS_ENABLE_OUTPUTS) != 0
|
||||||
self.valueBalance = struct.unpack("<q", f.read(8))[0]
|
self.valueBalance = struct.unpack("<q", f.read(8))[0]
|
||||||
|
@ -489,20 +493,23 @@ class OrchardBundle(object):
|
||||||
r = b""
|
r = b""
|
||||||
r += ser_vector(self.actions)
|
r += ser_vector(self.actions)
|
||||||
if len(self.actions) > 0:
|
if len(self.actions) > 0:
|
||||||
flags = 0 ^ (
|
r += struct.pack("B", self.flags())
|
||||||
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("<q", self.valueBalance)
|
r += struct.pack("<q", self.valueBalance)
|
||||||
r += ser_uint256(self.anchor)
|
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)):
|
for i in range(len(self.actions)):
|
||||||
r += self.actions[i].spendAuthSig.serialize()
|
r += self.actions[i].spendAuthSig.serialize()
|
||||||
r += self.bindingSig.serialize()
|
r += self.bindingSig.serialize()
|
||||||
return r
|
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):
|
def __repr__(self):
|
||||||
return "OrchardBundle(actions=%r, enableSpends=%s, enableOutputs=%s, valueBalance=%i, proofs=%r, spendAuthSigs=%r, bindingSig=%r)" \
|
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])
|
digest.update(hashes[i+1])
|
||||||
newhashes.append(digest.digest())
|
newhashes.append(digest.digest())
|
||||||
hashes = newhashes
|
hashes = newhashes
|
||||||
return hashes[0]
|
return uint256_from_str(hashes[0])
|
||||||
|
|
||||||
def is_valid(self, n=48, k=5):
|
def is_valid(self, n=48, k=5):
|
||||||
# H(I||...
|
# H(I||...
|
||||||
|
|
|
@ -138,9 +138,9 @@ def orchard_digest(orchardBundle):
|
||||||
digest.update(orchard_actions_compact_digest(orchardBundle))
|
digest.update(orchard_actions_compact_digest(orchardBundle))
|
||||||
digest.update(orchard_actions_memos_digest(orchardBundle))
|
digest.update(orchard_actions_memos_digest(orchardBundle))
|
||||||
digest.update(orchard_actions_noncompact_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(struct.pack('<q', orchardBundle.valueBalance))
|
||||||
digest.update(bytes(orchardBundle.anchor))
|
digest.update(ser_uint256(orchardBundle.anchor))
|
||||||
|
|
||||||
return digest.digest()
|
return digest.digest()
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ def orchard_auth_digest(orchardBundle):
|
||||||
digest = blake2b(digest_size=32, person=b'ZTxAuthOrchaHash')
|
digest = blake2b(digest_size=32, person=b'ZTxAuthOrchaHash')
|
||||||
|
|
||||||
if len(orchardBundle.actions) > 0:
|
if len(orchardBundle.actions) > 0:
|
||||||
digest.update(orchardBundle.proofs)
|
digest.update(bytes(orchardBundle.proofs))
|
||||||
for desc in orchardBundle.actions:
|
for desc in orchardBundle.actions:
|
||||||
digest.update(desc.spendAuthSig.serialize())
|
digest.update(desc.spendAuthSig.serialize())
|
||||||
digest.update(orchardBundle.bindingSig.serialize())
|
digest.update(orchardBundle.bindingSig.serialize())
|
||||||
|
|
Loading…
Reference in New Issue