# Capability Spec Delta: bitpacker-crypto (extract-shared-crypto-interfaces) **Package**: `@suaveplan/bitpacker-crypto` **Status**: PROPOSED — gated on user sign-off of the API shape > Modifies the four DI-using primitives shipped by the archived > `bitpacker-crypto-rewrite` (2026-05-02) to accept `verifier` and > `aead` via dependency injection. No wire-format change. ## MODIFIED Requirements ### Requirement: SignedBitSchema integrates with Authority + DomainTag (verifier injected) The `defineSignedBitSchema(bodyCodec, options)` function SHALL accept a required `verifier: AuthorityVerifier` field on its options bundle. The primitive SHALL call `verifier(domain, parentPublicSlot, body, signature)` instead of importing `verifyAuthoritySignature` directly. All other behavior (verify-then-decode order, `unpackSelf` trust-anchor flow, cross-domain replay rejection, `CRYPTO_VERIFY_FAILED` on tamper) remains identical to the archived `bitpacker-crypto-rewrite` spec. The primitive SHALL import `Authority`, `AuthorityVerifier`, `DomainTag`, `KeySlotPublic`, and `SlotId` types from `@suaveplan/types` instead of from `@suaveplan/crypto/authority` and `@suaveplan/crypto/domain-tags`. #### Scenario: missing `verifier` rejects at construction time - **WHEN** `defineSignedBitSchema(codec, { domain: D })` is called WITHOUT a `verifier` field - **THEN** the Zod options-validation throws AND no schema instance is returned #### Scenario: DI'd verifier produces same boolean answer as direct import - **WHEN** `defineSignedBitSchema(codec, { domain, verifier: verifyAuthoritySignature })` packs a payload and the result is `unpack`-ed - **THEN** the round-trip matches the original AND the verify path emits the same OTel span as the archived rewrite #### Scenario: bitpacker-crypto's compiled `dist/` contains zero references to `@suaveplan/crypto` - **WHEN** `bunx turbo build --filter=@suaveplan/bitpacker-crypto` runs - **THEN** `grep -r "@suaveplan/crypto" packages/bitpacker/bitpacker-crypto/dist/` returns zero matches in `*.js` and `*.d.ts` files **Verified by:** `packages/bitpacker/bitpacker-crypto/src/signed/signed-bitschema.test.ts::"missing verifier rejects at construction time"` AND `packages/bitpacker/bitpacker-crypto/src/signed/signed-bitschema.test.ts::"pack signs via Authority under the configured domain"` AND `packages/bitpacker/bitpacker-crypto/src/signed/signed-bitschema.spec-conformance.test.ts::"signed-pack emits OTel span with domain attribute"` --- ### Requirement: MultiSignedBitSchema accepts injected verifier The `defineMultiSignedBitSchema(bodyCodec, options)` function SHALL accept a required `verifier: AuthorityVerifier` field on its options bundle. The primitive SHALL call `verifier(...)` for each signature verification instead of importing `verifyAuthoritySignature`. Wire format, count enforcement, `firstBadSignerIndex` reporting, `CRYPTO_MULTISIGN_COUNT_MISMATCH` semantics — all unchanged. #### Scenario: missing `verifier` rejects at construction time - **WHEN** `defineMultiSignedBitSchema(codec, { domain: D, signerCount: 2 })` is called WITHOUT a `verifier` field - **THEN** the Zod options-validation throws #### Scenario: DI'd verifier preserves firstBadSignerIndex semantics - **WHEN** a 3-signer payload's second signature is flipped and `verify(bytes, [pubA, pubB, pubC])` is called with the DI'd `verifyAuthoritySignature` - **THEN** the result is `{ valid: false, firstBadSignerIndex: 1 }` identical to the archived rewrite **Verified by:** `packages/bitpacker/bitpacker-crypto/src/multisigned/multisigned-bitschema.test.ts::"missing verifier rejects at construction time"` AND `packages/bitpacker/bitpacker-crypto/src/multisigned/multisigned-bitschema.test.ts::"verify pinpoints which signer failed"` --- ### Requirement: HashChainSchema accepts injected verifier The `defineHashChainSchema(options)` function SHALL accept a required `verifier: AuthorityVerifier` field on its options bundle. `verifyEntry` and `verifyChain` SHALL use the DI'd verifier. All chain-walk, prev-hash linkage, tamper detection (`firstBadIndex`), pluggable hash registry behavior — all unchanged. #### Scenario: missing `verifier` rejects at construction time - **WHEN** `defineHashChainSchema({ domain: D, payloadCodec: c })` is called WITHOUT a `verifier` field - **THEN** the Zod options-validation throws #### Scenario: chain verification detects tamper at exact index with DI'd verifier - **WHEN** a 5-entry chain is built using the DI'd verifier, then entry-2's payload is mutated and `verifyChain` is called - **THEN** the result is `{ valid: false, firstBadIndex: 2 }` identical to the archived rewrite **Verified by:** `packages/bitpacker/bitpacker-crypto/src/hashchain/hashchain-schema.test.ts::"missing verifier rejects at construction time"` AND `packages/bitpacker/bitpacker-crypto/src/hashchain/hashchain-schema.test.ts::"chain verification detects tamper at exact index"` --- ### Requirement: EncryptedBitSchema accepts injected AEAD operations The `defineEncryptedBitSchema(bodyCodec, options)` function SHALL accept a required `aead: AeadOps` field on its options bundle. The primitive SHALL call `aead.encrypt(...)` and `aead.decrypt(...)` instead of importing `aeadEncrypt` / `aeadDecrypt` directly. The constants `AEAD_KEY_LENGTH = 32`, `AEAD_NONCE_LENGTH = 12`, and `AEAD_XCHACHA_NONCE_LENGTH = 24` SHALL be declared locally in `encrypted-bitschema.ts` (not imported from `@suaveplan/crypto`). Duplication is acceptable because these values are part of the bit-packer-crypto wire format. The primitive SHALL import `AeadAlgorithm`, `AeadOps`, `Rng` from `@suaveplan/types`. Wire format `[version u8][algTag u8][nonceLen u8] [nonce][ciphertext + tag]` and the closed `ENCRYPTED_ALG_TAGS` enum remain unchanged. #### Scenario: missing `aead` rejects at construction time - **WHEN** `defineEncryptedBitSchema(codec, { key })` is called WITHOUT an `aead` field - **THEN** the Zod options-validation throws #### Scenario: DI'd AEAD bundle preserves round-trip semantics - **WHEN** `defineEncryptedBitSchema(codec, { key, aead: { encrypt: aeadEncrypt, decrypt: aeadDecrypt } })` packs and unpacks a payload - **THEN** the round-trip matches the original AND tampered ciphertext rejects with `CRYPTO_DECRYPT_FAILED` identical to the archived rewrite #### Scenario: bitpacker-crypto's `package.json` lists `@suaveplan/crypto` only as devDependency - **WHEN** `cat packages/bitpacker/bitpacker-crypto/package.json | jq -r '.dependencies | keys[]'` runs - **THEN** the output does NOT include `@suaveplan/crypto` - **AND** `cat packages/bitpacker/bitpacker-crypto/package.json | jq -r '.devDependencies | keys[]'` DOES include `@suaveplan/crypto` **Verified by:** `packages/bitpacker/bitpacker-crypto/src/encrypted/encrypted-bitschema.test.ts::"missing aead rejects at construction time"` AND `packages/bitpacker/bitpacker-crypto/src/encrypted/encrypted-bitschema.test.ts::"round-trips through XChaCha20-Poly1305"` AND `packages/bitpacker/bitpacker-crypto/src/encrypted/encrypted-bitschema.test.ts::"tampered ciphertext rejects with CRYPTO_DECRYPT_FAILED"`