Hey everyone,
I've been digging into that old vulnerability that pops up when you reuse the same R while signing with the same private key. I've got a bunch of examples that show this really does happen.
But I came across two specific transactions:
fae3e414425f008196f9127a01dcea59e22ab66768ce5bcb4aba260993494de1
ab1deb8544de4bb1d3319e67b1bfc354601406d4a00ecbe8cbdd7674f96e9699
Both of these transactions used the address 14tVK2JhEPsZEL7yYzMNXDYQ6dG3FnzzEY and they both had the same R value.
I figured out z1 and z2 the same way (using the unlock script instead of the signature and setting the irrelevant input scripts to a length of 0). Here are the values I ended up with:
r :9ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b
s1 : 2da94e7cb83e17d307d46c80df4f3315b17af13c4a04ef352495f1442562a290
s2 : 43273c2390b15bbe7e4d38559b1d4e6c0d63aad2c586652ec423d851df065271
z1 : 068fbde1dd7e06f4e88ae63a50f8ee07eff41c4b9586cbef1235b83281ab145d
z2 : 015b14bdc6f69058bfa8dcdc0e8bcd1fc87f4303804f200bfa6aadf627a8d5f6
But when I try to calculate the private key linked to 14tVK2JhEPsZEL7yYzMNXDYQ6dG3FnzzEY using the formula (doing inverse((r*(s2-s1)),p)), it doesn't give me the expected result. I'm puzzled as to why this case is acting different from others. I’m thinking maybe I messed up some math, but since it worked for a lot of other examples, I’m really confused. Anyone see anything off with my values or can suggest what I might be overlooking?
Extracting key from duplicate R values
7 replies 332 views
gr3g.0rbitHero Member
Posts: 1025 · Reputation: 2646
#2Nov 3, 2020, 08:47 PM
All seem good except the R value is missing its first byte 0x00.
Based from the signature, its length should be 33 bytes which includes the omitted 0x00 on its front.
By the way, I didn't computed the private key, I just pointed out the missing byte.
Thanks for pointing this out, I left it out here but it was present.
For the calculated key, these leading zeroes don't not seem to matter though. So it must be something else.
R: 009ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b
S1: 16c91427cb20a7321029c311e757dfee67f5e3f9bad23266fad8bf8aaf5cac01
Z1: e69364d551385880ddbb19338b633e2d6c17f1922817b3e3776851780b6aacb5
S2: 43273c2390b15bbe7e4d38559b1d4e6c0d63aad2c586652ec423d851df065271
Z2: 015b14bdc6f69058bfa8dcdc0e8bcd1fc87f4303804f200bfa6aadf627a8d5f6
PubKey: 036ee29b13e9d9f060d078bdcee464cb21aeafe5b5bc15206c5fa3c62f882c97c9
....aaaaaaand the result is "9674578d05e0bc65284cc4db99420957858e1f57505baebb1d0d3e0d25957b7c"
hint: try to use ( ORDER - S1 ) instead of S1
oh that's it indeed! Thanks.
So I understand now we take the inverse of S1 in this case, but is there a general rule as to when this happens? Is this when S goes beyond some boundary?
#Python3
from fastecdsa.curve import secp256k1
from fastecdsa.point import Point
def extended_gcd(aa, bb):
lastremainder, remainder = abs(aa), abs(bb)
x, lastx, y, lasty = 0, 1, 1, 0
while remainder:
lastremainder, (quotient, remainder) = remainder, divmod(lastremainder, remainder)
x, lastx = lastx - quotient*x, x
y, lasty = lasty - quotient*y, y
return lastremainder, lastx * (-1 if aa < 0 else 1), lasty * (-1 if bb < 0 else 1)
def modinv(a, m):
g, x, y = extended_gcd(a, m)
if g != 1:
raise ValueError
return x % m
def sqrt_mod(a, p):
if pow(a, (p - 1) // 2, p) != 1:
raise ValueError(f"No square root exists for {a} modulo {p}")
if p % 4 == 3:
return pow(a, (p + 1) // 4, p)
raise ValueError("Cannot find square root for this modulus with current method")
def pub2point(pub_hex):
x = int(pub_hex[2:66], 16)
if len(pub_hex) < 70:
prefix = int(pub_hex[:2], 16)
y_square = (x**3 + secp256k1.a * x + secp256k1.b) % secp256k1.p
y = sqrt_mod(y_square, secp256k1.p)
if (y % 2 == 0 and prefix == 3) or (y % 2 == 1 and prefix == 2):
y = secp256k1.p - y
else:
y = int(pub_hex[66:], 16)
return Point(x, y, curve=secp256k1)
Q = "034903acabebcd2185bd64afa44632af51813c4ef25d34b3310d0018271c73f122"
Q = pub2point(Q)
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
G = secp256k1.G
r = 0x9ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b
s2 = 0x2da94e7cb83e17d307d46c80df4f3315b17af13c4a04ef352495f1442562a290
z2 = 0x68fbde1dd7e06f4e88ae63a50f8ee07eff41c4b9586cbef1235b83281ab145d
s1 = 0x43273c2390b15bbe7e4d38559b1d4e6c0d63aad2c586652ec423d851df065271
z1 = 0x15b14bdc6f69058bfa8dcdc0e8bcd1fc87f4303804f200bfa6aadf627a8d5f6
try:
doubler1 = ((z2 * s1 - z1 * s2) * modinv(r * (s2 - s1), n)) % n
doubler2 = ((z2 * (-s1) - z1 * s2) * modinv(r * (s2 + s1), n)) % n
if Q == doubler1 * G:
print(f"Double R : ( X = 0x{hex(doubler1)[2:].zfill(64)} )")
else:
print(f"Double R : ( X = 0x{hex(doubler2)[2:].zfill(64)} )")
except ValueError as e:
print(f"An error occurred in modular inversion: {e}")
omega_bearFull Member
Posts: 116 · Reputation: 780
#8Nov 5, 2020, 05:37 PM
Hi
can you modyfy r,ang get r,s,z, for same pubkey ?
Related topics
- Extracting R values from blockchain signatures 14
- Bitcoin Core displaying coins (UTXOs) in a new tab 13
- Are you in favor of BIP-110? Let's get a Bitcoin poll going. 0
- Erlay seems to have some issues here’s a better proposal for a bitcoin protocol without invites 3
- New Optional Hourglass Implementation is Live 3
- Ways to earn some sats by contributing to bitcoin core development 5