Skip to content

Commit df483b1

Browse files
authored
Update crack_weak_ECDSA_nonces_with_LLL.py: optimize: add argparse, mmap, sparse matrix
1 parent 378a1bf commit df483b1

File tree

1 file changed

+103
-56
lines changed

1 file changed

+103
-56
lines changed

crack_weak_ECDSA_nonces_with_LLL.py

Lines changed: 103 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,110 +5,157 @@
55
# https://www.youtube.com/watch?v=6ssTlSSIJQE
66

77
import sys
8-
9-
# import ecdsa
10-
import random
8+
import argparse
9+
import mmap
1110
from sage.all_cmdline import *
1211
import gmpy2
1312

14-
# order is from secp256k1 curve it can be used any other.
15-
order = 115792089237316195423570985008687907852837564279074904382605163141518161494337
13+
# Default order from secp256k1 curve
14+
DEFAULT_ORDER = 115792089237316195423570985008687907852837564279074904382605163141518161494337
1615

1716

1817
def modular_inv(a, b):
18+
"""Efficient modular inverse"""
1919
return int(gmpy2.invert(a, b))
2020

2121

22-
def load_csv(filename, limit=None):
23-
msgs = []
24-
sigs = []
25-
pubs = []
26-
fp = open(filename)
27-
n = 0
28-
if limit is None:
29-
limit = -1
30-
for line in fp:
31-
if (limit == -1) or (n < limit):
32-
l = line.rstrip().split(",")
33-
tx, R, S, Z, pub = l
34-
msgs.append(int(Z, 16))
35-
sigs.append((int(R, 16), int(S, 16)))
36-
pubs.append(pub)
37-
n += 1
22+
def load_csv(filename, limit=None, mmap_flag=False):
23+
"""Load CSV with ECDSA data, optimized to handle file efficiently (with optional mmap)."""
24+
msgs, sigs, pubs = [], [], []
25+
26+
# Open the file with mmap if requested
27+
if mmap_flag:
28+
with open(filename, 'r') as f:
29+
# Memory map the file for efficient access
30+
mapped_file = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
31+
lines = mapped_file.splitlines()
32+
for n, line in enumerate(lines):
33+
if limit is not None and n >= limit:
34+
break
35+
l = line.decode('utf-8').rstrip().split(",")
36+
tx, R, S, Z, pub = l
37+
msgs.append(int(Z, 16))
38+
sigs.append((int(R, 16), int(S, 16)))
39+
pubs.append(pub)
40+
else:
41+
# Regular file reading without mmap
42+
with open(filename, 'r') as fp:
43+
for n, line in enumerate(fp):
44+
if limit is not None and n >= limit:
45+
break
46+
tx, R, S, Z, pub = line.rstrip().split(",")
47+
msgs.append(int(Z, 16))
48+
sigs.append((int(R, 16), int(S, 16)))
49+
pubs.append(pub)
50+
3851
return msgs, sigs, pubs
3952

4053

