10 KiB
10 KiB
Archived 2026-04-19: all tasks retroactively ticked; packages shipped under universal/crypto/.
Phase 0 — Scaffold + replaceability architecture (supply-chain namespace win)
Scaffold the new packages
- Create
packages/universal/crypto-utils/with full genesis layout (package.json, vite.config.ts, vitest.config.ts, tsconfig.lib.json, README.md, CHANGELOG.md, src/index.ts). Depends on:@suaveplan/error,@suaveplan/types,tslib,zod. - Create
packages/universal/crypto-curve/with full genesis layout. Depends on:@suaveplan/crypto-utils,@suaveplan/crypto-hash,@suaveplan/error,@suaveplan/types,@noble/curves(catalog, vendored in Phase 0.5). - Create
packages/universal/crypto-cipher/with full genesis layout. Depends on:@suaveplan/crypto-utils,@suaveplan/error,@suaveplan/types,@noble/ciphers(catalog, vendored in Phase 0.5). - Register all three in
genesis/CLAUDE.mdUniversal listing with one-line descriptions.
Rename @suaveplan/hash → @suaveplan/crypto-hash
- Rename directory
packages/universal/hash→packages/universal/crypto-hash. - Update
package.jsonnamefield →@suaveplan/crypto-hash. - Update
@suaveplan/crypto'spackage.jsondep:@suaveplan/hash→@suaveplan/crypto-hash. - Rewrite every
from "@suaveplan/hash"import across genesis tofrom "@suaveplan/crypto-hash". (Currently only@suaveplan/cryptodepends on it.) - Update
genesis/CLAUDE.mdlisting: rename bullet for hash → crypto-hash. - Update
enterprise-cryptochange'sspecs/crypto/spec.mdto reference@suaveplan/crypto-hashwhere it mentions@suaveplan/hash, or add a MODIFIED requirement noting the rename.
Replaceability architecture — shared infrastructure
- Create
genesis/crypto.config.tsat repo root. Typed asRecord<PrimitiveId, 'noble' | 'native'>. All values initialised to'noble'. - Define
PrimitiveIdunion type inpackages/universal/crypto-utils/src/primitive-id/primitive-id.ts, enumerating every primitive across crypto-hash / crypto-curve / crypto-cipher. - Write
scripts/gen-crypto-selects.ts— readscrypto.config.ts, emits<id>.select.tsfor each primitive as a one-line re-export of the chosenimpl/<id>.{noble,native}.ts. Idempotent. Writes a/* GENERATED — DO NOT EDIT */banner. Fails the build if any generated file has been hand-edited. - Wire
gen-crypto-selects.tsinto each package'sprebuildscript and as an input toturbo.json'sbuildpipeline. - Add
scripts/check-crypto-selects.tsto CI to fail if generated selects are out of date vscrypto.config.tsor vs the primitive-id registry. - Define
PrimitiveNotImplementederror class in@suaveplan/crypto-utils/errors, extendingSuaveplanErrorfrom@suaveplan/error, with stable codeCRYPTO.UTILS.PRIMITIVE_NOT_IMPLEMENTED.
crypto-utils implementation
encoding/hex/hex.ts—encode(bytes): string,decode(text): Uint8Array. ThrowsInvalidEncodingon malformed input.encoding/base64/base64.ts— standard + url-safe variants exported separately.encoding/base58/base58.ts— bitcoin alphabet.bytes/concat-bytes/concat-bytes.ts— variadic Uint8Array concat.bytes/xor/xor.ts— length-matched xor.bytes/i2osp/i2osp.ts+bytes/os2ip/os2ip.ts— integer ↔ octet string primitives.constant-time/constant-time-equal/constant-time-equal.ts— no early return.random/random-bytes/random-bytes.ts— wrapsglobalThis.crypto.getRandomValues.zeroize/zeroize.ts— overwrite with zeros.merkle/merkle-root/merkle-root.ts+merkle/merkle-proof/merkle-proof.ts— generic over any hash primitive.testing/run-vectors/run-vectors.ts— KAT harness. Runs a vector list against N impls; reports per-impl pass/fail/pending.testing/diff-fuzz/diff-fuzz.ts— differential-fuzz harness. Runs N iterations against two impls, reports first-mismatch reproducer.primitive-id/primitive-id.ts— union type + registry of all primitive ids.- 95% coverage, full
.mddocs per co-located file.
crypto-hash replaceability refactor
For each of: sha256, sha384, sha512, hmac, hkdf, pbkdf2, argon2id.
- Move implementation into
src/primitives/<id>/impl/<id>.noble.tsas a thin adapter over the existing noble-based code. - Create
src/primitives/<id>/impl/<id>.native.tsthat throwsPrimitiveNotImplemented('<id>'). - Create
src/primitives/<id>/<id>.types.tswith the interface. - Create
src/primitives/<id>/<id>.vectors.tswith RFC/NIST/Wycheproof test vectors. - Create
src/primitives/<id>/<id>.test.tsthat runs vectors × both impls + cross-impl equivalence. - Create
src/primitives/<id>/<id>.mdper docs template. - Codegen
src/primitives/<id>/<id>.select.ts. - Update package-root
<id>.tsto re-export fromsrc/primitives/<id>/<id>.ts(which re-exports from select). - Existing subpath exports in
package.jsonpreserved — map them to the new primitive paths.
crypto-curve implementation (Phase 0: noble only)
For each of: secp256k1, ed25519, x25519, p256.
src/primitives/<id>/impl/<id>.noble.ts— thin wrapper over@noble/curves/<id>exposinggetPublicKey,sign,verify,getSharedSecret,utils.randomPrivateKeyas appropriate for the curve.src/primitives/<id>/impl/<id>.native.ts— stub throwingPrimitiveNotImplemented.<id>.types.ts,<id>.vectors.ts(RFC 8032 + Wycheproof),<id>.test.ts,<id>.md.- Codegen
<id>.select.ts. - Public-API surface via
src/index.tsand subpath exports./<id>per curve. - 95% coverage on the noble impl; native stub tests assert
PrimitiveNotImplemented.
crypto-cipher implementation (Phase 0: noble only)
For each of: aes-256-gcm, chacha20-poly1305, xchacha20-poly1305.
src/primitives/<id>/impl/<id>.noble.ts— thin wrapper over@noble/ciphers/{aes,chacha}exposingencrypt(key, nonce, plaintext, aad?)/decrypt(key, nonce, ciphertext, aad?).src/primitives/<id>/impl/<id>.native.ts— stub.<id>.types.ts,<id>.vectors.ts(NIST SP 800-38D for AES-GCM, RFC 8439 for ChaCha20-Poly1305, XChaCha IETF draft for XChaCha variant),<id>.test.ts,<id>.md.- Codegen
<id>.select.ts. - Public-API surface + subpath exports per AEAD.
- Primitives do NOT generate nonces internally — caller supplies them. Length validation enforced.
- 95% coverage on noble impl.
Rewire @suaveplan/crypto
- Replace
@noble/curves/...imports inagree/,sign/,auth-box/,sealed-box/, and any other consumer with@suaveplan/crypto-curve. - Replace
@noble/ciphers/...imports inaead/with@suaveplan/crypto-cipher. - Remove
@noble/curvesand@noble/ciphersfrom@suaveplan/crypto'spackage.jsondependencies. - Add
@suaveplan/crypto-curve,@suaveplan/crypto-cipher,@suaveplan/crypto-utilstodependencies. - Full
@suaveplan/cryptotest suite green with zero regressions. - Full
@suaveplan/crypto-hashtest suite green.
CI wiring
crypto:katturbo task — PR-blocking, runs vector tests across crypto-hash / crypto-curve / crypto-cipher.crypto:fuzzturbo task — nightly, differential fuzz native-vs-noble wherever both impls exist.crypto:timingturbo task — nightly,dudect-style stats on native impls.scripts/ct-lint.ts— scans anyimpl/*.native.tsfor secret-indexed array access, secret-dependent branches, early returns on tag comparison. Wired as a per-file check in CI..github/pull_request_template/flip-primitive.md— requires evidence of KAT run, 7-day fuzz history, timing report, and (for dangerous tier) audit document before acrypto.config.tsflip merges.
Phase 0 exit criteria
@suaveplan/crypto-utils,@suaveplan/crypto-curve,@suaveplan/crypto-cipherexist at 0.0.0 with 95% coverage.@suaveplan/crypto-hashexists (renamed from hash) at 0.0.0 with 95% coverage, refactored to primitive selector layout.@suaveplan/cryptosource contains zero@noble/*imports. Itsdependenciescontain zero@noble/*entries.crypto.config.tshas every primitive set to'noble'.- Every primitive has a
.native.tsstub that throwsPrimitiveNotImplemented. bunx openspec validate crypto-primitive-replaceability --strictgreen.
Phase 0.5 — Vendor noble source (zero external runtime deps)
- Pin
@noble/hashes,@noble/curves,@noble/ciphersupstream commits. - Copy upstream source trees into
packages/universal/crypto-{hash,curve,cipher}/src/vendored/preserving LICENSE. - Rewrite internal noble imports in vendored tree to relative paths — no code reflow.
NOBLE_UPSTREAM.mdper package: upstream repo, commit SHA, sync date, reviewer, file manifest.scripts/sync-noble.ts— fetches upstream, diffs vs vendored, opens a PR for human review. No auto-merge.- Each
impl/<id>.noble.tsrewired to import from../vendored/...instead of@noble/*. - Remove
@noble/hashes,@noble/curves,@noble/ciphersfrom every package'sdependencies. - CI task
crypto:noble-sync— weekly, runssync-noble.tsin dry-run, opens PR if upstream moved.
Phase 1 — Native tier-safe primitives (no audit required)
- Native
hmac— flip after gates 1-3. (Dangerous tier — requires audit. Keep on noble if no audit budget.) - Native
hkdf— flip after gates 1-3. - Native
pbkdf2— flip after gates 1-3.
Phase 2 — Native hash compression functions
- Native
sha256,sha384,sha512— flip after gates 1-3 plus benchmark within 2× of noble.
Phase 3 — Dangerous tier (audit required per primitive)
- Native
secp256k1,ed25519,x25519,p256— audit per curve. - Native
aes-256-gcm,chacha20-poly1305,xchacha20-poly1305— audit per AEAD. - Native
argon2id— audit (memory-hard KDF, timing-sensitive).
Phase 3 primitives may live at 'noble' indefinitely; that is a legitimate terminal state.