Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Part IV · Surviving the long run No new concepts here. Just one demonstration, and it is the most persuasive in the book.

In chapter 2 you hopped around a curve mod 97. In chapter 3 you signed on a curve mod 23 with twenty-nine points.

Those curves were small enough to work through by hand — and small enough that you personally brute-forced a private key out of one.

This chapter swaps them for the curve Bitcoin actually uses: secp256k1.

And you will find that almost none of your code has to change.


13.1 The real parameters

The curve equation is still the familiar shape:

y2=x3+7(modp)y^2 = x^3 + 7 \pmod p

Only p, G and n are enormous.

how big is p: 115792089237316195423570985008687907853269984665640564039457584007908834671663
bit length: 256
possible private keys ~ 2^256 ~ 115792089237316195423570985008687907853269984665640564039457584007913129639936

Be clear what those five lines are: five integers.

  • Chapter 2: p = 97, G = (0, 10)

  • This chapter: p is a 78-digit decimal number, G a pair of 78-digit coordinates

Structurally identical. The numbers are simply larger. In fact a, b = 0, 7 is simpler than chapter 2’s 2, 3 — secp256k1’s a is zero, so the equation does not even have an ax term.

The shape of p is worth one glance: FFFF...FFFE FFFFFC2F. It is nearly 2²⁵⁶, just barely short. That was chosen deliberately — primes of this form let the modular reduction be implemented with bit tricks.

2²⁵⁶ possible private keys — roughly the number of atoms in the observable universe (~10⁸⁰).

The crack() you wrote in chapter 2 is still perfectly correct against this curve. It simply never finishes.


13.2 Point addition: not one character changed

is the generator G on the real curve: True
is G + G on it as well              : True

Put this next to section 2.2 — it is the same code.

Not one character differs. Tangent slope, chord slope, inv(), % p: all carried over intact.

Mathematics does not care how large the numbers are. That is the first thing worth pausing on: everything you learned on a 97-position curve holds without modification on a curve with 2²⁵⁶ points. No patches required.

(That pow(x, m-2, m) inside inv still works too — Fermat’s little theorem is size-agnostic, and Python’s pow is fast modular exponentiation, so a 256-bit exponent costs a few hundred steps.)


13.3 The one thing that must change

Chapter 2’s scalar_mul looked like this:

def scalar_mul(k, G):
    R = None
    for _ in range(k):      # <- loop k times
        R = point_add(R, G)
    return R

On the real curve k can be as large as 2²⁵⁶. That for loop would still be running after the sun burns out.

So the algorithm has to change: double-and-add.

The idea: to compute k·G, don’t add one at a time. Double repeatedly, and add selectively according to the binary digits.

private key: 0x1234567890abcdef1234567890abcdef
public key x: 0x9377c312145a5afb911bf9e8c067bcf6094c533603687850df502b61290bbf5e
           y: 0x742ba607d6ae1fc82dcccb176e7c8f9bdc7bb61e3ab527cecaf6144b679779fb
is the public key on the curve: True

Double-and-add is just long multiplication in binary.

To compute 13 · G, and 13 in binary is 1101:

13 · G = 8G + 4G + 1G
       = (G doubled three times) + (G doubled twice) + G

So you keep doubling the addend (G → 2G → 4G → 8G) and add it into the result wherever the binary digit is 1.

A 256-bit k needs at most 256 doublings. From 2²⁵⁶ steps down to 256.

This is the other face of chapter 2’s complexity intuition:

StepsTime
Forwards (private → public)double-and-add, ~256instant
Backwards (public → private)no shortcut, 2²⁵⁶never

That asymmetry is where the security of every bitcoin in existence lives.

And note carefully: the asymmetry is not a theorem. It is “nobody has found a shortcut yet.” If someone ever did — a sufficiently large quantum computer, say (appendix B) — the asymmetry disappears.

A deep sanity check

does n * G give the point at infinity (None)?: True

Prints True.

That single line is a consistency check on the entire parameter set. In chapter 2 you computed 50G = None on a fifty-point curve. Here n·G = None, where n is a 78-digit number.