41-
def make_matrix(msgs, sigs, pubs, B):
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."""
4256
m = len(msgs)
43-
sys.stderr.write("Using: %d sigs...\n" % m)
44-
matrix = Matrix(QQ, m + 2, m + 2)
57+
sys.stderr.write(f"Using: {m} sigs...\n")
58+
59+
if matrix_type == "sparse":
60+
matrix = SparseMatrix(QQ, m + 2, m + 2)
61+
else:
62+
matrix = Matrix(QQ, m + 2, m + 2)
4563

46-
msgn, rn, sn = [msgs[-1], sigs[-1][0], sigs[-1][1]]
64+
msgn, rn, sn = msgs[-1], sigs[-1][0], sigs[-1][1]
4765
rnsn_inv = rn * modular_inv(sn, order)
4866
mnsn_inv = msgn * modular_inv(sn, order)
4967

50-
for i in range(0, m):
68+
# Fill diagonal with the order
69+
for i in range(m):
5170
matrix[i, i] = order
5271

53-
for i in range(0, m):
54-
x0 = (sigs[i][0] * modular_inv(sigs[i][1], order)) - rnsn_inv
55-
x1 = (msgs[i] * modular_inv(sigs[i][1], order)) - mnsn_inv
56-
matrix[m + 0, i] = x0
57-
matrix[m + 1, i] = x1
72+
# Set values for the matrix (only first m columns)
73+
for i in range(m):
74+
matrix[m, i] = (sigs[i][0] * modular_inv(sigs[i][1], order)) - rnsn_inv
75+
matrix[m + 1, i] = (msgs[i] * modular_inv(sigs[i][1], order)) - mnsn_inv
5876

59-
matrix[m + 0, i + 1] = int(2**B) / order
60-
matrix[m + 0, i + 2] = 0
61-
matrix[m + 1, i + 1] = 0
62-
matrix[m + 1, i + 2] = 2**B
77+
# Populate last two columns with specific values
78+
matrix[m, m + 1] = int(2**B) / order
79+
matrix[m + 1, m + 1] = 2**B
6380

6481
return matrix
6582

6683

67-
def privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix):
84+
def privkeys_from_reduced_matrix(msgs, sigs, pubs, matrix, order):
85+
"""Extract private keys from reduced matrix."""
6886
keys = []
69-
msgn, rn, sn = [msgs[-1], sigs[-1][0], sigs[-1][1]]
87+
msgn, rn, sn = msgs[-1], sigs[-1][0], sigs[-1][1]
88+
7089
for row in matrix:
7190
potential_nonce_diff = row[0]
72-
potential_priv_key = (
73-
(sn * msgs[0])
74-
- (sigs[0][1] * msgn)
75-
- (sigs[0][1] * sn * potential_nonce_diff)
76-
)
7791
try:
92+
potential_priv_key = (
93+
(sn * msgs[0])
94+
- (sigs[0][1] * msgn)
95+
- (sigs[0][1] * sn * potential_nonce_diff)
96+
)
7897
potential_priv_key *= modular_inv(
7998
(rn * sigs[0][1]) - (sigs[0][0] * sn), order
8099
)
81100
key = potential_priv_key % order
82101
if key not in keys:
83102
keys.append(key)
84103
except Exception as e:
85-
sys.stderr.write(str(e) + "\n")
104+
sys.stderr.write(f"Error extracting key: {str(e)}\n")
86105
return keys
87106

88107

89108
def display_keys(keys):
90-
for key in keys:
91-
sys.stdout.write("%064x\n" % key)
109+
"""Display private keys in hexadecimal format."""
110+
sys.stdout.write("\n".join([f"{key:064x}" for key in keys]) + "\n")
92111
sys.stdout.flush()
93112
sys.stderr.flush()
94113

95114

96115
def main():
97-
filename = sys.argv[1]
98-
B = int(sys.argv[2])
99-
limit = int(sys.argv[3])
100-
run_mode = "LLL"
101-
102-
msgs, sigs, pubs = load_csv(filename, limit=limit)
103-
matrix = make_matrix(msgs, sigs, pubs, B)
104-
105-
if run_mode == "LLL":
116+
"""Main function to load data, perform lattice reduction, and display keys."""
117+
parser = argparse.ArgumentParser(description="ECDSA private key recovery using lattice reduction")
118+
119+
# Command line arguments
120+
parser.add_argument("filename", help="CSV file containing the ECDSA messages and signatures")
121+
parser.add_argument("B", type=int, help="Parameter B for matrix construction")
122+
parser.add_argument("limit", type=int, help="Limit for number of records to process")
123+
parser.add_argument(
124+
"--matrix_type", choices=["dense", "sparse"], default="dense",
125+
help="Type of matrix to use: 'dense' or 'sparse' (default: dense)"
126+
)
127+
parser.add_argument(
128+
"--order", type=int, default=DEFAULT_ORDER,
129+
help="Order of the curve. Default is the secp256k1 order"
130+
)
131+
parser.add_argument(
132+
"--reduction", choices=["LLL", "BKZ"], default="LLL",
133+
help="Reduction algorithm: LLL (default) or BKZ"
134+
)
135+
parser.add_argument(
136+
"--mmap", action="store_true",
137+
help="Enable memory-mapping for the CSV file for faster processing"
138+
)
139+
140+
# Parse arguments
141+
args = parser.parse_args()
142+
143+
# Load messages, signatures, and public keys with optional mmap
144+
msgs, sigs, pubs = load_csv(args.filename, limit=args.limit, mmap_flag=args.mmap)
145+
146+
# Construct matrix for lattice reduction
147+
matrix = make_matrix(msgs, sigs, pubs, args.B, args.order, matrix_type=args.matrix_type)
148+
149+
# Perform LLL or BKZ reduction
150+
if args.reduction == "LLL":
106151
new_matrix = matrix.LLL(early_red=True, use_siegel=True)
107152
else:
108153
new_matrix = matrix.BKZ(early_red=True, use_siegel=True)
109-
keys = privkeys_from_reduced_matrix(msgs, sigs, pubs, new_matrix)
154+
155+
# Extract and display private keys
156+
keys = privkeys_from_reduced_matrix(msgs, sigs, pubs, new_matrix, args.order)
110157
display_keys(keys)
111158

112159

113160
if __name__ == "__main__":
114-
main()
161+
main()

0 commit comments

Comments
 (0)