def principal_subroots(self, sublength): """Detects in corresponding order the roots of the successive, leftmost, full binary subtrees of maximum (and thus decreasing) length, whose lengths sum up to the provided argument. Detected nodes are prepended with a sign (+1 or -1), carrying information for subsequent generation of consistency proofs. :param sublength: non negative integer smaller than or equal to the tree's current length, such that the corresponding sequence of subroots exists :returns: Signed roots of the detected subtrees, whose hashes to be utilized in generation of consistency proofs :rtype: list of signed nodes :raises NoPrincipalSubroots: if the provided number does not fulfill the prescribed conditions """ if sublength < 0: # Mask negative input as incompatiblitity raise NoPrincipalSubroots principal_subroots = [] append = principal_subroots.append powers = decompose(sublength) start = 0 for power in powers: try: subroot = self.subroot(start, power) except NoSubtreeException: # Incompatibility issue detected raise NoPrincipalSubroots try: child = subroot.child grandchild = child.child except NoChildException: if subroot.is_left_parent(): append((+1, subroot)) else: append((-1, subroot)) else: if child.is_left_parent(): append((+1, subroot)) else: append((-1, subroot)) finally: start += 2**power if len(principal_subroots) > 0: # Modify last sign principal_subroots[-1] = (+1, principal_subroots[-1][1]) return principal_subroots
def test_decompose(num, powers): """Tests decompose for all possible combination of powers from 0 to 10 """ assert utils.decompose(num) == reverseTupleFromList(powers)
def test_decompose_negative_convention(): """Tests that decompose returns the nonsensical empty tuple for arguments smaller than zero """ assert utils.decompose(-1) == ()
def test_decompose_zero_convention(): """Tests that decompose returns the nonsensical empty tuple for arguments equal to zero """ assert utils.decompose(0) == ()
def update(self, record=None, digest=None): """ Updates the Merkle-tree by storing the digest of the inserted record into a newly-created leaf. Restructures the tree appropriately and recalculates appropriate interior hashes :param record: [optional] The record whose digest is to be stored into a new leaf :type record: str or bytes :param digest: [optional] The digest to be stored into the new leaf :type digest: str .. warning:: Exactly one of either record or digest should be provided :raises LeafConstructionError: if both record and digest were provided :raises UndecodableRecord: if the Merkle-tree is not in raw-bytes mode and the provided record does not fall under its configured type """ encoding = self.encoding hash = self.hash if self: leaves = self.leaves append = leaves.append add = self.nodes.add # ~ Height and root of the *full* binary subtree with maximum # ~ possible length containing the rightmost leaf last_power = decompose(len(leaves))[-1] last_subroot = leaves[-1].descendant(degree=last_power) # Encrypt new record into new leaf try: new_leaf = Leaf(hash, encoding, record, digest) except (LeafConstructionError, UndecodableRecord): raise # Assimilate new leaf append(new_leaf) add(new_leaf) try: old_child = last_subroot.child # Save child info before bifurcation except NoChildException: # last_subroot was previously root self.__root = Node(hash, encoding, last_subroot, new_leaf) add(self.__root) else: # Create bifurcation node new_child = Node(hash, encoding, last_subroot, new_leaf) add(new_child) # Interject bifurcation node old_child.set_right(new_child) new_child.set_child(old_child) # Recalculate hashes only at the rightmost branch of the tree current_node = old_child while 1: current_node.recalculate_hash(hash_func=hash) try: current_node = current_node.child except NoChildException: break else: # Empty tree case try: new_leaf = Leaf(hash, encoding, record, digest) except (LeafConstructionError, UndecodableRecord): raise self.leaves = [new_leaf] self.nodes = set([new_leaf]) self.__root = new_leaf