Skip to content

Proof of History: A Verifiable Clock from a Hash Chain

The Throughput Problem ended on a specific villain. Before a single global state machine can execute anything, every node has to agree on the order in which transactions arrive — and on other chains that agreement is a conversation. Validators exchange messages (“I saw this first”, “no, I did”) until they converge. At a few hundred transactions per second that chatter is tolerable. At the rates this book is chasing it is the wall you hit first: you cannot run a state machine at hardware speed if establishing its input order costs a network round trip per batch.

This page builds Solana’s answer to that, and it is a small, strange, powerful idea. We build a verifiable clock — a data structure any node can check on its own, from the bytes alone, that fixes the order of events by construction so nobody has to ask anyone what time it is. The whole thing is one hash function fed back into itself, plus the discipline to record what you did. We build the generator, prove the properties that make it trustworthy, and see exactly why it is sequential to produce but parallel to verify — which is the trick that makes it worth building at all.

The core idea: a chain you cannot fake without doing the work

Section titled “The core idea: a chain you cannot fake without doing the work”

Take SHA-256 and feed its output back into itself, over and over:

h0 = sha256(seed)
h1 = sha256(h0)
h2 = sha256(h1)
hN = sha256(h(N-1))

Now ask the load-bearing question: how does anyone know hN? SHA-256 is pre-image resistant — given an output there is no known way to work backwards to an input, and no known shortcut to jump k steps ahead without computing the k intermediate hashes. So the only way to produce hN is to actually compute all N hashes, one after another.

Two consequences fall out, and they are the entire foundation:

  1. You cannot skip ahead. There is no closed form for “the state after a million hashes.” You pay for every step.
  2. You cannot parallelize production. Each hash needs the previous one’s output as its input, so a thousand cores are exactly as fast as one. The chain advances at one core’s hashing speed and no faster.

That second point is what turns a hash chain into a clock. Because each hash costs a roughly fixed amount of time on a given machine, and because the steps are forced to be sequential, the number of hashes performed is a count of real elapsed sequential work — a measure of time that cannot be forged cheaply. Somebody showing you a chain of a million hashes is showing you proof they (or someone) did a million hashes’ worth of sequential work. The count is the clock; the tick is one hash.

A bare clock only tells time. To make it useful we stamp events into it. To record an event, at one step you hash the current state concatenated with the event, instead of the state alone:

normal tick: h_next = sha256(h)
record event: h_next = sha256(h || sha256(event))

Because every hash after that point takes this one as input, every later state now depends on the event. That is the magic. The event provably happened after everything hashed before it and before everything hashed after it — its position in the timeline is not a timestamp somebody asserted, it is baked into the arithmetic of everything downstream.

genesis ─►(tick)─►(tick)─►(record A)─►(tick)─►(record B)─►(tick)─►
▲ ▲
A sealed here B sealed here, after A

Order is established by construction. No wall clocks, no timestamp fields anyone can lie about, no agreement protocol. This is the property that lets validators agree on order without messaging each other: they do not vote on order, they recompute it.

Everything below lives in the book’s companion crate, solmini, in src/poh.rs. Start with the primitives. Hash is just a 32-byte array — fixed size, stack-allocated, Copy, so we can pass the clock state around by value and never worry about who owns it:

use sha2::{Digest, Sha256};
/// A 32-byte SHA-256 digest — the unit of the clock.
pub type Hash = [u8; 32];
pub fn sha256(input: &[u8]) -> Hash {
let mut h = Sha256::new();
h.update(input);
h.finalize().into() // GenericArray<u8, 32> -> [u8; 32]
}
/// One bare tick of the clock: h = sha256(h).
pub fn hash_once(h: &Hash) -> Hash {
sha256(h)
}
/// One tick that folds an event in: h = sha256(h || mixin).
pub fn hash_with(h: &Hash, mixin: &Hash) -> Hash {
let mut hasher = Sha256::new();
hasher.update(h);
hasher.update(mixin);
hasher.finalize().into()
}
/// Reduce arbitrary event bytes (a transaction, a batch root, …) to 32 bytes.
pub fn mixin_of(data: &[u8]) -> Hash {
sha256(data)
}

