How Does Nonce Bias Really Leak in ECDSA? If You Found It — Please Share Code or Insights!
Hi everyone,
I'm currently learning about ECDSA nonce bias attacks, and I’d love help understanding how even small weaknesses (1–4 bit biases) in the nonce k can leak private key information. I've seen claims of private key recovery using as little as 1-bit bias — but never seen full working code or data.
🔍 What I’m Trying to Understand:
How does a biased bit in k show up in the signature values (r, s, z)?
Can you detect this leakage just by looking at bit-level patterns in multiple signatures?
How many biased bits + how many signatures = enough to break d?
🧪 My Analysis Method (With Code Below)
Here’s how I currently try to analyze for bias:
Collect multiple valid ECDSA signatures → extract r, s, z per sig.
Convert each value to 256-bit binary.
Count how often each bit is 1 across all samples.
✅Code reference?
✅ Can you share Code & data or results?
Report any bit position with strong deviation from randomness (ideal = 0.5 probability of 1).
import hashlib
from ecdsa import SigningKey, SECP256k1
from statistics import mean
from math import sqrt
curve = SECP256k1
n = curve.order
# -- ECDSA Sign with fixed nonce (for research/testing only!) --
def sign(privkey_int, nonce_int, message_bytes):
G = curve.generator
d = privkey_int
k = nonce_int
# Hash the message
h = hashlib.sha256(message_bytes).digest()
z = int.from_bytes(h, byteorder='big') % n # ensure z < n
# Calculate R = k*G
R = k * G
r = R.x() % n
if r == 0:
raise ValueError("Invalid r = 0")
# s = k⁻¹ (z + r*d) mod n
k_inv = pow(k, -1, n)
s = (k_inv * (z + r * d)) % n
if s == 0:
raise ValueError("Invalid s = 0")
return r, s, z
# -- Bit-level bias analysis on z --
def analyze_z_bias(z_list, bit_length=256):
print("[+] Bit-Level Bias in Z (MSB first):")
for bit in reversed(range(bit_length)):
ones = sum((z >> bit) & 1 for z in z_list)
ratio = ones / len(z_list)
line = f"Bit {bit:3d}: 1s = {ratio:.3f}"
if ratio in (0.0, 1.0) or abs(ratio - 0.5) > 0.05:
line += " <-- biased"
print(line)
# -- Correlation calculation --
def pearson_corr(x_vals, y_vals):
xm = mean(x_vals)
ym = mean(y_vals)
num = sum((x - xm) * (y - ym) for x, y in zip(x_vals, y_vals))
den = sqrt(sum((x - xm)**2 for x in x_vals) * sum((y - ym)**2 for y in y_vals))
return num / den if den else 0.0
# -- Demo: Generate signatures and analyze bias --
def run_demo():
privkey = 0xdeadbeef123456789abcdef0feedfacecafebabe123456789abcdef0123456789
r_list = []
s_list = []
z_list = []
for i in range(1000):
msg = f"message {i}".encode()
nonce = i + 1 # use incrementing nonces (DANGEROUS in real use!)
try:
r, s, z = sign(privkey, nonce, msg)
r_list.append(r)
s_list.append(s)
z_list.append(z)
except Exception as e:
print(f"Skipping failed signature at i={i}: {e}")
analyze_z_bias(z_list)
print("\n[+] Correlation Analysis:")
print(f"Corr(R,Z): {pearson_corr(r_list, z_list):.4f}")
print(f"Corr(S,Z): {pearson_corr(s_list, z_list):.4f}")
# Run everything
if __name__ == "__main__":
run_demo()
#output
python bias.py
- Bit-Level Bias in Z (MSB first):
Bit 255: 1s = 0.519
Bit 254: 1s = 0.514
Bit 253: 1s = 0.519
Bit 252: 1s = 0.488
Bit 251: 1s = 0.510
Bit 250: 1s = 0.506
Bit 249: 1s = 0.485
Bit 248: 1s = 0.515
Bit 247: 1s = 0.487
Bit 246: 1s = 0.490
Bit 245: 1s = 0.494
Bit 244: 1s = 0.512
Bit 243: 1s = 0.505
Bit 242: 1s = 0.479
Bit 241: 1s = 0.515
Bit 240: 1s = 0.512
Bit 239: 1s = 0.521
Bit 238: 1s = 0.518
Bit 237: 1s = 0.499
Bit 236: 1s = 0.513
Bit 235: 1s = 0.520
Bit 234: 1s = 0.511
Bit 233: 1s = 0.504
Bit 232: 1s = 0.495
Bit 231: 1s = 0.476
Bit 230: 1s = 0.544
Bit 229: 1s = 0.499
Bit 228: 1s = 0.503
Bit 227: 1s = 0.491
Bit 226: 1s = 0.510
Bit 225: 1s = 0.524
Bit 224: 1s = 0.479
Bit 223: 1s = 0.502
Bit 222: 1s = 0.509
Bit 221: 1s = 0.523
Bit 220: 1s = 0.510
Bit 219: 1s = 0.526
Bit 218: 1s = 0.508
Bit 217: 1s = 0.490
Bit 216: 1s = 0.514
Bit 215: 1s = 0.514
Bit 214: 1s = 0.491
Bit 213: 1s = 0.501
Bit 212: 1s = 0.490
Bit 211: 1s = 0.502
Bit 210: 1s = 0.501
Bit 209: 1s = 0.509
Bit 208: 1s = 0.509
Bit 207: 1s = 0.500
Bit 206: 1s = 0.495
Bit 205: 1s = 0.510
Bit 204: 1s = 0.495
Bit 203: 1s = 0.498
Bit 202: 1s = 0.490
Bit 201: 1s = 0.501
Bit 200: 1s = 0.510
Bit 199: 1s = 0.484
Bit 198: 1s = 0.498
Bit 197: 1s = 0.533
Bit 196: 1s = 0.494
Bit 195: 1s = 0.518
Bit 194: 1s = 0.508
Bit 193: 1s = 0.528
Bit 192: 1s = 0.494
Bit 191: 1s = 0.492
Bit 190: 1s = 0.468
Bit 189: 1s = 0.478
Bit 188: 1s = 0.517
Bit 187: 1s = 0.519
Bit 186: 1s = 0.519
Bit 185: 1s = 0.475
Bit 184: 1s = 0.522
Bit 183: 1s = 0.500
Bit 182: 1s = 0.531
Bit 181: 1s = 0.522
Bit 180: 1s = 0.470
Bit 179: 1s = 0.506
Bit 178: 1s = 0.500
Bit 177: 1s = 0.511
Bit 176: 1s = 0.512
Bit 175: 1s = 0.491
Bit 174: 1s = 0.497
Bit 173: 1s = 0.477
Bit 172: 1s = 0.486
Bit 171: 1s = 0.516
Bit 170: 1s = 0.492
Bit 169: 1s = 0.474
Bit 168: 1s = 0.501
Bit 167: 1s = 0.475
Bit 166: 1s = 0.513
Bit 165: 1s = 0.469
Bit 164: 1s = 0.522
Bit 163: 1s = 0.515
Bit 162: 1s = 0.486
Bit 161: 1s = 0.486
Bit 160: 1s = 0.518
Bit 159: 1s = 0.479
Bit 158: 1s = 0.522
Bit 157: 1s = 0.476
Bit 156: 1s = 0.511
Bit 155: 1s = 0.505
Bit 154: 1s = 0.500
Bit 153: 1s = 0.512
Bit 152: 1s = 0.502
Bit 151: 1s = 0.497
Bit 150: 1s = 0.495
Bit 149: 1s = 0.503
Bit 148: 1s = 0.476
Bit 147: 1s = 0.481
Bit 146: 1s = 0.495
Bit 145: 1s = 0.501
Bit 144: 1s = 0.526
Bit 143: 1s = 0.474
Bit 142: 1s = 0.476
Bit 141: 1s = 0.507
Bit 140: 1s = 0.486
Bit 139: 1s = 0.529
Bit 138: 1s = 0.512
Bit 137: 1s = 0.495
Bit 136: 1s = 0.472
Bit 135: 1s = 0.508
Bit 134: 1s = 0.506
Bit 133: 1s = 0.473
Bit 132: 1s = 0.467
Bit 131: 1s = 0.498
Bit 130: 1s = 0.508
Bit 129: 1s = 0.482
Bit 128: 1s = 0.471
Bit 127: 1s = 0.511
Bit 126: 1s = 0.508
Bit 125: 1s = 0.497
Bit 124: 1s = 0.532
Bit 123: 1s = 0.488
Bit 122: 1s = 0.509
Bit 121: 1s = 0.516
Bit 120: 1s = 0.497
Bit 119: 1s = 0.481
Bit 118: 1s = 0.502
Bit 117: 1s = 0.480
Bit 116: 1s = 0.520
Bit 115: 1s = 0.494
Bit 114: 1s = 0.510
Bit 113: 1s = 0.503
Bit 112: 1s = 0.503
Bit 111: 1s = 0.518
Bit 110: 1s = 0.511
Bit 109: 1s = 0.523
Bit 108: 1s = 0.497
Bit 107: 1s = 0.498
Bit 106: 1s = 0.506
Bit 105: 1s = 0.504
Bit 104: 1s = 0.478
Bit 103: 1s = 0.502
Bit 102: 1s = 0.505
Bit 101: 1s = 0.504
Bit 100: 1s = 0.469
Bit 99: 1s = 0.496
Bit 98: 1s = 0.513
Bit 97: 1s = 0.497
Bit 96: 1s = 0.510
Bit 95: 1s = 0.472
Bit 94: 1s = 0.498
Bit 93: 1s = 0.509
Bit 92: 1s = 0.478
Bit 91: 1s = 0.467
Bit 90: 1s = 0.514
Bit 89: 1s = 0.480
Bit 88: 1s = 0.497
Bit 87: 1s = 0.520
Bit 86: 1s = 0.497
Bit 85: 1s = 0.516
Bit 84: 1s = 0.484
Bit 83: 1s = 0.502
Bit 82: 1s = 0.467
Bit 81: 1s = 0.513
Bit 80: 1s = 0.494
Bit 79: 1s = 0.511
Bit 78: 1s = 0.512
Bit 77: 1s = 0.478
Bit 76: 1s = 0.485
Bit 75: 1s = 0.513
Bit 74: 1s = 0.500
Bit 73: 1s = 0.513
Bit 72: 1s = 0.496
Bit 71: 1s = 0.505
Bit 70: 1s = 0.496
Bit 69: 1s = 0.523
Bit 68: 1s = 0.512
Bit 67: 1s = 0.494
Bit 66: 1s = 0.489
Bit 65: 1s = 0.493
Bit 64: 1s = 0.510
Bit 63: 1s = 0.516
Bit 62: 1s = 0.513
Bit 61: 1s = 0.507
Bit 60: 1s = 0.495
Bit 59: 1s = 0.511
Bit 58: 1s = 0.500
Bit 57: 1s = 0.509
Bit 56: 1s = 0.488
Bit 55: 1s = 0.512
Bit 54: 1s = 0.500
Bit 53: 1s = 0.522
Bit 52: 1s = 0.512
Bit 51: 1s = 0.466
Bit 50: 1s = 0.489
Bit 49: 1s = 0.503
Bit 48: 1s = 0.497
Bit 47: 1s = 0.521
Bit 46: 1s = 0.513
Bit 45: 1s = 0.476
Bit 44: 1s = 0.519
Bit 43: 1s = 0.488
Bit 42: 1s = 0.492
Bit 41: 1s = 0.506
Bit 40: 1s = 0.538
Bit 39: 1s = 0.530
Bit 38: 1s = 0.501
Bit 37: 1s = 0.506
Bit 36: 1s = 0.528
Bit 35: 1s = 0.498
Bit 34: 1s = 0.503
Bit 33: 1s = 0.503
Bit 32: 1s = 0.500
Bit 31: 1s = 0.509
Bit 30: 1s = 0.501
Bit 29: 1s = 0.497
Bit 28: 1s = 0.486
Bit 27: 1s = 0.464
Bit 26: 1s = 0.525
Bit 25: 1s = 0.498
Bit 24: 1s = 0.511
Bit 23: 1s = 0.507
Bit 22: 1s = 0.493
Bit 21: 1s = 0.498
Bit 20: 1s = 0.470
Bit 19: 1s = 0.495
Bit 18: 1s = 0.502
Bit 17: 1s = 0.494
Bit 16: 1s = 0.510
Bit 15: 1s = 0.492
Bit 14: 1s = 0.490
Bit 13: 1s = 0.493
Bit 12: 1s = 0.500
Bit 11: 1s = 0.518
Bit 10: 1s = 0.490
Bit 9: 1s = 0.475
Bit 8: 1s = 0.505
Bit 7: 1s = 0.518
Bit 6: 1s = 0.511
Bit 5: 1s = 0.492
Bit 4: 1s = 0.498
Bit 3: 1s = 0.484
Bit 2: 1s = 0.531
Bit 1: 1s = 0.473
Bit 0: 1s = 0.495
Corr(R,Z): 0.0000
Corr(S,Z): -0.0000