Spek
A post-quantum, unbonded proof-of-stake ledger with secret, ungrindable 30-second leader election, and no energy race.
Technical white paper, November 2026. Describes the reference implementation (Rust, 20 workspace crates, MIT / Apache-2.0) and its leader election as specified in RFC-0012. Designed and directed by one developer; implemented, tested and reviewed almost entirely by Claude Code.
Two disclosures before anything else.
- SQIsign is not a NIST standard. It is a candidate in NIST's additional post-quantum signature process (advanced to Round 3 in May 2026, with evaluation expected to run about two years). "NIST security level 1" and "level 5" below name the security categories the parameter sets target, not approvals. NIST's finalised post-quantum signature standards (ML-DSA, SLH-DSA) and the selected FN-DSA are different schemes, and Spek uses none of them.
- The code has been audited only by AI models (Anthropic's Claude). No independent human security audit, and no review by a cryptographer or by the SQIsign authors, has taken place before launch. That covers the Rust implementation, the vendored SQIsign and RandomX C code, and the FFI boundary between them.
Abstract#
Spek is an unbonded proof-of-stake UTXO blockchain in which time, not work, schedules blocks. The clock is divided into fixed 30-second timeslots anchored at a genesis timestamp; block height equals timeslot number, so every block's timestamp is derived rather than declared. In each slot every eligible stake output has exactly one draw: the middle-out distance from the centre of the 256-bit hash space of a hash over the epoch's beacon, the height, the miner key and a per-height slot secret that only the key owner can compute and that was committed inside the stake output before the beacon existed. Nobody, the owner included, can rank keys for a future slot before the beacon is fixed, and nobody else can know a miner's score until its header is published. The highest score whose full block validates wins; if no candidate arrives, every node constructs the same unsigned null block so the chain never stalls. Eligibility is what the protocol prices: during a 45-day seed phase a RandomX ticket per 3-day round, recorded on chain before it can count, and afterwards one output at the miner's stake address worth at least 80% of the block reward, created at least a day before it counts.
Block headers and mining keys use SQIsign Level I (isogeny-based, targeting NIST security level 1); long-lived custody outputs can opt into SQIsign Level V, and the genesis allocation and its guardians use it. SQIsign is a Round 3 candidate, not a NIST standard. Hashing is BLAKE3 throughout. Public keys stay hidden behind a hash until an output is spent, and Taproot-style script commitments are rebuilt on hash commitments rather than curve tweaks. Consensus time comes from an authenticated Roughtime quorum projected forward on the hardware-rate monotonic clock, so neither the host's wall clock nor its NTP daemon can steer a node's slot. Forks within 4 blocks of the tip resolve by cumulative score once each branch holds two blocks above the fork point; transactions settle by confirmations sized to an assumed attacker share, and a weak-subjectivity checkpoint grid every 4,000 blocks is the hard barrier. Supply is capped at 450,000,000 coins: a 30,000,000-coin genesis allocation (founders 5%, vesting over five years; a stewardship fund 1.67%, vesting over twenty) plus a 50-coin reward halving every 4,200,000 blocks.
Contents#
- Introduction
- Design goals
- Timeslots and the verified clock
- Leader election
- Slot settlement and the null block
- Chain selection and settlement
- Eligibility: ticket records and stake outputs
- Cryptography
- Transactions and scripts
- Monetary policy
- Network protocol
- Synchronisation and checkpoints
- Node architecture and storage
- Governance and upgrades
- Threat model
- Comparison
- Known limitations and open items
1. Introduction#
Proof-of-work buys Sybil resistance with energy and rewards the party that burns the most. Bonded proof-of-stake buys it with locked capital and inherits a thicket of slashing, validator sets, and finality gadgets. Spek is unbonded proof of stake, mechanically simpler than either: a per-slot election over stake outputs that each hold a coin-eligibility floor, with nothing locked and nothing slashed.
Three ideas carry the design:
- Time is the schedule. A block exists for every 30-second slot since genesis, whether or not anyone produced one. Height, timestamp, reward, fee floor, coin requirement, and vesting are all pure functions of that slot index.
- One stake output, one secret draw. A miner's score for slot h depends only on values fixed before the draw's beacon existed: its key, its committed slot secret for h, and the height. There is no nonce to grind, no key to swap in after the fact, and nothing a producer can re-sign or re-order to improve its own or any descendant's chances. Until a miner publishes its header, nobody else can compute its score.
- Eligibility is the security parameter. Since any selection rule over independent uniform draws awards a party controlling N of N+H draws exactly N/(N+H) of slots, the only lever is what a draw costs to field. Spek prices that explicitly, and differently in two phases (§7).
The result is a network whose honest operating cost is a node and a held balance, not a power bill. The remainder of this paper describes the mechanism as built, including the parts that are deliberately plain and the parts that are still open.
Positioning note. Spek is unbonded proof of stake. Stake is a threshold met by one output, never a bond: nothing is locked, burned, or slashed, and there is no validator set and no delegation. The first 45 days price keys with RandomX tickets instead (§7.2), so the chain is not proof-of-work even then: work buys eligibility for a round, not a block. Spek has no staking product (no yield, no locking, no pools), so it should never be described with staking-product language such as "staking rewards" or "stake your coins"; the block winner earns the block reward.
2. Design goals#
- Post-quantum by construction. SQIsign for every signature (Level I for headers and everyday outputs, Level V opt-in for long-lived custody), keys hidden behind hashes until spent, no elliptic-curve code linked into the signing crate, and a reserved, versioned migration path (§8.5).
- No computational waste. One BLAKE3 evaluation per stake output per slot decides candidacy; producing the slot secret costs fewer than 96 hashes from cached checkpoints. Signing costs 25–40 ms once per slot; verifying a header costs one SQIsign verification (2.7–5.0 ms) plus a reveal walk of at most 8,651 hashes (0.9–2.6 ms).
- Ungrindable, unpredictable election. Every score input is fixed before the beacon it is drawn against exists, and each winner adds secret entropy to the next beacon. No winner schedule exists to read ahead of time, so there is no predicted winner to target.
- Deterministic everything. Timestamps, rewards, tie-breaks, null blocks, and genesis bytes are identical on every node. Where determinism is impossible (SQIsign signatures are randomised) the protocol is designed so the non-determinism cannot matter.
- Honest settlement claims. Propagation forks are compared within a 4-block window (~2 minutes). Transactions settle by confirmations sized to an assumed attacker share (Appendix B), and a running node never reorgs below its restoration pin (33–67 h). There is no finality gadget and the paper does not pretend there is.
- Fail fast, fail closed. Cheap structural gates reject bad input in microseconds; expensive checks run only on the top-ranked candidate; "not ready" is never treated as "invalid".
- Universal tuning. No constant is sized from the developer's machine; every budget has an operator knob, a safe default, and a hard correctness floor. The one benchmark-derived consensus constant, the Level V witness weight, is a ratio between two algorithms taken at its worst case across backends.
- Plain governance. One upgrade mechanism (block version + flag height), no on-chain voting, a transparent multisig council for the stewardship fund.
3. Timeslots and the verified clock#
3.1 Slots#
Let G be the genesis timestamp. The slot currently being mined, and therefore the height of the block that does not yet exist, is
time_derived_timeslot = (now − G) / 30
settled_tip = time_derived_timeslot − 1
derived_timestamp(h) = G + 30·h
A slot is unsettled while it is being mined and settled once it closes. The header carries no timestamp field at all: a block at height h can only ever have timestamp G + 30h, which removes timestamp grinding, median-time-past, and the whole tolerance-window class of validation by construction. Block height is a u32 (about 4,000 years of 30-second slots); derived timestamps are 64-bit.
Slots are grouped into epochs of L = 1,440 slots (12 h), which carry the election beacon (§4.2), and segments of S = 8,640 slots (3 days), which carry each key's slot-secret hash chains (§4.1).
3.2 Two phases per slot#
Each slot is split into a headers-only phase (0–6 s) in which 743-byte candidate headers flood the mesh and the ranking settles, and a block phase (6–30 s) in which nodes pull full bodies only for the top three candidates (rank 1 from three peers, ranks 2–3 from one each) and serve the top five. Fetching bodies only after the ranking is known bounds bandwidth and removes the "premature block flood" denial-of-service vector. The 24-second body window is sized for megabyte-scale blocks crossing intercontinental links, with room for two pull-retry rounds.
0 s 6 s 30 s
|--- headers only --|--- pull top-3 bodies · validate rank-1 · serve top-5 ---|
slot h opens boundary: winner of h committed, or null
Header push is paced at 6 per second per peer in fixed one-second windows; current-slot block serving allows 5 requests per 3 seconds per peer, one per retained candidate.
3.3 Where time comes from#
Because a wrong clock puts a node in the wrong slot, Spek does not trust the operating system's clock for consensus. Each node maintains an in-process verified clock:
- Source. Roughtime (IETF draft, three wire versions pinned per server). Every response is authenticated end to end: the server's long-term Ed25519 key signs a delegation certificate, the delegated online key signs the response, the node's own request is bound into the signed Merkle root (defeating replay), and the midpoint must sit inside the delegation's validity span.
- Quorum. Servers are queried in parallel and every sample is RTT-corrected and normalised to a single shared epoch. The median is one vote per operator (key custody), not per address: each operator's vote is the median of its own surviving samples, votes more than 2 s from the median of votes are discarded, at least 3 voting operators must remain (operator-configurable upward, hard floor 3), and the median of the surviving votes is adopted, rounded to whole seconds. Extra entries under one operator are availability headroom for its single vote, never extra votes.
- Projection. Between syncs the verified time is projected forward on
CLOCK_MONOTONIC_RAW, the undisciplined hardware oscillator. The NTP-disciplined monotonic clock behindstd::time::Instantfollows whatever the host's NTP daemon is told, and a measured on-path NTP offset walked an earlier design a full slot in about six minutes while the drift gauge read zero. Raw oscillators run 30–100 ppm off on commodity and cloud hosts, so the node learns its oscillator's rate from successive syncs and corrects the projection by it. Each sync may move the learned rate by at most 50 ppm and the rate is clamped at 250 ppm, so a steered quorum cannot install a large false rate in one step. - Cadence. A node syncs hourly while it learns its rate, and every 4 h once a sync confirms the rate to within 5 ppm (at most about 72 ms of drift over the interval). A sync whose residual shows the rate has changed (a migrated VM, a changed oscillator) drops it back to hourly.
- Step bound. A newly offered median is accepted unconditionally only within 5 s of the projected baseline. Backward steps beyond that are refused; forward steps beyond it need corroboration, either from the node's own independently measured drift (the suspend/resume case) or from a second sync at an escalated quorum.
- Mining hold. A node that has never acquired a verified baseline does not sign blocks. Everything else (relay, serve, sync) continues. The opt-out flag exists for the contingency that every configured server is down, and it is loud.
Consequence. Cross-node clock agreement is bounded by the residual after rate correction over one sync interval plus Roughtime path asymmetry, not by a "sub-second" promise. The 2-second outlier bound, the 5-second step bound, and the 30-second slot are sized around that.
4. Leader election#
The election needs a per-slot value that is (a) secret until used, (b) unique per height so it cannot be ground, and (c) committed before the randomness it is drawn against exists. A unique-signature VRF would give (a) and (b), but no deployable post-quantum VRF exists, and a randomised signature such as SQIsign is not unique. Spek gets all three from BLAKE3 alone, with a committed hash chain.
4.1 Slot secrets and horizons#
Each miner key file holds a SQIsign Level I key pair and a 32-byte slot seed drawn from the operating system's random source when the key is generated (385 bytes in all). The slot seed is independent of the signing key, so recovering a SQIsign secret key reveals nothing about future slot secrets.
σ = BLAKE3-derive-key("spek 2026-10-20 slot-secret v1", slot_seed)
s_g = BLAKE3("spek/slot-chain/v1" ‖ σ ‖ g) // one chain per 3-day segment g
T_g = step^S(s_g) // segment top, step = BLAKE3("spek/slot-step/v1" ‖ x)
r_h = step^(S − 1 − (h mod S))(s_g(h)) // slot reveal for height h
leaf_j = BLAKE3("spek/slot-leaf/v1" ‖ pk ‖ T_(J0 + j)) // j = 0 … 1023, key-bound
C = Merkle root over the 1,024 leaves // one horizon
Each segment's chain is revealed backwards, one value per slot, so publishing r_h exposes only values for heights already passed. A horizon is 1,024 consecutive segments starting at segment J0: about 8.4 years on mainnet. Its root C, bound to the key in every leaf, is committed in the stake output (§7.1) before any beacon it will be drawn against exists, so for every key, commitment and height exactly one r_h verifies.
A header carries the reveal, its 10-hash Merkle path and J0. A verifier walks the reveal up to its segment top (at most S hashes), hashes the key-bound leaf, climbs the path, and uses the derived root to rebuild the stake address; if the reveal is wrong, there is simply no stake at that address. The root is never carried. A mining node keeps the 1,024 segment tops (32 KB) and checkpoints in the current segment, so producing r_h takes fewer than 96 hashes; building a horizon takes 1–3 s.
A node creates its next horizon automatically about a year before the current one ends (--horizon-renewal-lead-segments, node policy, default 122 segments), with two segments of overlap so eligibility never lapses.
4.2 The epoch beacon#
B_e = BLAKE3("spek/beacon/v1" ‖ e ‖ genesis.merkle_root) e ∈ {0, 1}
B_e = BLAKE3("spek/beacon/v1" ‖ e ‖ election_seed@beacon_height(e)) e ≥ 2
beacon_height(e) = (e − 1)·L − 1
The beacon for epoch e is read from the election seed at the last block of epoch e−2, a full epoch before e starts. Every block of that source epoch mixes its winner's secret r_h into the seed (§4.4), so the beacon is unpredictable until the source epoch is produced. It is the same on every fork that shares that history, which covers every propagation fork (at most 5 deep against L = 1,440). The two launch epochs read the genesis merkle root, which commits to the launch ticket records and a launch nonce derived from a Bitcoin block mined after the record list was sealed (§7.2), so nobody, the organiser included, can choose it after learning the launch beacons.
The miner at a beacon height can withhold its block and let the runner-up set the beacon instead, and because it holds its own future reveals it can evaluate both choices. Simulation puts the gain at 1.9–3.0% more of its own wins per beacon it controls, 0.3–0.6% averaged over all epochs, with no compounding; it was measured and accepted.
4.3 The score#
score = middle_out( BLAKE3("spek/score/v2" ‖ B_e ‖ h ‖ pk ‖ r_h) )
x = hash as a 256-bit integer, C = 2^255
middle_out(x) = x ≥ C ? (x − C) + 1 : C − x // range [1, 2^255], arbitrary-precision integers
Both 0x00…00 and 0xFF…FF score the maximum; 0x7F…FF and 0x80…00 score 1. The genesis block scores the fixed 2^255; a null block scores 0.
Every input is fixed before the beacon exists: the key and its commitment sit in a stake output that had to be created below a cutoff one full epoch before the beacon height (§7.1), and r_h is the unique reveal of that commitment. The key is inside the preimage, so a copied commitment or a replayed reveal gives the copier an independent draw, never the victim's score. A genuine tie between two distinct keys has probability about 2^−255; the tie-break (highest score → lowest pubkey → lowest merkle root) is a determinism guarantee, not a grinding surface.
0x00…00 C = 2^255 0xFF…FF
|==================================|==================================|
score 2^255 (max) score 1 (min) score 2^255 (max)
higher score = further from centre = more likely to win
At receipt a node first computes the header's claimed score (one hash) and drops it before any expensive check if it could not enter the slot's top five; a header is inserted into the ranking only after its signature, reveal and eligibility pass, so an unverified claim can never displace a verified entry. With top-5 relay a node verifies about 5·(1 + ln(N/5)) headers per slot: about 31 at a thousand miners, 66 at a million.
4.4 The election seed chain#
The header carries two hash chains with distinct jobs. prev_block is the identity chain: the BLAKE3 hash of the parent's signed fields. The signature is left out, because SQIsign signatures are not unique: anyone holding one can derive another valid one on the same bytes, so an identity that covered it could be split by any relay. The identity stays producer-malleable through the free merkle root, which is fine for an identifier. election_seed is the beacon chain:
election_seed(h) = BLAKE3("spek/election-seed/v2" ‖ election_seed(h−1) ‖ miner_pubkey ‖ r_h)
election_seed(0) = 0x00…00
The seed binds only the parent's seed, the winner's identity and its committed reveal. No signature bytes, merkle root, or header hash enter it, so re-signing or re-ordering a block cannot move any later beacon. This closed a grinding channel present in an earlier design that seeded from the parent's full hash. A later rule that scored directly from this chain (seed ‖ pubkey) was also replaced, because the winner of one slot knew the next seed and could fund, in its own block, whichever pre-generated key drew best next; the beacon, the stake cutoff and the slot secrets close that.
4.5 Mining a block#
At each slot boundary a mining node, in one chain-state read scope so that the parent, its seed, the beacon, and the stake verdict all come from a single consistent state:
- checks the sync condition
chain_height + 1 == current_slot(a node behind the clock does not sign a block it knows is at the wrong height; this is not suppression during download, which the design forbids — see §12.2); - picks its eligible horizon (in the seed phase, the one whose stake program holds the round's ticket record);
- computes
r_hand the Merkle path from its slot-secret store, then the score and the next election seed; - applies its own eligibility rule through the same function block validation uses, and forfeits the slot rather than produce a block every peer would reject;
- builds a template from the mempool and self-checks it (structure, locktime, genesis spend rules, size and count caps, merkle root), because the winner-commit path applies the node's own block without re-validation;
- signs the 543-byte header prefix with SQIsign;
- persists the signed attempt to disk so a crash-restart within the slot re-announces the same attempt instead of a fresh signature (which would be self-equivocation);
- floods the header.
4.6 Equivocation#
Only one entry per miner key per parent per slot is kept. SQIsign signatures are randomised, and anyone holding one can derive other valid signatures over the same content, so the block hash leaves the signature out and a second header with identical signed content is the same block: it is merged. A second header with different signed content over the same attempt is equivocation: the miner's entry is evicted, the attempt is negated slot-wide under its attempt-identity hash BLAKE3(prev_block ‖ miner_pubkey), and a compact proof is gossiped. Any "pick one variant" rule would be either unbounded (a miner floods variants) or arrival-order dependent (a partition); forfeiting the slot is bounded and order-independent.
5. Slot settlement and the null block#
At the boundary, nodes cascade through retained candidates in rank order (up to the top five) and commit the first whose full body is in hand and passes authoritative validation. Holding five while pulling three lets rank 3 promote after up to two same-slot disqualifications, and leaves two backup bodies for the case where a top-3 body is never served (for example a winner whose machine died after announcing). The node waits a bounded 2.5 s for the rank-1 verdict if it is in flight.
If a slot closes with no committable candidate, every node constructs the canonical null block for (height, prev_block): version 1, all-zero miner key, signature and slot fields, zero merkle root, no transactions and no coinbase, with an election seed derived by the normal rule using a fixed null identity and an all-zero reveal so the beacon chain continues unbroken. It is constructed, not mined, so no relay is needed and no bytes cross the wire. Its score is defined as 0, strictly below the minimum real score of 1, so any real block for that height outranks it and fork choice heals a null the moment a late winner surfaces, a depth-1 reorg with no ban and no negation. Nulls mint nothing and are stored and served like ordinary blocks.
Two consequences are worth stating. First, a chain whose height and hashes agree across nodes can still have an unhealthy mesh underneath, because null slots are silent and deterministic; health must be read from per-node telemetry. Second, silence is not evidence about a peer: rules that infer misbehaviour from an absence of data must first establish that data existed, and the implementation's inactivity reaper and isolation accounting are gated accordingly.
Residual. Per-boundary settlement fills exactly one missed slot. If every node simultaneously misses two or more boundaries (a coordinated restart, a network-wide freeze) no node is exactly one behind and the chain wedges. Recovery is an operator-invoked, locally-served RPC that fills deterministic nulls for settled slots, capped per call at the propagation fork window and refused whenever any peer proves a better chain exists. It cannot be automatic: a single node cannot distinguish "the whole network was down" from "I was down".
6. Chain selection and settlement#
6.1 Cumulative score#
chainwork(b) = chainwork(parent(b)) + score(b) // score under the beacon of b's epoch on that chain
Chainwork is an arbitrary-precision sum that is never truncated or reset; truncation would enable long-range attacks. Each block is scored against the beacon of its epoch on the chain being scored, resolved from that chain's own headers; a header whose beacon cannot yet be resolved is quarantined, never scored with a guess.
6.2 Fork resolution ladder#
- Propagation forks (the hot path). When a competing branch forks within the propagation fork window (
PROPAGATION_FORK_WINDOW= 4 blocks below the tip), both branches are compared by cumulative score once each holds at least two blocks above the fork point; the heavier wins and the loser is dropped. Waiting for both sides removes the tip thrash that per-block score variance would otherwise cause. The reorg is validated against a copy-on-write UTXO overlay before any live state changes, so a fork block that fails validation leaves the chain untouched. Rollback is bounded at 5 blocks. - Divergence-triggered download. If no common ancestor lies inside the window while the peer's chain keeps pace with the clock (the equal-height lockstep that every partition heal produces, since both sides mine every slot), the node escalates to a full download, locates the true branch point, compares cumulative work, and reorgs at any depth above its restoration pin using per-block undo data or, failing that, an exact UTXO rebuild.
- Checkpoint barrier (weak subjectivity). A fork whose branch point lies behind the node's restoration pin or a hard checkpoint is never auto-resolved; the offering peer is banned. Checkpoints sit on a 4,000-block grid (~33 h), and the restoration pin is the newest boundary buried at least one full interval below the tip, so it trails the tip by 33–67 h. A partition must outlast a boundary by more than an interval before the two sides hold different settled checkpoints; a partition that merely straddles a boundary heals by the ordinary fork walk or weight comparison. Only that state needs operator recovery.
Coinbase outputs mature after 3,000 blocks (~25 h).
6.3 Settlement#
Spek has three settlement layers, and states each for what it is:
- Confirmations (probabilistic). Draws are fixed per height, so an attacker can rewrite only by out-scoring the honest chain over the same heights. Appendix B gives the confirmations needed for under one reversible slot per century against an attacker holding share q of the draws, assuming it may choose its fork point in hindsight: 37 confirmations (18 min) at 20%, 136 (68 min) at 33%, 412 (3.4 h) at 40%. Integrators choose their depth from the share they want to tolerate.
- Restoration pins (per node). A running node never reorgs below its pin, which trails the tip by 33–67 h.
- Checkpoints (weak subjectivity). A node with no history of its own trusts the release's compiled-in pins and pin files signed by a threshold of checkpoint signers (§12.3).
There is no finality gadget and no rollback cap shorter than the pin. A cap was designed and dropped: as drafted, both sides of a long partition finalized and refused each other forever, and an eclipsed node finalized the eclipser's chain.
7. Eligibility: ticket records and stake outputs#
Because the election geometry cannot be made Sybil-resistant, the cost of fielding a draw is where all of Spek's security lives. That cost has two regimes, and in both the thing that confers eligibility must exist on chain before the beacon it competes against.
7.1 Post-seed: one stake output#
min_coin_requirement(h) = max(0.8 × block_reward(h), 0.1 coin) for h ≥ 129,600
stake_address = hidden-key address of (pk, BLAKE3("spek/slot-commit/v1" ‖ C ‖ J0))
stake_cutoff(h) = beacon_height(epoch(h)) − L
A header at height h is eligible only if its horizon covers h and its stake address, rebuilt from the header's key and the root its reveal derives, holds one unspent output that:
- is worth at least the requirement (several smaller outputs do not add up);
- was created below
stake_cutoff(h), one full epoch below the beacon height, so new stake first counts about 24–36 h after it is created; - if it is a coinbase output, is mature.
Eligibility is judged against the parent block's ledger, so spending the stake output ends eligibility from the next block, and on a fork each branch's own ledger decides. One output per draw keeps the check to a bounded walk of a value-ordered index: every output it steps over holds at least a full requirement, so stuffing a stake address with dust cannot slow anyone's check. Each stake output is one draw per slot, so a key with two funded horizons fields two draws, economically the same as two keys.
This is Spek's unbonded proof of stake. Post-seed block production is capital-proportional: a holder of X coins can fund ⌊X/requirement⌋ stake outputs and wins slots in that proportion. The requirement level (80% rather than some other ratio) sets the entry quantum and identity granularity, not the attack cost: matching the honest network costs roughly the aggregate capital it holds, whatever the per-draw price. The requirement halves with the reward, and the 0.1-coin floor only binds about nine halvings (~36 years) out.
The stake key pk is public in every header the miner publishes and lives on the mining machine, so the node keeps exposure to one stake unit. Node policy, not consensus: the coinbase pays the stake address of the first horizon not yet funded, then pays a separate hidden-key payout address (--payout-address). A winner therefore renews its next horizon for free; a key that never wins renews with one ordinary transaction, which the node makes itself from any surplus at the current stake address about a year before the horizon ends (or, with no surplus, the operator funds from a wallet). The node also merges a stake address funded in pieces into a single output.
This also breaks the classic proof-of-work death spiral. Honest operating cost is ~zero, so a falling price gives no miner a reason to unplug and the honest draw count does not collapse with price. Price exposure sits on the attacker's acquisition side: buying a large share drives the price up as it buys, and the stake it holds loses value when the attack is seen.
7.2 Seed phase: RandomX ticket records#
At launch nobody holds coins, so a coin floor would be zero and a draw would cost one SQIsign keygen (sub-second, parallel, pre-computable). The first 45 days (129,600 blocks) therefore use a different price. The seed phase is divided into 15 rounds of W = 8,640 blocks (3 days). A ticket is a nonce such that
RandomX_K( "SPEK-SEED-TICKET-V2" ‖ round ‖ round_seed ‖ A ‖ nonce ) < D, D = 2^228
A = the stake program (the 32-byte hidden-key program of the miner's stake address)
K = genesis hash (fixed for the whole seed phase)
round_seed_0 = genesis hash
round_seed_1 = hash of the block at height W/2
round_seed_r = hash of the block at height (r−1)·W for r ≥ 2
A ticket counts only once it is recorded on chain: a 48-byte record ("SKTR" ‖ round ‖ A ‖ nonce) carried as a zero-value OP_RETURN output in a seed-phase coinbase, at most 16 per block, one per (A, round). Records relay on their own gossip topic and wait in a bounded pool; every block producer includes pending ones. A record counts for an epoch only if it landed below that epoch's stake cutoff, so to cover a whole round it must land about two epochs before the round starts (a 48-hour search window per round). Because the ticket is committed before the beacon it competes against exists, a miner cannot rank free keys by their scores in an already-known epoch and buy tickets only for the best ones.
The RandomX instance is a custom-parameterised Spek variant (unique Argon2 salt plus documented-safe instruction-frequency changes), so rented marketplace hash-power cannot be pointed at it. Verification runs in light mode (15–30 ms, 256 MiB cache); searching uses the ~2 GiB dataset. D was calibrated so that a 2018-era 8-core desktop's spare cycles yield about three tickets per round; the residual blank-round probability is absorbed by the null block.
The round seed is resolved by walking the candidate chain's own ancestry, never by reading the observer's main chain at a height, so ticket validity is a pure function of the block and its chain and re-verifies identically during download. Round 1 is seeded from mid-round-0 rather than from genesis so that a single pre-launch grinding budget buys at most one round of tickets, not two.
Open launch. Epochs 0–2 (the first 36 hours) count only records written into the genesis coinbase, and anyone can win one. The search opens at a pre-announced Bitcoin block: its hash, with the network name, gives the launch key K_L, so no ticket can exist before it. Anyone then searches for a round-0 ticket under K_L (the same target D, with K_L as RandomX key and round seed) and submits the record to a public submission channel, which checks each ticket on arrival and lists every valid entry with its ticket hash so anyone can recheck the listing. After the deadline the organiser seals the list: it publishes the list with the genesis timestamp, the vout-0 message and the allocation, and a digest over all of them that independent witnesses record before a second pre-announced Bitcoin block is mined. The genesis takes up to 1,973 records, one per key, chosen by lowest ticket hash, so when more are submitted than fit, searching longer buys better odds. The launch nonce is derived from the launch key, the seal digest and that second block's hash, and the genesis builder is deterministic, so the launch beacons depend on a value nobody knew while any input could still change, and anyone can recompute the whole selection and genesis from public inputs. One record is one key, and a node mines one key, so a seat costs what every later round costs: one ticket.
The effect is that seed-phase draws are priced by sustained per-round compute rather than one-time keygen: a key bank is worthless without fresh work per key per round, and a flood decays within two rounds of its operator ceasing to pay for it. Ticket holders must fund a stake output below the cutoff of the first post-seed epoch; the node warns seven days ahead.
7.3 What the schedule does and does not do#
Both regimes award blocks in proportion to the draws held, and post-seed draws are bought with coins that (absent trading) only block rewards mint, so the coin-share distribution at the end of the seed phase is self-preserving under the post-seed lottery. Share changes afterwards come from coins changing hands, not from the protocol. One-node-one-key is client policy, not consensus: nothing ties a miner key to a machine, and the design does not rely on hardware scaling for its economics.
8. Cryptography#
8.1 Signatures: SQIsign Level I and Level V#
Every signature in the system is SQIsign (isogeny-based), produced by a vendored reference implementation behind an FFI boundary. There is no ECDSA, no Schnorr, and no elliptic-curve library linked into the signature crate; that invariant is asserted by test on the dependency graph.
| Level | Target | Public key | Signature | Secret key | Verify (ADX / portable) | Used for |
|---|---|---|---|---|---|---|
| I | NIST level 1 | 83 B | 200 B | 270 B | 2.68 / 5.03 ms | Headers, mining keys, stake outputs, everyday outputs (the default) |
| V | NIST level 5 | 169 B | 406 B | 549 B | 16.4 / 26.7 ms | Opt-in custody outputs; every genesis allocation, council and guardian key |
Signing a Level I header takes 25–40 ms (once per slot for a miner; the signer verifies its own signature before releasing it), with 6 ms used as the planning worst case for a verification everywhere a latency budget depends on it. Level V signing runs on a dedicated thread with its own stack.
Why headers stay at Level I. A forger who broke Level I still could not win a live slot as someone else, because it would also need that key's unrevealed r_h, which comes from an independent random seed and is protected only by BLAKE3. It could copy a just-revealed r_h into a second header and force an equivocation negation (a liveness attack, not a theft), and it could sign branches for past heights down to the restoration pin, which would hold at any level that broke. Stake outputs are capped at one stake unit per key by the payout rule (§7.1). Mining keys rotate with every horizon, so moving headers to a new level later is a hard fork that phases in as horizons expire, with no coin moves.
Standardisation and review status. SQIsign is not a NIST standard. It is one of nine candidates NIST advanced to Round 3 of its additional digital-signature process in May 2026. That round is expected to last about two years, a draft standard could not appear before about 2028, and SQIsign may be dropped. "Level I" and "Level V" refer to the security categories the parameters target (comparable to AES-128 and AES-256 key search), not to any NIST approval. Spek chose SQIsign over the standardised ML-DSA for its size: a 200-byte signature and a 83-byte key against roughly 2.4 KB and 1.3 KB. The trade-off is that SQIsign's security rests on a younger assumption (the supersingular endomorphism-ring problem) with less cryptanalytic history than lattices, and on a reference implementation that has not been built for production. Spek's copy of that implementation and its Rust FFI boundary have been audited only by AI models (Anthropic's Claude). No cryptographer, SQIsign author, or independent human auditor has reviewed them before launch.
A public key has exactly one valid encoding: every field half must be canonical (below the field prime), and the starting curve E₀, whose endomorphism ring is public, is never an acceptable key. A non-canonical encoding is a decode error, never a silently substituted value. A positive-only verification cache (262,144 entries) makes re-presented valid triples a hash lookup; it never caches failures and never changes a verdict.
8.2 Hashing: BLAKE3#
BLAKE3 is used for block hashes, election seeds, beacons, scores, slot secrets and their Merkle trees, transaction merkle trees, script and hidden-key commitments, the OP_BLAKE3 opcode, addresses, and the UTXO script index. Every consensus hash is domain-separated by a tag string, and transaction merkle trees tag leaves (0x00) and interior nodes (0x01) so no byte string can be valid as both; odd levels duplicate the last node, with the resulting duplicate-txid ambiguity closed by an explicit in-block duplicate-txid rule.
8.3 Block header#
| Field | Bytes | Role |
|---|---|---|
version |
4 | Integer selector for header/signature scheme; only 1 valid until an upgrade activates 2 |
prev_block |
32 | Identity chain: BLAKE3 of the parent's 743-byte header |
merkle_root |
32 | Domain-separated BLAKE3 merkle root of transactions |
election_seed |
32 | Beacon chain (§4.4) |
block_height |
4 | Equals timeslot; timestamp is derived |
miner_pubkey |
83 | SQIsign Level I key; input to score, seed, stake address and attempt identity |
horizon_start |
4 | J0, the first segment of the horizon the reveal belongs to |
slot_reveal |
32 | r_h (§4.1) |
slot_path |
320 | The 10-hash Merkle path from the reveal's leaf to the horizon root |
miner_signature |
200 | SQIsign signature over the first 543 bytes |
The signature covers the whole field set, not just the score inputs: signing only the score inputs would let an attacker substitute a different transaction set under a winning header. The encoding is a hand-written fixed layout (743 bytes, length pinned, trailing bytes rejected) so canonical hashing does not depend on a serialisation library. Headers grow by about 781 MB a year, independent of the number of miners. The horizon root is derived, never carried; the seed-phase ticket lives in a coinbase record, not the header, and there is no reserved header field.
8.4 Hidden keys and script commitments instead of Taproot#
Isogeny keys cannot be tweaked on a curve, so Spek's Taproot analogue, ScriptCommit, commits to the script tree with a hash:
commitment = BLAKE3(T ‖ T ‖ pubkey ‖ script_merkle_root), T = BLAKE3("SpekScriptCommit")
key-path-only outputs use commitment = 0x00…00
The default output hides even the key. A hashed output is 34 bytes, a witness version and a 32-byte program, the same shape as a Bitcoin P2WSH or P2TR output:
v1 (Level I): OP_1 <32 B> program = TH("SpekHashedCommit", pubkey83 ‖ commitment)
v2 (Level V): OP_2 <32 B> program = TH("SpekHashedCommitL5", pubkey169 ‖ commitment)
A key-path spend reveals the key and commitment next to one signature; a script-path spend reveals the leaf, its merkle proof and a control block, and the validator recomputes the commitment and the program before running the leaf in the script VM (§9.3). Leaves under a v2 output verify Level V signatures. Multisig, timelocks, hash locks and atomic swaps are expressible; key aggregation is not. The plain ScriptCommit output (OP_1 <83-byte pubkey> <32-byte commitment>, 118 bytes) remains valid for outputs that want the key visible.
Addresses are Bech32m with human-readable part spek: witness version 1 carries a 32-byte hashed program or the 115-byte plain program, and version 2 a 32-byte Level V program. Decoding re-encodes and requires a byte-exact match, so each output script has exactly one lowercase address string. Wallets use the hashed form for every receive, change and coinbase address, so a public key is exposed only when its output is spent, except a stake key, which every header it signs makes public.
8.5 Crypto-agility#
There is deliberately no per-transaction algorithm menu and no runtime negotiation: at any height exactly one scheme is valid for a given output form or header version, which closes downgrade and algorithm-confusion attacks. The witness version selects the output scheme (1 = Level I, 2 = Level V; versions 3–16 are reserved), and the block version selects the header scheme, activated through the height-indexed upgrade schedule. Because the signed message commits to every spent output's locking script, a signature for a scheme-X output can never be reinterpreted under scheme Y. A hash-committed output is exactly the shape other chains are retrofitting for post-quantum migration; Spek ships with it, and with a second, larger parameter set already live for custody.
9. Transactions and scripts#
9.1 Model#
Spek is a UTXO ledger. Values are unsigned 64-bit integer bits (10⁸ bits per coin); there is no floating point anywhere in the protocol or its RPC. Exactly four output forms are admissible at creation: hashed Level I (34 bytes), hashed Level V (34 bytes), plain ScriptCommit (118 bytes), and zero-value OP_RETURN data carriers, which never enter the UTXO set. There is no pay-to-pubkey form. Everything else is rejected at both block and mempool validation, because an unspendable output is a permanent fund burn.
The transaction id is the BLAKE3 hash of every field except the witnesses, so it is fixed before signing and no relay can change it, and the block merkle tree commits to a second hash over the full wire encoding, witnesses included. The signature message (a BIP143-style preimage) commits to the chain (its genesis block hash), inputs, outputs, every spent output's value and locking script, and the version, but not to the witness. The chain binding matters for offline signers: the same committed keys produce byte-identical scripts on every chain that commits them, so without it a signature made on a test or rehearsal chain for a plan naming a mainnet output would be valid on mainnet. This pairing means a spend's txid is stable only while its satisfying witness is unique, so the validator enforces the full canonicality set: canonical witness wire, minimal numbers, clean stack, minimal conditionals, and a single signature rule under which a non-empty signature that fails verification aborts the script rather than evaluating to false. Only SIGHASH_ALL semantics exist.
9.2 Time locks#
All locks are block-height locks; there is no timestamp variant because height is time. Absolute nLockTime, relative nSequence locks (BIP68 semantics, version ≥ 2), OP_CHECKLOCKTIMEVERIFY and OP_CHECKSEQUENCEVERIFY are enforced with one spending-height convention shared by block validation and mempool admission ("the height the transaction would be mined at"), and a parity test asserts the mempool is never more permissive than consensus.
9.3 Script VM#
A minimal stack machine for ScriptCommit leaves, dispatching on raw bytes: 82 executable opcodes frozen per leaf version (0xc0), with OP_BLAKE3 on Bitcoin's SHA-256 byte, OP_SHA256 at 0xbb so cross-chain atomic swaps can share one hash lock with Bitcoin-family chains, and OP_CHECKSIG/CHECKSIGVERIFY/CHECKSIGADD verifying SQIsign at the level of the output's witness version; the legacy CHECKMULTISIG opcodes are disabled. Limits: 10,000-byte scripts, 1,000-item stacks, 520-byte elements, 100-deep conditionals, 128-deep merkle proofs. Because verification is the dominant cost and an invalid signature is uncacheable, each input carries a BIP342-style signature budget (213 bytes per Level I verify, 421 per Level V verify, debited before the verify) so an attacker-authored leaf cannot amplify SQIsign work beyond roughly one verification per genuine signature's worth of witness bytes. Non-signature VM cost was measured at under 2 ms per input at the limits and needs no opcode counter.
9.4 Virtual size, fees and the mempool#
A Level V verification costs up to 6.119 times a Level I verification (the larger of the two backends' measured ratios), so each Level V input's witness bytes count ρ = 151/50 = 3.02 times in a transaction's virtual size. Blocks are limited by header + Σ vsize ≤ 2 MiB, and every fee rate and template budget is measured in vsize, so no block can take longer to verify than a full Level I block. For a transaction with no Level V input, vsize equals its wire size.
A relay fee floor decays geometrically per halving era (250,000 bits at launch), flat for transactions up to 3,000 vbytes and linear above, so permanent UTXO residency is priced while ordinary payments are not. The mempool is bounded (200 MB, 100,000 transactions by default, with hard minimums), performs admission in three phases (structural pre-check, input resolution, resolved verification), and re-judges height-coupled rules whenever the tip can move backwards. Block templates fill 85% of a block by package feerate and the rest oldest-first within a 10 KB package bound, and a miner's coinbase claims subsidy + fees, never more. One transaction shape is admitted outside the fee market: a guardian cancel of a confirmed staged covenant output cannot raise its own fee, so the mempool admits it without the capacity feerate contest, never evicts it, and lets nothing but another cancel replace it; it ages through the oldest-first share and moves to its front once its deadline is within a day, so a flood of higher-paying traffic cannot run out the veto window (section 10.2).
10. Monetary policy#
| 450M | 50 | 4.2M | 30M | 3,000 |
|---|---|---|---|---|
| hard cap (coins) | reward at block 1 | blocks per halving (~4 y) | genesis allocation | coinbase maturity (blocks) |
10.1 Emission#
reward(0) = 30,000,000 coins (one-time genesis allocation)
reward(h) = max(50 coins >> ⌊(h−1)/4,200,000⌋, 1 bit) clamped so Σ ≤ 450,000,000 coins
Halvings count from block 1, so the first occurs at height 4,200,001. The geometric series sums to ~420,000,000 coins; with the 30,000,000-coin genesis mint the cap is filled exactly, with no unallocated residual. The 1-bit tail avoids a discontinuity and only ever fills sub-coin rounding. Once cumulative issuance reaches the cap the reward is zero and miners are paid by fees alone; a fee-less zero-subsidy block uses a canonical zero-value OP_RETURN coinbase.
10.2 Genesis allocation (6.67%)#
| Outputs | Recipient | Amount | Unlock |
|---|---|---|---|
| vout 0 | Genesis message ("Make Crypto Decentralized Again") | 0 | Permanently unspendable |
| vout 1–15 | Founders (5.00%), 15 per-founder 2-of-3 vaults | 22,500,000 | Five annual tranches of 3 outputs (one per founder), years 1–5 |
| vout 16 | Stewardship fund launch slice | 1,500,000 | From coinbase maturity |
| vout 17–26 | Stewardship fund tranches, 10 × 600,000 | 6,000,000 | Years 2, 4, … 20 |
The open launch's ticket records follow these outputs in the genesis coinbase (§7.2).
Vesting is a UTXO-level consensus rule keyed on the output index: a spend of a locked genesis output is invalid below its unlock height regardless of the transaction's own locktime, so a leaked founder or council key cannot move a tranche early. Every allocation output is a 34-byte Level V hashed vault (witness version 2) over a provably unusable (NUMS) internal key, rejected by both the encoder and an explicit gate, so no key-path spend exists; every allocation, council and guardian key is SQIsign Level V. Each founder's outputs sit under that founder's own 2-of-3 multisig; the stewardship fund sits under one 3-of-5 council multisig, with no reserve keys and no timelocked recovery path (succession is a physical key handoff under the fund's charter). Allocation value is held by a consensus covenant: it can leave a vault only by being staged, a staged output can be released only after 86,400 blocks (~30 days), and inside that window its guardians can cancel it back to the vault (3-of-5 fund guardians for the stewardship fund, 1-of-3 per founder for that founder's vault). Consensus guarantees the public 30-day veto window; it does not constrain where a released output goes, so what guardians check before letting a staged spend through is their published policy, not a protocol rule. There is no premine beyond this allocation and no on-chain treasury voting at launch.
11. Network protocol#
11.1 Transport and topology#
Spek runs on libp2p over QUIC only (TLS 1.3 built in, stream multiplexing, 0-RTT reconnection for mobile and home users). Connection migration is disabled at the application layer so IP-keyed rate limits and bans remain meaningful; roughly 5–15% of hosts behind UDP-blocking networks are an accepted exclusion. Peers are found from a hard-coded bootstrap list and a Kademlia random walk (aggressive for ten minutes after start, then every ten minutes). Every connection, however discovered, passes diversity gates: at most one peer per IP, at most two per network group, a 34% cap on any hosting autonomous system, 20 outbound and 60 total on mainnet. Native IPv6 is rejected at launch.
11.2 Propagation#
Headers flood over FloodSub and are verified off the event loop on a priority validation pool (signature 3–6 ms plus the reveal walk, never awaited inline) with per-peer in-flight caps, after the one-hash claimed-score pre-filter has dropped any header that could not rank; bodies are pulled, never pushed. Seed-phase ticket records travel on their own topic and are checked on the mempool lane, never on the slot-critical path. Current-slot pulls go compact-first: the server sends short transaction ids keyed by a server-chosen nonce (a block-hash-only key would let an offline collision grind force the fallback on every node identically), the client reconstructs against its mempool and must reach the header's merkle root, and every failure converts to a classic full-block request. Full blocks remain the unconditional fallback and the initial-download path.
The scheduler guarantees that the current slot's rank-1 block validation gets service ahead of download, catch-up and mempool work. Starving live propagation is the one scheduling failure that costs the network mining power, and an earlier ordering that strictly demoted catch-up instead produced 11-deep self-forks under load; propagation and catch-up are co-equal above download and mempool admission, with a reserved lane for the slot-critical verdict.
11.3 Misbehaviour#
Violations are counted per canonical IP (IPv4-mapped IPv6 normalised at the type level, so evasion by address form is unrepresentable), in three classes (header, transaction, block) with their own thresholds. Bans escalate 1 day → 4 days → 90 days and persist to disk; every ban is timed, so the bounded ban table always has an eviction victim and a strike memory that survives expiry keeps a returning offender on the top rung. Expiry runs on the verified clock so a stepped host clock cannot shorten them. Divergence across a checkpoint caps at 16 days so an honest node on the wrong side of a fork can always return. Objective invalidity (a bad signature, a reveal outside its horizon, an invalid ticket record) is a strike. So is a header that builds directly on the receiving node's tip but is ineligible there or carries a seed that does not derive from the tip's: that verdict depends only on the tip's ledger, so every node on that tip reaches it, and an honest node publishes a header only after ranking it on its own tip, so no honest relayer ever delivers one. Five header strikes inside 18 hours ban the address. Drops that could be the relayer's fault rather than the producer's, or that depend on a ledger view that differs across forks (a negated attempt, no stake at the derived address for a header off the tip, a collision), are silent, never strikes.
12. Synchronisation and checkpoints#
12.1 Initial block download#
A joining node downloads headers in batches and runs signature and horizon checks in parallel, then pulls bodies in order into a staged store and applies them strictly sequentially with the same authoritative validator used at the tip, which derives each header's horizon root and checks its stake output or ticket record against the ledger it has built. Each candidate chain resolves its own beacons from its own headers. There is no "assume-valid" height: every block at every height is fully SQIsign-verified. Download starts only with at least 8 sync peers, or 1 if a hard-coded bootstrap seed is connected; the bar cannot be lowered by any flag or RPC, by design, so an eclipse cannot relax it. The node keeps mining and relaying while deferred.
12.2 Mining is never suppressed by download#
If a candidate chain could pause mining, an attacker could advertise a fake heavy chain to halt honest block production and exploit the weakened window. Mining on the main chain continues unconditionally; if the download selects a better chain, the reorg orphans the node's blocks naturally.
12.3 Checkpoints#
Checkpoints sit on a 4,000-block grid shared by the checkpoint-handshake identity, the served checkpoint chain, the durability fsync, and the self-produced UTXO snapshot. Hard checkpoints pin settled history for nodes that have none of their own (a new joiner, a resync, a node offline for days): exchanged work claims are unauthenticated and could otherwise lock such a node onto a fabricated heavier chain. They are permanent weak subjectivity, not a seed-phase measure. Unbonded stake cannot bound signing after the fact: a key that held stake at a fork point can sign a branch from that point at any later time, even after its owner sold the coins, and a private branch knows its own future beacons. Pins are therefore never retired. Each release compiles in boundaries the running network already holds as restoration pins, and between releases a pin file extends them. The node authenticates that file itself: it must name the network and genesis and carry signatures from a threshold of the checkpoint signers compiled into the binary, and it may add to the compiled-in table but never contradict it. The mitigations are threshold signing by signers who each confirm the hash on their own fully validated node, reproducible builds, and independent reproduction of every published pin.
13. Node architecture and storage#
The reference implementation is a Rust workspace of 20 crates: config, util, crypto (with a separate wallet-only seeded-keygen crate so the node binary never links a DRBG, and a build crate for the vendored SQIsign stacks), RandomX, core types and chain state, consensus, script, mempool, FloodSub, network, storage, RPC, node, wallet, covenant and guardian tooling, a test client and an operator GUI. Production code contains no unwrap; errors are typed. Tests favour end-to-end harnesses over unit tests.
State lives in RocksDB (headers, blocks with undo data, UTXO set, main-chain index, chainwork, seed-phase ticket records, metadata, bans, snapshots), written as atomic batches without per-block fsync (a disk stall on the commit path would delay honest propagation; durability is recovered at checkpoint boundaries). The UTXO set is fully resident with a 20-byte script index for O(log N) lookups and a value-ordered stake index for the eligibility walk. Three operating modes exist: archive, pruned (default, 100 GB target, 20,000-block floor; pruned nodes can mine), and assume-UTXO from a snapshot. An archive year is roughly 2 TB at full 2 MiB blocks; a pruned node around 15–20 GB plus about 0.7 GB of headers per year.
One authenticated RPC surface is served: JSON-RPC (port 4521), with one 128-permit admission semaphore and integer-only values. The API is intentionally not Bitcoin-compatible: there is no difficulty, nonce, bits, or median time; headers expose miner_pubkey, miner_signature, election_seed, horizon_start, slot_reveal and slot_path, and getstakeaddress reports each horizon's stake address, eligibility window and counted output.
14. Governance and upgrades#
Spek has one upgrade mechanism: a monotonically increasing block version activated at a fixed flag height in a compile-time-checked schedule, empty at launch. A signature-scheme or header-format change is always a hard fork and uses this path; rule tightenings use it too, relying on off-chain coordination. There is no BIP-9-style version-bit signalling: it coordinates soft forks, and a slot-winner signal is a weak, grindable readiness proxy. If a soft fork later needs on-chain coordination it will be designed against Spek's actual consensus, preferably with guaranteed eventual activation. Process governance is off-chain and social; every full node enforces every rule; each operator independently opts into or declines an update.
15. Threat model#
| Threat | Mechanism | Residual |
|---|---|---|
| Score grinding | Score inputs (key, commitment, reveal) all fixed before the beacon; seed binds only parent seed + key + reveal; no producer-malleable bytes | Withholding at a beacon height: 0.3–0.6% of an attacker's own wins over all epochs, no compounding |
| Stake shifting into a pre-chosen key | Stake counts only below a cutoff a full epoch before the beacon height | Closed |
| Commitment copying / reveal replay | Key bound into the score preimage and every commitment leaf | Closed |
| Predicted winners (targeted DoS, bribery) | Scores unknown until each header is published | None for live slots |
| Sybil (post-seed) | One output ≥ 80% of reward per draw, aged 24–36 h | Attack share = capital share; concentration risk is economic, not protocol |
| Sybil (seed phase) | RandomX ticket per stake address per 3-day round, recorded on chain before it counts; Spek-variant VM | Launch seats: the search opens at a Bitcoin block, so every searcher starts together and no record exists before it; the organiser could leave a listed entry out of the sealed list, which the public listings and the witnessed seal digest expose. No party has an informed abort: the launch beacons depend on a Bitcoin block mined after the seal |
| Private-branch rewrite | Fixed draws; pins bound running nodes; checkpoints bound fresh nodes | A private branch chooses its own beacons: break-even attacker share 0.45–0.47 within the pin; about 0.42 for a fresh node without checkpoints |
| Bought or dormant keys signing old branches | Restoration pins and compiled checkpoints | Unbonded stake cannot bound this; such keys add to an attacker's share at no cost to the seller |
| Clock steering | Authenticated Roughtime quorum, operator diversity, RAW-clock projection, 5 s step bound, mining hold | A quorate colluding cohort can walk the clock 5 s per sync; pool composition is the security parameter (§17) |
| Equivocation | One entry per key per attempt; different content → slot negated, proof gossiped | None beyond forfeited slot |
| Header / body flood | Claimed-score pre-filter, structural gates before signature; per-peer verify caps; pull-only bodies; rate limits; escalating IP bans | Griefing bounded; winning power unaffected by flood rate |
| Stake-address dust | One counted output per draw, value-ordered index | Each extra step costs a full stake unit |
| Validation-cost amplification | Per-input signature budget debited before verify; Level V witness bytes weighted by ρ; positive-only cache; rank-1-only full validation | Adversarial leaf ≈ 1.27× honest signature density |
| Deep reorg / long range | Depth-4 comparison, bounded rollback, untruncated chainwork, restoration pins, checkpoint barrier | Partitions longer than the pin lag need operator recovery |
| Fake-chain mining halt | Mining never suppressed by download | None |
| Eclipse during sync | 8-peer minimum (1 with hard-coded seed), diversity caps, no relaxing flag | Small deployments depend on seed reachability for deep-fork resolution |
| Fund burn | Output-form whitelist; canonical keys and addresses | Leaf authors remain responsible for witness-injective scripts |
| Witness malleability | Identities exclude witness bytes: txids and block hashes do not cover signatures, the merkle root commits to the exact witnesses; canonical wire, minimal numbers, clean stack, minimal-if, one signature rule | SQIsign signatures themselves stay malleable (no strong unforgeability, by design): a mempool copy may differ from the one mined, which costs a re-verify, never an identity |
| Harvest-now quantum attack | Keys hidden behind hashes until spent; Level V for long-lived custody | Stake keys are public in headers; the payout rule keeps one stake unit per key |
| Quantum or classical break of SQIsign | SQIsign everywhere; independent slot secrets; reserved version selectors | Depends on SQIsign holding (a NIST Round 3 candidate, not a NIST standard; implementation audited only by AI models); a Level I break allows equivocation negations and history rewrites down to the pin, not live-slot theft; migration is a scheduled hard fork |
16. Comparison#
| Bitcoin (PoW) | Typical bonded PoS | Spek | |
|---|---|---|---|
| Category | Proof of work | Bonded proof of stake | Unbonded proof of stake (RandomX tickets for the first 45 days) |
| Block schedule | Poisson, ~10 min | Slots, often 12 s with committees | Fixed 30 s slots; a block (or null) exists for every slot |
| Producer selection | First valid nonce | VRF / committee rotation over bonded stake | Max hash distance over a beacon, the key and a committed per-height secret; one draw per stake output |
| Leader schedule known in advance | No | Often an epoch ahead | No: each score is unknown until its header is published |
| Cost of a ticket | Energy per attempt, perpetual | Bonded, slashable capital | One aged output at the stake address (a threshold, never locked); RandomX compute in seed phase |
| Penalty for attacking | Hardware and energy spent | Slashing | No slashing; the attacker's purchases raise the price it pays, and its held stake loses value when the attack is seen |
| Honest opex | Dominant | Low | ~Zero beyond a node |
| Settlement | Probabilistic, ~60 min convention | Finality gadget, minutes | Confirmations sized to an attacker share (68 min against one third); restoration pin at 33–67 h; checkpoint barrier |
| Timestamp | Declared, MTP rule | Slot-derived | Derived; no header field |
| Signatures | ECDSA / Schnorr | BLS / Ed25519 | SQIsign Level I, Level V for custody (post-quantum) |
| Public keys on chain | Exposed by many output types | Exposed | Hidden until spent (stake keys excepted) |
| Empty-network liveness | Chain halts | Skipped slots | Deterministic null block, score 0 |
| Upgrades | BIP-9 / BIP-8 signalling | Varies, often on-chain | Block version + flag height only |
Where Spek is genuinely strong. None of the chains above has all of these at once:
- no ASIC race;
- no lock-up or slashing;
- an ordinary computer is enough (the published minimum is 4 dedicated cores, 8 GB of RAM and an SSD);
- no recurring fee just to take part: the only participation cost is one ordinary transaction fee to fund a stake output whose slot-secret horizon covers about 8.4 years (1,024 segments of 3 days), plus one renewal transfer the node makes about a year before that horizon ends;
- each stake unit gets exactly one draw per slot, so beyond owning coins there's no advantage to being big.
17. Known limitations and open items#
A white paper that omits its own open problems is marketing. As of this draft:
- SQIsign is not a NIST standard. It is a Round 3 candidate in NIST's additional-signature process (§8.1). If later cryptanalysis weakens it, or NIST drops it, Spek's only remedy is a signature-scheme migration through a hard fork (§8.5, §14). Every operator chooses for themselves whether to adopt that fork.
- No independent audit. The code, including the vendored SQIsign and RandomX C and the FFI boundary, has been audited only by AI models (Anthropic's Claude). No human security firm, cryptographer, or SQIsign author has reviewed it. Mainnet is its first exposure to outside attackers. The uniqueness of the slot reveal (§4.1) is the property most worth a cryptographer's time: a related hash-based construction for proof-of-stake election, X-VRF, was later broken on exactly that property.
- Signing after the fact. Unbonded stake cannot stop a key that held stake at a fork point from signing a branch from that point later, including keys whose owners have since sold. Only restoration pins (running nodes) and compiled checkpoints (fresh nodes) bound it. On its own private branch an attacker also knows each beacon before that beacon's stake cutoff; simulation puts the break-even share at 0.45–0.47 for a branch inside the pin, and at about 0.42 (with a million candidate fits) for a fresh node that has no checkpoint past the fork. Comparing deep forks by early-window density (the Ouroboros Genesis rule) was evaluated and not adopted, because ordinary honest null slots let a dormant-stake branch win some early window far below the modelled threshold.
- Pins advance without network evidence. A restoration pin moves with the node's own tip, so a node partitioned, or mining alone, for longer than the pin lag (33–67 h) pins its own branch and refuses the other side on reconnect. Advancing pins only when the interval shows enough of the network producing it is planned follow-up work.
- No fast finality. Settlement is by confirmations (Appendix B) until the pin; there is no finality gadget. A committee-certificate design that needs no slashing is a candidate for a later upgrade.
- Download-time checks during the seed phase. Seed-phase eligibility needs the block bodies that hold the ticket records, so the header stage of a download checks signatures and horizons but no RandomX work. A fake seed-phase header chain is therefore as cheap to build as a fake post-seed one; pins, block-apply validation and the download candidate budgets bound both.
- Stake keys are hot and public. Every header shows its stake key, and the key lives on the mining machine. The payout rule keeps one stake unit per horizon at that address (two for the year before a renewal), and everything else goes to a hidden-key payout address. A node with no payout address configured keeps paying its stake address and warns daily.
- Roughtime operator count. The compiled-in pool has four independent operators against a quorum of three: any one operator may be dark or key-rotated and syncs still complete, but with one dark every remaining operator must answer, and two colluding operators can steer the vote median (by at most the per-sync step bound, per applied sync). Widening the operator set with independently operated servers is the remedy, never lowering the quorum.
- Network-wide multi-slot outage wedges the chain until an operator fills nulls (§5). Single-node outages self-heal.
- Withholding the top five. A node ranks at most five peer headers per slot plus its own (§5). If the five best headers of a slot are all withheld, every node that holds them settles the same canonical null: no body in the top five is committable, and a node's own block ranked sixth is skipped because no peer retains it, so committing it would leave each miner on a different block. Safety holds (one block per height; a node the withheld headers did not reach commits a real block, and any real block outranks the null), the slot's liveness is lost, and the withholder forfeits the slot's rewards. With an attacker share p of the draws for a slot, about p⁵ of slots are nulled; nulling a fraction f takes p = f^(1/5), that is p/(1−p) times the honest draw count: about 0.66× for 1% of slots, 1.7× for 10%, 6.7× for 50%. These are model estimates (independent uniform scores, every withheld header reaching every node), not simulation results. Each draw costs a recorded seed ticket per round in the seed phase and an aged stake output after it (§7).
- Multiple draws per operator. One key per node is policy, not consensus. An operator holding K funded stake outputs fields K draws for a one-time, refundable K × requirement. Whether the network's cost structure ends up capital-at-rest rather than consumed-opex turns on this, and it deserves explicit economic treatment rather than reliance on hardware scaling.
- Launch-time constants. Allocation keys, the stewardship commitment, the sealed launch ticket set, the launch nonce and the genesis timestamp are fixed by the open launch (§7.2); every one can be recomputed from public inputs.
- Not built, by decision: SPV proofs (the merkle domain separation is the prerequisite; wire hardening remains), DNS-seed discovery, native IPv6, data-availability sampling (removed, with no reserved header field), on-chain soft-fork signalling, post-sync assume-UTXO verification, NTS (licence-incompatible library), cross-parent double-sign forfeiture (a block's score does not depend on its parent, so signing two parents gains nothing a forfeiture would remove).
- Hard checkpoints are a permanent trust assumption for nodes with no history of their own (weak subjectivity): such a node trusts the release's compiled-in pins and the checkpoint signer set. They have no sunset; the signer set, its threshold and the signing procedure are part of the launch disclosure.
Appendix A. Parameter reference#
| Parameter | Value | Notes |
|---|---|---|
TIMESLOT_DURATION |
30 s | Block time; height = slot |
PHASE2_START_MS |
6,000 | Headers-only before; bodies after |
MAX_BLOCK_SIZE |
2,097,152 B | Header plus the sum of transaction vsizes |
MAX_TRANSACTIONS_PER_BLOCK |
8,000 | |
PROPAGATION_FORK_WINDOW |
4 | Deepest fork resolved on the propagation path; deeper forks go to download |
PROPAGATION_FORK_MAX_ROLLBACK |
5 | Hot-path reorg bound |
CHECKPOINT_GRID_INTERVAL |
4,000 blocks | ~33.3 h; restoration pin trails the tip by 33–67 h |
election_epoch_blocks (L) |
1,440 | 12 h; beacon epoch |
slot_segment_blocks (S) |
8,640 | 3 days; one slot-secret hash chain |
SLOT_HORIZON_DEPTH (d) |
10 | 1,024 segments per horizon, ~8.4 years |
| Stake age | 2L+2 to 3L+1 blocks | About 24–36 h from creation to first counted height |
| Horizon renewal lead | 122 segments | ~1 year; node policy, operator-configurable |
coinbase_maturity |
3,000 blocks | ~25 h |
initial_block_reward |
50 coins | Block 1 |
halving_interval |
4,200,000 | Counted from block 1 |
tail_reward |
1 bit | Floor until cap |
max_supply |
450,000,000 coins | Exactly filled |
genesis_reward |
30,000,000 coins | Founders 22.5M + stewardship 7.5M |
vesting_interval_blocks |
1,051,200 | ~1 year |
seed_phase_blocks |
129,600 | 45 days |
seed_ticket_round_blocks (W) |
8,640 | 3 days; 15 rounds |
SEED_TICKET_TARGET_D |
2^228 | ~3 tickets/round on a 2018-era 8-core desktop |
ticket_records_per_block_max |
16 | Seed-phase coinbase records per block |
| Coin requirement | max(80% reward, 0.1 coin) | Post-seed; one output at the stake address, aged |
FEE_FLOOR (era 0) |
250,000 bits | Flat ≤ 3,000 vB, linear above |
| ρ (Level V witness weight) | 151/50 = 3.02 | vsize multiplier for Level V input witness bytes |
| SQIsign Level I pk / sig / sk | 83 / 200 / 270 B | Headers, mining keys, default outputs |
| SQIsign Level V pk / sig / sk | 169 / 406 / 549 B | Opt-in custody; allocation, council, guardians |
| Miner key file | 385 B | Level I secret key, public key, 32-byte slot seed |
| Header / signed prefix | 743 / 543 B | |
| Hashed output / plain ScriptCommit | 34 / 118 B | |
| Roughtime sync interval | 1 h learning, 4 h once the rate is confirmed (≤ 5 ppm) | Rate correction clamped at 250 ppm, at most 50 ppm per sync |
| Roughtime quorum / outlier / step | 3 operators (floor 3) / 2 s / 5 s | One vote per operator |
| Peers: per IP / per netgroup / ASN cap / outbound / total | 1 / 2 / 34% / 20 / 60 | Mainnet |
| IBD minimum peers | 8 (1 with bootstrap seed) | Not overridable |
| Ban ladder | 1 d → 4 d → 90 d | Checkpoint divergence capped 16 d |
| Ports (mainnet) | 4520 p2p / 4521 JSON-RPC | Testnet 14500s, regtest 24500s |
| Script limits | 10,000 B / 1,000 items / 520 B / depth 100 / proof 128 |
Appendix B. Confirmations against an attacker share#
Probability that a transaction at k confirmations is ever reversible, for an attacker holding share q of the draws who may choose its fork point in hindsight (RFC-0012, Appendix B):
| q | k = 6 | 10 | 30 | 60 | 120 | 240 |
|---|---|---|---|---|---|---|
| 0.10 | 3.6e-4 | 4.8e-6 | 3.8e-15 | 1.3e-28 | 2.3e-55 | ~0 |
| 0.20 | 1.8e-2 | 2.4e-3 | 2.0e-7 | 2.2e-13 | 3.7e-25 | 1.4e-48 |
| 0.33 | 2.4e-1 | 1.3e-1 | 7.3e-3 | 1.4e-4 | 6.4e-8 | 1.8e-14 |
| 0.40 | 5.5e-1 | 4.3e-1 | 1.5e-1 | 3.5e-2 | 2.3e-3 | 1.3e-5 |
Confirmations for under one reversible slot per century (1,051,200 slots a year):
| q | 10% | 20% | 25% | 30% | 33% | 40% | 45% |
|---|---|---|---|---|---|---|---|
| k | 16 (8 min) | 37 (18 min) | 58 (29 min) | 95 (48 min) | 136 (68 min) | 412 (3.4 h) | 1,686 (14 h) |
Parameters marked as placeholders are substituted at launch. This document describes the implementation as it exists and is not an offer of any kind.