Skip to content

Instantly share code, notes, and snippets.

@jaonoctus
Last active July 21, 2025 20:07
Show Gist options
  • Save jaonoctus/e774b19d529ac109739baf3b77ae9a6b to your computer and use it in GitHub Desktop.
Save jaonoctus/e774b19d529ac109739baf3b77ae9a6b to your computer and use it in GitHub Desktop.
How can you extract secret keys from ed25519 by doing: a = (S1 - S2) * (e1 - e2) ^ -1 % q
from hashlib import sha512
import os
# Prime field and constants
p = 2^255 - 19
q = 2^252 + 27742317777372353535851937790883648493
F = GF(p)
Zq = Integers(q)
# Curve constant d
d = F(-121665) / F(121666)
# Base point (RFC 8032)
Bx = F(15112221349535400772501151409588531511454012693041857206046113283949847762202)
By = F(46316835694926478169428394003475163141307993866256225615783033603165251855960)
B = (Bx, By)
# Group operations
def edwards_add(P, Q):
(x1, y1), (x2, y2) = P, Q
denom = F(1) + d * x1 * x2 * y1 * y2
x3 = (x1*y2 + x2*y1) / denom
y3 = (y1*y2 - x1*x2) / denom
return (x3, y3)
def edwards_double(P):
return edwards_add(P, P)
def scalar_mult(n, P):
Q = None
for i in reversed(bin(n)[2:]):
if Q is not None:
Q = edwards_double(Q)
if i == '1':
Q = P if Q is None else edwards_add(Q, P)
return Q
# Crypto utilities
def clamp_scalar(h_bytes):
h = bytearray(h_bytes)
h[0] &= 248
h[31] &= 63
h[31] |= 64
return int.from_bytes(h, 'little') % q
def H(m):
return sha512(m).digest()
def Hint(m):
h = H(m)
return int.from_bytes(h[::-1], 'big') % q
def generate_seed():
return os.urandom(32)
def publickey(seed):
h = H(seed)
a_bytes = h[:32]
a = clamp_scalar(a_bytes)
A = scalar_mult(a, B)
return a, A
def compress_x(P):
x, _ = P
return int(x).to_bytes(32, 'little')
def signature(m_bytes, seed, pk_bytes):
h = H(seed)
a_bytes, inter = h[:32], h[32:]
a = clamp_scalar(a_bytes)
r = Hint(inter + m_bytes)
R = scalar_mult(r, B)
R_bytes = compress_x(R)
e = Hint(R_bytes + pk_bytes + m_bytes)
S = (r + e * a) % q
return R_bytes, S, e, a
# ---
msg = b"Hello"
print("Message:", msg.decode())
sk1 = generate_seed()
a1, pk1 = publickey(sk1)
pk1_bytes = compress_x(pk1)
sk2 = generate_seed()
a2, pk2 = publickey(sk2)
pk2_bytes = compress_x(pk2)
print("Secret key 1: ", sk1.hex())
print("Verifing key 1: ", pk1_bytes.hex())
print("Secret key 2: ", sk2.hex())
print("Verifing key 2: ", pk2_bytes.hex())
R1, S1, e1, a1_extracted = signature(msg, sk1, pk1_bytes)
R2, S2, e2, _ = signature(msg, sk1, pk2_bytes)
diff_e = Zq(e1 - e2)
diff_s = Zq(S1 - S2)
inv_diff_e = diff_e.inverse_of_unit()
recovered_a = (diff_s * inv_diff_e)
print("\nRecovered private key element: ", int(recovered_a))
print("Actual Private key element: ", a1_extracted)
if int(recovered_a) == a1_extracted:
print("\nPrivate key signing element has been recovered")
print("Signature Success")
else:
print("\nFailed to recover private key")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment