Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
# `@suaveplan/crypto-curve` — Add P-384 (`secp384r1`) and P-521 (`secp521r1`) primitives
**Package**: `@suaveplan/crypto-curve` (extension)
**Status**: PROPOSED
**Type**: capability-extension
**Date**: 2026-05-10
**Tier**: universal
## Why
`@suaveplan/cbor` ships RFC 9052 COSE_Sign1 verify but the ES384 / ES512
algorithm branches throw `CborError(UNEXPECTED_TYPE)` with the message
*"not implemented in @suaveplan/crypto-curve yet"* because the curve
primitives were not previously exposed. Downstream consumers (WebAuthn
attestation, federation tokens, FIDO2 device certificates) need both
algorithms to interoperate with mainstream issuers — Apple's WebAuthn
attestation, in particular, signs with ES384 in some configurations.
`@noble/curves@1.9.7` (already installed across the workspace) exposes
`p384` and `p521` with the same surface contract as `p256`. The work is
purely additive — mirroring the existing `p256` per-primitive layout for
two more curves and threading them through to the COSE verifier.
## What changes
### New primitives in `@suaveplan/crypto-curve`
#### P-384
- ADDED: `primitives/p384/p384.ts` — re-exports the select layer.
- ADDED: `primitives/p384/p384.select/p384.select.ts` — selector pinned
to the noble implementation (generated forwarder, mirroring p256).
- ADDED: `primitives/p384/impl/p384.noble/p384.noble.ts` — noble-backed
`getPublicKey`, `sign`, `verify`, `randomPrivateKey`.
- ADDED: `primitives/p384/impl/p384.native/p384.native.ts` —
`PrimitiveNotImplemented` stubs matching the p256 native pattern.
#### P-521
- ADDED: `primitives/p521/p521.ts` and full mirror of the p384 layout
(`p521.select`, `impl/p521.noble`, `impl/p521.native`).
#### Barrel + subpath exports
- ADDED: `export * as p384 from "./primitives/p384/p384.js"` to root barrel.
- ADDED: `export * as p521 from "./primitives/p521/p521.js"` to root barrel.
- ADDED: `./p384` and `./p521` subpath exports in `package.json`.
- ADDED: corresponding entries in `vite.config.ts` external list (so
consumers can tree-shake to the individual primitive).
### COSE wiring in `@suaveplan/cbor`
- MODIFIED: `verifyCoseSign1` switch — `ES384` and `ES512` branches call
`p384.verify(sig, sha384(tbs), pk)` and `p521.verify(sig, sha512(tbs), pk)`
respectively (replacing the `UNEXPECTED_TYPE` placeholder throws).
- MODIFIED: `ec2ToCompressedPoint` — generalised to accept coordinate
length (32 / 48 / 66) so the same helper compresses P-256 / P-384 / P-521.
### Dependencies
No new dependencies. `@noble/curves@1.9.7` is the catalog-pinned version
that ships both `p384` and `p521` subpath exports.
## Out of scope
- WebCrypto-native impls — the existing `p256.native` stub pattern
(`PrimitiveNotImplemented`) is preserved for both new curves; an opt-in
WebCrypto path lands in a separate change when needed.
- COSE Sign1 / Mac0 / Encrypt0 surfaces beyond the alg switch — already
wired generically and need no changes.
## Source mapping
| Requirement | Source | Notes |
|---|---|---|
| P-384 ECDSA | FIPS 186-4 §6 + NIST SP 800-186 | `@noble/curves/p384` |
| P-521 ECDSA | FIPS 186-4 §6 + NIST SP 800-186 | `@noble/curves/p521` |
| COSE ES384 / ES512 | RFC 9053 §2.1 | Already mapped in `alg-map.ts` |
| Per-primitive selector layout | Existing crypto-curve convention | mirrors p256 |
## Verification approach
- `packages/crypto/crypto-curve/src/primitives/p384/p384.test.ts` — noble
- native impl: sign / verify round-trip, signature length, tampered-sig
rejection, native `PrimitiveNotImplemented` throw.
- `packages/crypto/crypto-curve/src/primitives/p521/p521.test.ts` — same.
- `packages/vault/cbor/src/cose/sign1.test.ts` — extend to cover ES384
and ES512 round-trip via the new primitives.
## Migration impact
Additive. No existing consumers break — the deferred branches in cbor
were already throwing.
## Impact
### Affected dependents
`@suaveplan/cbor` — ES384 / ES512 paths become operational; previously
threw `UNEXPECTED_TYPE`.
For all other packages: no behavioural change. The new primitives are
opt-in subpath imports.

