Expand description
Bit-exact software model of every compression a slice issues.
§Why this file exists
The hardware never calls “BLAKE3 of this message”. It feeds one 64-byte
compression at a time into its BLAKE3 lane, and it has to choose every
chaining value, counter, length and flag itself. One wrong flag bit gives
a different hash, and a key that no verifier accepts. The high-level
software reference cannot help locate such a bug,
because it calls a BLAKE3 library that hides those details.
So this file sits in between. It rebuilds a whole key out of the same single compressions the hardware issues, written as plain Rust, one function per kind of request. A test proves the result equals the reference. After that, this file is the readable specification of what the RTL must send, and RTL tests can compare against it request by request.
reference (BLAKE3 library) ══ test: equal ══ slice_model (single compressions)
║ RTL tests: equal, request by request
slice_factory / slice_signer (hardware)§What it models
Each function below returns the exact BLAKE3 request (chaining value,
block, counter, length, flags) that one Op
sends into a slice’s BLAKE3 lane. model_key builds a complete one-time
key using only these requests, compress, dsig_chain and the
packer stream layout. A test proves the result equals
the high-level reference, so:
- the flags, counters and lengths in this file are the specification the RTL must follow, one function per op, and
- RTL tests can compare against
model_keyrequest by request.
host: prefix_cv = BLAKE3-derive-key(context) ∘ (master ‖ signer) [once per run]
Seed cv=prefix_cv block=epoch‖batch‖index (20 B) CHUNK_END|ROOT|DERIVE_KEY_MATERIAL
PkNonce cv=IV block=LE32(0x5eed)‖seed (36 B) single-block root
Nonce cv=IV block=LE32(0xc0ca)‖seed (36 B) single-block root
Xof(t) cv=IV block=seed (32 B), counter t single-block root
Chunk0(j) cv=IV or CV block=stream[64j..] counter 0 START on j=0, END on j=15
Chunk1(m) cv=IV or CV block=stream[1024+64m..] ctr 1 START on m=0, END on m=3 (24 B)
Parent cv=IV block=CV0‖CV1 PARENT|ROOT → pk_hash
Merkle cv=IV block=left‖right single-block root
Message cv=IV block=pk_hash‖nonce‖message single-block root → digitsPer key that is 1 seed + 2 nonces + 20 XOF + 20 commitment blocks + 1
parent = 44 requests (the test checks this count). Merkle and Message
are not part of one key: they serve the subtree and the signer.
§The story of one key, in data
seed ──▶ pk_nonce, nonce
└───▶ XOF 20 × 64 B = 1,280 B ──▶ 68 chain secrets of 18 B (1,224 B used)
position 0 ──Haraka──▶ position 1 ──▶ position 2 ──▶ position 3 (chain end)
(all four positions of all 68 chains go to the key cache)
pk_nonce ‖ 68 chain ends = 1,240 B ──▶ chunk 0 (16 blocks) + chunk 1 (4 blocks)
──▶ Parent(CV0, CV1) = pk_hash (the Merkle leaf)1,240 bytes is more than one 1,024-byte BLAKE3 chunk, so BLAKE3 hashes it as two chunks and one parent node. That is standard BLAKE3; the model just spells it out.
The master seed never enters the FPGA: the trusted host derives
prefix_cv, the chaining value after the first 64 material bytes, and
loads that instead. Both are equally secret.
Structs§
- Model
Key - Everything a slice produces for one key, built only from the requests above.
- Namespace
- The batch namespace a slice works in.
- Request
- One BLAKE3 compression request, exactly as the lane receives it.
Constants§
- NONCE_
PREFIX - Prefix of the signer-private message nonce derivation.
- PK_
NONCE_ PREFIX - Prefix of the public
pk_noncederivation.
Functions§
- commitment_
blocks - The 1,240-byte commitment stream split into the packer’s 20 blocks.
- commitment_
request Chunk0(j)(chunk = 0) orChunk1(m)(chunk = 1) of the commitment.- context_
key - BLAKE3 derive-key context key for
SEED_DERIVATION_CONTEXT. - merkle_
request Merkle: one batch-tree parent,BLAKE3(left ‖ right).- message_
digest - The message digest (64 XOF words) as the slice computes it: one block in profile 1, two chained blocks in profile 2.
- message_
request Message: salted digit hash for a 16-byte message.- message_
tail_ request - Profile 2’s second digest block: message bytes 16..64 (48 bytes),
chained from the first block’s chaining value
cv(its output words 0..8),CHUNK_END | ROOT. Its output is the 64-byte XOF. - model_
key - Build key
indexof the namespace purely from slice requests. - model_
root - Batch root from 128 leaves using only
merkle_request. - nonce_
request PkNonce/Nonce: hashLE32(prefix) ‖ seed, keep 16 bytes.- parent_
request Parent: combine the two chunk chaining values intopk_hash.- prefix_
cv - Host-side: chaining value after hashing
master ‖ signeras key material. - seed_
request Seed: the second (final) material block,epoch ‖ batch ‖ index.- xof_
request Xof(t): output blocktofBLAKE3-XOF(seed).