import sys, os, re, json
_RS = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'rs', 'src')
def _hexes(path, start):
src = open(os.path.join(_RS, path)).read()
body = src[src.index(start):]
body = body[:body.index('];')]
return [int(x, 16) for x in re.findall(r'0x([0-9A-Fa-f]+)', body)]
RC = _hexes('constants.rs', 'pub const ROUND_CONSTANTS_U64') DIAG = _hexes('field.rs', 'pub const MATRIX_DIAG_16')
assert len(RC) == 144 and len(DIAG) == 16
P = 0xFFFFFFFF00000001
RF_HALF, RP = 4, 16
def inv(x):
return pow(x, P - 2, P) if x else 0
def m4(a, b, c, d):
return ((2*a + 3*b + c + d) % P, (a + 2*b + 3*c + d) % P,
(a + b + 2*c + 3*d) % P, (3*a + b + c + 2*d) % P)
def m_e(s):
out = []
for j in range(4):
out.extend(m4(*s[4*j:4*j+4]))
sums = [sum(out[j*4 + k] for j in range(4)) % P for k in range(4)]
return [(out[i] + sums[i % 4]) % P for i in range(16)]
def m_i(s):
t = sum(s) % P
return [(DIAG[i] * s[i] + t) % P for i in range(16)]
def permute(s, rc=RC, rf_half=RF_HALF, rp=RP, partial='inv'):
ext, intl = rc[:128], rc[128:]
s = m_e(list(s))
r = 0
for _ in range(rf_half):
s = [pow((s[i] + ext[r*16 + i]) % P, 7, P) for i in range(16)]
s = m_e(s); r += 1
for j in range(rp):
x = (s[0] + intl[j]) % P
s[0] = inv(x) if partial == 'inv' else pow(x, 7, P)
s = m_i(s)
r = 4 for k in range(rf_half):
s = [pow((s[i] + ext[(r + k)*16 + i]) % P, 7, P) for i in range(16)]
s = m_e(s)
return s
def encode(block56):
return [int.from_bytes(block56[7*i:7*i+7] + b'\0', 'little') for i in range(8)]
def hash_bytes(msg, domain=0, rc=RC):
s = [0]*16
s[11] = domain
n = len(msg)
full = n // 56
for b in range(full):
blk = encode(msg[56*b:56*b+56])
s = [(s[i] + blk[i]) % P if i < 8 else s[i] for i in range(16)]
s = permute(s, rc)
tail = msg[56*full:] + b'\x01'
tail = tail + b'\0'*(56 - len(tail))
blk = encode(tail)
s = [(s[i] + blk[i]) % P if i < 8 else s[i] for i in range(16)]
s[10] = n
s = permute(s, rc)
return b''.join(x.to_bytes(8, 'little') for x in s[:4])
def bootstrap():
zero = [0]*144
seed = b'cyber'
pad = seed + b'\x01' + b'\0'*(56 - len(seed) - 1)
s = [0]*16
blk = encode(pad)
for i in range(8):
s[i] = (s[i] + blk[i]) % P
s[10] = len(seed)
s = permute(s, zero)
out, pos = [], 0
for i in range(144):
if pos >= 8:
s = permute(s, zero); pos = 0
out.append(s[pos]); pos += 1
return out
if __name__ == '__main__':
vec = json.load(open(os.path.join(_RS, '..', '..', 'vectors', 'hemera.json')))['hash']
for name, msg in [('empty', b''), ('hello', b'hello'), ('hemera', b'hemera')]:
got = hash_bytes(msg).hex()
print(name, got == vec[name], got)
print('bootstrap constants match rs/src/constants.rs:', bootstrap() == RC)