55# https://www.youtube.com/watch?v=6ssTlSSIJQE
66
77import sys
8-
9- # import ecdsa
10- import random
8+ import argparse
9+ import mmap
1110from sage .all_cmdline import *
1211import 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
1817def 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
89108def 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
96115def 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
113160if __name__ == "__main__" :
114- main ()
161+ main ()
0 commit comments