Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
duyuefeng0708
GitHub Repository: duyuefeng0708/Cryptography-From-First-Principle
Path: blob/main/foundations/06-elliptic-curves/connect/ecdsa-bitcoin-ethereum.ipynb
483 views
unlisted
Kernel: SageMath 10.0

Connect: ECDSA in Bitcoin and Ethereum

Module 06 | Real-World Connections

Every Bitcoin and Ethereum transaction is authorized by an ECDSA signature on the secp256k1 curve. The key generation, signing, and verification you learned in Notebook 06f are exactly what happens when you send cryptocurrency.

Introduction

Bitcoin (2009) chose ECDSA on secp256k1 as its transaction authorization mechanism. Ethereum (2015) adopted the same curve with minor modifications. As of 2025, over $1 trillion in assets are secured by this exact combination of curve and signature scheme.

The flow is:

  1. Key generation: A private key dd (256 random bits) generates a public key Q=dGQ = dG.

  2. Address derivation: The public key is hashed to produce a Bitcoin/Ethereum address.

  3. Transaction signing: To spend coins, the owner signs the transaction hash with ECDSA.

  4. Verification: Every node in the network verifies the signature before accepting the transaction.

Let's trace each step.

The secp256k1 Curve

secp256k1 is defined by:

y2=x3+7over Fpy^2 = x^3 + 7 \quad \text{over } \mathbb{F}_p

where p=2256232977p = 2^{256} - 2^{32} - 977. This is a short Weierstrass curve with a=0,b=7a = 0, b = 7.

The curve has a generator GG of prime order n2256n \approx 2^{256}, with cofactor h=1h = 1 (every point other than O\mathcal{O} generates the full group).

Why secp256k1? Satoshi Nakamoto chose it over the more common P-256 (secp256r1). The a=0a = 0 coefficient makes point doubling slightly faster, and the curve parameters are "nothing up my sleeve" numbers (the simplest curve y2=x3+7y^2 = x^3 + 7 over that prime).

# === secp256k1 parameters === p_btc = 2^256 - 2^32 - 977 E_btc = EllipticCurve(GF(p_btc), [0, 7]) # Standard generator point Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 G_btc = E_btc(Gx, Gy) # Group order n_btc = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 print("secp256k1 (the Bitcoin/Ethereum curve)") print(f" Equation: y^2 = x^3 + 7") print(f" Field: F_p where p = 2^256 - 2^32 - 977") print(f" p = {p_btc}") print(f" p bits: {p_btc.nbits()}") print(f" Order n = {n_btc}") print(f" n bits: {n_btc.nbits()}") print(f" n is prime: {is_prime(n_btc)}") print(f" Cofactor h = 1 (all points are in the prime-order subgroup)") print(f" G on curve? {G_btc in E_btc}")

Step 1: Key Generation

A Bitcoin private key is simply a random integer d{1,,n1}d \in \{1, \ldots, n-1\}. The corresponding public key is Q=dGQ = dG.

In Bitcoin:

  • The private key is 32 bytes (256 bits), often encoded in WIF (Wallet Import Format).

  • The public key is the point QQ, encoded as 33 bytes (compressed: x-coordinate + parity bit) or 65 bytes (uncompressed: both coordinates).

  • The Bitcoin address is derived by hashing the public key: RIPEMD160(SHA256(Q)).

Let's demonstrate on a small curve (so the numbers fit on screen), then show the real secp256k1.

# === Toy example: key generation on a small curve === p_toy = 10007 E_toy = EllipticCurve(GF(p_toy), [0, 7]) # same form as secp256k1: y^2 = x^3 + 7 G_toy = E_toy.gens()[0] n_toy = G_toy.order() print(f"Toy curve: y^2 = x^3 + 7 over F_{p_toy} (same shape as secp256k1)") print(f"Generator G = {G_toy}") print(f"Order n = {n_toy}, is prime: {n_toy.is_prime()}") # Generate a key pair d_toy = 7331 # private key Q_toy = d_toy * G_toy # public key print(f"\n=== Key Generation ===") print(f"Private key: d = {d_toy}") print(f"Public key: Q = d * G = {Q_toy}") print(f"\nIn Bitcoin, the address would be derived from Q via hashing.") print(f"The private key d is what you keep secret in your wallet.")

Step 2: Transaction Signing

When you send Bitcoin, you create a transaction that says "transfer X BTC from address A to address B." To prove you own address A, you sign the transaction hash with your private key using ECDSA.

