I realize that you can't recreate the attack by using the R,S,Z(H) on blockchain.com. I am not sure why. I believe for different software, it can hash the signatures over and over and over again. So technically, we will never know whats the correct R,S,Z(H) Signatures.
The Only way is to create the R,S,Z signatures. Which i did by inputting my Public Keys X,Y Coordinates in the full code below.
Input
import argparse
import base64
import functools
import json
import hashlib
import os
import tarfile
import urllib.request
# Original data set for traces get from :
RESOURCE_URL = (
"https://github.com/orangecertcc/ecdummyrpa/raw/"
"main/sample.tar.gz"
)
# Helpers from ecdsa_lib
def sha2(raw_message):
# SHA-2 256
return hashlib.sha256(raw_message).digest()
def sha2_int(data):
return int.from_bytes(sha2(data), "big")
# Special helpers for this case
def sigDER_to_ints(sigDER):
lenr = int(sigDER[3])
lens = int(sigDER[5 + lenr])
r = int.from_bytes(sigDER[4 : lenr + 4], "big")
s = int.from_bytes(sigDER[lenr + 6 : lenr + 6 + lens], "big")
return r, s
def pubkeyPEM_to_X962(PEMstring):
PEMparts = PEMstring.split("-----")
pubkey_b64 = PEMparts[2].strip("\r\n")
# end of DER is X962 public key
return base64.b64decode(pubkey_b64)[-65:]
def pubkeyX962_to_intpair(DERpubk):
x_int = int.from_bytes(DERpubk[1:33], "big")
y_int = int.from_bytes(DERpubk[33:], "big")
return [x_int, y_int]
def pubkeyPEM_to_xy(PEMstr):
return pubkeyX962_to_intpair(pubkeyPEM_to_X962(PEMstr))
def load_traces():
# Reads traces from this RPA campain
# Prepare to an almost compliant with LatticeAttack
# But it requires then filtering to compute "kp" from "trace"
files = os.listdir("test")
nsig = len(files) // 3
print(f"{len(files)} files detected for {nsig} signatures")
traces = []
for i in range(nsig):
with open(f"test/trace_{i}.txt", "r") as tracef:
data_trace = [float(line) for line in tracef]
with open(f"test/signature_{i}.bin", "rb") as sigf:
DERsig = sigf.read()
with open(f"test/message_{i}.txt", "rb") as msgf:
msg = msgf.read()
trace_data = {}
sig_ints = sigDER_to_ints(DERsig)
trace_data["hash"] = sha2_int(msg)
trace_data["r"] = sig_ints[0]
trace_data["s"] = sig_ints[1]
trace_data["trace"] = data_trace
traces.append(trace_data)
return traces
KNOWN_BITS = 7
def mean_compute(table_array):
# compute arithmetic mean value to get the height of the valley
return functools.reduce(lambda i, j: i + j, table_array) / len(table_array)
def select_sig(sig_candidate):
# Filtering the good signatures
# mean value < limit
# "A valley considerably lower than the others indicating a nonce that has
# its 7 least significant bits set to 0."
LIMIT = 20
DISCARD_SIZE = 0.25 # Discard first and last 25% = keeps "half" middle
trace_len = len(sig_candidate["trace"])
start_idx = int(trace_len * DISCARD_SIZE)
trace_interest = sig_candidate["trace"][start_idx : trace_len - start_idx]
val = mean_compute(trace_interest)
return val < LIMIT
def compute_kp(onesig):
# Generate final data objects (with kp)
sigout = {}
sigout["hash"] = onesig["hash"]
sigout["r"] = onesig["r"]
sigout["s"] = onesig["s"]
sigout["kp"] = [""]
return sigout
def get_data_source(res_url):
# Get tar gz file at given url and extract files locally
# Use this only on known trusted or friendly TAR files,
# as this can write files anywhere locally
with urllib.request.urlopen(
urllib.request.Request(res_url, headers={"User-Agent": "Mozilla"})
) as remote_data:
tardata = tarfile.open(fileobj=remote_data, mode="r:gz")
print("Extracting data files ...")
tardata.extractall()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Load ecdummyRPA traces mesurements for ECDSA attack file format."
)
parser.add_argument(
"-f",
default="data.json",
help="File name output",
metavar="fileout",
)
arg = parser.parse_args()
# Test if data were downloaded by testing presence of pubkey file
if not os.path.exists("pubkey.pem"):
print("Downloading raw data ...")
get_data_source(RESOURCE_URL)
print("Loading files ...")
sigs_data = load_traces()
print("Filtering signatures traces")
sigs_data_selected = [compute_kp(asig) for asig in sigs_data if select_sig(asig)]
with open("pubkey.pem", "r") as pkf:
pubkey_pem = pkf.read()
global_data = {
"curve": "SECP256k1",
"public_key": (Input X here, Input Y here),
"known_type": "LSB",
"known_bits": KNOWN_BITS,
"signatures": sigs_data_selected,
}
with open(arg.f, "w") as fout:
json.dump(global_data, fout)
print(f"File {arg.f} written with all data.")
Output
"curve": "SECP256K1",
"public_key": [
31504125288796341338541169388783846543997786027594142627385926708036691251730,
29015715595623874326232564738946807912877814040423899127791236573353650594580
],
"known_type": "LSB",
"known_bits": 6,
"signatures": [
{
"r": 17456122099107622875979177060034160065534440309384765110770021588156777535269,
"s": 39548918176628970790297874101648966881380966278908886743977542233652364916621,
"kp": 20,
"hash": 92211084921678517086309436176248290676365250150878786017110534405419087808719
},
{
"r": 23913541315081963661847822603754285037803706978735367208728912448433920129361,
"s": 92518508463905780686593084152628940334651401106826544410053244574796065086889,
"kp": 13,
"hash": 21236183650018659484557387187850143636235316156689640733937135357581028327853
},
Massive Credit to Bitlogik for the gen_input.py on
https://github.com/bitlogik/lattice-attack then with the R,S,Z(H) signatures given, i input the signatures in the code below. Originally coded by William J. Buchanan
And article written here:
https://asecuritysite.com/ecc/ecd7Modified by me to allow public keys and RSZ inputs.
Input
import ecdsa
import random
import libnum
import hashlib
import sys
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
G = ecdsa.SECP256k1.generator
order = G.order()
priv1 = random.randrange(1,order)
Public_key = (31504125288796341338541169388783846543997786027594142627385926708036691251730,
29015715595623874326232564738946807912877814040423899127791236573353650594580)
k = random.randrange(1, 2**127)
msg1="HelloHello"
msg2="IamBB"
if (len(sys.argv)>1):
msg=(sys.argv[1])
# Input R, S Signatures here
r = 17456122099107622875979177060034160065534440309384765110770021588156777535269
s = 39548918176628970790297874101648966881380966278908886743977542233652364916621
h = int(hashlib.sha256(msg1.encode()).hexdigest(),base=16)
# Now generate a fault
rf = r
sf = (libnum.invmod(k,order)*(h+priv1*rf)) % order
hf = int(hashlib.sha256(msg2.encode()).hexdigest(),base=16)
kf = hf*(s-sf) * libnum.invmod(sf*r-s*rf,order)
valinv = libnum.invmod( (sf*r-s*rf),order)
dx = (hf*(s-sf)* valinv) % order
print(f"Sig 1 (Good):")
print(f" R :{r}")
print(f" S1:{s}")
print(f" H1:{h}")
print(f" K :{k}")
print(f" ------------------------------------------------")
print(f" ------------------------------------------------")
print(f" ------------------------------------------------")
print(f"Sig 2 (Faulty):")
print(f" R :{rf}")
print(f" S2:{sf}")
print(f" H2:{hf}")
print(f" K :{kf}")
print (f"\nRecovered private key: {dx}")
Output
Sig 1 (Good):
R :17456122099107622875979177060034160065534440309384765110770021588156777535269
S1:39548918176628970790297874101648966881380966278908886743977542233652364916621
H1:11209404430005450692776394377220775389388011163944676048947869460159787075727
K :15903292315272842822984172996837488417
------------------------------------------------
------------------------------------------------
------------------------------------------------
Sig 2 (Faulty):
R :17456122099107622875979177060034160065534440309384765110770021588156777535269
S2:96338585688289334914636720032979620932082282355556627366484199230071141146598
H2:54726123683982229645080045215760981633879898681032674670062989804976143680540
K :-188432705557505607344590839989163044641633590292582796389468919499880245866943402758495487463160167555366829032436838039969514252512664280259846882814057850528865220835685847937360676866739560844105311285127539338911992423544765060
Recovered private key: 107749115139875514357274396597987236665757310837906895959705889341744968705665
I took one of the RSZ generated and create the same R signature to give out the K value(Which does not matter cause it was randomly generated) and i got the correct Private Key.
However, i want to create a different R signature and now try out another attack. (Same nonce K use to sign different message)
What should i change at the R here? or What is the correct formula in python?
# Now generate a fault
rf = r
sf = (libnum.invmod(k,order)*(h+priv1*rf)) % order
hf = int(hashlib.sha256(msg2.encode()).hexdigest(),base=16)
kf = hf*(s-sf) * libnum.invmod(sf*r-s*rf,order)
valinv = libnum.invmod( (sf*r-s*rf),order)
dx = (hf*(s-sf)* valinv) % order