The generator owns two things: the rolling state (the latest hash) and a running count of how many hashes it has done since genesis. Each checkpoint it emits is an Entry: how many hashes happened since the last entry, the resulting hash, and — if this entry recorded an event — the value that was mixed in.

/// One recorded checkpoint in the chain.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Entry {
pub num_hashes: u64, // hashes since the previous entry (always >= 1)
pub hash: Hash, // chain state right after this entry
pub mixin: Option<Hash>, // the event folded into the last hash, if any
}
/// The generator: owns the rolling state and a count of work done.
#[derive(Clone, Debug)]
pub struct Poh {
state: Hash,
count: u64,
}
impl Poh {
pub fn new(seed: &[u8]) -> Self {
Poh { state: sha256(seed), count: 0 }
}
/// "Time passed and nothing happened": n bare hashes, no event.
pub fn tick(&mut self, n: u64) -> Entry {
for _ in 0..n {
self.state = hash_once(&self.state);
self.count += 1;
}
Entry { num_hashes: n, hash: self.state, mixin: None }
}
/// Seal an event: n - 1 bare hashes, then one hash that folds in sha256(data).
pub fn record(&mut self, n: u64, data: &[u8]) -> Entry {
assert!(n >= 1, "an entry must contain at least one hash");
for _ in 0..n - 1 {
self.state = hash_once(&self.state);
self.count += 1;
}
let mix = mixin_of(data);
self.state = hash_with(&self.state, &mix);
self.count += 1;
Entry { num_hashes: n, hash: self.state, mixin: Some(mix) }
}
}

Sit with the shape of the two methods, because the bookkeeping is the whole contract with the verifier:

  • tick(n) does exactly n bare hashes. This is the runtime saying “time is passing and no transactions arrived.” Ticks give the chain a steady cadence so the clock keeps advancing even in a quiet moment.
  • record(n, data) does n - 1 bare hashes, then one hash that mixes the event in. So num_hashes always counts total hashes for the entry, including the mixing one. The event lands at the very end of the entry’s segment, and from that hash onward the event is sealed into everything that follows.

Notice there is exactly one source of truth for “where the clock is now”: the owned state: Hash, advanced by value each step. Nothing aliases it, nothing else can write it, so the chain is deterministic by construction — which matters enormously, because the verifier is going to reproduce it hash for hash.

Verify it: the same work, but anyone can check it

Section titled “Verify it: the same work, but anyone can check it”

Verification replays the chain from the genesis hash and confirms every recorded hash matches what recomputation produces. For a tick, redo num_hashes bare hashes. For a record, redo num_hashes - 1 bare hashes then one hash with the recorded mixin:

/// Replay a chain from `start` and confirm every recorded hash. False on first mismatch.
pub fn verify(start: Hash, entries: &[Entry]) -> bool {
let mut cur = start;
for e in entries {
if e.num_hashes == 0 {
return false; // an entry with no work is not a real checkpoint
}
match e.mixin {
None => {
for _ in 0..e.num_hashes {
cur = hash_once(&cur);
}
}
Some(mix) => {
for _ in 0..e.num_hashes - 1 {
cur = hash_once(&cur);
}
cur = hash_with(&cur, &mix);
}
}
if cur != e.hash {
return false; // forged or corrupt
}
}
true
}

The verifier does the same number of hashes the producer did — there is no cheaper check, and that is on purpose. Its power is not in doing less work; it is in doing work nobody can fake. Three tests in solmini pin down the properties that make this a clock you can trust.

A clean chain verifies, and the verifier does the producer’s exact work

