Expand description
The engine’s only shared logic: the Merkle top and the Ed25519 certificate.
§The problem
Every batch of 128 one-time keys needs one certificate: an Ed25519 signature over the batch’s Merkle root, so that a verifier who trusts the signer’s Ed25519 public key can trust all 128 keys. The keys of a batch are spread over all the slices, and each slice can only hash its own subtree. Somebody has to join the subtree roots into the batch root and get it signed. That is this unit, and it is the only piece of logic all slices share.
slice 0 ─ subtree root r0 ─┐ ┌─▶ slice 0: certificate
slice 1 ─ subtree root r1 ─┤ Merkle top ──▶ Ed25519 ──▶ ├─▶ slice 1: certificate
… ├─▶ (this unit) (this unit) │ …
slice 7 ─ subtree root r7 ─┘ └─▶ slice 7: certificate§Joining the subtrees
Each slice builds one subtree of every batch (see
KEYS_PER_SLICE). This unit joins the
SLICES subtree roots into the batch root, using the
classic array (“heap”) layout of a binary tree:
tree[1] root (index 1 is the root)
tree[2], tree[3] ┌──┴──┐ parent i = H(tree[2i] ‖ tree[2i+1])
… children of i are 2i and 2i + 1
tree[SLICES + s] slice roots r0 … r(SLICES−1)For the 8-slice build that is 4 + 2 + 1 compressions (levels 5, 6, 7); for 2 slices it is a single one. They run one tree level at a time.
8-slice heap, by index
[1] batch root
[2] [3]
[4] [5] [6] [7]
[8] [9] [10] [11] [12] [13] [14] [15]
r0 r1 r2 r3 r4 r5 r6 r7The heap layout makes “my sibling” and “my parent” plain bit operations
(i ^ 1, i >> 1), which is what hardware wants.
§Signing and handing out
envelope DSIGFP01 ‖ 1 ‖ 128 ‖ epoch ‖ batch ‖ root ──▶ Ed25519 ──▶ signatureEvery slice then receives the same commitment and signature, plus the
TOP_LEVELS siblings above its subtree: at level
t, tree[((SLICES + s) >> t) ^ 1] (the other child of its ancestor).
With the siblings inside its own subtree, a signature carries the full
7-sibling inclusion proof.
§Why it is pipelined
The Ed25519 core is barrel-threaded: one signature takes about 25,000
cycles from request to result, but with ~32 requests in flight it
completes one every ~895 cycles (the vendored Ed25519SignCoreSmall; the
earlier 1,1,1,0 profile managed ~758). One certificate covers a batch of
128 keys, so 30M signatures/s at 205 MHz needs one every
128 × 205 M / 30 M ≈ 875 cycles: right at the core’s rate, which is why
this unit must keep many batches in the core at once:
roots ─▶ Merkle front ──request(tag = batch)──▶ Ed25519 core (≤ 32 in flight)
│ ~100 cycles/batch │ results, any order
▼ ▼
records[batch mod 32] signatures[tag mod 32], done bit
(8 roots, 6 inner nodes, envelope) │
└──────────────┬──────────────────────────┘
▼
distribution, strictly in batch order ──▶ eight slices(Barrel-threaded means the core interleaves many independent signatures
through one datapath, so it is slow per request but fast in total. The
planner, crate::config, uses the same measured ~895 cycles per
signature with ≥ 32 in flight.)
The front never waits for a signature, only for a free ring entry. A
completion is accepted only if its tag names a requested, not yet completed
batch; anything else is a sticky fault (the core’s contract: reject unknown
or reused tags). IN_FLIGHT = 32 matches both the core’s credits and the
slices’ 32-batch key caches. busy_cycles counts cycles with any batch
inside the unit.
§Tags and in-order delivery
Each request carries its relative batch number as a tag, and the core
returns the tag with the signature. Results may come back out of order, so
the unit files each signature by tag mod 32 and sets a done bit. The
slices, however, spend keys strictly in order, so certificates must leave
in order. Distribution therefore waits for the done bit of the oldest
undistributed batch, dist, even if younger batches are already done.
ring entry: 0 1 2 3 4 … dist = 1, front = 5
batch: - b1 b2 b3 b4 (b0 already distributed)
done bit: - 0 1 1 0 b2, b3 signed early: they wait
▲ distribution waits here until b1's signature returns
accepted tag = a batch in [dist, front) whose done bit is still 0Each slice offers at most two subtree roots ahead (the root queue of each slice holds two); roots arrive in batch order because each slice finishes its keys in order.
Re-exports§
pub use crate::SLICES;
Structs§
- Cert
Config - Configuration, stable while running.
- Cert
Control - Resettable control.
- Cert
Data - Resetless datapath of the Merkle front.
- Cert
Input - Inputs from the slices.
- Cert
Output - Outputs to the slices.
- Cert
Record - Everything about one batch except its signature, kept until distribution.
- Cert
Unit - The shared certificate unit.
Enums§
- Phase
- Phase of the Merkle front (one batch at a time, about 100 cycles each).
Constants§
- FLAGS_
SINGLE_ BLOCK - Flags for a 64-byte single-block root hash.
- IN_
FLIGHT - Batches that may be between “signature requested” and “distributed”.
- MAX_
OUTSTANDING - Batches the front may have outstanding:
IN_FLIGHT, or 1 with the measurement-onlycert-serialfeature, which reproduces the earlier one-batch-at-a-time unit so its cost can be measured (see the crate page, “How it got fast”). - ROOT_
QUEUE - Subtree roots each slice may have waiting in the cert unit’s root queue.
- TREE_
NODES - Heap slots of the Merkle top: index 0 unused,
1..SLICESinner nodes,SLICES..2·SLICESslice roots.
Functions§
- cert_
unit_ kernel - The certificate unit’s wiring.
- pair_
request_ kernel - A single-block BLAKE3 root hash of
left ‖ right. - words64_
to_ bytes_ kernel - Little-endian bytes of eight 64-bit words.