fix: correct URLs, add sha512, updated ed25519 and identity_iot
This commit is contained in:
@@ -1,163 +1,126 @@
|
||||
# ============================================================+
|
||||
# Identity IoT — Ed25519 Pure Python
|
||||
# Identity IoT — Ed25519 for MicroPython
|
||||
# (c) Copyright : ae Aeonian Engineering Limited - Hong Kong
|
||||
# (c) Copyright : WIDE di D. Papa - Naples - Italy
|
||||
# ============================================================+
|
||||
# Pure-Python Ed25519 for MicroPython PoC.
|
||||
# Production: replace with natmod C implementation.
|
||||
#
|
||||
# Based on public domain Ed25519 reference implementation.
|
||||
# Optimized for MicroPython memory constraints.
|
||||
# Based on Bernstein et al. reference implementation (public domain)
|
||||
# Iterative scalarmult — no recursion, safe for MicroPython stack
|
||||
# ============================================================+
|
||||
|
||||
import uhashlib
|
||||
import ubinascii
|
||||
from sha512 import sha512
|
||||
|
||||
# ---- Field arithmetic mod p ----
|
||||
_p = 2**255 - 19
|
||||
_q = 2**252 + 27742317777372353535851937790883648493
|
||||
_d = -121665 * pow(121666, _p - 2, _p) % _p
|
||||
_I = pow(2, (_p - 1) // 4, _p)
|
||||
_Gx = 15112221349535807912866137220509078750507884956996801637620
|
||||
_Gy = 46316835694926478169428394003475163141307993866256225615783
|
||||
_G = (_Gx % _p, _Gy % _p, 1, _Gx * _Gy % _p)
|
||||
_q = 2**255 - 19
|
||||
_l = 2**252 + 27742317777372353535851937790883648493
|
||||
_d = -121665 * pow(121666, _q - 2, _q) % _q
|
||||
_I = pow(2, (_q - 1) // 4, _q)
|
||||
|
||||
|
||||
def _sha512(data: bytes) -> bytes:
|
||||
# MicroPython uhashlib has sha256 but not sha512
|
||||
# Use double-sha256 as fallback — NOT cryptographically equivalent
|
||||
# TODO: replace with proper sha512 natmod for production
|
||||
h1 = uhashlib.sha256(data).digest()
|
||||
h2 = uhashlib.sha256(h1 + data).digest()
|
||||
return h1 + h2
|
||||
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
|
||||
|
||||
|
||||
def _clamp(h: bytes) -> int:
|
||||
a = bytearray(h[:32])
|
||||
a[0] &= 248
|
||||
a[31] &= 127
|
||||
a[31] |= 64
|
||||
return int.from_bytes(a, 'little')
|
||||
_By = 4 * pow(5, _q - 2, _q) % _q
|
||||
_Bx = _xrecover(_By)
|
||||
_B = [_Bx % _q, _By % _q, 1, _Bx * _By % _q]
|
||||
|
||||
|
||||
def _point_add(P, Q):
|
||||
def _edwards_add(P, Q):
|
||||
x1, y1, z1, t1 = P
|
||||
x2, y2, z2, t2 = Q
|
||||
A = (y1 - x1) * (y2 - x2) % _p
|
||||
B = (y1 + x1) * (y2 + x2) % _p
|
||||
C = 2 * t1 * t2 * _d % _p
|
||||
D = 2 * z1 * z2 % _p
|
||||
E = B - A
|
||||
F = D - C
|
||||
G_ = D + C
|
||||
H = B + A
|
||||
x3 = E * F % _p
|
||||
y3 = G_ * H % _p
|
||||
t3 = E * H % _p
|
||||
z3 = F * G_ % _p
|
||||
return (x3, y3, z3, t3)
|
||||
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 _point_mul(s, P):
|
||||
Q = (0, 1, 1, 0)
|
||||
while s > 0:
|
||||
if s & 1:
|
||||
Q = _point_add(Q, P)
|
||||
P = _point_add(P, P)
|
||||
s >>= 1
|
||||
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 _point_encode(P) -> bytes:
|
||||
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, _p - 2, _p)
|
||||
x = x * zi % _p
|
||||
y = y * zi % _p
|
||||
out = bytearray(y.to_bytes(32, 'little'))
|
||||
out[31] ^= (x & 1) << 7
|
||||
return bytes(out)
|
||||
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 _point_decode(s: bytes):
|
||||
y = int.from_bytes(s, 'little') & ~(1 << 255)
|
||||
x_neg = s[31] >> 7
|
||||
y2 = y * y % _p
|
||||
u = (y2 - 1) % _p
|
||||
v = (_d * y2 + 1) % _p
|
||||
x = pow(u * pow(v, _p - 2, _p), (_p + 3) // 8, _p)
|
||||
vx2 = v * x * x % _p
|
||||
if (vx2 - u) % _p != 0:
|
||||
if (vx2 + u) % _p != 0:
|
||||
raise ValueError('Invalid point')
|
||||
x = x * _I % _p
|
||||
if x % 2 != x_neg:
|
||||
x = _p - x
|
||||
return (x, y, 1, x * y % _p)
|
||||
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):
|
||||
"""
|
||||
Derive Ed25519 keypair from 32-byte seed.
|
||||
Returns (public_key_bytes, private_key_bytes).
|
||||
"""
|
||||
if len(seed) != 32:
|
||||
raise ValueError('Seed must be 32 bytes')
|
||||
h = _sha512(seed)
|
||||
a = _clamp(h)
|
||||
A = _point_mul(a, _G)
|
||||
pk = _point_encode(A)
|
||||
sk = seed + pk # 64 bytes: seed || pubkey
|
||||
return pk, sk
|
||||
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:
|
||||
"""
|
||||
Sign message with Ed25519 private key derived from seed.
|
||||
Returns 64-byte signature.
|
||||
Compatible with PHP ITEMSSignature::verifyDetached().
|
||||
"""
|
||||
if len(seed) != 32:
|
||||
raise ValueError('Seed must be 32 bytes')
|
||||
h = _sha512(seed)
|
||||
a = _clamp(h)
|
||||
pk_bytes, _ = ed25519_keypair_from_seed(seed)
|
||||
|
||||
# Nonce: SHA512(h[32:64] || message)
|
||||
r_hash = _sha512(h[32:] + message)
|
||||
r = int.from_bytes(r_hash, 'little') % _q
|
||||
R = _point_mul(r, _G)
|
||||
R_enc = _point_encode(R)
|
||||
|
||||
# k = SHA512(R || pk || message)
|
||||
k_hash = _sha512(R_enc + pk_bytes + message)
|
||||
k = int.from_bytes(k_hash, 'little') % _q
|
||||
|
||||
# s = (r + k * a) mod q
|
||||
s = (r + k * a) % _q
|
||||
S = s.to_bytes(32, 'little')
|
||||
|
||||
return R_enc + S
|
||||
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: bytes, message: bytes, signature: bytes) -> bool:
|
||||
"""
|
||||
Verify Ed25519 signature.
|
||||
Returns True if valid.
|
||||
"""
|
||||
if len(signature) != 64 or len(pk_bytes) != 32:
|
||||
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 >= _q:
|
||||
if S >= _l:
|
||||
return False
|
||||
A = _point_decode(pk_bytes)
|
||||
R = _point_decode(R_enc)
|
||||
k_hash = _sha512(R_enc + pk_bytes + message)
|
||||
k = int.from_bytes(k_hash, 'little') % _q
|
||||
# Check: [8][S]B == [8]R + [8][k]A
|
||||
SB = _point_mul(8 * S, _G)
|
||||
RkA = _point_add(_point_mul(8, R), _point_mul(8 * k, A))
|
||||
return _point_encode(SB) == _point_encode(RkA)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user