Life of a payment#

Follow one payment, Alice paying Bob 25 units, from a private key to a block buried under others, through a fork that undoes it and the reinclusion that restores it. Each stage uses a different subpackage, and each stage answers a different question: who authorized it, who knows about it, which history holds it, and how sure we are.

All snippets use import blockchainkit as bk. The keys and nonces are teaching fixtures; never use fixed values like these for real money.

1. Keys and addresses#

Alice’s private key is a number; her public key is a point on secp256k1; her address is the hash of the public key (Hashes everywhere).

>>> alice_key, bob_key = 7, 11
>>> alice_public = bk.crypto.public_key(alice_key)
>>> alice = bk.structures.address(alice_public)
>>> bob = bk.structures.address(bk.crypto.public_key(bob_key))
>>> len(alice), alice == bk.crypto.sha256(bk.crypto.encode_point(alice_public)).hex()
(64, True)

Who can spend? Whoever knows alice_key. Nothing else identifies Alice.

2. Authorization: a signed transaction#

The transaction names the sender’s public key, the recipient, the amount, the sender’s account nonce and the chain ID, and is signed with a Schnorr signature over all of them (Nonces everywhere).

>>> payment = bk.structures.Transaction(alice_public, bob, 25, 0)
>>> payment.is_valid()
False
>>> payment = payment.signed(alice_key, signing_nonce=17)
>>> payment.is_valid()
True
>>> from dataclasses import replace
>>> replace(payment, amount=2500).is_valid()  # Any change breaks the signature.
False

A valid signature says Alice authorized this payment. It does not say she can afford it, or that she has not signed a conflicting one.

3. Validation against a state#

Each node keeps a ledger of balances and account nonces. Applying a payment returns a new ledger and leaves the old one untouched; a payment that overspends or reuses a nonce is rejected.

>>> genesis_state = bk.structures.Ledger({alice: 100})
>>> after = genesis_state.apply([payment])
>>> after.balances[bob], after.balances[alice], genesis_state.balances.get(bob, 0)
(25, 75, 0)
>>> greedy = bk.structures.Transaction(alice_public, bob, 101, 0).signed(alice_key, signing_nonce=19)
>>> try:
...     genesis_state.apply([greedy])
... except ValueError:
...     print("rejected: insufficient funds")
rejected: insufficient funds

4. Propagation: gossip#

Alice’s node broadcasts the payment, and peers forward it to their neighbors. For a while, peers disagree about what is pending.

>>> net = bk.network.SimulatedNetwork(["alice", "relay", "miner"], seed=1)
>>> net.connect("alice", "relay", latency=(1, 3))
>>> net.connect("relay", "miner", latency=(1, 3))
>>> net.broadcast("alice", payment.txid)
>>> _ = net.run(until=1)
>>> sorted(d.recipient for d in net.deliveries)
['alice', 'relay']
>>> _ = net.run()
>>> sorted(d.recipient for d in net.deliveries)
['alice', 'miner', 'relay']

See Discrete-event simulation: a network on an event queue (GPSS 1961, Simula 1965).

5. Inclusion: a mined block#

A miner puts the payment in a block, whose header commits to the transactions through a Merkle root, and searches for a nonce that makes the header’s hash small enough.

>>> genesis = bk.consensus.mine(bk.structures.Block(difficulty=4)).block
>>> chain = bk.structures.Blockchain(genesis, genesis_state)
>>> block = bk.consensus.mine(bk.structures.Block(genesis.hash, (payment,), 1, 1, 4)).block
>>> bk.consensus.valid_pow(block), chain.add(block)
(True, True)
>>> chain.state.balances[bob]
25

A light client that holds only the header can check inclusion with a Merkle proof:

>>> proof = bk.structures.MerkleTree([payment.to_bytes()]).proof(0)
>>> bk.structures.verify_proof(payment.to_bytes(), proof, block.merkle_root)
True

6. Confirmation: how sure?#

Each block mined on top makes reversal less likely. With an attacker holding 10% of the hash power, Nakamoto’s calculation gives:

>>> [round(bk.consensus.attacker_success_probability(0.1, z), 5) for z in (1, 3, 6)]
[0.20459, 0.01317, 0.00024]

This is why merchants wait for confirmations. There is no point at which a proof-of-work payment becomes impossible to reverse, only ever less likely (compare Casper’s finality in Who do you trust?).

7. A fork undoes it#

Meanwhile, another miner who never saw the payment built a longer branch. Every node switches to the branch with the most work, and the payment’s effect disappears.

>>> rival = bk.consensus.mine(bk.structures.Block(genesis.hash, (), 1, 2, 4)).block
>>> rival_2 = bk.consensus.mine(bk.structures.Block(rival.hash, (), 2, 3, 4)).block
>>> chain.add(rival), chain.add(rival_2)
(True, True)
>>> chain.tip == rival_2, chain.state.balances.get(bob, 0)
(True, 0)
>>> len(chain.tips())  # Both branches are kept; only one is selected.
2

The signature is still valid, and on the new branch Alice has not used account nonce 0, so the payment is eligible again: back to the pending queue.

8. Reinclusion, and no replay#

>>> chain.state.apply([payment]).balances[bob]
25
>>> again = bk.consensus.mine(bk.structures.Block(rival_2.hash, (payment,), 3, 4, 4)).block
>>> chain.add(again), chain.state.balances[bob]
(True, 25)
>>> try:
...     chain.state.apply([payment])
... except ValueError:
...     print("replay rejected: account nonce already used")
replay rejected: account nonce already used

Had the winning branch contained a conflicting payment from Alice with the same account nonce, the original would not have been eligible again, despite its valid signature (see Exercises: ledgers and data structures, problem 7).

Where each question was answered#

Question

Answered by

Subpackage

Did Alice authorize it?

Schnorr signature

blockchainkit.crypto

Can she afford it? Is it a replay?

ledger state and account nonce

blockchainkit.structures

Who knows about it?

gossip

blockchainkit.network

Which history holds it?

proof of work and fork choice

blockchainkit.consensus

How sure are we?

confirmations against an attacker’s share

blockchainkit.consensus

The full simulation, with two peers, a partition, and a balance and queue table, is One payment from signature to reorganization and reinclusion.