def test_three(): def sort_join(first, second): return ''.join(sorted([first, second])) hash_0 = 'a' * 32 hash_1 = 'b' * 32 hash_2 = 'c' * 32 leaves = [hash_0, hash_1, hash_2] tree = Merkletree(leaves) merkle_root = tree.merkleroot hash_01 = ( b'me\xef\x9c\xa9=5\x16\xa4\xd3\x8a\xb7\xd9\x89\xc2\xb5\x00' b'\xe2\xfc\x89\xcc\xdc\xf8x\xf9\xc4m\xaa\xf6\xad\r[' ) assert keccak(hash_0 + hash_1) == hash_01 calculated_root = keccak(hash_2 + hash_01) merkle_proof0 = tree.make_proof(hash_0) assert merkle_proof0 == [hash_1, hash_2] assert merkle_root == calculated_root assert check_proof(merkle_proof0, merkle_root, hash_0) merkle_proof1 = tree.make_proof(hash_1) assert merkle_proof1 == [hash_0, hash_2] assert merkle_root == calculated_root assert check_proof(merkle_proof1, merkle_root, hash_1) # with an odd number of values, the last value wont appear by itself in the # proof since it isn't hashed with another value merkle_proof2 = tree.make_proof(hash_2) assert merkle_proof2 == [keccak(hash_0 + hash_1)] assert merkle_root == calculated_root assert check_proof(merkle_proof2, merkle_root, hash_2)
def test_merkle_proof_missing_byte(tree, tester_state, tester_nettingchannel_library_address): """ computeMerkleRoot must fail if the proof is missing a byte. """ auxiliary = deploy_auxiliary_tester(tester_state, tester_nettingchannel_library_address) hashes = [sha3(element) for element in tree] merkle_tree = Merkletree(hashes) element = hashes[-1] merkle_proof = merkle_tree.make_proof(element) # for each element of the proof, remove a byte from the start and the end and test it for element_to_tamper in range(len(merkle_proof)): tampered_proof = list(merkle_proof) tampered_proof[element_to_tamper] = tampered_proof[ element_to_tamper][:-1] with pytest.raises(TransactionFailed): auxiliary.computeMerkleRoot( element, ''.join(tampered_proof), ) tampered_proof = list(merkle_proof) tampered_proof[element_to_tamper] = tampered_proof[element_to_tamper][ 1:] with pytest.raises(TransactionFailed): auxiliary.computeMerkleRoot( element, ''.join(tampered_proof), )
def test_withdraw_tampered_lock_amount(tree, tester_channels, tester_state, tester_token, settle_timeout): """ withdraw must fail if the lock amonut is tampered. """ pkey0, pkey1, nettingchannel, channel0, _ = tester_channels[0] current_block = tester_state.block.number expiration = current_block + settle_timeout - 1 locks = [ make_lock( hashlock=hashlock, expiration=expiration, ) for hashlock in tree ] merkle_tree = Merkletree(sha3(lock.as_bytes) for lock in locks) opened_block = nettingchannel.opened(sender=pkey0) nonce = 1 + (opened_block * (2**32)) direct_transfer = make_direct_transfer( nonce=nonce, channel=channel0.channel_address, locksroot=merkle_tree.merkleroot, token=tester_token.address, recipient=privatekey_to_address(pkey1)) address = privatekey_to_address(pkey0) sign_key = PrivateKey(pkey0) direct_transfer.sign(sign_key, address) direct_transfer_hash = sha3(direct_transfer.packed().data[:-65]) nettingchannel.close( direct_transfer.nonce, direct_transfer.transferred_amount, direct_transfer.locksroot, direct_transfer_hash, direct_transfer.signature, sender=pkey1, ) for lock in locks: secret = HASHLOCKS_SECRESTS[lock.hashlock] lock_encoded = lock.as_bytes merkle_proof = merkle_tree.make_proof(sha3(lock_encoded)) tampered_lock = make_lock( amount=lock.amount * 100, hashlock=lock.hashlock, expiration=lock.expiration, ) tampered_lock_encoded = sha3(tampered_lock.as_bytes) with pytest.raises(TransactionFailed): nettingchannel.withdraw( tampered_lock_encoded, ''.join(merkle_proof), secret, sender=pkey1, )
def test_withdraw_fails_with_partial_merkle_proof(tree, tester_channels, tester_state, settle_timeout): """ withdraw must fail if informed proof is not complete. """ pkey0, pkey1, nettingchannel, channel0, _ = tester_channels[0] current_block = tester_state.block.number expiration = current_block + settle_timeout - 1 locks = [ make_lock( hashlock=hashlock, expiration=expiration, ) for hashlock in tree ] merkle_tree = Merkletree(sha3(lock.as_bytes) for lock in locks) opened_block = nettingchannel.opened(sender=pkey0) nonce = 1 + (opened_block * (2**32)) direct_transfer = make_direct_transfer( nonce=nonce, channel=channel0.channel_address, locksroot=merkle_tree.merkleroot, recipient=privatekey_to_address(pkey1)) address = privatekey_to_address(pkey0) sign_key = PrivateKey(pkey0) direct_transfer.sign(sign_key, address) direct_transfer_hash = sha3(direct_transfer.packed().data[:-65]) nettingchannel.close( direct_transfer.nonce, direct_transfer.transferred_amount, direct_transfer.locksroot, direct_transfer_hash, direct_transfer.signature, sender=pkey1, ) for lock in locks: secret = HASHLOCKS_SECRESTS[lock.hashlock] lock_encoded = lock.as_bytes merkle_proof = merkle_tree.make_proof(sha3(lock_encoded)) # withdraw must fail regardless of which part of the proof is removed for hash_ in merkle_proof: tampered_proof = list(merkle_proof) tampered_proof.remove(hash_) with pytest.raises(TransactionFailed): nettingchannel.withdraw( lock_encoded, ''.join(tampered_proof), secret, sender=pkey1, )
def test_two(): hash_0 = 'a' * 32 hash_1 = 'b' * 32 leaves = [hash_0, hash_1] tree = Merkletree(leaves) merkle_root = tree.merkleroot merkle_proof0 = tree.make_proof(hash_0) assert merkle_proof0 == [hash_1] assert merkle_root == keccak(hash_0 + hash_1) assert check_proof(merkle_proof0, merkle_root, hash_0) merkle_proof1 = tree.make_proof(hash_1) assert merkle_proof1 == [hash_0] assert merkle_root == keccak(hash_0 + hash_1) assert check_proof(merkle_proof1, merkle_root, hash_1)
def test_one(): hash_0 = 'a' * 32 leaves = [hash_0] tree = Merkletree(leaves) merkle_root = tree.merkleroot merkle_proof = tree.make_proof(hash_0) assert merkle_proof == [] assert merkle_root == hash_0 assert check_proof(merkle_proof, merkle_root, hash_0) is True
def test_many(tree_up_to=10): for number_of_leaves in range(tree_up_to): leaves = [sha3(str(value)) for value in range(number_of_leaves)] tree = Merkletree(leaves) merkleroot = tree.merkleroot for value in leaves: merkle_proof = tree.make_proof(value) assert check_proof(merkle_proof, merkleroot, value) assert merkleroot == Merkletree(reversed(leaves)).merkleroot
def test_withdraw_tampered_merkle_proof(tree, tester_channels, tester_state, settle_timeout): """ withdraw must fail if the proof is tampered. """ pkey0, pkey1, nettingchannel, _, _ = tester_channels[0] current_block = tester_state.block.number expiration = current_block + settle_timeout - 1 locks = [ make_lock( hashlock=hashlock, expiration=expiration, ) for hashlock in tree ] merkle_tree = Merkletree(sha3(lock.as_bytes) for lock in locks) opened_block = nettingchannel.opened(sender=pkey0) nonce = 1 + (opened_block * (2**32)) direct_transfer = make_direct_transfer( nonce=nonce, locksroot=merkle_tree.merkleroot, recipient=privatekey_to_address(pkey1)) address = privatekey_to_address(pkey0) sign_key = PrivateKey(pkey0) direct_transfer.sign(sign_key, address) direct_transfer_data = str(direct_transfer.packed().data) nettingchannel.close(direct_transfer_data, sender=pkey1) for lock in locks: secret = HASHLOCKS_SECRESTS[lock.hashlock] lock_encoded = lock.as_bytes merkle_proof = merkle_tree.make_proof(sha3(lock_encoded)) # withdraw must fail regardless of which part of the proof is tampered for pos, hash_ in enumerate(merkle_proof): # changing arbitrary bytes from the proof tampered_hash = bytearray(hash_) tampered_hash[5], tampered_hash[6] = tampered_hash[ 6], tampered_hash[5] tampered_proof = list(merkle_proof) tampered_proof[pos] = str(tampered_hash) with pytest.raises(TransactionFailed): nettingchannel.withdraw( lock_encoded, ''.join(tampered_proof), secret, sender=pkey1, )
def compute_proof_for_lock(self, secret, lock): alllocks = chain(self.hashlock_pendinglocks.values(), self.hashlock_unclaimedlocks.values(), self.hashlock_unlockedlocks.values()) # forcing bytes because ethereum.abi doesnt work with bytearray lock_encoded = bytes(lock.as_bytes) lock_hash = sha3(lock_encoded) tree = Merkletree(lock.lockhashed for lock in alllocks) merkle_proof = tree.make_proof(lock_hash) return UnlockProof( merkle_proof, lock_encoded, secret, )
def test_merkle_proof(tree, tester_state, tester_nettingchannel_library_address): """ computeMerkleRoot and the python implementation must compute the same value. """ auxiliary = deploy_auxiliary_tester(tester_state, tester_nettingchannel_library_address) hashes = [sha3(element) for element in tree] merkle_tree = Merkletree(hashes) for element in tree: proof = merkle_tree.make_proof(sha3(element)) smart_contact_root = auxiliary.computeMerkleRoot( element, ''.join(proof), ) assert smart_contact_root == merkle_tree.merkleroot