Skip to content

Commit 6147ecf

Browse files
committed
Update project
1 parent 4755094 commit 6147ecf

File tree

1 file changed

+42
-69
lines changed

1 file changed

+42
-69
lines changed
Lines changed: 42 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
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
63

74
import sys
85
import argparse
96
import mmap
107
import gmpy2
11-
import binascii
12-
from ecdsa import SigningKey, SECP256k1
138
from fpylll import IntegerMatrix, LLL, BKZ
9+
from ecdsa import SigningKey, SECP256k1
10+
1411

15-
# Default order from secp256k1 curve
1612
DEFAULT_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
1713

1814

1915
def modular_inv(a, b):
20-
"""Efficient modular inverse"""
2116
return int(gmpy2.invert(a, b))
2217

2318

2419
def load_csv(filename, limit=None, mmap_flag=False):
25-
"""Load CSV with ECDSA data."""
2620
msgs, sigs, pubs = [], [], []
27-
2821
if mmap_flag:
2922
with open(filename, 'r') as f:
3023
mapped_file = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
@@ -46,18 +39,13 @@ def load_csv(filename, limit=None, mmap_flag=False):
4639
msgs.append(int(Z, 16))
4740
sigs.append((int(R, 16), int(S, 16)))
4841
pubs.append(pub)
49-
5042
return msgs, sigs, pubs
5143

5244

53-
def make_matrix_fpylll(msgs, sigs, B, order):
54-
"""
55-
Construct IntegerMatrix for fpylll reduction.
56-
"""
45+
def make_matrix_fpylll(msgs, sigs, B, order, integer_mode=False):
5746
m = len(msgs)
5847
m1, m2 = m + 1, m + 2
5948
B2 = 1 << B
60-
6149
mat = IntegerMatrix(m2, m2)
6250

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

6856
for i in range(m):
6957
mi_sigi_order = modular_inv(sigs[i][1], order)
58+
delta_r = (sigs[i][0] * mi_sigi_order - rnsn_inv) % order
59+
delta_z = (msgs[i] * mi_sigi_order - mnsn_inv) % order
60+
7061
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)
62+
if integer_mode:
63+
mat[m, i] = int(order * delta_r)
64+
mat[m1, i] = int(order * delta_z)
65+
else:
66+
mat[m, i] = int(delta_r)
67+
mat[m1, i] = int(delta_z)
7368

74-
mat[m, m1] = B2 // order
69+
if integer_mode:
70+
mat[m, m1] = B2
71+
else:
72+
mat[m, m1] = int(B2 // order)
7573
mat[m1, m1] = B2
7674

7775
return mat
@@ -87,10 +85,6 @@ def reduce_matrix(matrix, algorithm="LLL"):
8785

8886

8987
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
9488
keys = set()
9589
m = len(msgs)
9690
msgn, rn, sn = msgs[-1], sigs[-1][0], sigs[-1][1]
@@ -102,10 +96,7 @@ def privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, order, max_rows=20):
10296
c = sn * msgs[i]
10397
d = msgn * sigs[i][1]
10498
cd = (c - d) % order
105-
if a == b:
106-
ab_list = None
107-
else:
108-
ab_list = [(a - b) % order, (b - a) % order]
99+
ab_list = None if a == b else [(a - b) % order, (b - a) % order]
109100
params.append((b, cd, ab_list))
110101

111102
row_norms = []
@@ -124,70 +115,52 @@ def privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, order, max_rows=20):
124115
for ab in ab_list:
125116
if ab:
126117
inv = modular_inv(ab, order)
127-
key = (base * inv) % order
128-
keys.add(key)
118+
keys.add((base * inv) % order)
129119
return list(keys)
130120

131121

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

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

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

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}")
162144

163145
def main():
164146
parser = argparse.ArgumentParser(description="ECDSA private key recovery using lattice reduction (fpylll)")
165147
parser.add_argument("filename", help="CSV file containing ECDSA traces")
166148
parser.add_argument("B", type=int, help="log2 bound parameter B")
167149
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-
150+
parser.add_argument("--order", type=int, default=DEFAULT_ORDER, help="Curve order (default: secp256k1)")
151+
parser.add_argument("--reduction", choices=["LLL", "BKZ"], default="LLL", help="Lattice reduction algorithm")
152+
parser.add_argument("--mmap", action="store_true", help="Enable mmap for fast CSV access")
153+
parser.add_argument("--integer_mode", action="store_true", help="Scale matrix to ensure integer values")
181154
args = parser.parse_args()
182155

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

186-
matrix = make_matrix_fpylll(msgs, sigs, args.B, args.order)
159+
matrix = make_matrix_fpylll(msgs, sigs, args.B, args.order, integer_mode=args.integer_mode)
187160
matrix = reduce_matrix(matrix, algorithm=args.reduction)
188161
keys = privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, args.order)
189-
display_keys(keys)
162+
display_keys(keys, pubs)
190163

191164

192165
if __name__ == "__main__":
193-
main()
166+
main()

0 commit comments

Comments
 (0)