Skip to main content

Module cert_unit

Module cert_unit 

Source
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    r7

The 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 ──▶ signature

Every 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 0

Each 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§

CertConfig
Configuration, stable while running.
CertControl
Resettable control.
CertData
Resetless datapath of the Merkle front.
CertInput
Inputs from the slices.
CertOutput
Outputs to the slices.
CertRecord
Everything about one batch except its signature, kept until distribution.
CertUnit
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-only cert-serial feature, 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..SLICES inner nodes, SLICES..2·SLICES slice 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.