Files
suaveplan/openspec/specs/crypto-utils/spec.md
2026-05-27 23:29:53 +01:00

96 lines
5.5 KiB
Markdown

# crypto-utils
## Purpose
`@suaveplan/crypto-utils` is the cross-package crypto-helper layer for the Suaveplan monorepo. It owns the pure-composition utilities every primitive package builds on: hex / base64 / base64url / base58 encoding (delegated to `@suaveplan/bytes` where overlap exists), byte helpers (`concatBytes`, `i2osp`, `os2ip`, `xor`), constant-time equality, cryptographically-secure `randomBytes`, eager `zeroize`, the `PrimitiveId` registry + `PrimitiveNotImplemented` error, and the KAT / differential-fuzz test harness consumed by every `@suaveplan/crypto-*` engine selector. The package is deliberately field-arithmetic-free and side-channel-clean (no secret-data branches beyond constant-time compare) so it can ship as the universal-tier dependency for `crypto-cipher`, `crypto-curve`, `crypto-hash`, and the higher-level `crypto` package.
## Requirements
### Requirement: Pure-composition utility surface
`@suaveplan/crypto-utils` SHALL expose encoding, byte, constant-time, and test-harness utilities that are safe to author from scratch (no field arithmetic, no secret-data side channels beyond constant-time equality).
#### Scenario: Encoding utilities available
- **WHEN** a consumer imports from `@suaveplan/crypto-utils`
- **THEN** `hex`, `base64`, `base64url`, `base58` encoders/decoders are exported, each with `encode(bytes): string` and `decode(text): Uint8Array`
- **AND** `toHex` / `fromHex` and `toBase64` / `fromBase64` delegate to `@suaveplan/bytes`; decoders throw `BytesError` (a `SuaveplanError` subclass) on malformed input
- **AND** `toBase64Url` / `fromBase64Url` are implemented in-package by post-processing the standard base64 result
### Requirement: Encoding delegation to @suaveplan/bytes
`@suaveplan/crypto-utils` SHALL delegate `toHex` / `fromHex` and `toBase64` / `fromBase64` to `@suaveplan/bytes`, and `concatBytes` to `concatUint8Arrays` from `@suaveplan/bytes`. The exported function names are preserved so downstream callers need no import changes.
#### Scenario: Hex delegates to bytes
- **WHEN** `toHex` or `fromHex` is called
- **THEN** the call is forwarded to `bytesToHex` / `hexToBytes` from `@suaveplan/bytes`
- **AND** errors thrown are `BytesError` instances with code `BYTES.ENCODE.INVALID_HEX`
#### Scenario: Standard base64 delegates to bytes
- **WHEN** `toBase64` or `fromBase64` is called
- **THEN** the call is forwarded to `bytesToBase64` / `base64ToBytes` from `@suaveplan/bytes`
- **AND** errors thrown are `BytesError` instances with code `BYTES.ENCODE.INVALID_BASE64`
#### Scenario: concatBytes delegates to bytes
- **WHEN** `concatBytes` is called
- **THEN** the call is forwarded to `concatUint8Arrays` from `@suaveplan/bytes`
#### Scenario: Byte utilities available
- **WHEN** a consumer imports from `@suaveplan/crypto-utils`
- **THEN** `concatBytes(...arrays: Uint8Array[]): Uint8Array`, `xor(a: Uint8Array, b: Uint8Array): Uint8Array`, `i2osp(n: bigint, len: number): Uint8Array`, `os2ip(bytes: Uint8Array): bigint` are exported
#### Scenario: Constant-time, random, and zeroize utilities available
- **WHEN** a consumer imports from `@suaveplan/crypto-utils`
- **THEN** `constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean` is exported and implemented without early return
- **AND** `randomBytes(n: number): Uint8Array` is exported, sourcing from `globalThis.crypto.getRandomValues`
- **AND** `zeroize(bytes: Uint8Array): void` is exported
### Requirement: Test-vector and fuzz harness
`@suaveplan/crypto-utils` SHALL expose harness functions that `crypto-hash`, `crypto-curve`, and `crypto-cipher` use to run KATs and differential fuzz against their primitive impls.
#### Scenario: KAT harness
- **WHEN** a primitive test imports `runVectors` from `@suaveplan/crypto-utils/testing`
- **THEN** it can pass a vector list and a set of impls, and receive a per-impl pass/fail/pending report
- **AND** pending impls (throwing `PrimitiveNotImplemented`) are distinguished from failing impls
#### Scenario: Differential fuzz harness
- **WHEN** a primitive test imports `diffFuzz` from `@suaveplan/crypto-utils/testing`
- **THEN** it can pass two impls, an input generator, and an iteration count, and receive a mismatch report
- **AND** any byte-level divergence in outputs is reported with the reproducing input
### Requirement: Primitive id registry
`@suaveplan/crypto-utils` SHALL export the `PrimitiveId` union type enumerating every hash, curve, and AEAD primitive across the crypto family, and a `PRIMITIVE_IDS` const array for runtime iteration.
#### Scenario: Registry available
- **WHEN** a consumer imports `PrimitiveId` and `PRIMITIVE_IDS` from `@suaveplan/crypto-utils/primitive-id`
- **THEN** `PRIMITIVE_IDS` contains every id supported by `crypto-hash`, `crypto-curve`, and `crypto-cipher`
- **AND** `PrimitiveId` narrows to the same union at compile time
### Requirement: No vendored code
`@suaveplan/crypto-utils` SHALL contain only original code authored in this monorepo. No `src/vendored/` directory.
#### Scenario: No vendored directory
- **WHEN** the package tree is inspected
- **THEN** no path matches `packages/universal/crypto-utils/src/vendored/**`
### Requirement: Merkle helpers
`@suaveplan/crypto-utils` SHALL expose Merkle tree construction and inclusion-proof helpers generic over any hash primitive from `@suaveplan/crypto-hash`.
#### Scenario: Merkle helpers available
- **WHEN** a consumer imports `merkleRoot` and `merkleProof` from `@suaveplan/crypto-utils/merkle`
- **THEN** they accept a hash function parameter and a list of leaves, returning a root and an inclusion proof respectively