View File

@@ -0,0 +1,129 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: crypto-curve (crypto-curve-p384-p521)
**Package**: `@suaveplan/crypto-curve`
**Status**: PROPOSED — additive delta; folded into canonical spec on archive.
## ADDED Requirements
### Requirement: NIST P-384 (secp384r1) ECDSA primitive
The package SHALL export a `p384` namespace from the root barrel and a
matching `./p384` subpath export, providing `getPublicKey`, `sign`,
`verify`, and `randomPrivateKey`. Private keys SHALL be 48 bytes,
compressed public keys SHALL be 49 bytes, and compact ECDSA signatures
SHALL be 96 bytes. Callers SHALL pre-hash messages; the module operates
on digests. The selector SHALL forward to the noble-backed implementation
by default; the native implementation SHALL throw
`PrimitiveNotImplemented("p384")` until a WebCrypto bridge lands.
#### Scenario: P-384 sign + verify round-trip
- **GIVEN** a freshly-generated P-384 private key and 48-byte digest
- **WHEN** `verify(sign(digest, sk), digest, getPublicKey(sk))` is computed
- **THEN** the result is `true`
#### Scenario: P-384 verify rejects tampered signature
- **WHEN** a single byte of a valid P-384 signature is XORed with `0xff`
- **THEN** `verify` returns `false`
#### Scenario: P-384 verify returns false on malformed inputs
- **WHEN** `verify` is invoked with non-`Uint8Array` inputs or empty
buffers
- **THEN** the call returns `false` (does not throw)
**Verified by:**
`packages/crypto/crypto-curve/src/primitives/p384/p384.test.ts::"sign + verify round-trips"`
AND `…::"verify returns false for tampered signature (no throw)"`
AND `…::"verify returns false for bogus input (caught)"`
---
### Requirement: NIST P-521 (secp521r1) ECDSA primitive
The package SHALL export a `p521` namespace from the root barrel and a
matching `./p521` subpath export, providing `getPublicKey`, `sign`,
`verify`, and `randomPrivateKey`. Private keys SHALL be 66 bytes (521-bit
scalar left-padded to a byte boundary), compressed public keys SHALL be
67 bytes, and compact ECDSA signatures SHALL be 132 bytes. Callers SHALL
pre-hash messages; the module operates on digests. The selector SHALL
forward to the noble-backed implementation by default; the native
implementation SHALL throw `PrimitiveNotImplemented("p521")`.
#### Scenario: P-521 sign + verify round-trip
- **GIVEN** a freshly-generated P-521 private key and 64-byte digest
- **WHEN** `verify(sign(digest, sk), digest, getPublicKey(sk))` is computed
- **THEN** the result is `true` and the signature is 132 bytes long
#### Scenario: P-521 verify rejects tampered signature
- **WHEN** a single byte of a valid P-521 signature is XORed with `0xff`
- **THEN** `verify` returns `false`
#### Scenario: P-521 verify returns false on malformed inputs
- **WHEN** `verify` is invoked with non-`Uint8Array` inputs or empty
buffers
- **THEN** the call returns `false` (does not throw)
**Verified by:**
`packages/crypto/crypto-curve/src/primitives/p521/p521.test.ts::"sign + verify round-trips"`
AND `…::"verify returns false for tampered signature (no throw)"`
AND `…::"verify returns false for bogus input (caught)"`
---
### Requirement: Native stubs preserve layout
The package SHALL ship native-backend placeholder modules
(`primitives/p384/impl/p384.native/`,
`primitives/p521/impl/p521.native/`) whose every exported function throws
`PrimitiveNotImplemented(<curve>)`. The placeholders SHALL match the
existing `p256.native` pattern so the selector switch is mechanical when
a real native implementation lands.
#### Scenario: P-384 native stub throws on every operation
- **WHEN** any of `getPublicKey`, `sign`, `verify`, or `randomPrivateKey`
is invoked on the native module
- **THEN** the call throws `PrimitiveNotImplemented("p384")`
#### Scenario: P-521 native stub throws on every operation
- **WHEN** the same operations are invoked on the P-521 native module
- **THEN** the call throws `PrimitiveNotImplemented("p521")`
**Verified by:**
`…/p384/p384.test.ts::"every operation throws PrimitiveNotImplemented"`
AND `…/p521/p521.test.ts::"every operation throws PrimitiveNotImplemented"`
---
### Requirement: Subpath exports for P-384 and P-521
The package SHALL declare `./p384` and `./p521` entries in
`package.json` `exports` so consumers tree-shake to the individual
primitive. The `vite.config.ts` build entry list SHALL include both new
primitives so the generated `dist/` tree carries the expected `.d.ts` and
`.js` files at the published path.
#### Scenario: P-384 subpath resolves
- **WHEN** a consumer imports `@suaveplan/crypto-curve/p384`
- **THEN** the import resolves with no `Module not found` error and
exposes `getPublicKey`, `sign`, `verify`, `randomPrivateKey`
#### Scenario: P-521 subpath resolves
- **WHEN** a consumer imports `@suaveplan/crypto-curve/p521`
- **THEN** the import resolves with no `Module not found` error and
exposes the same four symbols
**Verified by:** TypeScript compile of consumers (e.g.
`packages/vault/cbor/src/cose/sign1.ts`) that import these subpaths.
<!-- markdownlint-enable MD024 -->

