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 I · Bricks: the cryptographic floor By the end of this chapter you will have a function that derives a public key from a private key, and a second function that cracks the private key back out. You get to hold both the sword and the shield, and that is the point.

Chapter 1’s hash is purely one-way: nobody gets back, ever. What you build here is different. It has a back door, and the back door is called a private key.


2.0 Nail down the usage before touching the math

Forget the mathematics for a moment. First be clear about what a key pair is for.

A piggy bank only you can open. There is a slot in the top — anyone can drop coins in. There is a lock on the bottom, and you hold the only key. You build the bank, put it out in public, and anyone can pay into it. Only you can open it and take the money out.

Piggy bankCryptographyThe point
The open slotPublic key / addressOut in the open; anyone can pay in
The key underneathPrivate keyOnly you have it; never shown
Someone paying inReceiving a paymentRequires no key at all
You opening itSpending with the private keyOnly you can

This is literally how Bitcoin works. A Bitcoin address is a piggy bank: you make one, publish the address, people pay into it, and when you want to spend you open it with your key. What Bitcoin does all day long is make banks to receive, and open them with keys to spend.

One key, unlimited banks

Here is where the piggy bank beats “a lock” as an image, and it happens to be Bitcoin’s actual mechanism:

The same key can make a whole pile of banks that look nothing like each other, and it opens every one of them.

A real Bitcoin wallet derives thousands of addresses from one seed, all controlled by one master key. “One key, many banks” is not a metaphor — it is the literal design. You will grow that tree by hand in chapter 15.

And because you can make unlimited banks, you can use a fresh one for every payment. That is not fussiness. Here is why:

PropertyWhat it meansWhat it protects
① You can’t derive the keyAnyone who finds one of your banks cannot work backwards to your keyNobody can take your money
② You can’t link themA hundred banks made by one key look unrelated to outsidersNobody can see how much you have

These are two different guarantees, and confusing them is a common mistake. Your key can be perfectly safe (①) while you destroy your own privacy by receiving everything into one address, because ② has nothing to do with ①. Hence: use each address once.

A hook for later: banks are mutually anonymous by default — but the moment you pour several of them into one new bank, you have publicly announced “these were all mine.” That is precisely the problem change addresses and CoinJoin have to deal with.

The opening condition is programmable

The best thing about the piggy bank picture: “who can open it, and under what conditions” is not fixed. A Bitcoin address is really a locking condition that says what must be satisfied before the coins move:

  • An ordinary bank = “whoever holds this key” (single signature)

  • A safe needing two people to turn keys together = multisig

  • A bank that won’t open until a certain date = timelock

  • A bank that opens for the right password = hashlock (the foundation of Lightning)

So an address is not a fixed lock. It is a spending-condition contract. In chapter 14 you will write the tiny virtual machine that makes that sentence executable.


2.1 The thing a piggy bank cannot explain

Real locks and real keys are manufactured together. Neither one “produces” the other. Actual key pairs have a direction:

  • private key → public key: easy, one computation

  • public key → private key: effectively impossible

And be careful about which way is which: your key can produce unlimited banks (private → address, forward is trivial); but nobody who finds a bank can cut the key (address → private, backward is hopeless).

“Forward is trivial, backward is hopeless” is not something a piggy bank can express. We need a different picture.

A world with a boundary

Don’t look at a curve yet. First picture a clock with exactly 97 positions: numbers don’t run off to infinity, they wrap. Reach 97 and you’re back at 0.

Addition, subtraction and multiplication are easy — do the arithmetic, take the remainder. Division is the interesting one.

There are no fractions in this world, so “6 ÷ 2” cannot mean “cut in half.” It can only mean: find x such that 2 × x = 6. Follow that thought and you get a new object:

The inverse of a is the number satisfying a × inv(a) = 1. With it, division becomes multiplication: a ÷ b = a × inv(b).

Use a smaller clock (mod 7) to see it clearly:

=== arithmetic in the world of mod 7 ===

ADDITION:
  5 + 3 = 8 (ordinary)
  5 + 3 = 1 (mod 7) -- 8 wraps past 7, leaving 1
  5 + 4 = 2 (mod 7) -- 9 wraps past 7, leaving 2

