n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 # order of base point G
r = 0xd47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad1
s1 = 0x78c9d47ef31caf0102f9ae2489d7c78ab51692ddd898b6eb20b16a0d25b01c78
z1 = 0x4435b0704795962ac9efe71b841a5366434f552d8b5beca04a48426c15fd9ad7
s2 = 0x240bcda3967d66c71c92ffc4c4486d99968183f198c5fe1612a5cc99a05ba99a
z2 = 0x6b8bb3201a7ce4c7ed72eddc46d9b6d7350bc2eb8c28df9763518de8d66b0b52
def modinv(x, n=n): return pow(x, n-2, n) # modular multiplicative inverse when(requires that n is prime)
k = (z1 - z2) * modinv(s1 - s2) % n ; print('k = {:x}'.format(k))
print('privkey = {:x}'.format( (s1 * k - z1) * modinv(r) % n )) # these two should
print('privkey = {:x}'.format( (s2 * k - z2) * modinv(r) % n )) # be the same
Updated code
Here's a more complete (but also more difficult to read) version which which (a) displays different possibilities to compensate for negated s values (as noted by David Grayson in this answer), and (b) verifies the private key against the signature-derived public keys if you have pycoin installed.
# order of base point G of secp256k1
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
# modular multiplicative inverse (requires that n is prime)
def modinv(x, n=n):
return pow(x, n-2, n)
# the two k candidates which aren't just negations of themselves
def k_candidates(s1, z1, s2, z2, n=n):
z1_z2 = z1 - z2
yield z1_z2 * modinv(s1 - s2, n) % n
yield z1_z2 * modinv(s1 + s2, n) % n
# generates two tuples, each with (privkey, k_possibility_1, k_possibility_2)
def privkey_k_candidates(r, s1, z1, s2, z2, n=n):
modinv_r = modinv(r, n)
for k in k_candidates(s1, z1, s2, z2, n):
yield (s1 * k - z1) * modinv_r % n, k, -k % n
r = 0xd47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad1
s1 = 0x78c9d47ef31caf0102f9ae2489d7c78ab51692ddd898b6eb20b16a0d25b01c78
z1 = 0x4435b0704795962ac9efe71b841a5366434f552d8b5beca04a48426c15fd9ad7
s2 = 0x240bcda3967d66c71c92ffc4c4486d99968183f198c5fe1612a5cc99a05ba99a
z2 = 0x6b8bb3201a7ce4c7ed72eddc46d9b6d7350bc2eb8c28df9763518de8d66b0b52
try:
from pycoin.ecdsa import *
pubkeys = possible_public_pairs_for_signature(generator_secp256k1, z1, (r, s1))
for privkey, k1, k2 in privkey_k_candidates(r, s1, z1, s2, z2):
if public_pair_for_secret_exponent(generator_secp256k1, privkey) in pubkeys:
print('k = {:x}'.format(k1))
print('or k = {:x}'.format(k2))
print('privkey = {:x}'.format(privkey))
break
else:
print('privkey not found')
except ImportError:
for privkey, k1, k2 in privkey_k_candidates(r, s1, z1, s2, z2):
print('possible k = {:x}' .format(k1))
print('possible k = {:x}' .format(k2))
print('possible privkey = {:x}\n'.format(privkey))