Exercise 2: CTF#
Containing 3 Challenges
Requirements#
Prog. Lang.:
Python 3
Python libraries:
blockcypher
binascii
mnemonic
bip-utils
hashlib
ecdsa
Tools:
A web browser
IDE that supports Jupyter Notebooks (e.g. VSCode)
https://live.blockcypher.com (block explorer)
https://iancoleman.io/bip39/ (mnemonic code converter)
CTF 1. Unlocking a bitcoin transaction — 100#
We are given the Bitcoin testnet transaction hash: 23c9470a2269cf6430569afdd3e2e8f35f633b6cdbc34107ddbab932c4146ba3
Goal: We want to know how to spent this tBTC output.
Hint: The unlocking script (scriptSig) can be find in 8 Byte of HEX and can be converted to Cleartext ASCII.
Hints:
Find the SCRIPTPUBKEY that locks the output value
Understand how the input must look like, to unlock this locking script.
Find a way and search for hints to unlock the transaction with the (scriptSig)
"""
Helpful functions for the challenge.
# Alternative https://www.rapidtables.com/convert/number/hex-to-ascii.html
"""
import binascii
hex_string = "68656C6C6F776F726C64"
decoded_string = binascii.unhexlify(hex_string).decode('utf-8')
print(decoded_string)
helloworld
CTF 2. Dark Net Money — 200#
You are given the following mnemonic phrase:
play fever bullet unlock error palm insect pottery tower torch memory liquid
Your task is to find how much tBTC this wallet holds unspent.
Hint: The correct derivation path follows the classic BIP44 scheme. You need to find the receiving address of the first account at a specific index.
Use the mnemonic phrase on a well-known BIP39 tool.
This could be helpful:
You need to select Bitcoin Testnet as the coin type.
The derivation path is m/44’/1’/0’/0/137. Documentation.
For the developers:
from mnemonic import Mnemonic
from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins, Bip44Changes
Alternatively for the tool users:
CTF 3. ECDSA Nonce Reuse - 200#
In this challenge, you are given two ECDSA signatures that have reused the same nonce. Your task is to recover the private key used to generate these signatures. This is a classic cryptographic vulnerability that occurs when the same nonce is used in multiple ECDSA signatures.
r: 0x5d66e837a35ddc34be6fb126a3ec37153ff4767ff63cbfbbb32c04a795680491
s1: 0x1a53499a4aafb33d59ed9a4c5fcc92c5850dcb23d208de40a909357f6fa2c12c
message1: "what up defcon"
s2: 0xd67006bc8b7375e236e11154d576eed0fc8539c3bba566f696e9a5340bb92bee
message2: "uh oh this isn't good"
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
s1 and s2 are the signature parameters.
h1 and h2 are the message digests.
r is the same nonce for both signatures due to nonce reuse.
n is the order of the elliptic curve group.
Task:
Use the Python script for performing a ECDSA Nonce Reuse and recover the private key using the provided information and hints.
Once the private key is recovered, we as a attacker can use it to sign new transactions, effectively allowing them to transfer Bitcoin from the compromised wallet to our own addresses. This can lead to unauthorized spending of the victim’s Bitcoin.
Hint:
\( d_A = \left( \frac{s_2 \cdot h_1 - s_1 \cdot h_2}{r \cdot (s_1 - s_2)} \right) \mod n \). With this equation an attacker can obtain two different ECDSA signatures that reused the same nonce, they can use this equation to recover the private key \( d_A \). This allows the attacker to potentially forge signatures or decrypt messages that were intended to be secure.
The original equation is:
To express the denominator of the fraction as an inverse modulo equation, we need to find the modular inverse of \( (s_1 - s_2) \pmod{n} \). The modular inverse of \( (s_1 - s_2) \) is a value \( k \) such that:
This can be written as:
So, the original equation can be rewritten using the modular inverse as:
Where:
is the modular inverse of \( ( r \cdot (s_1 - s_2) ) \pmod{n} \).
from hashlib import sha256
from ecdsa import SECP256k1, SigningKey
from ecdsa.util import sigdecode_string
from ecdsa.numbertheory import inverse_mod
def hash_message(message):
"""Hash the message using SHA-256."""
message = 0
assert len("{0:b}".format(message)) == 255, f"Expected binary length of 255, but got {len("{0:b}".format(message))}" # Helpful assertion
return None
def recover_private_key(h1, h2, s1, s2, r1, r2, n):
"""Recover the private key via nonce reuse.
Recover the private key from two different signatures
that use the same random nonce `k` during signature
generation. Note that if the same `k` is used in two
signatures, this implies that the secp256k1 32-byte
signature parameter `r` is identical.
Parameters
----------
h1: int
The 32-byte message digest of the message `m1`.
h2: int
The 32-byte message digest of the message `m2`.
s1: int
The secp256k1 32-byte signature parameter `s1`.
s2: int
The secp256k1 32-byte signature parameter `s2`.
r1: int
The secp256k1 32-byte signature parameter `r1`.
r2: int
The secp256k1 32-byte signature parameter `r2`.
n: int
The 32-byte integer order of G (part of the public key).
Returns
-------
pk: int
Return a 32-byte private key.
"""
assert r1 == r2, "No ECDSA nonce reuse detected."
return None
if __name__ == "__main__":
# Provided values
r = 0x5d66e837a35ddc34be6fb126a3ec37153ff4767ff63cbfbbb32c04a795680491
signature1 = 0x1a53499a4aafb33d59ed9a4c5fcc92c5850dcb23d208de40a909357f6fa2c12c
signature2 = 0xd67006bc8b7375e236e11154d576eed0fc8539c3bba566f696e9a5340bb92bee
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
# Messages
message1 = "what up defcon"
message2 = "uh oh this isn't good"
# Step 1: Hash the messages
z1 = hash_message(message1)
z2 = hash_message(message2)
# Step 2: Solve for the private key
recovered_private_key = recover_private_key(z1, z2, signature1, signature2, r, r, n)
print(f"Recovered private key: {recovered_private_key}")
Cell In[2], line 10
assert len("{0:b}".format(message)) == 255, f"Expected binary length of 255, but got {len("{0:b}".format(message))}" # Helpful assertion
^
SyntaxError: f-string: unmatched '('