127 lines
3.6 KiB
Python
127 lines
3.6 KiB
Python
# ============================================================+
|
|
# Identity IoT — Ed25519 for MicroPython
|
|
# (c) Copyright : ae Aeonian Engineering Limited - Hong Kong
|
|
# (c) Copyright : WIDE di D. Papa - Naples - Italy
|
|
# ============================================================+
|
|
# Based on Bernstein et al. reference implementation (public domain)
|
|
# Iterative scalarmult — no recursion, safe for MicroPython stack
|
|
# ============================================================+
|
|
|
|
from sha512 import sha512
|
|
|
|
_q = 2**255 - 19
|
|
_l = 2**252 + 27742317777372353535851937790883648493
|
|
_d = -121665 * pow(121666, _q - 2, _q) % _q
|
|
_I = pow(2, (_q - 1) // 4, _q)
|
|
|
|
|
|
def _xrecover(y):
|
|
x2 = (y * y - 1) * pow(_d * y * y + 1, _q - 2, _q)
|
|
x = pow(x2, (_q + 3) // 8, _q)
|
|
if (x * x - x2) % _q != 0:
|
|
x = x * _I % _q
|
|
if x % 2 != 0:
|
|
x = _q - x
|
|
return x
|
|
|
|
|
|
_By = 4 * pow(5, _q - 2, _q) % _q
|
|
_Bx = _xrecover(_By)
|
|
_B = [_Bx % _q, _By % _q, 1, _Bx * _By % _q]
|
|
|
|
|
|
def _edwards_add(P, Q):
|
|
x1, y1, z1, t1 = P
|
|
x2, y2, z2, t2 = Q
|
|
A = (y1 - x1) * (y2 - x2) % _q
|
|
B = (y1 + x1) * (y2 + x2) % _q
|
|
C = t1 * 2 * _d * t2 % _q
|
|
D = z1 * 2 * z2 % _q
|
|
E = B - A; F = D - C; G = D + C; H = B + A
|
|
return [E * F % _q, G * H % _q, F * G % _q, E * H % _q]
|
|
|
|
|
|
def _scalarmult(P, e):
|
|
# Iterative double-and-add — no recursion
|
|
Q = [0, 1, 1, 0] # neutral element
|
|
while e > 0:
|
|
if e & 1:
|
|
Q = _edwards_add(Q, P)
|
|
P = _edwards_add(P, P)
|
|
e >>= 1
|
|
return Q
|
|
|
|
|
|
def _encodeint(y):
|
|
bits = [(y >> i) & 1 for i in range(256)]
|
|
return bytes([sum([bits[i * 8 + j] << j for j in range(8)]) for i in range(32)])
|
|
|
|
|
|
def _encodepoint(P):
|
|
x, y, z, _ = P
|
|
zi = pow(z, _q - 2, _q)
|
|
x = x * zi % _q
|
|
y = y * zi % _q
|
|
bits = [(y >> i) & 1 for i in range(255)] + [x & 1]
|
|
return bytes([sum([bits[i * 8 + j] << j for j in range(8)]) for i in range(32)])
|
|
|
|
|
|
def _bit(h, i):
|
|
return (h[i // 8] >> (i % 8)) & 1
|
|
|
|
|
|
def _hint(m):
|
|
return int.from_bytes(sha512(m), 'little')
|
|
|
|
|
|
def _decodepoint(s):
|
|
y = sum(_bit(s, i) * 2 ** i for i in range(255))
|
|
x = _xrecover(y)
|
|
if x & 1 != _bit(s, 255):
|
|
x = _q - x
|
|
return [x, y, 1, x * y % _q]
|
|
|
|
|
|
# ============================================================
|
|
# Public API
|
|
# ============================================================
|
|
|
|
def ed25519_keypair_from_seed(seed: bytes):
|
|
if len(seed) != 32:
|
|
raise ValueError('Seed must be 32 bytes')
|
|
h = sha512(seed)
|
|
a = 2 ** 254 + sum(2 ** i * _bit(h, i) for i in range(3, 254))
|
|
A = _scalarmult(_B, a)
|
|
pk = _encodepoint(A)
|
|
return pk, seed + pk
|
|
|
|
|
|
def ed25519_sign(seed: bytes, message: bytes) -> bytes:
|
|
if len(seed) != 32:
|
|
raise ValueError('Seed must be 32 bytes')
|
|
h = sha512(seed)
|
|
a = 2 ** 254 + sum(2 ** i * _bit(h, i) for i in range(3, 254))
|
|
pk = ed25519_keypair_from_seed(seed)[0]
|
|
r = _hint(h[32:64] + message)
|
|
R = _scalarmult(_B, r)
|
|
S = (r + _hint(_encodepoint(R) + pk + message) * a) % _l
|
|
return _encodepoint(R) + _encodeint(S)
|
|
|
|
|
|
def ed25519_verify(pk: bytes, message: bytes, signature: bytes) -> bool:
|
|
if len(signature) != 64 or len(pk) != 32:
|
|
return False
|
|
try:
|
|
R_enc = signature[:32]
|
|
S = int.from_bytes(signature[32:], 'little')
|
|
if S >= _l:
|
|
return False
|
|
A = _decodepoint(pk)
|
|
R = _decodepoint(R_enc)
|
|
h = _hint(R_enc + pk + message)
|
|
lhs = _scalarmult(_B, 8 * S)
|
|
rhs = _edwards_add(_scalarmult(R, 8), _scalarmult(A, 8 * h))
|
|
return _encodepoint(lhs) == _encodepoint(rhs)
|
|
except Exception:
|
|
return False
|