Post
Topic
Board Development & Technical Discussion
Re: Secp256k1 Attack with known nonces (Forge Signatures)
by
dexizer7799
on 12/05/2025, 11:11:32 UTC
Nonce Reuse Attack

Quote
import hashlib
import secrets
import libnum

p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
K = GF(p)
a = K(0x0000000000000000000000000000000000000000000000000000000000000000)
b = K(0x0000000000000000000000000000000000000000000000000000000000000007)
E = EllipticCurve(K, (a, b))
G = E(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
E.set_order(0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 * 0x1)

q = E.order()

privateKey = secrets.randbelow(int(q))

pub = privateKey * G

print("Private Key :=> " + str(privateKey))

def sign(m, d, k = None):
   z = int.from_bytes(hashlib.sha256(m).digest(), 'big')
   
   if k == None:
      k = secrets.randbits(256)
   
   P = k * G
   x1 = int(P.xy()[0])

   r = int(x1 % q)
   s = int(pow(k, -1, q) * (z + r * d) % q)
   
   return (r, s, z)

def _forgeSignature(r, s, z, bits = 256):
   newR, newS, newZ = None, None, None

   R = E.lift_x(Integer(r))

   r_ = int(((int(pow(4, -1, q)) * int(2 ** bits - 1) * G) - (int(pow(4, -1, q)) * R)).xy()[0])
   s_ = int((-r_ * s * int(pow(r, -1, q)) * 4) % q)
   z_ = int(int(pow(4, -1, q)) * s_ * int(2 ** bits - 1 - int(pow(s, -1, q)) * z) % q)
   
   w_ = pow(s_, -1, q)
   
   u1_ = int(z_ * w_ % q)
   u2_ = int(r_ * w_ % q)
   
   if (u1_ * G + u2_ * pub).xy()[0] == r_:
      newR = r_
      newS = s_
      newZ = z_

   return newR, newS, newZ

def forgeSignature(r, s, z, bits = 256):
   newR, newS, newZ = _forgeSignature(r, s, z, bits)
   
   return newR, newS, newZ

nonce = secrets.randbelow(int(q))

print("\nNonce :=> " + str(nonce))

r1, s1, z1 = sign(b'Hello World 1', privateKey, nonce)

r2, s2, z2 = sign(b'Hello World 2', privateKey, nonce)

r1, s1, z1 = forgeSignature(r1, s1, z1)

r2, s2, z2 = forgeSignature(r2, s2, z2)

temp = libnum.invmod((s1 - s2), q)

recoveredK = ((z1 - z2) * temp) % q

print("\n\nRecovered K :=> " + str(recoveredK))

recoveredPriv = (libnum.invmod(r1, q) * ((s1 * recoveredK) - z1)) % q

print("\nRecovered PrivateKey :=> " + str(recoveredPriv))

if privateKey == recoveredPriv:
   print("\nSuccessfully Recovered")