Example #1
0
def get_padding_elements(start_index: int, num_padding_chunks: int,
                         path_bit_length: int) -> Iterable[ProofElement]:
    r"""
    Get the padding elements for a proof.


    0:                           X
                                / \
                              /     \
                            /         \
                          /             \
                        /                 \
                      /                     \
                    /                         \
    1:             0                           1
                 /   \                       /   \
               /       \                   /       \
             /           \               /           \
    2:      0             1             0             1
           / \           / \           / \           / \
          /   \         /   \         /   \         /   \
    3:   0     1       0     1       0     1       0     1
        / \   / \     / \   / \     / \   / \     / \   / \
    4: 0   1 0   1   0   1 0   1   0   1 0   1   0   1 0   1

                                                      |<--->|
                                                      [  2  ]
                                                    |<----->|
                                                    [1][  2 ]
                                                |<-PADDING->|
                                                [    4      ]
                                            |<---PADDING--->|
                                            [1] [    4      ]
                                        |<-----PADDING----->|
                                        [  2  ] [    4      ]
                                      |<------PADDING------>|
                                      [1][  2 ] [    4      ]
                                  |<--------PADDING-------->|
                                  [            8            ]

    Padding is always on the right-hand-side of the tree.

    One nice property of the binary tree is that we can very efficiently
    determine the minimal set of subtree nodes needed to represent the full
    padding.

    We do this by computing the total number of padding nodes, and then
    decomposing that into the powers of two needed to make up that number.

    These determine our subtrees.  The bit-length of each number tells us how
    many levels of zero hashes we will need, and we use a pre-computed set of
    these hashes for efficiency sake.
    """
    for power_of_two in decompose_into_powers_of_two(num_padding_chunks):
        depth = power_of_two.bit_length() - 1
        left_index = start_index + power_of_two
        left_path = chunk_index_to_path(left_index, path_bit_length)
        padding_hash_tree_root = ZERO_HASHES[depth]
        yield ProofElement(left_path[:path_bit_length - depth],
                           padding_hash_tree_root)
Example #2
0
 def has_chunk(self, chunk_index: int) -> bool:
     path = chunk_index_to_path(chunk_index, self.path_bit_length)
     try:
         self.get_element(path)
     except IndexError:
         return False
     else:
         return True
Example #3
0
    def get_first_padding_chunk_path(self) -> TreePath:
        """
        Return the path of the first padding chunk.

        Raise `IndexError` if the tree has no padding.
        """
        return chunk_index_to_path(self.get_first_padding_chunk_index(),
                                   self.path_bit_length)
Example #4
0
def compute_proof_elements(chunks: Sequence[Hash32],
                           chunk_count: int) -> Iterable[ProofElement]:
    """
    Compute all of the proof elements, including the right hand padding
    elements for a proof over the given chunks.
    """
    # By using the full bit-length here we leave room for the length which gets
    # mixed in at the root of the tree.
    path_bit_length = chunk_count.bit_length()

    for idx, chunk in enumerate(chunks):
        path = chunk_index_to_path(idx, path_bit_length)
        yield ProofElement(path, chunk)

    start_index = len(chunks) - 1
    num_padding_chunks = chunk_count - len(chunks)

    yield from get_padding_elements(start_index, num_padding_chunks,
                                    path_bit_length)
Example #5
0
    def get_minimal_proof_elements(
        self,
        start_chunk_index: int,
        num_chunks: int,
    ) -> Iterable[ProofElement]:
        """
        Return the minimal set of tree nodes needed to prove the designated
        section of the tree.  Nodes which belong to the same subtree are
        collapsed into the parent intermediate node.

        The `group_by_subtree` utility function implements the core logic for
        this functionality and documents how nodes are grouped.
        """
        if num_chunks < 1:
            raise Exception("Invariant")

        end_chunk_index = start_chunk_index + num_chunks - 1

        num_chunks = end_chunk_index - start_chunk_index + 1
        chunk_index_groups = group_by_subtree(start_chunk_index, num_chunks)
        groups_with_bit_lengths = tuple(
            # Each group will contain an even "power-of-two" number of
            # elements.  This tells us how many tailing bits each element has
            # which need to be truncated to get the group's common prefix.
            (group[0], (len(group) - 1).bit_length())
            for group in chunk_index_groups)
        subtree_paths = tuple(
            # We take a candidate element from each group and shift it to
            # remove the bits that are not common to other group members, then
            # we convert it to a tree path that all elements from this group
            # have in common.
            chunk_index_to_path(
                chunk_index >> bits_to_truncate,
                self.path_bit_length - bits_to_truncate,
            ) for chunk_index, bits_to_truncate in groups_with_bit_lengths)
        for path in subtree_paths:
            element_group = self.get_elements_under(path)
            if len(element_group) == 1:
                yield element_group[0]
            else:
                yield ProofElement(path=path,
                                   value=merklize_elements(element_group))