Section titled “A clean chain verifies, and the verifier does the producer’s exact work”
let seed = b"solmini-genesis";
let mut poh = Poh::new(seed);
let entries = vec![
poh.tick(10),
poh.record(5, b"transfer: alice -> bob 50"),
poh.tick(8),
poh.record(3, b"counter: increment"),
];
assert!(verify(sha256(seed), &entries));
// The verifier's total work equals the producer's total work.
assert_eq!(total_hashes(&entries), poh.count()); // 10 + 5 + 8 + 3 == 26

Tampering with any recorded hash is caught

Section titled “Tampering with any recorded hash is caught”

Flip a single byte of any entry’s recorded hash and verification fails, because the recomputed chain no longer matches the corrupted checkpoint:

let mut entries = vec![poh.tick(4), poh.record(2, b"event"), poh.tick(4)];
entries[1].hash[0] ^= 0x01; // flip one bit of the middle checkpoint
assert!(!verify(sha256(seed), &entries)); // caught

This is the one to internalize. You cannot swap two recorded events even if you keep both of them, because each event’s position is part of what everything after it hashed:

let a = poh.record(3, b"event-A");
let b = poh.record(3, b"event-B");
assert!(verify(sha256(seed), &vec![a, b])); // the real order verifies
assert!(!verify(sha256(seed), &vec![b, a])); // swapping does not

event-B’s recorded hash was computed from a state that already had event-A folded in. Put b first and the verifier recomputes b from a state that never saw A — the hashes diverge immediately. “Ordered by construction” is not a slogan; it is what these three tests demonstrate. A fourth test in the crate makes the same point about skipping ahead: claim tick(1000) but hand over a hash you invented instead of derived, and verification fails, because you never actually did the thousand sequential hashes.

Under the hood — why production is serial but verification is parallel

Section titled “Under the hood — why production is serial but verification is parallel”

Here is the asymmetry that makes Proof of History worth the trouble. Producing the chain is unavoidably sequential: hash k+1 needs hash k, so the leader’s clock advances at exactly one core’s speed. Verifying it is embarrassingly parallel — because every Entry records its own ending hash, a verifier already knows the start hash of each segment (it is the previous entry’s recorded hash). So it can recompute each segment independently, on a different core, all at once:

produce (1 core): ─h─h─h─h─h─h─h─h─h─h─► (must be sequential)
verify (K cores): ├──seg──┤├──seg──┤├──seg──┤├──seg──┤ (independent, parallel)

With K cores a verifier checks roughly faster than it could produce — and a forger gets no such help, because to fake a future state they would have to actually run the sequential chain that they were trying to avoid. Our verify above is linear for clarity; a real validator splits the segments across cores. This is exactly the parallelism you will build for execution in The Account-Locks Scheduler — the same “independent pieces, many cores” move, applied to a different part of the pipeline.

Return to the villain from the previous page. With a verifiable clock, the current leader does not negotiate order with anyone. It streams transactions into the PoH chain as they arrive — each record stamps a transaction into a fixed position — and broadcasts the entries. Every other validator verifies the chain locally and in parallel, and now the entire network agrees on the order without a single round of “what time did you see this?” messaging.

Ordering, which was a chatty consensus sub-problem, becomes a property you read off the data. That decoupling — establish order first and cheaply via PoH, then vote on validity separately — is what keeps the pipeline fed. It is the first of the several tricks this Part assembles into a state machine that runs at hardware speed: get the input order for free, so the expensive machinery downstream never waits on a network conversation.

Tie back to Bitcoin: same chain, repurposed

Section titled “Tie back to Bitcoin: same chain, repurposed”

