Justice
Another nice chall by the great bebo07 in ASCWG 2025
Handouts
from hashlib import sha1,sha256
from Crypto.Util.number import bytes_to_long, long_to_bytes,inverse
from random import randint
from sage.all import *
FLAG = b'ASCWG{TESTTEST}'
a = 'redacted'
b = 'redacted'
q= 11722926242306411491117245929733953191095935082498997045653937809623622288511133179535069277231629076589496415875947858138184916449452869671412741618610351
E = EllipticCurve(GF(q), (a,b))
p = E.order
G = E.gen()
d=bytes_to_long(FLAG)
pub = d * G
def sign(msg,d):
hsh=bytes_to_long(sha1(msg).digest())
while True:
k=randint(1,2**450); print(f"🔑 Random k: {k}")
r=int((k*G)[0])
if r== 0:
continue
s=inverse(k,p)*(hsh+r*d)
s=s%p
if s==0:
continue
break
return r,s
def verify(msg,r,s):
hsh = bytes_to_long(sha1(msg.encode()).digest())
u1=int((hsh*inverse(s,p))%p)
u2 = int((r*inverse(s,p))%p)
P=u1*G+u2*pub
if P == E(0):
return False
return r == int(P[0])%p
def banner():
print("="*50)
print("🔐 ECDSA Signing & Verification Tool 🔐")
print("="*50)
x, y = G.xy()
print(f"📍 Generator Point G:\n x = {x}\n y = {y}")
print("-" * 50)
px, py = pub.xy()
print(f"🔑 Public Key Point P = d * G:\n x = {px}\n y = {py}")
print("="*50)
def menu():
print("\n1. Sign a message")
print("2. Verify a signature")
print("3. Exit")
return input("> ")
def main():
banner()
while True:
choice = menu()
if choice == '1':
msg = input("Enter the message to sign: ").encode()
r, s = sign(msg, d)
print(f"✅ Signature:\n r = {r}\n s = {s}")
elif choice == '2':
msg = input("Enter the message: ")
r = int(input("Enter r: "))
s = int(input("Enter s: "))
if verify(msg, r, s):
print("✅ Signature is valid.")
else:
print("❌ Invalid signature.")
elif choice == '3':
print("👋 Bye!")
break
else:
print("❗ Invalid choice. Try again.")
if __name__ == "__main__":
main()
This was originally a sage script but I made it into a python one.
Analysis & Solver
First thing, we don't have params, but we get 2 points and we got the prime, so easily recoverable.
I also hardcoded the order and G cz it takes a while to find each run.
Essentially, the vuln is in the bias of the generation of the nonce, we can assume zero MSB because it,s generated between 1 and 2^450 and both prime and order are 512 bits long giving us 62 bits of 0 MSB, reducing it to HNP given enough signatures and large enough zero MSB.
Lattice Construction
We collect ( N ) ECDSA signatures on messages , and define:
( )
( )
( )
( )
From the ECDSA signature equation:
Assuming the nonces ( k_i ) are biased and thus small, we construct a lattice ( \mathcal{L} \in \mathbb{Z}^{(N+1) \times (N+1)} ) with basis matrix:
We define the target vector:
After applying the LLL algorithm to ( \mathbf{M} ), we obtain a short vector ( \mathbf{v} \in \mathcal{L} ) such that:
for some ( \mathbf{b} \in \mathbb{Z}^{N+1} ). The private key ( d ) is then recovered from:
Feedback
Tbh? amazing challenge, learned alot about HNP.
Last updated