SUBTRACTION:
  2 - 5 = -3 (ordinary)
  2 - 5 = 4 (mod 7) -- negatives wrap around too

MULTIPLICATION:
  3 x 4 = 5 (mod 7) -- 12 wraps past 7, leaving 5
  5 x 6 = 2 (mod 7) -- 30 wraps past 7, leaving 2

=== which numbers multiply together to give 1 ===

  1 x 1 = 1 (mod 7)  ->  inv(1) = 1
  2 x 4 = 1 (mod 7)  ->  inv(2) = 4
  3 x 5 = 1 (mod 7)  ->  inv(3) = 5
  4 x 2 = 1 (mod 7)  ->  inv(4) = 2
  5 x 3 = 1 (mod 7)  ->  inv(5) = 3
  6 x 6 = 1 (mod 7)  ->  inv(6) = 6

Stare at that last loop’s output:

1 x 1 = 1 (mod 7)  ->  inv(1) = 1
2 x 4 = 1 (mod 7)  ->  inv(2) = 4
3 x 5 = 1 (mod 7)  ->  inv(3) = 5
4 x 2 = 1 (mod 7)  ->  inv(4) = 2
5 x 3 = 1 (mod 7)  ->  inv(5) = 3
6 x 6 = 1 (mod 7)  ->  inv(6) = 6

Every number from 1 to 6 has exactly one inverse. Not one is missing.

That is not luck — it happens because 7 is prime. (Try 6 instead and you’ll find 2, 3 and 4 have no inverse at all. That is why the modulus in cryptography is always prime.)

Inverses in code

=== inverses mod 7 ===

inv(1) =  1  ->  1 x 1 = 1 (mod 7)
inv(2) =  4  ->  2 x 4 = 1 (mod 7)
inv(3) =  5  ->  3 x 5 = 1 (mod 7)
inv(4) =  2  ->  4 x 2 = 1 (mod 7)
inv(5) =  3  ->  5 x 3 = 1 (mod 7)
inv(6) =  6  ->  6 x 6 = 1 (mod 7)

=== division via inverse ===

6 / 2 (mod 7) = 6 x inv(2) = 6 x 4 = 3

=== now the prime we actually use, p = 97 ===

a=1    inv(a)=1    a*inv(a) % 97 = 1
a=2    inv(a)=49   a*inv(a) % 97 = 1
a=5    inv(a)=39   a*inv(a) % 97 = 1
a=40   inv(a)=17   a*inv(a) % 97 = 1
a=96   inv(a)=96   a*inv(a) % 97 = 1

inv_brute and inv compute the same thing — one loops, one is a single expression. Keep that contrast in mind, because this chapter is about to make the same point at a much larger scale: the same problem, trivial in one direction, hopeless in the other.

The last block prints 1 every time. Whether a is 2 or 96, a × inv(a) % 97 is 1. That is the definition, made concrete.

YOUR TURN

Question 1 is worth thinking through: inv(0) would need a number that times 0 gives 1, but anything times 0 is 0, so it does not exist. In code, pow(0, 95, 97) quietly returns 0 instead of raising — that is a trap, and real cryptographic libraries check for it explicitly.


2.2 A curve you can hop around

Now spread that 97-position clock over a curve:

y2=x3+2x+3(mod97)y^2 = x^3 + 2x + 3 \pmod{97}

Don’t worry about where that equation came from. You only need one fact: two points on this curve can be “added” to produce a third.

How the addition is defined

This “addition” has nothing to do with adding coordinates. It is a geometric convention:

  • P ≠ Q: draw the line through P and Q. It usually hits the curve a third time. Reflect that point vertically, and you have P + Q.

  • P == Q: you can’t draw a line through two points when there’s only one. Use the tangent at P instead; where it meets the curve again, reflect, and you have 2P.

  • Why reflect? Convention. Defined this way, these points behave like ordinary numbers under addition — stable, associative, with an identity.

As slopes, it’s two lines of algebra:

  • P ≠ Q: chord slope m = (y2 - y1) / (x2 - x1)

  • P == Q: tangent slope m = (3·x1² + a) / (2·y1)

