def update(self, old, new): """Updates a leaf. :param old: a leaf that is already in the tree. It can by either a real object (int, str, etc.) or the hash value of that object. :param old: a leaf that will replace the old one. """ # accepting leaves of the same type only if type(old) != type(new): raise TypeError( 'Old and the new value are of different types.' 'You should hash them to avoid the error.' ) mapping, hasher = self._mapping, self._hasher # first, assuming leaves are hash values in hex leaf = mapping.get(utils.from_hex(old)) if leaf is None: leaf = mapping.get(hasher.hash_leaf(old)) new = hasher.hash_leaf(new) # raise the error if the leaf's not found if not isinstance(leaf, MerkleNode): raise KeyError('Invalid old value.') leaf.hash = utils.from_hex(new) self._rehash(leaf)
def __eq__(self, other): """Checks if the trees are identical.""" root_hash = self._root.hash if isinstance(other, MerkleTree): other_root_hash = utils.from_hex(other.merkle_root) else: other = utils.to_string(other) other_root_hash = utils.from_hex(other) return root_hash == other_root_hash
def verify_tree_consistency(new_tree, old_root, old_size): """Verifies that the new tree contains the same nodes and in the same order as a given subtree. :param new_tree: Merkle tree whose certain nodes will be concatenated and hashed to produce the Merkle hash root of a subtree; thus proving consistency of both trees. :param old_root: the Merkle hash root of the old tree (or a subtree). :param old_size: number of leaves in the old tree. :return: True if both the old and new trees are consistent. """ if not isinstance(new_tree, MerkleTree): raise TypeError(f'Expected MerkleTree, got {type(new_tree)}') new_size = len(new_tree) # the number of leaves in the old tree # cannot be greater than in the new tree if new_size < old_size: return False # assuming both hashes are hexadecimal strings old_root = utils.from_hex(old_root) new_root = utils.from_hex(new_tree.merkle_root) # if the number of leaves is identical # then roots also must be identical if new_size == old_size: return old_root == new_root leaves = new_tree.leaves index, paths = 0, [] while old_size > 0: # level is the largest power of two smaller than old_size # log2(level) will indicate where we should be climbing level = 2**(old_size.bit_length() - 1) node = _climb_to(leaves[index], math.log(level, 2)) if node is None: return False paths.append(node) index += level old_size -= level # if old_size is power of two (len(paths) == 1) # then we have our searched Merkle hash root # otherwise we will need to concatenate all nodes if len(paths) > 1: paths = paths[::-1] hasher = new_tree.hasher concat = lambda a,b: _concat(hasher, a, b) new_root = functools.reduce(concat, paths) else: new_root = paths[0].hash return new_root == old_root
def lists_to_proof(hashlist, typelist): nodepath = [] for nodehash, nodetype in zip(hashlist, typelist): nodepath.append(merklelib.AuditNode(utils.from_hex(nodehash), nodetype)) proof = merklelib.AuditProof(nodepath) return proof
def get_proof(self, leaf): """Provides an audit proof for a leaf. :param leaf: a leaf which is represented by either a real object (int, str, etc.) or the hash value of that object. :return audit proof: a collection of all hashes such that if traversed and concatenated, will produce the original merkle hash root """ mapping, hasher = self._mapping, self._hasher # assuming that leaf in hexadecimal representation target = mapping.get(utils.from_hex(leaf)) if target is None: target = mapping.get(hasher.hash_leaf(leaf)) # no leaf in mapping, return an empty AuditProof object if not isinstance(target, MerkleNode): return AuditProof([]) root, paths = self._root, [] # saving every sibiling node (if not _empty) # until the root node is reached while target is not root: sibiling = target.sibiling if sibiling is not _empty: node = AuditNode(sibiling.hash, sibiling.type) paths.append(node) target = target.parent return AuditProof(paths)
def verify_leaf_inclusion(target, proof, hashobj, root_hash): """Verifies that a tree includes a leaf. :param target: a leaf which is represented by either a real object (int, str, etc.) or the hash value of that object. :param proof: a data structure that contains AuditNode objects which serve to recreate the original Merkle hash root. :param hashobj: a hash function or Hasher. If a hash function is provided, it will be used to convert a Hasher instance. :param root_hash: Merkle hash root provided by a trusted authority. :return: True if the leaf is included in the tree. """ if not isinstance(hashobj, Hasher): if not callable(hashobj): raise TypeError(f'Expected callable, got {type(hashobj)}') hashobj = Hasher(hashobj) hasher = hashobj paths = None # any collection containing AuditNode objects. if isinstance(proof, collections.Iterable): if isinstance(proof[0], AuditNode): paths = proof elif isinstance(proof, AuditProof): paths = proof._nodes if paths is None: raise TypeError( 'Proof must be either <AuditProof>, ' 'a collection of <AuditNode> objects.' ) # keep it dry concat = lambda x,y: _concat(hasher, x, y) def _calculate_root(target): _proof = [target] + paths return functools.reduce(concat, _proof) new_root = _calculate_root(target) root_hash = utils.from_hex(root_hash) # try again if the user forgot to hash the target if new_root != root_hash: try: new_root = _calculate_root(hasher.hash_leaf(target)) except: pass return new_root == root_hash
def _wrapper(*args, **kwargs): hash_value = func(*args, **kwargs) return utils.from_hex(hash_value)