26 KiB
26 KiB
In [1]:
#collapse-hide
# Setup and imports
import hashlib
import numpy as np
from functools import reduce
def assert_equal(a, b):
return np.testing.assert_equal(a, b)
def assert_not_equal(a, b):
return np.testing.assert_raises(AssertionError, np.testing.assert_equal, a, b)In [2]:
def hash_m(e):
hash_bytes = list(hashlib.sha512(e).digest())[:64] # hash the bytes e, convert the digest into a list of 64 bytes
return np.array(hash_bytes, dtype=np.uint8).reshape((8,8)) # convert the digest bytes into a numpy array with the appropriate data type and shapeIn [3]:
print(hash_m(b"Hello A"))
print()
print(hash_m(b"Hello B"))[[ 14 184 108 217 131 164 222 93] [132 227 82 144 111 178 195 109] [ 25 250 155 17 131 183 151 217] [212 60 138 36 0 60 115 181] [ 51 0 87 43 93 252 56 61] [108 239 175 222 23 142 41 216] [203 98 234 13 65 169 255 240] [ 46 127 15 167 112 153 222 94]] [[ 63 144 188 5 57 146 32 56] [ 27 189 98 140 113 194 70 87] [115 21 136 27 116 167 85 48] [ 29 162 119 29 104 32 145 241] [166 197 57 165 132 213 50 202] [ 48 71 33 19 230 26 58 164] [242 172 65 202 193 50 193 141] [206 110 165 129 52 132 250 73]]
In [4]:
def mul_m(he1, he2):
return np.matmul(he1, he2, dtype=np.uint8) # just, like, multiply themIn [5]:
#
# `elements` is a list of 3 elements
elements = [b"A", b"Hello", b"World"]
# first, make a new list with the hash of each element
element_hashes = [hash_m(e) for e in elements]
# get the hash of the list by reducing the hashes by matrix multiplication
list_hash1 = mul_m(mul_m(element_hashes[0], element_hashes[1]), element_hashes[2])
# an alternative way to write the reduction
list_hash2 = reduce(mul_m, element_hashes)
# check that these are equivalent
assert_equal(list_hash1, list_hash2)In [6]:
#collapse-output
print("List of elements:\n", elements, "\n")
print("List of element hashes:\n", element_hashes, "\n")
print("Merklist hash of the list", elements, ":\n", list_hash1, "\n")List of elements:
[b'A', b'Hello', b'World']
List of element hashes:
[array([[ 33, 180, 244, 189, 158, 100, 237, 53],
[ 92, 62, 182, 118, 162, 142, 190, 218],
[246, 216, 241, 123, 220, 54, 89, 149],
[179, 25, 9, 113, 83, 4, 64, 128],
[ 81, 107, 208, 131, 191, 204, 230, 97],
[ 33, 163, 7, 38, 70, 153, 76, 132],
[ 48, 204, 56, 43, 141, 197, 67, 232],
[ 72, 128, 24, 59, 248, 86, 207, 245]], dtype=uint8), array([[ 54, 21, 248, 12, 157, 41, 62, 215],
[ 64, 38, 135, 249, 75, 34, 213, 142],
[ 82, 155, 140, 199, 145, 111, 143, 172],
[127, 221, 247, 251, 213, 175, 76, 247],
[119, 211, 215, 149, 167, 160, 10, 22],
[191, 126, 127, 63, 185, 86, 30, 233],
[186, 174, 72, 13, 169, 254, 122, 24],
[118, 158, 113, 136, 107, 3, 243, 21]], dtype=uint8), array([[142, 167, 115, 147, 164, 42, 184, 250],
[146, 80, 15, 176, 119, 169, 80, 156],
[195, 43, 201, 94, 114, 113, 46, 250],
[ 17, 110, 218, 242, 237, 250, 227, 79],
[187, 104, 46, 253, 214, 197, 221, 19],
[193, 23, 224, 139, 212, 170, 239, 113],
[ 41, 29, 138, 172, 226, 248, 144, 39],
[ 48, 129, 208, 103, 124, 22, 223, 15]], dtype=uint8)]
Merklist hash of the list [b'A', b'Hello', b'World'] :
[[178 188 57 157 60 136 190 127]
[ 40 234 254 224 38 46 250 52]
[156 72 193 136 219 98 28 4]
[197 2 43 132 132 232 254 198]
[ 93 64 113 215 2 246 130 192]
[ 91 107 85 13 149 60 19 173]
[ 84 77 244 98 0 239 123 17]
[ 58 112 98 250 163 20 27 6]]
In [7]:
he1 = hash_m(b"A")
he2 = hash_m(b"B")
he3 = hash_m(b"C")In [8]:
x = np.matmul(np.matmul(he1, he2), he3)
y = np.matmul(he1, np.matmul(he2, he3))
# observe that they produce the same summary
assert_equal(x, y)In [9]:
#collapse-output
print(x, "\n\n", y)[[236 186 154 122 127 118 202 95] [132 196 196 78 80 160 68 166] [208 62 184 219 85 118 143 111] [ 7 176 219 162 243 25 29 54] [233 49 39 237 233 209 201 221] [187 196 20 155 17 241 207 101] [132 186 67 153 231 160 68 62] [ 8 184 160 149 100 160 189 56]] [[236 186 154 122 127 118 202 95] [132 196 196 78 80 160 68 166] [208 62 184 219 85 118 143 111] [ 7 176 219 162 243 25 29 54] [233 49 39 237 233 209 201 221] [187 196 20 155 17 241 207 101] [132 186 67 153 231 160 68 62] [ 8 184 160 149 100 160 189 56]]
In [10]:
x = np.matmul(he1, he2)
y = np.matmul(he2, he1)
# observe that they produce different summaries
assert_not_equal(x, y)In [11]:
#collapse-output
print(x, "\n\n", y)[[ 3 123 188 218 224 245 207 138] [ 6 234 158 180 156 14 148 118] [ 1 224 198 114 201 102 144 157] [ 11 141 205 36 186 218 16 40] [ 76 243 130 82 188 146 230 84] [211 24 6 46 87 234 89 206] [ 16 239 112 27 11 202 139 137] [103 30 53 187 203 140 26 146]] [[253 223 41 178 183 140 102 18] [111 185 54 143 153 96 117 15] [109 111 168 203 253 0 102 84] [106 74 245 252 178 202 226 201] [ 12 105 29 102 167 152 161 226] [253 244 27 149 227 247 235 21] [121 15 36 5 9 32 10 92] [ 42 189 91 117 135 176 52 152]]
In [12]:
# Create a list of 1024 elements and reduce them one by one
list1 = [hash_m(b"A") for _ in range(0, 1024)]
hash1 = reduce(mul_m, list1)
# Take a starting element and square/double it 10 times. With 1 starting element over 10 doublings = 1024 elements
hash2 = reduce((lambda m, _ : mul_m(m, m)), range(0, 10), hash_m(b"A"))
# Observe that these two methods of calculating the hash have the same result
assert_equal(hash1, hash2)
# lets call it double
def double_m(m, d=1):
return reduce((lambda m, _ : mul_m(m, m)), range(0, d), m)
assert_equal(hash1, double_m(hash_m(b"A"), 10))
def identity_m():
return np.identity(8, dtype=np.uint8)
# generalize double_m to any length, not just doublings, performed in ln(N) matmuls
def repeat_m(m, n):
res = identity_m()
while n > 0:
# concatenate the current doubling iff the bit representing this doubling is set
if n & 1:
res = mul_m(res, m)
n >>= 1
m = mul_m(m, m) # double matrix m
# print(s)
return res
# repeat_m can do the same as double_m
assert_equal(hash1, repeat_m(hash_m(b"A"), 1024))
# but it can also repeat any number of times
hash3 = reduce(mul_m, (hash_m(b"A") for _ in range(0, 3309)))
assert_equal(hash3, repeat_m(hash_m(b"A"), 3309))
# Even returns a sensible result when requesting 0 elements
assert_equal(identity_m(), repeat_m(hash_m(b"A"), 0))
# make helper for reducing an iterable of hashes
def reduce_m(am):
return reduce(mul_m, am)In [13]:
print(hash1, "\n\n", hash2)[[ 68 252 159 3 14 52 199 199] [136 124 6 34 58 174 206 54] [ 3 234 2 13 120 240 7 163] [102 47 66 61 87 234 246 72] [ 19 135 80 115 75 242 242 5] [244 165 250 28 76 43 188 254] [233 46 187 39 151 241 175 130] [132 138 6 215 20 132 89 33]] [[ 68 252 159 3 14 52 199 199] [136 124 6 34 58 174 206 54] [ 3 234 2 13 120 240 7 163] [102 47 66 61 87 234 246 72] [ 19 135 80 115 75 242 242 5] [244 165 250 28 76 43 188 254] [233 46 187 39 151 241 175 130] [132 138 6 215 20 132 89 33]]
In [14]:
a = hash_m(b"A")
b = hash_m(b"B")
a499 = repeat_m(a, 499)
a500 = repeat_m(a, 500)
# this should work because they're all a's
assert_equal(reduce_m([a, a499]), a500)
assert_equal(reduce_m([a499, a]), a500)
# these are lists of 999 elements of a, with one b at position 500 (x) or 501 (y)
x = reduce_m([a499, b, a500])
y = reduce_m([a500, b, a499])
# shifting the b by one element changed the hash
assert_not_equal(x, y)In [15]:
assert_equal(reduce_m([a, x, y, a]), reduce_m([a500, b, repeat_m(a500, 2), b, a500]))