But a finite field has no ordinary division. So every / something in the code becomes * inv(something) % p. That is what the previous section was for — the inverse is not a math exercise, it is the part that makes this code run.

One more thing is needed: a special point, the point at infinity, written None. It is the zero of this addition: P + None = P.

Is G on the curve?: True
G + G = (65, 32)
Is G+G on the curve?: True

Be clear about what point_add actually is: an ordinary function that eats two tuples and returns a tuple.

point_add((0, 10), (0, 10))   ->   (65, 32)

No objects, no state, no magic. “Elliptic curve” in code is that function, plus is_on_curve to check you haven’t wandered off.

The output gives G + G = (65, 32), and it is still on the curve. Pause on that: take any two points on the curve, apply the rules above, and the result is guaranteed to be on the curve too. That closure is the precondition for everything else — otherwise you’d fall off after a few hops.


2.3 A private key is a hop count; a public key is where you land

Now the definition at the heart of this chapter, and it is simpler than you expect:

Private key = the number of hops, k. Public key = the point you land on after hopping k times from G.

Two sentences. That is all.

private key = 7
public key  = (10, 76)

Your private key is the integer 7. Your public key is the tuple (10, 76). That concrete.

In real Bitcoin the private key is a 256-bit integer (64 hex characters) and the public key is a pair of 256-bit coordinates. Apart from the size, the structure is identical.

 k  ->  public key (landing point)
 1  ->  (0, 10)
 2  ->  (65, 32)
 3  ->  (23, 24)
 4  ->  (52, 68)
 5  ->  (88, 56)
 6  ->  (95, 66)
 7  ->  (10, 76)
 8  ->  (84, 37)
 9  ->  (11, 80)
10  ->  (80, 10)
11  ->  (17, 87)
12  ->  (74, 20)
13  ->  (87, 70)
14  ->  (46, 72)
15  ->  (53, 24)
16  ->  (92, 81)
17  ->  (1, 43)
18  ->  (21, 73)
19  ->  (85, 26)
20  ->  (3, 91)
21  ->  (47, 79)
22  ->  (32, 7)
23  ->  (49, 34)
24  ->  (24, 2)
25  ->  (30, 0)
26  ->  (24, 95)
27  ->  (49, 63)
28  ->  (32, 90)
29  ->  (47, 18)
30  ->  (3, 6)
31  ->  (85, 71)
32  ->  (21, 24)
33  ->  (1, 54)
34  ->  (92, 16)
35  ->  (53, 73)
36  ->  (46, 25)
37  ->  (87, 27)
38  ->  (74, 77)
39  ->  (17, 10)
40  ->  (80, 87)
41  ->  (11, 17)
42  ->  (84, 60)
43  ->  (10, 21)
44  ->  (95, 31)
45  ->  (88, 41)
46  ->  (52, 29)
47  ->  (23, 73)
48  ->  (65, 65)
49  ->  (0, 87)

Look down the x-coordinates:

0, 65, 23, 52, 88, 95, 10, 84, 11, 80, ...

No pattern whatsoever. Hop 1 lands at 0, hop 2 jumps to 65, hop 3 doubles back to 23, hop 4 leaps to 52. Not increasing, not decreasing, not cycling through anything you can see.

That column of scrambled numbers is the entire source of security in this chapter. Remember what it looks like.

YOUR TURN

Question 1 prints None. Fifty hops returns you to zero: the points reachable from G form a closed loop of size 50 — 49 visible landing points plus the None. That 50 is called the order of G.

(A number worth getting right: the whole curve has 100 points, while only 50 of them are reachable from G. Those are different quantities. In real Bitcoin they happen to be equal, but that is a property deliberately selected for, not a general rule.)

Question 2 prints True, and it matters more than it looks: 3 hops + 4 hops = 7 hops. These points add up the way integers do. All of signature mathematics rests on that.


2.4 Crack your own private key

Everything above went forwards. Now go backwards.

Forwards (private → public): one function call. Backwards (public → private): no shortcut. Start at 1 and try every k until you land on the target.

published public key: (47, 18)
cracked private key : 29
correct?            : True

It works. crack finds 29 instantly.

This is one of the few moments in this book where you personally break a cryptographic primitive. Take a good look, because that same code is completely harmless against real Bitcoin.