The signing process (from Notebook 06f):

  1. Hash the transaction: e=SHA256(SHA256(tx))e = \text{SHA256}(\text{SHA256}(\text{tx})) (Bitcoin uses double-SHA256)

  2. Pick random nonce kk, compute R=kGR = kG, r=xRmodnr = x_R \bmod n

  3. Compute s=k1(e+dr)modns = k^{-1}(e + d \cdot r) \bmod n

  4. Output signature (r,s)(r, s)

# === Toy ECDSA signing (simulating a Bitcoin transaction) === Zn_toy = Integers(n_toy) def ecdsa_sign(message, d, G, n): """ECDSA signing (simplified).""" Zn = Integers(n) e = Zn(hash(message) % n) while True: k = randint(1, n - 1) R = k * G r = Zn(R[0]) if r == 0: continue s = Zn(k)^(-1) * (e + Zn(d) * r) if s == 0: continue return (Integer(r), Integer(s), k) def ecdsa_verify(message, sig, Q, G, n, E): """ECDSA verification.""" r, s = sig[0], sig[1] Zn = Integers(n) if not (1 <= r < n and 1 <= s < n): return False e = Zn(hash(message) % n) w = Zn(s)^(-1) u1 = Integer(e * w) u2 = Integer(Zn(r) * w) R_prime = u1 * G + u2 * Q if R_prime == E(0): return False return Integer(R_prime[0]) % n == r # Simulate a Bitcoin transaction tx = "Send 0.5 BTC from Alice to Bob" sig = ecdsa_sign(tx, d_toy, G_toy, n_toy) r_sig, s_sig, k_used = sig print(f"Transaction: '{tx}'") print(f"\n=== Signing (Alice's wallet) ===") print(f" Hash e = H(tx) mod n = {Integer(Zn_toy(hash(tx)))}") print(f" Nonce k = {k_used} (random, secret, never reused!)") print(f" Signature: (r={r_sig}, s={s_sig})") # Verify valid = ecdsa_verify(tx, sig, Q_toy, G_toy, n_toy, E_toy) print(f"\n=== Verification (every Bitcoin node) ===") print(f" Valid? {valid}")
# === Tampered transactions are rejected === tx_tampered = "Send 500 BTC from Alice to Bob" # changed amount valid_tampered = ecdsa_verify(tx_tampered, sig, Q_toy, G_toy, n_toy, E_toy) tx_redirect = "Send 0.5 BTC from Alice to Eve" # changed recipient valid_redirect = ecdsa_verify(tx_redirect, sig, Q_toy, G_toy, n_toy, E_toy) print("=== Tamper Detection ===") print(f"Original tx valid? {ecdsa_verify(tx, sig, Q_toy, G_toy, n_toy, E_toy)}") print(f"Amount-changed tx valid? {valid_tampered}") print(f"Recipient-changed tx valid? {valid_redirect}") print(f"\nAny modification to the transaction invalidates the signature.") print(f"This is why Bitcoin is secure: you cannot alter a signed transaction.")

Ethereum Addition: Public Key Recovery

Ethereum uses the same curve (secp256k1) and the same ECDSA, but adds a feature: public key recovery from the signature.

An Ethereum signature includes a recovery parameter v{0,1}v \in \{0, 1\} (encoded as 27 or 28). This allows the verifier to recover the signer's public key from just the message and signature, without needing the public key in advance.

The recovery works as follows:

  1. From rr, compute the point R=(r,y)R = (r, y) where yy is chosen based on vv.

  2. Compute Q=r1(sReG)Q = r^{-1}(s \cdot R - e \cdot G).

  3. The Ethereum address is keccak256(Q)[12:] (last 20 bytes of the hash).