Example #6
0
def test_chunk_index_to_path(chunk_index, expected):
    path = chunk_index_to_path(chunk_index, 4)
    assert path == expected
Example #7
0
    def to_partial(self, start_at: int, partial_data_length: int) -> "Proof":
        """
        Return another proof with the minimal number of tree elements necessary
        to prove the slice of the underlying bytestring denoted by the
        `start_at` and `partial_data_length` parameters.
        """
        # First retrieve the overall content length from the proof.  The `length`
        # should always be found on the path `(True,)` which should always be
        # present in the tree.
        length = self.get_content_length()

        # Ensure that we aren't requesting data that exceeds the overall length
        # of the actual content.
        end_at = start_at + partial_data_length
        if end_at > length:
            raise Exception(
                f"Cannot create partial that exceeds the data length: {end_at} > {length}"
            )

        # Compute the chunk indices and corresponding paths for the locations
        # in the tree where the partial data starts and ends.
        first_partial_chunk_index = start_at // CHUNK_SIZE

        if partial_data_length == 0:
            last_partial_chunk_index = first_partial_chunk_index
        else:
            last_partial_chunk_index = (end_at - 1) // CHUNK_SIZE

        first_partial_chunk_path = chunk_index_to_path(
            first_partial_chunk_index, self.path_bit_length)
        last_partial_chunk_path = chunk_index_to_path(last_partial_chunk_index,
                                                      self.path_bit_length)

        # Get all of the leaf nodes for the section of the tree where the
        # partial data is located.  Ensure that we have a contiguous section of
        # leaf nodes for this part of the tree.
        partial_elements = self.get_elements(
            left=first_partial_chunk_path,
            right=last_partial_chunk_path,
            right_inclusive=True,
        )
        expected_partial_chunk_count = (last_partial_chunk_index -
                                        first_partial_chunk_index) + 1
        if len(partial_elements) != expected_partial_chunk_count:
            raise Exception(
                "Proof is missing leaf nodes required for partial construction."
            )

        minimal_data_elements_left_of_partial: Tuple[ProofElement, ...]
        if first_partial_chunk_index == 0:
            minimal_data_elements_left_of_partial = ()
        else:
            minimal_data_elements_left_of_partial = self.get_minimal_proof_elements(
                0,
                first_partial_chunk_index,
            )

        last_data_chunk_index = self.get_last_data_chunk_index()

        minimal_data_elements_right_of_partial: Tuple[ProofElement, ...]
        if last_partial_chunk_index == last_data_chunk_index:
            minimal_data_elements_right_of_partial = ()
        else:
            minimal_data_elements_right_of_partial = self.get_minimal_proof_elements(
                last_partial_chunk_index + 1,
                last_data_chunk_index - (last_partial_chunk_index + 1) + 1,
            )

        padding_elements = self.get_padding_elements()

        length_element = self.get_element((True, ))

        # Now re-assembly the sections of the tree for the minimal proof for
        # the partial data.
        partial_elements = sum(
            (
                minimal_data_elements_left_of_partial,
                partial_elements,
                minimal_data_elements_right_of_partial,
                padding_elements,
                (length_element, ),
            ),
            (),
        )

        return Proof(partial_elements, self.sedes)
Example #8
0
 def get_last_data_chunk_path(self) -> TreePath:
     """
     Compute the `TreePath` of the last data chunck.
     """
     return chunk_index_to_path(self.get_last_data_chunk_index(),
                                self.path_bit_length)