first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
# RecursiveEd25519Codec
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.