Files
suaveplan/openspec/archive/2026-05-02-bitpacker-crypto-rewrite/proposal.md
2026-05-27 23:29:53 +01:00

132 lines
8.9 KiB
Markdown

# Bitpacker crypto rewrite — auxiliary primitives only (slim)
**Package**: `@suaveplan/bitpacker-crypto`
**Status**: PROPOSED — supersedes `bitpacker-crypto-port`. Slimmed 2026-05-02 after discovering parallel work in `restore-crypto-keychain-packer`.
**Type**: clean-sheet additions of auxiliary bit-packer-aware primitives
## Why (slim version)
The 2026-05-02 audit drove an initial design with seven primitives. A subsequent uncommitted-OpenSpec scan found that **`restore-crypto-keychain-packer`** (parallel work, partially implemented) already proposes a new opinionated `@suaveplan/crypto` layer that contains essentially every primitive whose design depends on `Authority` + `DomainTag` + `Keychain`:
| First-draft primitive | Owned by `restore-crypto-keychain-packer` |
|---|---|
| `SignedBitSchema` (Authority + DomainTag) | `Packer` (encrypt-and-sign envelope) + `Authority.sign()` |
| `EnvelopeSchema(magic, version, body)` | `packPublicBundle` shape + Packer envelope |
| `HashChainSchema` (Merkle-chained signed log) | `AuditLog` (Merkle log + `proveInclusion` / `verifyInclusion`) |
| `MultiSignedBitSchema` (N-of-N co-sign) | `RevocationList` (root-signed); co-signing belongs in the `Authority`-owning crypto layer if needed |
| `EncryptedBitSchema` (niche AEAD) | `Packer` (covers this exactly) |
| New domain tags (`DOMAIN_BITPACKER_*_V1`) | Closed-list extensions belong in `@suaveplan/crypto/domain-tags` |
After dropping every overlapping primitive, the **two remaining additions** in this change are pure bit-packer utilities that don't touch `Authority` or the closed-list `DomainTag` registry:
- **`SchemaFingerprint`** — content-hash a `BitPackerSchema` (canonical form). Drives mesh schema-agreement handshakes per `docs/network/security-and-tokens.md`.
- **`HashAlgorithmRegistry`** — pluggable hash-algorithm registry (xxhash32 / crc32 / sha256 default; consumers register BLAKE3, future PQC).
Both compose with `restore-crypto-keychain-packer` without conflict.
## Decision history
1. **`bitpacker-crypto-port`** (2026-05-02) — proposed deprecate-and-route through `@suaveplan/crypto` family. Superseded by this rewrite.
2. **`bitpacker-crypto-rewrite` first draft** (2026-05-02) — proposed seven bit-packer-aware primitives consolidating eight inline consumers. Superseded by this slim version after `restore-crypto-keychain-packer` discovery.
3. **`bitpacker-crypto-rewrite` slim** (this proposal) — keeps only the two primitives that don't overlap `restore-crypto-keychain-packer`'s `@suaveplan/crypto` layer.
The audit findings from the first draft (eight inline consumers manually re-implementing crypto+bitpacker patterns) **still hold** — but they're consolidated by `restore-crypto-keychain-packer`'s `Packer` / `Authority` / `AuditLog` / `RevocationList`, not by primitives in `bitpacker-crypto`. Per-consumer migration to those primitives is `restore-crypto-keychain-packer`'s scope, not this change's.
## What this package SHALL ship
### `schemaFingerprint(schema, algorithm?, registry?) → Promise<Uint8Array>`
Content-hash a `BitPackerSchema` after canonical serialization (sorted keys, no whitespace, deterministic encoding). Equivalent schemas in different declaration orders produce byte-identical fingerprints. Used for "do we agree on the schema?" handshakes between mesh peers.
```ts
function schemaFingerprint(
schema: BitPackerSchema,
algorithm?: string, // default: "sha256"
registry?: HashAlgorithmRegistry // default: process-wide singleton
): Promise<Uint8Array>;
function assertSchemaFingerprint(
schema: BitPackerSchema,
expected: Uint8Array,
algorithm?: string,
registry?: HashAlgorithmRegistry
): Promise<void>;
```
Companion `assertSchemaFingerprint` throws `BitpackerError` code `CRYPTO_SCHEMA_FINGERPRINT_MISMATCH` on disagreement.
### `HashAlgorithmRegistry` (pluggable extension)
Open registry of hash algorithms. Defaults shipped: `xxhash32`, `crc32`, `sha256` (the existing checksum surface in `bitpacker-crypto`). Consumers register their own (`blake3` for drive whole-file hash, future PQC algorithms, etc.).
```ts
interface HashAlgorithm {
readonly name: string;
readonly outputBytes: number;
hash(data: Uint8Array): Uint8Array | Promise<Uint8Array>;
}
interface HashAlgorithmRegistry {
register(alg: HashAlgorithm): void;
resolve(name: string): HashAlgorithm | undefined;
has(name: string): boolean;
list(): readonly string[];
}
function defaultHashRegistry(): HashAlgorithmRegistry; // process-wide singleton
function createHashRegistry(): HashAlgorithmRegistry; // fresh
```
Re-registering an algorithm name with a new implementation is last-write-wins; the replacement emits an OTel span `bitpacker-crypto.hash-registry.replaced`.
### KEPT (existing checksum surface unchanged)
- `crc32`, `adler32`, `appendCrc32`, `verifyAndStripCrc32`
- `calculateXxhash32`, `packXxhash32`, `unpackXxhash32`, `appendXxhash32`, `verifyXxhash32`, `verifyAndStripXxhash32`
These remain as in the current package; the `HashAlgorithmRegistry`'s default implementations consume them.
## What this package SHALL NOT ship (dropped from first draft)
| Dropped | Reason |
|---|---|
| `SignedBitSchema` | `restore-crypto-keychain-packer` ships `Packer` (encrypt-and-sign envelope under DomainTag via Authority); for sign-only use cases, callers use `Authority.sign(domain, slot, payload)` directly. No bitpacker-side primitive needed. |
| `EnvelopeSchema(magic, version, bodySchema)` | Pattern lives in `@suaveplan/crypto`'s `packPublicBundle` and `Packer` envelope; bit-packer doesn't need a generic envelope primitive when the only consumers are crypto-layer primitives. |
| `HashChainSchema` | `AuditLog` in `@suaveplan/crypto` is a Merkle-chained, root-signed append-only log with `proveInclusion` / `verifyInclusion`. Strict superset. |
| `MultiSignedBitSchema` | Co-signing is an `Authority`-using primitive; lives next to `Packer` / `RevocationList` in `@suaveplan/crypto`. If/when `MeshTrustToken` (NET-110) needs it, the spec delta lands in the new `@suaveplan/crypto`, not here. |
| `EncryptedBitSchema` | `Packer` covers small-payload encrypt-and-sign exactly. No niche left to fill. |
| New `DOMAIN_BITPACKER_*_V1` constants | `DomainTag` is a closed list owned by `@suaveplan/crypto`; new domains are wire-format extensions in that package, not bit-packer. |
| `ChunkVerifier` | Already dropped in first audit pass. Genesis `@suaveplan/packer::ChunkVerifier` covers locked v1 chunks. |
## Source mapping
| Requirement | Source | Notes |
|---|---|---|
| `schemaFingerprint` canonicalization + algorithm registry | `network-registry::Xxhash32HashProvider`/`Crc32HashProvider` (upstream) | Pattern: hash-provider over consistent bytes. Schema canonicalization is the bit-packer-side novelty. |
| `assertSchemaFingerprint` mismatch error | Mesh schema-agreement handshake — `docs/network/security-and-tokens.md`, `docs/network/architecture.md` | Future consumer; the primitive prepares for it. |
| `HashAlgorithmRegistry` default algorithms (xxhash32, crc32, sha256) | Existing `bitpacker-crypto/src/index.ts` | Re-uses checksum impls already shipping. |
| `HashAlgorithmRegistry` BLAKE3 plug-in path | `docs/drive/encryption.md` L79, L105 (whole-file BLAKE3) | Future consumer registers BLAKE3 once. |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| Pluggable hash-registry pattern | User directive on extensibility / plugins / middleware. |
| Last-write-wins re-register with OTel emission | Standard genesis registry pattern (matches `@suaveplan/random` algorithm registries). |
| Zod-validated public types | enterprise/bitpacker.md L38-L39. |
| OTel span on every primitive | enterprise/bitpacker.md L41. |
## Out of scope
- Everything in `restore-crypto-keychain-packer` (Packer, Authority, AuditLog, RevocationList, Keychain, Vault, etc.)
- Domain-tag closed-list additions (those belong in `@suaveplan/crypto/domain-tags`)
- The `bytes` field type and `byteAlign()` core-bitpacker additions (already specced in `restore-crypto-keychain-packer/specs/bitpacker/spec.md`)
- Per-consumer migration of inline crypto patterns — those go to follow-up changes after `restore-crypto-keychain-packer` lands its `Packer` / `AuditLog` primitives.
- All seven dropped primitives (see "What this package SHALL NOT ship" above)
## Risks
1. **`restore-crypto-keychain-packer` may revise the closed-list `DomainTag` design.** No risk to this slim change since we don't add domain tags. If their design changes, only their spec needs revision.
2. **Hash registry singleton state leakage between tests.** Mitigated by `createHashRegistry()` for tests + a `spec-conformance` test that singletons are read-only beyond `register`.
3. **BLAKE3 not in genesis yet.** The `HashAlgorithmRegistry` ships extension points but doesn't ship a BLAKE3 implementation. The drive layer's BLAKE3 use (per `docs/drive/encryption.md`) becomes its own follow-up change to register a BLAKE3 implementation.