View File

@@ -0,0 +1,44 @@
# Tasks — `@suaveplan/crypto-curve` — add P-384 + P-521
## 1. P-384 primitive
- [x] 1.1 `src/primitives/p384/p384.ts` (re-export barrel)
- [x] 1.2 `src/primitives/p384/p384.select/p384.select.ts` (generated forwarder)
- [x] 1.3 `src/primitives/p384/impl/p384.noble/p384.noble.ts` (noble-backed impl)
- [x] 1.4 `src/primitives/p384/impl/p384.native/p384.native.ts` (PrimitiveNotImplemented stubs)
- [x] 1.5 `src/primitives/p384/p384.test.ts` (noble round-trip + native stub coverage)
- [x] 1.6 Sibling `.md` for every `.ts` (≥200 words)
## 2. P-521 primitive
- [x] 2.1 `src/primitives/p521/p521.ts`
- [x] 2.2 `src/primitives/p521/p521.select/p521.select.ts`
- [x] 2.3 `src/primitives/p521/impl/p521.noble/p521.noble.ts`
- [x] 2.4 `src/primitives/p521/impl/p521.native/p521.native.ts`
- [x] 2.5 `src/primitives/p521/p521.test.ts`
- [x] 2.6 Sibling `.md` for every `.ts`
## 3. Barrel + exports
- [x] 3.1 `src/index.ts` — add `export * as p384` / `export * as p521`
- [x] 3.2 `package.json` — add `./p384` and `./p521` subpath exports
- [x] 3.3 `vite.config.ts` — add new entry points
## 4. cbor wiring
- [x] 4.1 `cose/sign1.ts` — replace ES384/ES512 placeholder throws with real p384 / p521 verify paths
- [x] 4.2 `cose/sign1.ts` — generalise `ec2ToCompressedPoint` to accept coordinate length (32 / 48 / 66)
- [x] 4.3 `cose/sign1.test.ts` — replace not-implemented test with ES384 + ES512 end-to-end round-trips
- [x] 4.4 `vite.config.ts` — add `@suaveplan/crypto-curve/p384` and `/p521` to externals
## 5. Pre-archive verification
- [x] 5.1 `bunx turbo lint typecheck test:coverage build --filter=@suaveplan/crypto-curve` — green
- [x] 5.2 `bunx turbo lint typecheck test:coverage build --filter=@suaveplan/cbor` — green
- [x] 5.3 Coverage 100/100/100/100 on both packages
- [x] 5.4 681 cbor tests pass
## 6. Archive
- [x] 6.1 Fold spec delta into `openspec/specs/crypto-curve/spec.md`
- [x] 6.2 Move `openspec/changes/crypto-curve-p384-p521/` → `openspec/archive/2026-05-10-crypto-curve-p384-p521/`