Why the identical code never finishes on the real curve

This toy curve has 50 reachable positions. crack tries at most 50.

Real Bitcoin’s curve (you’ll swap it in during chapter 13) has about 2²⁵⁶ ≈ 10⁷⁷.

That number means nothing to human intuition, so here are two anchors:

  • The observable universe contains roughly 10⁸⁰ atoms

  • Suppose you had a machine doing a trillion tries per second, and a billion of those machines, running since the Big Bang. You’d be somewhere around 10⁴⁰ — not a rounding error away from the target, but a rounding error of a rounding error.

The for loop does not change by a single character. Scale it from 50 to 2²⁵⁶ and it goes from “instant” to “never.”

That is the real conclusion of this chapter: security does not come from the algorithm being clever. It comes from scale. crack remains perfectly correct on the real curve. It simply never terminates.

YOUR TURN

can it be cracked?: 48

Question 3 finds 48, because max_k=1000 dwarfs this curve’s 50. That is exactly the problem with a toy curve — the attacker’s budget is always sufficient.


2.5 Two kinds of one-way, and they are not the same

You now hold two things that are “easy forwards, hard backwards”: chapter 1’s hash, and this chapter’s key pair. They look like the same idea. They are not.

Key pair (hopping)Hash (blender)
Easy forwards, hard back?YesYes
Is there a back door?Yes — the private key (the other half of the pair walks it backwards)No — nobody gets back
Name for itTrapdoor one-wayPure one-way

In one line: both are easy-forwards-hard-backwards, but a key pair keeps a back door and a hash has none at all.

This is not taxonomy for its own sake. It determines what each can do:

  • No back door → chapter 1’s commitment must reveal, and the secret is burned on use

  • A back door → chapter 3’s signature never reveals, and the private key never appears

The back door is the value. A private key is not “a stronger hash.” It is a categorically different object: it lets you prove you hold a secret, repeatedly, without ever exposing it.

(If the multiplication analogy suits you better: 61 × 53 = 3233 is easy, factoring 3233 back into 61 and 53 is hard. Same class of trapdoor. Bitcoin uses the hopping version, but multiplication is perfectly good for intuition.)

The same pair works in reverse

One more common confusion, cleared up. A key pair has two uses, in opposite directions:

Who actsWhich keyPurpose
EncryptionSomeone → youPublic locks → private opensSecrecy (only you can read it)
SigningYou → everyonePrivate signs → public verifiesProof (confirming it’s you)

Bitcoin barely uses the encryption direction at all. On-chain transactions are public — anyone can see how much moved and to whom. Bitcoin protects “only I can spend my money,” not “nobody can read this.” So what it uses every single day is signatures.

(Strictly, a signature is not “encrypting with the private key” — it is separate mathematics, which you’ll write out yourself next chapter. But “lock forwards, stamp backwards” is a good enough picture. This book never takes the encryption road; see appendix A if you’re curious.)


2.6 The ledger

What you wroteField
inv() — modular inverseMathematics
point_add() / scalar_mul() — hopping on a curveMathematics
“Private key = hop count, public key = landing point”Cryptography
crack() — brute forceThe attacker’s view (and the definition of security itself)

Still mostly cryptography, but look at that last row: you wrote an attacker.

From here on the book keeps that habit. Every time you build something, you immediately ask how to break it. Chapter 6 builds a double-spend, chapter 7 a 51% attack, chapter 12 a reorg.

Three ideas to carry:

  1. Modular arithmetic — a world with a boundary. Division doesn’t exist; only multiplication by an inverse.

  2. Translating math into code — the / in a formula becomes * inv(...) % p in the editor. Mathematics isn’t mystical; it is code that hasn’t been typed yet.

  3. Complexity is security — the same loop, and scale decides between “instant” and “never.” This is one of the two or three most important sentences in the book.


Next

Signatures.

You have a hash (chapter 1) and a key pair (this chapter). Next chapter bolts them together into real ECDSA — the signature family Bitcoin actually uses, running on a smaller curve.

Then you will do one specific thing: count how many times priv appears inside the body of verify.

The answer is zero, and everything that follows in this book is built on that zero.