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_duplicates(): hash_0 = keccak('x') hash_1 = keccak('y') with pytest.raises(ValueError): Merkletree([hash_0, hash_0]) with pytest.raises(ValueError): Merkletree([hash_0, hash_1, hash_0])
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_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 create_secret(self, identifier, secret): hashlock = sha3(secret) from_ = self.our_state lock = from_.balance_proof.get_lock_by_hashlock(hashlock) lockhashed = sha3(lock.as_bytes) leafs = from_.balance_proof.unclaimed_merkletree() leafs.remove(lockhashed) locksroot_with_pending_lock_removed = Merkletree( leafs).merkleroot or EMPTY_MERKLE_ROOT transferred_amount = from_.transferred_amount + lock.amount nonce = self.get_next_nonce() secret = Secret( identifier, nonce, self.channel_address, transferred_amount, locksroot_with_pending_lock_removed, secret, ) return secret
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 compute_merkleroot_with(self, include): """ Compute the resulting merkle root if the lock `include` is added in the tree. """ leafs = self.balance_proof.unclaimed_merkletree() leafs.append(sha3(include.as_bytes)) locksroot = Merkletree(leafs).merkleroot return locksroot
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_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
def do_test_speed(rounds=100, num_hashes=1000): values = [keccak(str(i)) for i in range(num_hashes)] start_time = time.time() for __ in range(rounds): Merkletree(values).merkleroot elapsed = time.time() - start_time print '%d additions per second' % (num_hashes * rounds / elapsed)
def assert_locked(channel0, outstanding_locks): """ Assert the locks create from `channel`. """ # a locked transfer is registered in the _partner_ state hashroot = Merkletree(sha3(lock.as_bytes) for lock in outstanding_locks).merkleroot assert len(channel0.our_state.balance_proof.hashlock_pendinglocks) == len(outstanding_locks) assert channel0.our_state.balance_proof.merkleroot_for_unclaimed() == hashroot assert channel0.our_state.locked() == sum(lock.amount for lock in outstanding_locks) assert channel0.outstanding == sum(lock.amount for lock in outstanding_locks) for lock in outstanding_locks: assert lock.hashlock in channel0.our_state.balance_proof.hashlock_pendinglocks
def assert_locked(from_channel, pending_locks): """ Assert the locks created from `from_channel`. """ # a locked transfer is registered in the _partner_ state leafs = [sha3(lock.as_bytes) for lock in pending_locks] hashroot = Merkletree(leafs).merkleroot assert len(from_channel.our_state.balance_proof.hashlocks_to_pendinglocks) == len( pending_locks ) assert from_channel.our_state.balance_proof.merkleroot_for_unclaimed() == hashroot assert from_channel.our_state.locked() == sum(lock.amount for lock in pending_locks) assert from_channel.locked == sum(lock.amount for lock in pending_locks) for lock in pending_locks: assert lock.hashlock in from_channel.our_state.balance_proof.hashlocks_to_pendinglocks
def register_balanceproof_with_lock(self, balance_proof, lock): lockhashed = sha3(lock.as_bytes) if not isinstance(balance_proof, BalanceProofState): raise ValueError('balance_proof must be a BalanceProof instance') if self.is_known(lock.hashlock): raise ValueError('hashlock is already registered') leafs = self.unclaimed_merkletree() leafs.append(lockhashed) new_locksroot = Merkletree(leafs).merkleroot if balance_proof.locksroot != new_locksroot: raise InvalidLocksRoot(new_locksroot, balance_proof.locksroot) self.hashlocks_to_pendinglocks[lock.hashlock] = PendingLock( lock, lockhashed) self.balance_proof = balance_proof
def register_balanceproof_without_lock(self, balance_proof, lock): lockhashed = sha3(lock.as_bytes) if not isinstance(balance_proof, BalanceProofState): raise ValueError('balance_proof must be a BalanceProof instance') if not self.is_known(lock.hashlock): raise ValueError('hashlock is not registered') leaves = self.unclaimed_merkletree() leaves.remove(lockhashed) new_locksroot = Merkletree(leaves).merkleroot if balance_proof.locksroot != new_locksroot: raise InvalidLocksRoot(new_locksroot, balance_proof.locksroot) if lock.hashlock in self.hashlocks_to_pendinglocks: del self.hashlocks_to_pendinglocks[lock.hashlock] else: del self.hashlocks_to_unclaimedlocks[lock.hashlock] self.balance_proof = balance_proof
def register_locked_transfer(self, locked_transfer): if not isinstance(locked_transfer, LockedTransfer): raise ValueError('transfer must be a LockedTransfer') lock = locked_transfer.lock lockhashed = sha3(lock.as_bytes) if self.is_known(lock.hashlock): raise ValueError('hashlock is already registered') leafs = self.unclaimed_merkletree() leafs.append(lockhashed) new_locksroot = Merkletree(leafs).merkleroot if locked_transfer.locksroot != new_locksroot: raise ValueError('locksroot mismatch expected:{} got:{}'.format( pex(new_locksroot), pex(locked_transfer.locksroot), )) self.hashlocks_to_pendinglocks[lock.hashlock] = PendingLock( lock, lockhashed) self.transfer = locked_transfer self.hashlocks_to_unlockedlocks = dict()
def test_empty(): assert Merkletree([]).merkleroot == EMPTY_MERKLE_ROOT
def generate_merkle_tree(self): alllocks = chain(self.hashlocks_to_pendinglocks.values(), self.hashlocks_to_unclaimedlocks.values(), self.hashlocks_to_unlockedlocks.values()) return Merkletree(lock.lockhashed for lock in alllocks)
def merkleroot_for_unclaimed(self): alllocks = chain(self.hashlocks_to_pendinglocks.values(), self.hashlocks_to_unclaimedlocks.values()) tree = Merkletree(lock.lockhashed for lock in alllocks) return tree.merkleroot
def test_sender_cannot_overspend(): token_address = make_address() privkey1, address1 = make_privkey_address() address2 = make_address() balance1 = 70 balance2 = 110 reveal_timeout = 5 settle_timeout = 15 block_number = 10 our_state = ChannelEndState(address1, balance1, BalanceProof(None)) partner_state = ChannelEndState(address2, balance2, BalanceProof(None)) external_state = make_external_state() test_channel = Channel( our_state, partner_state, external_state, token_address, reveal_timeout, settle_timeout, ) amount = balance1 expiration = block_number + settle_timeout sent_mediated_transfer0 = test_channel.create_mediatedtransfer( address1, address2, fee=0, amount=amount, identifier=1, expiration=expiration, hashlock=sha3('test_locked_amount_cannot_be_spent'), ) sent_mediated_transfer0.sign(privkey1, address1) test_channel.register_transfer( block_number, sent_mediated_transfer0, ) lock2 = Lock( amount=amount, expiration=expiration, hashlock=sha3('test_locked_amount_cannot_be_spent2'), ) locksroot2 = Merkletree([ sha3(sent_mediated_transfer0.lock.as_bytes), sha3(lock2.as_bytes), ]).merkleroot sent_mediated_transfer1 = MediatedTransfer( identifier=2, nonce=sent_mediated_transfer0.nonce + 1, token=token_address, channel=test_channel.channel_address, transferred_amount=0, recipient=address2, locksroot=locksroot2, lock=lock2, target=address2, initiator=address1, fee=0, ) sent_mediated_transfer1.sign(privkey1, address1) # address1 balance is all locked with pytest.raises(InsufficientBalance): test_channel.register_transfer( block_number, sent_mediated_transfer1, )
def test_empty(): assert Merkletree([]).merkleroot == ''
def test_receiver_cannot_spend_locked_amount(): token_address = make_address() privkey1, address1 = make_privkey_address() privkey2, address2 = make_privkey_address() balance1 = 33 balance2 = 11 reveal_timeout = 7 settle_timeout = 21 block_number = 7 our_state = ChannelEndState(address1, balance1, BalanceProof(None)) partner_state = ChannelEndState(address2, balance2, BalanceProof(None)) external_state = make_external_state() test_channel = Channel( our_state, partner_state, external_state, token_address, reveal_timeout, settle_timeout, ) amount1 = balance2 expiration = block_number + settle_timeout receive_mediated_transfer0 = test_channel.create_mediatedtransfer( address1, address2, fee=0, amount=amount1, identifier=1, expiration=expiration, hashlock=sha3('test_locked_amount_cannot_be_spent'), ) receive_mediated_transfer0.sign(privkey2, address2) test_channel.register_transfer( block_number, receive_mediated_transfer0, ) # trying to send one unit of the locked token amount2 = balance1 + 1 lock2 = Lock( amount=amount2, expiration=expiration, hashlock=sha3('test_locked_amount_cannot_be_spent2'), ) locksroot2 = Merkletree([sha3(lock2.as_bytes)]).merkleroot send_mediated_transfer0 = MediatedTransfer( identifier=1, nonce=1, token=token_address, channel=test_channel.channel_address, transferred_amount=0, recipient=address2, locksroot=locksroot2, lock=lock2, target=address2, initiator=address1, fee=0, ) send_mediated_transfer0.sign(privkey1, address1) # address1 balance is all locked with pytest.raises(InsufficientBalance): test_channel.register_transfer( block_number, send_mediated_transfer0, )
def generate_merkle_tree(self): all_lockhashes = self.unclaimed_merkletree() return Merkletree(all_lockhashes)
def test_non_hash(): with pytest.raises(HashLengthNot32): Merkletree(['not32bytes', 'neither']) with pytest.raises(HashLengthNot32): assert Merkletree(['']).merkleroot == ''
def test_single(): hash_0 = keccak('x') assert Merkletree([hash_0]).merkleroot == hash_0