151 lines
7.1 KiB
Markdown
151 lines
7.1 KiB
Markdown
<!-- markdownlint-disable MD024 -->
|
|
|
|
# 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<T>(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<T>(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<TPayload>(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<T>(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"`
|
|
|
|
<!-- markdownlint-enable MD024 -->
|