# RecursiveEd25519Codec _(no submodules)_ ## Purpose `RecursiveEd25519Codec` is a fixed-Ed25519 alternate wire format for signed key chains in `@suaveplan/crypto-keys`. It trades the algorithm-flexibility of `TlvChainCodec` for a denser, predictable byte layout suitable for embedded systems, replicated logs, and any context where chains are known to be purely Ed25519. The codec walks bytes linearly leaf-first using the fixed 32-byte public key and 64-byte signature lengths defined by Ed25519, with a single u16 length prefix for each variable-length payload. ## Features - Fixed 32-byte Ed25519 public keys and 64-byte Ed25519 signatures — no per-level algorithm byte. - Compact 2-byte header carrying `version=1` plus the active `hasherName` byte (`raw`/`domain`/`merkle`). - u16 big-endian payload length (up to 65,535 bytes per level). - Leaf-first emission; the parent chain is appended back-to-back with no inter-level separator. - Strict validation: bad version byte, unknown hasher byte, truncated buffers, and out-of-bound payload lengths all raise `CHAIN_KEYS.INVALID_FORMAT`. - Non-Ed25519 algorithms raise `CHAIN_KEYS.UNSUPPORTED_ALGORITHM` with the `algorithm` discriminant populated, mirroring the `signWithSlot` failure surface. - After `decode(...)`, `hasherName` is set from the wire byte so a downstream `ChainedKeySigner` can reconfigure its hasher without an out-of-band channel. ## Usage: Basic ```ts import { RecursiveEd25519Codec } from "@suaveplan/crypto-keys/codecs/recursive-ed25519/recursive-ed25519"; const codec = new RecursiveEd25519Codec(); const level = { algorithm: "ed25519" as const, signerPublicKey: new Uint8Array(32), payload: new Uint8Array([1, 2, 3, 4]), signature: new Uint8Array(64), }; const bytes = codec.encode([level]); const decoded = codec.decode(bytes); // decoded[0].algorithm === "ed25519" ``` ## Usage: Advanced Round-tripping a multi-level chain and surfacing hasher state: ```ts import { RecursiveEd25519Codec } from "@suaveplan/crypto-keys/codecs/recursive-ed25519/recursive-ed25519"; import { ChainKeysError } from "@suaveplan/crypto-keys/error-codes/error-codes"; const writer = new RecursiveEd25519Codec(); writer.hasherName = "merkle"; const wire = writer.encode([leafLevel, midLevel, rootLevel]); const reader = new RecursiveEd25519Codec(); try { const levels = reader.decode(wire); // reader.hasherName is now "merkle" — propagate to the ChainedKeySigner // before verifying signatures. handleChain(levels, reader.hasherName); } catch (err) { if (err instanceof ChainKeysError && err.code === "CHAIN_KEYS.UNSUPPORTED_ALGORITHM") { // Routing decision: switch to TlvChainCodec or CborChainCodec. fallback(err.algorithm); } else { throw err; } } ``` ## API Reference ### `class RecursiveEd25519Codec implements ChainCodec` - `readonly name: "recursive-ed25519"` — codec identifier surfaced in diagnostics. - `hasherName: "raw" | "domain" | "merkle"` — hasher byte written on encode and overwritten on decode. Default `"raw"`. - `encode(levels: readonly ChainLevel[]): Uint8Array` — emits `[version=1, hasher, (pub32, payload_len_u16_be, payload, sig64)+]` leaf-first. Throws `ChainKeysError` with code `CHAIN_KEYS.UNSUPPORTED_ALGORITHM` for any non-Ed25519 level (with `algorithm` discriminant set), or `CHAIN_KEYS.INVALID_FORMAT` for wrong public-key/signature sizes or oversized payloads. - `decode(bytes: Uint8Array): readonly ChainLevel[]` — walks the buffer from offset 2 until end-of-buffer. Every decoded level carries `algorithm: "ed25519"` unconditionally. Throws `CHAIN_KEYS.INVALID_FORMAT` on a wrong version byte, unknown hasher byte, truncated read, or an in-stream `payload_len` exceeding the remaining bytes. ## Implementation Notes - The encoder pre-computes total length and writes into a single `Uint8Array`, avoiding per-level allocations and intermediate concatenations. - `payload_len` uses u16 big-endian via `DataView.setUint16(offset, len, false)` — keeping a network-byte-order convention consistent with TLV. - `decode(...)` calls `bytes.slice(...)` for each field so the returned `Uint8Array`s do not alias the input buffer; consumers may safely mutate or zero the wire bytes after decoding. - The hasher byte is validated as `< HASHER_FROM_BYTE.length` rather than a hard-coded `<= 2` so adding a fourth hasher only requires extending the lookup table. - Encoding a non-Ed25519 level raises `UNSUPPORTED_ALGORITHM` even when the supplied key/signature happen to be 32/64 bytes — the algorithm check fires first to keep the error surface predictable. - Decoded levels are returned `readonly` at the type level but the underlying `Uint8Array`s are fresh copies; callers can freeze them at a higher layer if mutation must be statically forbidden.