Same property, same line of code, a scale difference of about 2²⁵⁰.


13.4 Signing and verifying: still chapter 3

a signature on the real curve:
  r = 0xa5969d43528f2eaff287f9e892cad49948c547e8024c4925cd3c3b19524c22f2
  s = 0x276cf789dffe223227be004dede85f09d0cc0454a331e6618d28fe93262f746d

verify           :
 True
tampered amount  : False

Compare these two functions with chapter 3’s: not one character has changed.

z, w, u1, u2, that verification equation — all of it is what you derived by hand in chapter 3.

It now runs on the curve Bitcoin actually uses and produces a genuine 256-bit signature.

Change one character (1 → 1000) and it fails. Everything from chapter 3 holds at full scale.

This is what the chapter exists to show you:

What you hand-wrote in chapter 3 is not “a toy that resembles Bitcoin.” It is Bitcoin’s signature algorithm. The only difference is the size of the parameters.

(And verify still contains no priv. That fact holds identically at scale 29 and at scale 2²⁵⁶.)


13.5 Check against a real library

If you have the ecdsa library installed:

our hand-written public key: 0x9377c312145a5afb911bf9 ...
the library's public key   : 0x9377c312145a5afb911bf9 ...
identical? True

A few dozen lines of your Python and an audited cryptographic library produce the same public key.


13.6 Please do not use this

This chapter can give you a dangerous kind of confidence, so let me be direct:

This code is mathematically correct and not safe to use. The gap is here:

YoursA real implementation
Where k comes frompassed in by handRFC 6979 deterministic derivation (section 3.6)
Execution timevaries with the bits of the keyconstant time, to resist side channels
Memorythe key is copied all over the Python heapwiped immediately after use
Input validationessentially nonestrict: point on curve, not infinity, etc.

The second row is the killer.

Look at that line in scalar_mul: if k & 1: R = point_add(R, addend). If a bit of the private key is 1, an extra addition happens. If it is 0, it does not.

So the running time of this function leaks how many 1 bits your private key has. More refined attacks — measuring power draw, cache timing, electromagnetic emissions — can read the key out one bit at a time. This is a side-channel attack, one of the most common ways real keys leak in the field, and it has nothing to do with mathematics.

The real libsecp256k1 defends against this by ensuring that the exact same instruction sequence executes regardless of the key. That code is hundreds of times more complex than what you just wrote, and most of the complexity is not about computing correctly. It is about computing indistinguishably.

This is the third time the same lesson has appeared — after section 3.6 (reusing k) and section 11.6 (CVE-2012-2459): correct mathematics is not a safe system.

Chapter 18 brings it a fourth time.


13.7 The ledger

What you wroteField
secp256k1’s real parametersCryptography
point_add() — unchangedCryptography (chapter 2’s)
scalar_mul() — double-and-addAlgorithmic optimization
sign / verify — unchangedCryptography (chapter 3’s)
Side channels, constant timeEngineering safety

This is the second and last time in eighteen chapters that the ledger tips left.

Part IV ends here. Your chain now survives the long run and runs on real cryptographic parameters.

An interim total

Through thirteen chapters:

PartChaptersField
I · Bricks1–3Cryptography
II · The ancestors4–5Systems
III · The house6–9Systems + game theory + economics + social coordination
IV · Long run10, 12Economics + control theory + statistics
IV · Long run11, 13Cryptography

Five chapters of cryptography, eight of everything else. And those eight solved the problem nobody solved for thirty years.


Next

Part V begins: toward real engineering.

Thirteen chapters have produced a self-consistent machine. The next five connect it to the real world:

  • Ch 14: a spending condition is not a database field — it is a running program

  • Ch 15: how twelve words grow a tree of addresses

  • Ch 16: why the private key should live in a room with no network cable

  • Ch 17: running your chain as an actual network across several machines

  • Ch 18: why Bitcoin’s scripting language is deliberately feeble

Chapter 14 overturns an assumption you have been carrying since chapter 6: that “who can spend this” equals “who holds the private key.”

In real Bitcoin, that is only the simplest case.