Expand description
The verifier’s chain block: three recirculating chain lanes.
§The problem
To check a W-OTS+ signature, chain i must be hashed from its revealed
node to the end of the chain: 3 − digit_i steps, where the digit comes
from the message digest. Over a signature’s 68 chains that is 105 steps on
average, but anything from 0 to 3 per chain, and only known once the
digest is back. The signer’s chain engine (crate::slice_chain) cannot
be reused as it is: it pushes every chain through all three steps in a
fixed pipeline, which here would waste half of every lane.
§The idea: lanes that recirculate
Each lane is the signer’s chain lane (ChainLane, 20 cycles, one step
per clock), closed into a ring. What leaves the lane either goes round
again (it needs another step) or is finished (it is a chain end). A slot
that comes back empty, or carries a finished chain, takes the next new
chain. So every clock does a useful step whenever there is work:
┌───────────────────── recirculate (depth < 3) ─────────────────┐
│ │
nodes ─▶ fetch ─▶ skid ─▶ ┌mux┐ ─▶ [ chain lane, 20 cycles ] ─▶ ─ R ─▶ ───┘
(bank) │ FIFO └───┘ ▲ salt(depth) │
│ │ depth = 3
└── digit = 3: already an end ─▶ end queue ─┐ ▼
└──▶ end banks ─▶ row assembler
Chains whose digit is 3 need no step: they wait in their own small queue
for a cycle when the end port is free, so they never take a lane slot.
(Sharing one queue cost a lane slot per such chain, about six per record
and lane: 41.7 instead of about 35 cycles per record, measured in native
simulation.)Chain c always uses lane c mod 3, so each lane owns its chains’ node
and end storage and no two lanes ever write the same RAM.
§Storage
All RAMs are indexed by context (the slot of a record in flight, 0..64)
and by k = c / 3, the chain’s number within its lane (0..23):
| RAM | Per lane | Address | Written by | Read by |
|---|---|---|---|---|
| node banks | 2 (k even, odd) | ctx·16 + k/2 | the loader | this lane’s fetch |
| end banks | 5 (k mod 5) | ctx·8 + k/5 | this lane | the row assembler |
| digits | 1 | ctx | the hasher (digest result) | fetch, once per context |
pk_nonce | 1 (+1 for the row assembler) | ctx | the loader | fetch, once per context |
Two node banks let the loader write up to four nodes per beat (four
consecutive chains fall in four different banks, c mod 6). Five end
banks per lane, fifteen in all (c mod 15), let the row assembler read
any five consecutive chain ends at once: a 64-byte commitment block
touches at most five chains.
§The row assembler
The endpoint commitment hashes pk_nonce ‖ end₀ ‖ … ‖ end₆₇ (1,240
bytes) as 20 BLAKE3 blocks. The hasher asks for block j of a context;
two cycles later this block delivers it, built by a fixed byte map
(ROW_SOURCE) from the fifteen end banks and the context’s pk_nonce.
No block is ever stored: each is assembled when the BLAKE3 lane is about
to use it.
§Contract
- A context’s nodes and
pk_nonceare written before its digits arrive. - Digits arrive at most once per context between retirements.
ChainsOutput::done[ctx]rises once all 68 chain ends ofctxare stored, and stays high untilChainsInput::retirenamesctx.- A row request for
ctxis made only whiledone[ctx]is high.
Structs§
- Chains
Input - Inputs of the chain block.
- Chains
Output - Outputs of the chain block.
- Digits
Event - The digits of one record, from the hasher’s digest result.
- Lane
Digits - One lane’s share of a record’s digits: digit
kbelongs to chainlane + 3k. - Node
Write - One node write into a lane’s node bank.
- Nonce
Write - A context’s
pk_nonce, written once per record by the loader. - Retire
- A context leaves the unit: its counters restart from zero.
- Ring
Input - Inputs of one lane ring.
- Ring
Output - Outputs of one lane ring.
- RowRequest
- Row request from the hasher: commitment block
rowof contextctx. - Verify
Chains - The three rings, the row assembler and its
pk_noncecopy. - Verify
Ring - One chain lane closed into a ring, with its node, end, digit and nonce RAMs.
Constants§
- CONTEXTS
- Records in flight per unit; a record’s context is its number mod 64.
- END_
BANKS - End banks per lane (
k mod 5). - LANES
- Chain lanes per unit. Chain
cruns on lanec mod LANES. - LANE_
CHAINS - Most chains one lane serves per record: lanes 0 and 1 get 23, lane 2 gets 22.
- NODE_
BANKS - Node banks per lane (
k mod 2). - NONCE_
SOURCE - Source index of the context’s
pk_nonce(padded to 18 bytes). - ROWS
- 64-byte blocks of the endpoint commitment (16 + 68 × 18 = 1,240 bytes).
- ROW_
LATENCY - Cycles from a row request to its row on the output.
- ROW_
SLOT - For block
jand end banke, thek / 5of the chain the block needs from that bank (0 if it needs none). - ROW_
SOURCE - Where byte
pof commitment blockjcomes from:(source, byte). - ROW_
SOURCE_ BANK - The source half of
ROW_SOURCE(kernels index plainusizetables). - ROW_
SOURCE_ BYTE - The byte half of
ROW_SOURCE. - ZERO_
SOURCE - Source index of an all-zero node (bytes past the 1,240-byte message).
Functions§
- ring_
tag_ kernel - Packs a ring tag: context (6 bits), chain (7), depth (2),
k mod 5(3),k / 5(3). - verify_
chains_ kernel - The chain block’s logic: route writes to the rings, assemble rows.
- verify_
ring_ kernel - One ring’s logic, once per clock.