If this felt familiar, it should. Bitcoin’s blocks are a hash chain too: each block header commits to the previous block’s hash, which is what makes the ledger tamper-evident and gives it a single order. Proof of History takes that same primitive — a sequential, tamper-evident hash chain — and repurposes it. In Bitcoin the chain is glued together by proof-of-work (the hash linkage is a side effect of the mining puzzle, and the point is picking a leader). In Solana the hash chain is lifted out and made a first-class object: a standalone clock whose only job is timestamping and ordering, entirely separate from how leaders get chosen. Same “fast to check, slow to produce” shape; different purpose. Bitcoin used the chain to answer who writes next; Solana uses it to answer when — and getting a cheap, verifiable answer to when is what removes the ordering bottleneck.

There is an honest caveat worth stating: iterated SHA-256 is a Verifiable-Delay-Function idea but not a strict VDF. A true VDF verifies asymptotically cheaper than it evaluates; PoH verification does the same number of hashes as production — it is only parallelizable, not fundamentally less work. PoH’s security rests entirely on SHA-256 having no known shortcut faster than recomputing the chain.

  • Why does it exist? Because a distributed system has no trustworthy global clock, and agreeing on the order of events by messaging is the throughput bottleneck. PoH exists to make order a verifiable property of data instead of the output of a conversation.
  • What problem does it solve? It lets every validator independently agree on the order — and roughly the timing — of transactions with no inter-node coordination, by recomputing a chain rather than voting on timestamps.
  • What are the trade-offs? Production is strictly sequential (the leader’s clock runs at one core’s hashing speed, a hard ceiling), and verification does the same total work as production. You buy tamper-evident, coordination-free ordering; you pay with a single-threaded producer and a not-actually-cheaper verifier.
  • When should I avoid it? When throughput is not gated on ordering, or when a simple trusted timestamp or a few validators exchanging messages is fast enough. PoH’s machinery only pays off at rates where the ordering conversation is the wall.
  • What breaks if I remove it? Validators fall back to messaging to agree on order, and that conversation re-caps throughput at exactly the point The Throughput Problem identified — the rest of the runtime you are about to build would starve waiting for its input order.
  1. Why can’t the production of a PoH chain be parallelized, and why does that make a hash count a measure of elapsed sequential time?
  2. What exactly does record(n, data) compute at its last hash, and what guarantee does that give about the event’s position in the timeline?
  3. Production is serial but verification is parallel. Given the contents of an Entry, explain precisely what makes verification parallelizable.
  4. Why does swapping two recorded events fail verification even when both events are still present?
  5. How does a cheap, verifiable clock raise a chain’s throughput — what expensive step does it remove from agreeing on order?
Show answers
  1. Each hash takes the previous hash as its input (h_next = sha256(h)), forming a strict dependency chain, so extra cores cannot help — the chain advances at one core’s speed. Because each hash costs a roughly fixed time on a given machine, the number of hashes done is proportional to real elapsed time, and pre-image resistance means there is no shortcut to produce a later state without doing all the intermediate hashes.
  2. It does n - 1 bare sha256(state) hashes, then one hash of sha256(state || sha256(data)), folding the event into the rolling state. From that point every hash depends on the event, so it is provably after everything earlier and before everything later; its order is fixed by construction and cannot change without redoing all subsequent work.
  3. Each Entry records its own ending hash, which is the starting hash of the next segment. So a verifier already knows the start and claimed end of every segment and can recompute each one independently on a different core — segments depend only on the recorded boundary hashes, not on each other’s recomputation. A forger gets no such help, since faking a future state still requires running the sequential chain.
  4. The second event’s recorded hash was computed from a state that already had the first event folded in. Put them in the other order and the verifier recomputes the second event from a state that never saw the first — the recomputed hash diverges from the recorded one immediately, and verify returns false. Position is part of what gets hashed, so order is structural, not a reorderable label.
  5. It removes the inter-validator messaging needed to agree on the order and timing of transactions. Instead of a chatty protocol exchanging “when did you see this?”, the leader streams transactions into the PoH chain and every node verifies the order locally and in parallel. Ordering becomes a property read from the data, freeing the rest of the pipeline to push far more transactions per second.