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

8.9 KiB

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.

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.).

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.