# === Public key recovery (Ethereum style) === # On our toy curve, demonstrate recovering Q from (r, s, v) def ecdsa_sign_with_recovery(message, d, G, n, E): """ECDSA signing that also outputs the recovery parameter v.""" Zn = Integers(n) e = Zn(hash(message) % n) while True: k = randint(1, n - 1) R = k * G r = Integer(R[0]) % n if r == 0: continue s = Integer(Zn(k)^(-1) * (e + Zn(d) * Zn(r))) if s == 0: continue # Recovery parameter: which of the two possible y-values was used v = Integer(R[1]) % 2 # 0 or 1 (parity of y) return (r, s, v, R) def recover_pubkey(message, r, s, v, G, n, E): """Recover the signer's public key from (r, s, v).""" Zn = Integers(n) F = E.base_field() e = Zn(hash(message) % n) # Recover R from r and v x_R = F(r) y_sq = x_R^3 + F(E.a4()) * x_R + F(E.a6()) y_R = y_sq.sqrt() if Integer(y_R) % 2 != v: y_R = -y_R R = E(Integer(x_R), Integer(y_R)) # Recover Q = r^(-1) * (s*R - e*G) r_inv = Zn(r)^(-1) Q_recovered = Integer(r_inv) * (Integer(Zn(s)) * R - Integer(e) * G) return Q_recovered # Sign with recovery parameter tx_eth = "Transfer 1 ETH to 0xBob" r_eth, s_eth, v_eth, R_eth = ecdsa_sign_with_recovery(tx_eth, d_toy, G_toy, n_toy, E_toy) print(f"Transaction: '{tx_eth}'") print(f"Signature: (r={r_eth}, s={s_eth}, v={v_eth})") # Recover the public key Q_recovered = recover_pubkey(tx_eth, r_eth, s_eth, v_eth, G_toy, n_toy, E_toy) print(f"\nRecovered public key: {Q_recovered}") print(f"Actual public key: {Q_toy}") print(f"Match? {Q_recovered == Q_toy}") print(f"\nEthereum uses this to derive the sender's address from the signature.") print(f"No need to transmit the public key separately!")
# === Full pipeline: key gen -> sign -> verify -> recover === print("=== Complete Bitcoin/Ethereum ECDSA Pipeline ===") print(f"\n1. KEY GENERATION") print(f" Private key d = {d_toy} (kept in wallet)") print(f" Public key Q = {Q_toy}") # Sign multiple "transactions" transactions = [ "Send 0.1 BTC to Alice", "Send 2.5 BTC to Charlie", "Send 0.01 BTC to Dave (fee)", ] print(f"\n2. SIGNING (wallet signs each transaction)") for tx in transactions: sig = ecdsa_sign(tx, d_toy, G_toy, n_toy) valid = ecdsa_verify(tx, sig, Q_toy, G_toy, n_toy, E_toy) print(f" '{tx}'") print(f" sig = (r={sig[0]}, s={sig[1]}) valid={valid}") print(f"\n3. VERIFICATION (every network node checks)") print(f" Each node independently verifies the ECDSA signature.") print(f" Invalid signatures are rejected; the transaction is dropped.") print(f"\n4. SECURITY") print(f" To forge a signature, attacker must solve ECDLP on secp256k1.") print(f" Best known attack: ~2^128 operations (128-bit security).") print(f" At 10^12 ops/sec: ~10^26 years. The universe is ~10^10 years old.")

Concept Map: Module 06 in Bitcoin and Ethereum

Module 06 ConceptBlockchain Application
Point multiplication dGdGPrivate key \to public key
ECDSA signingTransaction authorization
ECDSA verificationNetwork consensus on valid transactions
ECDLP hardnessCannot forge signatures or derive private key from address
Nonce kk security (Break notebook)PS3 hack, Android wallet thefts
secp256k1 (y2=x3+7y^2 = x^3 + 7)Industry-standard curve for blockchain
Public key recoveryEthereum derives sender address from signature

Summary

ConceptKey idea
secp256k1The curve y2=x3+7y^2 = x^3 + 7 over a 256-bit prime, chosen by Bitcoin and adopted by Ethereum.
Key generationScalar multiplication turns a private key dd into a public key Q=dGQ = dG.
Transaction signingECDSA produces a signature (r,s)(r, s) that proves ownership of the private key.
Network verificationEvery node independently verifies the signature, requiring only the public key.
Public key recoveryEthereum adds a recovery parameter vv, allowing the signer's public key to be derived from the signature alone.
ECDLP securityGiven Q=dGQ = dG, finding dd is computationally infeasible (roughly 21282^{128} operations on secp256k1).
Nonce reuse dangerThe nonce-reuse attack from the Break notebook has stolen real Bitcoin. Wallets must use RFC 6979 deterministic nonces.

The nonce-reuse attack from the Break notebook is not theoretical: it has stolen real Bitcoin. Wallet implementations must use RFC 6979 deterministic nonces or risk catastrophic key leakage.


Back to Module 06: Elliptic Curves