Skip to main content

Module packer

Module packer 

Source
Expand description

Pack 18-byte chain nodes into 64-byte words.

§The problem

Hash chains produce 18-byte nodes (ELEMENT_BYTES), but the rest of the hardware moves 64-byte words: a BLAKE3 block is 64 bytes and an HBM beat (one transfer on the memory bus) is 64 bytes. Somebody has to glue a stream of 18-byte pieces into a stream of 64-byte words, with no gaps.

Two places in a slice turn a stream of 18-byte nodes into 64-byte words:

StreamPrefixThenTotal
Endpoint commitment (BLAKE3 blocks)pk_nonce, 16 B68 chain ends1,240 B
Signature frame tail (HBM beats)last 34 header bytes68 revealed nodes1,258 B

The first is used by the key factory to hash the 68 chain ends into pk_hash (the key’s public commitment). The second is used by the signer to lay the 68 revealed nodes into the signature frame right after the header. Both are the same circuit, ElementPacker, with a different prefix length P.

§Why it is tricky

18 does not divide 64, so each node lands at a different byte offset. The sequence of offsets repeats every 32 nodes (32 × 18 = 9 × 64).

  word 0: [ prefix (P bytes) | node 0 | node 1 | node 2 | node 3 (part) ]
  word 1: [ node 3 (rest) | node 4 | node 5 | node 6 (part) ]   …

For the signature stream (P = 34) the first words look like this:

  byte:    0          34        52     64
  word 0:  [ header tail | node 0 | node 1: 12 B ]
  word 1:  [ node 1: 6 B | node 2 | node 3 | node 4 | node 5: 4 B ] ...
  last:    [ ... node 67 ends at byte 42 | zero padding ]   (1,258 = 19 × 64 + 42)

The packer keeps a fill counter (bytes already used in the current word). Each new node is placed at offset fill. If it fits, fill grows by 18. If it crosses the 64-byte edge, the full word is emitted and the 0 to 17 overflow bytes (the remainder) start the next word.

ElementPacker accepts at most one node per cycle and emits at most one word per cycle. Both streams have the property that the 68th node never completes a word and leaves a remainder, so a single output suffices. A compile-time assertion checks this for each prefix length.

§Why the kernel looks unusual

RHDL lowers every array-element assignment into a copy of the whole array, so “for each offset, for each byte, maybe write it” would emit thousands of 512-bit copies. Instead each output byte is one OR of ANDs over a zero-padded copy of the node (place_kernel). Every index is a compile-time constant in range, and synthesis folds the padding zeros away, leaving an 18-way select per byte.

§Timing

One register stage: a node presented with valid in cycle t produces its word (if any) on the output in cycle t + 1.

Structs§

ElementPacker
Registered packer for a stream with a P-byte prefix and 68 nodes.
PackerInput
Packer input; start must accompany node 0 of each stream.
PackerOutput
One output word; bytes at and after bytes are zero.

Constants§

COMMITMENT_PREFIX
Prefix length of the endpoint-commitment stream (pk_nonce).
SIGNATURE_PREFIX
Prefix length of the signature-frame tail: the 1,642-byte wire format puts 418 header bytes before the nodes, and 418 = 6 × 64 + 34.

Functions§

element_packer_kernel
Wiring for ElementPacker.
place_kernel
Bytes of node shifted to start at offset fill, over 82 positions.
single_flush
Whether prefix keeps the 68th node from overflowing into a second word.