Merkle staticmethods

This commit is contained in:
Jack Robison 2020-11-29 13:35:43 -05:00
parent 84908ec8ec
commit 23035b9aa0
No known key found for this signature in database
GPG key ID: DF25C68FE0239BB2

View file

@ -43,10 +43,12 @@ class Merkle:
def __init__(self, hash_func=double_sha256): def __init__(self, hash_func=double_sha256):
self.hash_func = hash_func self.hash_func = hash_func
def tree_depth(self, hash_count): @staticmethod
return self.branch_length(hash_count) + 1 def tree_depth(hash_count):
return Merkle.branch_length(hash_count) + 1
def branch_length(self, hash_count): @staticmethod
def branch_length(hash_count):
"""Return the length of a merkle branch given the number of hashes.""" """Return the length of a merkle branch given the number of hashes."""
if not isinstance(hash_count, int): if not isinstance(hash_count, int):
raise TypeError('hash_count must be an integer') raise TypeError('hash_count must be an integer')
@ -54,7 +56,8 @@ class Merkle:
raise ValueError('hash_count must be at least 1') raise ValueError('hash_count must be at least 1')
return ceil(log(hash_count, 2)) return ceil(log(hash_count, 2))
def branch_and_root(self, hashes, index, length=None): @staticmethod
def branch_and_root(hashes, index, length=None, hash_func=double_sha256):
"""Return a (merkle branch, merkle_root) pair given hashes, and the """Return a (merkle branch, merkle_root) pair given hashes, and the
index of one of those hashes. index of one of those hashes.
""" """
@ -64,7 +67,7 @@ class Merkle:
# This also asserts hashes is not empty # This also asserts hashes is not empty
if not 0 <= index < len(hashes): if not 0 <= index < len(hashes):
raise ValueError(f"index '{index}/{len(hashes)}' out of range") raise ValueError(f"index '{index}/{len(hashes)}' out of range")
natural_length = self.branch_length(len(hashes)) natural_length = Merkle.branch_length(len(hashes))
if length is None: if length is None:
length = natural_length length = natural_length
else: else:
@ -73,7 +76,6 @@ class Merkle:
if length < natural_length: if length < natural_length:
raise ValueError('length out of range') raise ValueError('length out of range')
hash_func = self.hash_func
branch = [] branch = []
for _ in range(length): for _ in range(length):
if len(hashes) & 1: if len(hashes) & 1:
@ -85,44 +87,47 @@ class Merkle:
return branch, hashes[0] return branch, hashes[0]
def root(self, hashes, length=None): @staticmethod
def root(hashes, length=None):
"""Return the merkle root of a non-empty iterable of binary hashes.""" """Return the merkle root of a non-empty iterable of binary hashes."""
branch, root = self.branch_and_root(hashes, 0, length) branch, root = Merkle.branch_and_root(hashes, 0, length)
return root return root
def root_from_proof(self, hash, branch, index): # @staticmethod
"""Return the merkle root given a hash, a merkle branch to it, and # def root_from_proof(hash, branch, index, hash_func=double_sha256):
its index in the hashes array. # """Return the merkle root given a hash, a merkle branch to it, and
# its index in the hashes array.
#
# branch is an iterable sorted deepest to shallowest. If the
# returned root is the expected value then the merkle proof is
# verified.
#
# The caller should have confirmed the length of the branch with
# branch_length(). Unfortunately this is not easily done for
# bitcoin transactions as the number of transactions in a block
# is unknown to an SPV client.
# """
# for elt in branch:
# if index & 1:
# hash = hash_func(elt + hash)
# else:
# hash = hash_func(hash + elt)
# index >>= 1
# if index:
# raise ValueError('index out of range for branch')
# return hash
branch is an iterable sorted deepest to shallowest. If the @staticmethod
returned root is the expected value then the merkle proof is def level(hashes, depth_higher):
verified.
The caller should have confirmed the length of the branch with
branch_length(). Unfortunately this is not easily done for
bitcoin transactions as the number of transactions in a block
is unknown to an SPV client.
"""
hash_func = self.hash_func
for elt in branch:
if index & 1:
hash = hash_func(elt + hash)
else:
hash = hash_func(hash + elt)
index >>= 1
if index:
raise ValueError('index out of range for branch')
return hash
def level(self, hashes, depth_higher):
"""Return a level of the merkle tree of hashes the given depth """Return a level of the merkle tree of hashes the given depth
higher than the bottom row of the original tree.""" higher than the bottom row of the original tree."""
size = 1 << depth_higher size = 1 << depth_higher
root = self.root root = Merkle.root
return [root(hashes[n: n + size], depth_higher) return [root(hashes[n: n + size], depth_higher)
for n in range(0, len(hashes), size)] for n in range(0, len(hashes), size)]
def branch_and_root_from_level(self, level, leaf_hashes, index, @staticmethod
def branch_and_root_from_level(level, leaf_hashes, index,
depth_higher): depth_higher):
"""Return a (merkle branch, merkle_root) pair when a merkle-tree has a """Return a (merkle branch, merkle_root) pair when a merkle-tree has a
level cached. level cached.
@ -146,10 +151,10 @@ class Merkle:
if not isinstance(leaf_hashes, list): if not isinstance(leaf_hashes, list):
raise TypeError("leaf_hashes must be a list") raise TypeError("leaf_hashes must be a list")
leaf_index = (index >> depth_higher) << depth_higher leaf_index = (index >> depth_higher) << depth_higher
leaf_branch, leaf_root = self.branch_and_root( leaf_branch, leaf_root = Merkle.branch_and_root(
leaf_hashes, index - leaf_index, depth_higher) leaf_hashes, index - leaf_index, depth_higher)
index >>= depth_higher index >>= depth_higher
level_branch, root = self.branch_and_root(level, index) level_branch, root = Merkle.branch_and_root(level, index)
# Check last so that we know index is in-range # Check last so that we know index is in-range
if leaf_root != level[index]: if leaf_root != level[index]:
raise ValueError('leaf hashes inconsistent with level') raise ValueError('leaf hashes inconsistent with level')