Last active
July 21, 2025 20:07
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
run the code with SageMath clicking here
Analysis On Ed25519 Use Risks: Your Wallet Private Key Can Be Stolen
40 ed25519 Unsafe libs
Does changing the random number selected for each message increase security in Schnorr's scheme?