first commit
This commit is contained in:
7
docs/security-stack/crypto/crypto-curve/CHANGELOG.md
Normal file
7
docs/security-stack/crypto/crypto-curve/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# @suaveplan/crypto-curve
|
||||
|
||||
## 0.0.0
|
||||
|
||||
- Initial scaffold under `crypto-primitive-replaceability` Phase 0.
|
||||
- secp256k1, ed25519, x25519, p256 primitives with per-primitive selector layout.
|
||||
- Noble-backed impls (`@noble/curves`); native stubs throw `PrimitiveNotImplemented`.
|
||||
103
docs/security-stack/crypto/crypto-curve/README.md
Normal file
103
docs/security-stack/crypto/crypto-curve/README.md
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,52 @@
|
||||
# crypto-curve error codes
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`codes.ts` registers the stable error-code namespace for `@suaveplan/crypto-curve` with `defineCodes` from `@suaveplan/error`. Every error thrown by this package routes through one of these codes so consumers can write stable code-based dispatch and so observability backends see a finite tag set.
|
||||
|
||||
The codes are namespaced under `CRYPTO.CURVE` to keep them disjoint from sibling crypto packages (`CRYPTO.CIPHER.*`, `CRYPTO.HASH.*`, `CRYPTO.KEYS.*`).
|
||||
|
||||
## Features
|
||||
|
||||
- `CRYPTO_CURVE_CODES` — frozen registry mapping symbolic names to wire codes.
|
||||
- `CryptoCurveCode` — string union of all wire codes for type-safe dispatch.
|
||||
- Adding a code requires editing this file and re-running typecheck — there is no runtime path that injects a code.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { CRYPTO_CURVE_CODES, CryptoCurveError } from "@suaveplan/crypto-curve";
|
||||
|
||||
throw new CryptoCurveError(
|
||||
CRYPTO_CURVE_CODES.RECOVERY_BYTE_MISSING,
|
||||
"error.crypto.curve.recovery-byte-missing",
|
||||
{ context: { signatureLength: 64 } }
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { CRYPTO_CURVE_CODES, type CryptoCurveCode } from "@suaveplan/crypto-curve";
|
||||
|
||||
function isRecoverableError(code: CryptoCurveCode): boolean {
|
||||
return (
|
||||
code === CRYPTO_CURVE_CODES.RECOVERY_BYTE_MISSING ||
|
||||
code === CRYPTO_CURVE_CODES.RECOVERY_BYTE_INVALID
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `CRYPTO_CURVE_CODES.RECOVERY_BYTE_MISSING: "CRYPTO.CURVE.RECOVERY_BYTE_MISSING"` — thrown when `recoverPublicKey` receives a 64-byte signature without a recovery byte.
|
||||
- `CRYPTO_CURVE_CODES.RECOVERY_BYTE_INVALID: "CRYPTO.CURVE.RECOVERY_BYTE_INVALID"` — thrown when the recovery byte is not in `{0, 1}` (some libraries also emit `2` and `3` for compressed-form recovery, which are explicitly rejected here).
|
||||
- `CRYPTO_CURVE_CODES.RECOVERED_KEY_MISMATCH: "CRYPTO.CURVE.RECOVERED_KEY_MISMATCH"` — reserved for callers that need to surface the comparison-failure error rather than receiving `false` from `verifyWithRecovery`. Today the public API does not throw this code (it returns `false` instead) but it is registered for future use.
|
||||
- `CryptoCurveCode` — type alias of the union of all values above.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The codes are **stable wire identifiers** — once shipped they cannot be renamed or removed. Adding a new code is permitted; rename-or-delete requires a new spec change with a deprecation cycle. The codes use `SCREAMING_SNAKE_CASE` to match the suaveplan-wide convention; they include the package prefix to disambiguate when multiple packages route through a shared OTel exporter or log aggregator.
|
||||
@@ -0,0 +1,63 @@
|
||||
# CryptoCurveError
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`crypto-curve-error.ts` defines the `CryptoCurveError` class used by every error path in `@suaveplan/crypto-curve`. It is a thin alias of `SuaveplanError<CryptoCurveCode>` from `@suaveplan/error` — this means it inherits the full error substrate (cause-chain traversal, OTel span events, structured-logger redaction, i18n message-key resolution, and the wire-format JSON / binary serialiser) without any per-package re-implementation.
|
||||
|
||||
Routing every thrown error through this single class is what allows downstream consumers to write `instanceof CryptoCurveError` for package-level dispatch and `error.code === CRYPTO_CURVE_CODES.X` for code-level dispatch, simultaneously, with full type narrowing.
|
||||
|
||||
## Features
|
||||
|
||||
- Extends `SuaveplanError<CryptoCurveCode>` so the `code` field is typed as a finite union, not an arbitrary string.
|
||||
- Carries a `cause` chain — the secp256k1 ECDSA recovery path wraps `@noble/curves` errors as `cause` so the original message is preserved in logs.
|
||||
- Auto-instruments OTel span events when an active span exists (no caller setup required).
|
||||
- Participates in the wire-format error serialiser (Zod-validated JSON envelope) so errors round-trip across process boundaries cleanly.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { CRYPTO_CURVE_CODES, CryptoCurveError } from "@suaveplan/crypto-curve";
|
||||
|
||||
throw new CryptoCurveError(
|
||||
CRYPTO_CURVE_CODES.RECOVERY_BYTE_INVALID,
|
||||
"error.crypto.curve.recovery-byte-invalid",
|
||||
{ context: { recoveryByte: 7 } }
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { CryptoCurveError } from "@suaveplan/crypto-curve";
|
||||
|
||||
try {
|
||||
pubKey = recoverPublicKey(sig, digest);
|
||||
} catch (err) {
|
||||
if (err instanceof CryptoCurveError) {
|
||||
switch (err.code) {
|
||||
case "CRYPTO.CURVE.RECOVERY_BYTE_MISSING":
|
||||
throw new Error("Old wallet format — please re-sign with --recovery flag");
|
||||
case "CRYPTO.CURVE.RECOVERY_BYTE_INVALID":
|
||||
throw new Error("Signature corrupt or wrong format");
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `CryptoCurveError extends SuaveplanError<CryptoCurveCode>` — public class, exported from the package root.
|
||||
- `new CryptoCurveError(code, messageKey, options?)` — standard constructor mirroring `SuaveplanError`.
|
||||
- `error.code: CryptoCurveCode` — typed code field.
|
||||
- `error.context: Record<string, unknown> | undefined` — structured context attached at throw site.
|
||||
- `error.cause: unknown` — cause chain.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The constructor does no work beyond delegating to the base class — its only job is to set the generic parameter to `CryptoCurveCode` so consumers see the right type. The error class is the entire reason a separate `CRYPTO_CURVE_CODES` registry exists; without it, callers would either lose code-narrowing or have to import the registry separately. The pattern matches the sibling packages (`CryptoCipherError`, `CryptoHashError`) so all three are interchangeable from a structural standpoint, differing only in which code namespace they pin.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Ed25519
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [ed25519.select](./ed25519.select/ed25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_ - [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519](./ed25519.md) — <!-- begin:auto-docs --> _(no submodules)_- ed25519.select — <!-- begin:auto-docs --> _(no submo…
|
||||
- [ed25519.select](./ed25519.select/ed25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519](./ed25519.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.select](./ed25519.select/ed25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519](./ed25519.md) — <!-- begin:auto-docs --> _(no submodules)_- ed25519.select — <!-- begin:…
|
||||
- [ed25519.select](./ed25519.select/ed25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519](./ed25519.md) — <!-- begin:auto-docs --> _(no submodules)_- ed25519.select — <!-- begin:auto-docs --> _(no subm…
|
||||
- [ed25519.select](./ed25519.select/ed25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_ - [ed25519.native](./impl/ed25519.native/ed25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [ed25519.noble](./impl/ed25519.noble/ed25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [ed25519](./ed25519.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
Ed25519 is the Edwards-curve Digital Signature Algorithm defined in RFC 8032, the recommended signature scheme for new protocols. Compared with ECDSA it has deterministic signatures (no per-signature randomness needed), constant-time verification of well-formed inputs, small 32-byte public keys and 64-byte signatures, and formal soundness properties that make it the baseline choice for TLS, SSH, JWT signing, and peer-to-peer identity. This module exposes the primitive directly — key generation, signing, verification, and public-key derivation — as thin wrappers over `@noble/curves/ed25519`, with a paired `native` stub that throws `PrimitiveNotImplemented`. Selection between the two is controlled by `crypto.config.ts` at build time.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` → 32-byte compressed Edwards public key.
|
||||
- `sign(message, privateKey)` → 64-byte deterministic signature.
|
||||
- `verify(signature, message, publicKey)` → `boolean` that never throws for malformed inputs.
|
||||
- `randomPrivateKey()` → 32 random bytes (every 32-byte value is a valid Ed25519 secret; no retry loop needed).
|
||||
- RFC 8032 §7.1 vector 1 is covered in tests.
|
||||
- Native stub throws `PrimitiveNotImplemented('ed25519')` — flip to `'native'` only after per-primitive audit (Phase 3).
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as ed25519 from "@suaveplan/crypto-curve/ed25519";
|
||||
|
||||
const sk = ed25519.randomPrivateKey();
|
||||
const pk = ed25519.getPublicKey(sk);
|
||||
const msg = new TextEncoder().encode("hello");
|
||||
const sig = ed25519.sign(msg, sk);
|
||||
console.log(ed25519.verify(sig, msg, pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as ed25519 from "@suaveplan/crypto-curve/ed25519";
|
||||
import { zeroize } from "@suaveplan/crypto-utils/zeroize";
|
||||
|
||||
function signAndDiscardKey(msg: Uint8Array): { pk: Uint8Array; sig: Uint8Array } {
|
||||
const sk = ed25519.randomPrivateKey();
|
||||
try {
|
||||
const pk = ed25519.getPublicKey(sk);
|
||||
const sig = ed25519.sign(msg, sk);
|
||||
return { pk, sig };
|
||||
} finally {
|
||||
zeroize(sk);
|
||||
}
|
||||
}
|
||||
|
||||
const { pk, sig } = signAndDiscardKey(new Uint8Array([1, 2, 3]));
|
||||
console.log(pk.length, sig.length);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array` — 32-byte public key. Throws if `privateKey.length !== 32`.
|
||||
- `sign(message: Uint8Array, privateKey: Uint8Array): Uint8Array` — 64-byte signature.
|
||||
- `verify(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean` — returns `false` instead of throwing on malformed inputs.
|
||||
- `randomPrivateKey(): Uint8Array` — 32 random bytes via `@suaveplan/crypto-utils/random`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Noble's `verify` throws on some malformed inputs (bad-length public key, non-canonical signature encodings) rather than returning `false`. The `noble` impl wraps the call in a try/catch so every flavour exposes a boolean-only contract — CI's differential fuzz harness depends on that uniformity. Signing is deterministic, so the same `(msg, sk)` pair always produces the same signature: this is a deliberate property that makes replay/caching sound and prevents fault-attack reuse. For signing data larger than a few megabytes, streaming APIs are not offered here on purpose — callers should compose over the hash-then-sign idiom instead.
|
||||
@@ -0,0 +1,57 @@
|
||||
# ed25519.select
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`ed25519.select.ts` is the build-time primitive selector for the Ed25519 implementation in `@suaveplan/crypto-curve`. It is generated by `bun scripts/gen-crypto-selects.ts` and contains a single `export *` that points to either `ed25519.noble.ts` or `ed25519.native.ts` based on the `ed25519` entry in `crypto.config.ts`.
|
||||
|
||||
Callers import through `ed25519.ts` (the stable public entry point), which delegates here. The selector indirection allows switching backends by regenerating one file without changing any caller imports. Currently the Noble backend is selected because the native backend throws `PrimitiveNotImplemented`.
|
||||
|
||||
## Features
|
||||
|
||||
- Single-file re-export of `getPublicKey`, `sign`, `verify`, and `randomPrivateKey`.
|
||||
- Generated by `bun scripts/gen-crypto-selects.ts` — never hand-edited.
|
||||
- Controlled by `crypto.config.ts`: set `ed25519` to `'noble'` or `'native'`.
|
||||
- Enables static dead-code elimination of the inactive backend.
|
||||
- Maintains a stable API across backend switches.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Import via the stable public entry point.
|
||||
import * as ed25519 from "@suaveplan/crypto-curve/ed25519";
|
||||
|
||||
const sk = ed25519.randomPrivateKey();
|
||||
const pk = ed25519.getPublicKey(sk);
|
||||
const sig = ed25519.sign(new TextEncoder().encode("hello"), sk);
|
||||
console.log(ed25519.verify(sig, new TextEncoder().encode("hello"), pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as ed25519 from "@suaveplan/crypto-curve/ed25519";
|
||||
import { toHex } from "@suaveplan/crypto-hash/encoding";
|
||||
|
||||
// Generate a key pair and publish the hex-encoded public key.
|
||||
function generateKeyPair(): { secretKey: Uint8Array; publicKeyHex: string } {
|
||||
const secretKey = ed25519.randomPrivateKey();
|
||||
const pk = ed25519.getPublicKey(secretKey);
|
||||
return { secretKey, publicKeyHex: toHex(pk) };
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Re-exports from the active implementation:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array` — 32-byte public key.
|
||||
- `sign(message: Uint8Array, privateKey: Uint8Array): Uint8Array` — 64-byte deterministic signature.
|
||||
- `verify(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean` — never throws.
|
||||
- `randomPrivateKey(): Uint8Array` — 32 random bytes.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Unlike secp256k1 or P-256, every 32-byte sequence is a valid Ed25519 private key, so `randomPrivateKey` needs no rejection sampling. The selector file is marked `/* GENERATED — DO NOT EDIT */`; hand-editing it will be overwritten by the next generator run. The generator is idempotent.
|
||||
@@ -0,0 +1,64 @@
|
||||
# ed25519.native
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`ed25519.native.ts` is the native-backend placeholder for the Ed25519 primitive in `@suaveplan/crypto-curve`. It exports the same four functions as `ed25519.noble.ts` — `getPublicKey`, `sign`, `verify`, and `randomPrivateKey` — each throwing `PrimitiveNotImplemented("ed25519")`.
|
||||
|
||||
This stub maintains the two-implementation layout for Ed25519. It must be present so the selector generator can point to it without structural errors. When an in-house or platform-native Ed25519 implementation is ready, this file is updated and the selector regenerated.
|
||||
|
||||
## Features
|
||||
|
||||
- Satisfies `getPublicKey`, `sign`, `verify`, and `randomPrivateKey` interfaces.
|
||||
- Throws `PrimitiveNotImplemented("ed25519")` from every function.
|
||||
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
|
||||
- Clearly communicates that the native path is not yet active.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Active only when crypto.config.ts sets ed25519 to 'native'.
|
||||
import * as ed25519 from "@suaveplan/crypto-curve/ed25519";
|
||||
|
||||
try {
|
||||
ed25519.randomPrivateKey();
|
||||
} catch (err) {
|
||||
// PrimitiveNotImplemented: ed25519
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
// Detect availability before use in a capability-adaptive application.
|
||||
import * as ed25519 from "@suaveplan/crypto-curve/ed25519";
|
||||
|
||||
function isEd25519Available(): boolean {
|
||||
try {
|
||||
ed25519.randomPrivateKey();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All functions throw `PrimitiveNotImplemented("ed25519")`:
|
||||
|
||||
- `getPublicKey(_privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(_message: Uint8Array, _privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(_signature: Uint8Array, _message: Uint8Array, _publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Underscore-prefixed parameters suppress unused-variable diagnostics without lint-disable comments. When the native implementation is ready (RFC 8032 KAT suite must pass), parameters are renamed, `throw` replaced with the real algorithm, and the selector regenerated. The `PrimitiveNotImplemented` error participates in the full `@suaveplan/error` structured-error system.
|
||||
|
||||
### Status
|
||||
|
||||
Placeholder native stub — throws `PrimitiveNotImplemented` until a Node `crypto.sign("ed25519", ...)` bridge lands for the `native` engine. Callers compile against the `ed25519.select.ts` selector which falls through to the `@noble/curves` implementation in the `noble` engine slot, so shipping this stub does not break runtime behaviour on any platform today.
|
||||
@@ -0,0 +1,83 @@
|
||||
# ed25519.noble
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`ed25519.noble.ts` is the Noble-backed concrete implementation of the Ed25519 primitive in `@suaveplan/crypto-curve`. It wraps the Ed25519 operations from `@noble/curves/ed25519`, providing public-key derivation, deterministic signing, signature verification, and random private-key generation.
|
||||
|
||||
Ed25519 (RFC 8032 §5.1) is the Edwards-curve Digital Signature Algorithm on Curve25519. It is the recommended signature scheme for new protocols due to its deterministic signatures (no per-signature randomness), small key and signature sizes (32 and 64 bytes respectively), and formal soundness properties. This file is the production implementation; `ed25519.native.ts` is the placeholder stub.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — 32-byte compressed Edwards public key via scalar multiplication.
|
||||
- `sign(message, privateKey)` — 64-byte deterministic signature over the raw message (Noble handles internal hashing per RFC 8032).
|
||||
- `verify(signature, message, publicKey)` — boolean; wraps Noble's throwing `verify` in try/catch.
|
||||
- `randomPrivateKey()` — 32 random bytes from `@suaveplan/crypto-utils/random`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as ed25519 from "@suaveplan/crypto-curve/ed25519";
|
||||
|
||||
const sk = ed25519.randomPrivateKey();
|
||||
const pk = ed25519.getPublicKey(sk);
|
||||
const msg = new TextEncoder().encode("authenticate");
|
||||
const sig = ed25519.sign(msg, sk);
|
||||
console.log(ed25519.verify(sig, msg, pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as ed25519 from "@suaveplan/crypto-curve/ed25519";
|
||||
import { zeroize } from "@suaveplan/crypto-utils/zeroize";
|
||||
|
||||
// Sign a JWT payload and zeroize the private key immediately.
|
||||
function signPayload(payload: string): { pk: Uint8Array; sig: Uint8Array } {
|
||||
const sk = ed25519.randomPrivateKey();
|
||||
const encoded = new TextEncoder().encode(payload);
|
||||
try {
|
||||
return {
|
||||
pk: ed25519.getPublicKey(sk),
|
||||
sig: ed25519.sign(encoded, sk),
|
||||
};
|
||||
} finally {
|
||||
zeroize(sk);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `privateKey` | `Uint8Array` | Any 32-byte value. |
|
||||
|
||||
**Returns:** 32-byte compressed Edwards-form public key.
|
||||
|
||||
### `sign(message: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
Signs the raw message (Noble internally hashes with SHA-512 per RFC 8032).
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `message` | `Uint8Array` | Raw message bytes (any length). |
|
||||
| `privateKey` | `Uint8Array` | 32-byte private key. |
|
||||
|
||||
**Returns:** 64-byte signature.
|
||||
|
||||
### `verify(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
|
||||
Returns `false` for malformed inputs rather than throwing.
|
||||
|
||||
### `randomPrivateKey(): Uint8Array`
|
||||
|
||||
Returns 32 random bytes. No rejection sampling needed (all 32-byte values are valid).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Ed25519 signs the raw message, not a pre-hash, because the RFC 8032 construction hashes internally using SHA-512 for the nonce and the final signature computation. This differs from secp256k1/P-256, which expect a pre-hashed digest. Noble's `verify` throws on some malformed inputs; the `try/catch` wrapper normalises this to a boolean, consistent with the rest of the curve implementations. RFC 8032 §7.1 test vector 1 passes in the test suite.
|
||||
@@ -0,0 +1,73 @@
|
||||
# p256.native
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`p256.native.ts` is the native-backend placeholder for the NIST P-256 primitive in `@suaveplan/crypto-curve`. It exports the complete P-256 surface — `getPublicKey`, `sign`, `verify`, and `randomPrivateKey` — each throwing `PrimitiveNotImplemented("p256")`.
|
||||
|
||||
P-256 has native WebCrypto support in all modern browsers (`SubtleCrypto.sign` with `{ name: "ECDSA", hash: "SHA-256" }`) and Node.js 15+. A future native implementation would delegate to these platform APIs, enabling hardware-backed key operations. The stub keeps the layout complete until that work lands.
|
||||
|
||||
## Features
|
||||
|
||||
- Satisfies the full P-256 interface: `getPublicKey`, `sign`, `verify`, `randomPrivateKey`.
|
||||
- Throws `PrimitiveNotImplemented("p256")` from every function.
|
||||
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Active only when crypto.config.ts sets p256 to 'native'.
|
||||
import * as p256 from "@suaveplan/crypto-curve/p256";
|
||||
|
||||
try {
|
||||
p256.randomPrivateKey();
|
||||
} catch (err) {
|
||||
// PrimitiveNotImplemented: p256
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
// Capability detection for native P-256 availability.
|
||||
import * as p256 from "@suaveplan/crypto-curve/p256";
|
||||
|
||||
async function nativeP256Available(): Promise<boolean> {
|
||||
try {
|
||||
p256.randomPrivateKey();
|
||||
return true;
|
||||
} catch {
|
||||
// Fall back to WebCrypto directly.
|
||||
try {
|
||||
await crypto.subtle.generateKey(
|
||||
{ name: "ECDSA", namedCurve: "P-256" },
|
||||
false,
|
||||
["sign"]
|
||||
);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All functions throw `PrimitiveNotImplemented("p256")`:
|
||||
|
||||
- `getPublicKey(_privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(_digest: Uint8Array, _privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(_signature: Uint8Array, _digest: Uint8Array, _publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The native P-256 backend is particularly valuable for browser environments, where `SubtleCrypto` can use hardware security keys (YubiKey, TPM) via the non-extractable key pattern. A native implementation would wrap the async `SubtleCrypto` APIs and handle the DER-to-compact signature conversion that WebCrypto uses by default. Until that implementation is ready and has passed FIPS 186-4 CAVP vectors, this stub surfaces a clear error. Underscore-prefixed parameters suppress unused-variable warnings.
|
||||
|
||||
### Status
|
||||
|
||||
Placeholder native stub — throws `PrimitiveNotImplemented` until a WebCrypto `ECDSA P-256` bridge is wired through. Today the `p256.select` selector falls through to the Noble implementation in every engine mode; that path is covered by the NIST CAVP KAT harness in `@suaveplan/crypto-utils`, so the production path is fully exercised.
|
||||
@@ -0,0 +1,83 @@
|
||||
# p256.noble
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`p256.noble.ts` is the Noble-backed concrete implementation of the NIST P-256 primitive in `@suaveplan/crypto-curve`. It wraps the `p256` object from `@noble/curves/p256`, providing ECDSA signing and verification, public-key derivation, and rejection-sampled private-key generation.
|
||||
|
||||
P-256 (secp256r1, FIPS 186-4) is the most widely deployed NIST curve. Its operations — including ECDSA with SHA-256 (ES256) — are hardware-accelerated in browser WebCrypto and available natively across all major TLS implementations. Signatures are returned in compact 64-byte (r ‖ s) form. Callers must pre-hash the message; this module operates on digests.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — 33-byte compressed P-256 public key.
|
||||
- `sign(digest, privateKey)` — deterministic ECDSA (RFC 6979); compact 64-byte output.
|
||||
- `verify(signature, digest, publicKey)` — boolean; wraps Noble's throwing `verify`.
|
||||
- `randomPrivateKey()` — rejection-sampled 32-byte key via `@suaveplan/crypto-utils/random`.
|
||||
- Noble's `p256.utils.isValidPrivateKey` gates the rejection loop.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as p256 from "@suaveplan/crypto-curve/p256";
|
||||
import { sha256 } from "@suaveplan/crypto-hash/hash";
|
||||
|
||||
const sk = p256.randomPrivateKey();
|
||||
const pk = p256.getPublicKey(sk);
|
||||
const digest = sha256(new TextEncoder().encode("payload"));
|
||||
const sig = p256.sign(digest, sk);
|
||||
console.log(p256.verify(sig, digest, pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p256 from "@suaveplan/crypto-curve/p256";
|
||||
import { sha256 } from "@suaveplan/crypto-hash/hash";
|
||||
import { zeroize } from "@suaveplan/crypto-utils/zeroize";
|
||||
|
||||
// Create a one-time signing key pair for a request.
|
||||
function ephemeralSign(data: Uint8Array): { pk: Uint8Array; sig: Uint8Array } {
|
||||
const sk = p256.randomPrivateKey();
|
||||
try {
|
||||
return {
|
||||
pk: p256.getPublicKey(sk),
|
||||
sig: p256.sign(sha256(data), sk),
|
||||
};
|
||||
} finally {
|
||||
zeroize(sk);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `privateKey` | `Uint8Array` | 32-byte scalar in [1, n−1]. |
|
||||
|
||||
**Returns:** 33-byte compressed public key.
|
||||
|
||||
### `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `digest` | `Uint8Array` | Pre-computed message hash (32 bytes for SHA-256/ES256). |
|
||||
| `privateKey` | `Uint8Array` | 32-byte private key. |
|
||||
|
||||
**Returns:** 64-byte compact (r ‖ s) ECDSA signature.
|
||||
|
||||
### `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
|
||||
Returns `false` for malformed inputs rather than throwing.
|
||||
|
||||
### `randomPrivateKey(): Uint8Array`
|
||||
|
||||
Returns a valid P-256 scalar. Retries until `isValidPrivateKey` passes.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`sign` calls Noble's `.toBytes("compact")` to produce the flat 64-byte format; callers needing ASN.1 DER must convert externally. `verify` wraps Noble's call in try/catch because some malformed input combinations (non-canonical signatures, out-of-range coordinates) cause exceptions rather than `false` returns. The rejection loop in `randomPrivateKey` is statistically negligible — probability of an invalid scalar is approximately 1 in 2¹²⁸. NIST FIPS 186-4 / CAVP test vectors pass in the test suite.
|
||||
@@ -0,0 +1,64 @@
|
||||
# p256
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [p256.native](./impl/p256.native/p256.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256](./p256.md) — <!-- begin:auto-docs --> _(no submodules)_- p256.native — <!-- begin:auto-docs --> _(no submodul…
|
||||
- [p256.native](./impl/p256.native/p256.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256](./p256.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.native](./impl/p256.native/p256.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256](./p256.md) — <!-- begin:auto-docs --> _(no submodules)_- p256.native — <!-- begin:aut…
|
||||
- [p256.native](./impl/p256.native/p256.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256](./p256.md) — <!-- begin:auto-docs --> _(no submodules)_- p256.native — <!-- begin:auto-docs --> _(no submodu…
|
||||
- [p256.native](./impl/p256.native/p256.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p256.noble](./impl/p256.noble/p256.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p256.select](./p256.select/p256.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p256](./p256.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
`p256.ts` is the stable public entry point for the NIST P-256 elliptic-curve primitive in `@suaveplan/crypto-curve`. It re-exports everything from `p256.select.ts`, which forwards to either the Noble-backed or native implementation based on `crypto.config.ts`.
|
||||
|
||||
P-256 (also known as secp256r1 or prime256v1, standardised in FIPS 186-4) is the most widely deployed NIST curve. It is mandatory in TLS 1.3, used in ECDSA-P256 JWT signing (ES256), WebAuthn, and is natively available in every browser's WebCrypto API. Callers import from this file and remain insulated from implementation changes.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — 33-byte compressed public key.
|
||||
- `sign(digest, privateKey)` — compact 64-byte ECDSA signature (r ‖ s). Caller pre-hashes.
|
||||
- `verify(signature, digest, publicKey)` — boolean; never throws on malformed inputs.
|
||||
- `randomPrivateKey()` — rejection-sampled 32-byte private key.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as p256 from "@suaveplan/crypto-curve/p256";
|
||||
import { sha256 } from "@suaveplan/crypto-hash/hash";
|
||||
|
||||
const sk = p256.randomPrivateKey();
|
||||
const pk = p256.getPublicKey(sk);
|
||||
const msg = new TextEncoder().encode("hello P-256");
|
||||
const sig = p256.sign(sha256(msg), sk);
|
||||
console.log(p256.verify(sig, sha256(msg), pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p256 from "@suaveplan/crypto-curve/p256";
|
||||
import { sha256 } from "@suaveplan/crypto-hash/hash";
|
||||
import { toHex } from "@suaveplan/crypto-hash/encoding";
|
||||
|
||||
// Sign a JSON payload and produce a JWS-style compact token.
|
||||
function signPayload(payload: object, sk: Uint8Array): string {
|
||||
const header = btoa(JSON.stringify({ alg: "ES256" }));
|
||||
const body = btoa(JSON.stringify(payload));
|
||||
const sigInput = new TextEncoder().encode(`${header}.${body}`);
|
||||
const sig = p256.sign(sha256(sigInput), sk);
|
||||
return `${header}.${body}.${toHex(sig)}`;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All symbols re-exported from `p256.select.ts`:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a zero-logic re-export barrel. Its sole purpose is the stable import path `@suaveplan/crypto-curve/p256`. P-256 callers must pre-hash their messages before calling `sign`; unlike Ed25519, P-256's ECDSA does not hash internally. Use SHA-256 for ES256 compatibility.
|
||||
@@ -0,0 +1,59 @@
|
||||
# p256.select
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`p256.select.ts` is the build-time primitive selector for NIST P-256 in `@suaveplan/crypto-curve`. Generated by `bun scripts/gen-crypto-selects.ts`, it re-exports one concrete backend — `p256.noble.ts` or `p256.native.ts` — according to the `p256` field in `crypto.config.ts`.
|
||||
|
||||
Callers import through the stable `p256.ts` entry point. This selector level insulates all call sites from backend changes: regenerating this file is the only step required to switch implementations. Currently the Noble backend is active because the native stub throws `PrimitiveNotImplemented`.
|
||||
|
||||
## Features
|
||||
|
||||
- Single re-export of `getPublicKey`, `sign`, `verify`, and `randomPrivateKey`.
|
||||
- Generated — never hand-edited.
|
||||
- Controlled by `crypto.config.ts`: set `p256` to `'noble'` or `'native'`.
|
||||
- Enables dead-code elimination of the inactive backend.
|
||||
- Maintains a stable API across backend switches.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as p256 from "@suaveplan/crypto-curve/p256";
|
||||
|
||||
const sk = p256.randomPrivateKey();
|
||||
const pk = p256.getPublicKey(sk);
|
||||
console.log(pk.length); // 33
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p256 from "@suaveplan/crypto-curve/p256";
|
||||
import { sha256 } from "@suaveplan/crypto-hash/hash";
|
||||
|
||||
// Sign and verify in one call.
|
||||
function roundTrip(message: Uint8Array): boolean {
|
||||
const sk = p256.randomPrivateKey();
|
||||
const pk = p256.getPublicKey(sk);
|
||||
const digest = sha256(message);
|
||||
const sig = p256.sign(digest, sk);
|
||||
return p256.verify(sig, digest, pk);
|
||||
}
|
||||
|
||||
console.log(roundTrip(new TextEncoder().encode("test"))); // true
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Re-exports from the active implementation:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array` — 33-byte compressed P-256 public key.
|
||||
- `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array` — 64-byte compact ECDSA signature.
|
||||
- `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array` — rejection-sampled 32-byte key.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The selector file contains a single `export * from` line and is marked `/* GENERATED — DO NOT EDIT */`. The generator is idempotent; regenerating does not alter the file if the configured backend has not changed. Tree-shaking of the inactive backend happens at bundle time — both impl files are present in source but only the selected one is reachable from this export.
|
||||
@@ -0,0 +1,88 @@
|
||||
# p384.native
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`p384.native.ts` is the native-backend placeholder for the NIST P-384
|
||||
primitive in `@suaveplan/crypto-curve`. It exports the complete P-384
|
||||
surface — `getPublicKey`, `sign`, `verify`, and `randomPrivateKey` — each
|
||||
throwing `PrimitiveNotImplemented("p384")`.
|
||||
|
||||
P-384 has native WebCrypto support in all modern browsers
|
||||
(`SubtleCrypto.sign` with `{ name: "ECDSA", hash: "SHA-384" }`) and
|
||||
Node.js 15+. A future native implementation would delegate to these
|
||||
platform APIs, enabling hardware-backed key operations. The stub keeps
|
||||
the layout complete until that work lands.
|
||||
|
||||
## Features
|
||||
|
||||
- Satisfies the full P-384 interface: `getPublicKey`, `sign`, `verify`,
|
||||
`randomPrivateKey`.
|
||||
- Throws `PrimitiveNotImplemented("p384")` from every function.
|
||||
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Active only when crypto.config.ts sets p384 to 'native'.
|
||||
import * as p384 from "@suaveplan/crypto-curve/p384";
|
||||
|
||||
try {
|
||||
p384.randomPrivateKey();
|
||||
} catch (err) {
|
||||
// PrimitiveNotImplemented: p384
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p384 from "@suaveplan/crypto-curve/p384";
|
||||
|
||||
async function nativeP384Available(): Promise<boolean> {
|
||||
try {
|
||||
p384.randomPrivateKey();
|
||||
return true;
|
||||
} catch {
|
||||
try {
|
||||
await crypto.subtle.generateKey(
|
||||
{ name: "ECDSA", namedCurve: "P-384" },
|
||||
false,
|
||||
["sign"]
|
||||
);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All functions throw `PrimitiveNotImplemented("p384")`:
|
||||
|
||||
- `getPublicKey(_privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(_digest: Uint8Array, _privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(_signature: Uint8Array, _digest: Uint8Array, _publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The native P-384 backend is particularly valuable for browser
|
||||
environments, where `SubtleCrypto` can use hardware security keys via the
|
||||
non-extractable key pattern. A native implementation would wrap the async
|
||||
`SubtleCrypto` APIs and handle the DER-to-compact signature conversion
|
||||
that WebCrypto uses by default. Until that implementation is ready and
|
||||
has passed FIPS 186-4 CAVP vectors, this stub surfaces a clear error.
|
||||
|
||||
### Status
|
||||
|
||||
Placeholder native stub — throws `PrimitiveNotImplemented` until a
|
||||
WebCrypto `ECDSA P-384` bridge is wired through. Today the
|
||||
`p384.select` selector falls through to the Noble implementation in
|
||||
every engine mode; the production path is exercised by the noble
|
||||
roundtrip suite in `p384.test.ts`.
|
||||
@@ -0,0 +1,92 @@
|
||||
# p384.noble
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`p384.noble.ts` is the Noble-backed concrete implementation of the NIST
|
||||
P-384 primitive in `@suaveplan/crypto-curve`. It wraps the `p384` object
|
||||
from `@noble/curves/p384`, providing ECDSA signing and verification,
|
||||
public-key derivation, and rejection-sampled private-key generation.
|
||||
|
||||
P-384 (secp384r1, FIPS 186-4) is the higher-security NIST curve used for
|
||||
COSE `ES384` signatures, Apple WebAuthn attestation in some
|
||||
configurations, FIDO2 device certificates that opt out of P-256 for
|
||||
additional security margin, and US Government Suite B deployments
|
||||
requiring ≥192-bit security strength. Signatures are returned in compact
|
||||
96-byte (r ‖ s) form. Callers must pre-hash the message; this module
|
||||
operates on digests.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — 49-byte compressed P-384 public key.
|
||||
- `sign(digest, privateKey)` — deterministic ECDSA (RFC 6979); compact
|
||||
96-byte output.
|
||||
- `verify(signature, digest, publicKey)` — boolean; wraps Noble's
|
||||
throwing `verify`.
|
||||
- `randomPrivateKey()` — rejection-sampled 48-byte key via
|
||||
`@suaveplan/crypto-utils/random`.
|
||||
- Noble's `p384.utils.isValidPrivateKey` gates the rejection loop.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as p384 from "@suaveplan/crypto-curve/p384";
|
||||
import { sha384 } from "@suaveplan/crypto-hash";
|
||||
|
||||
const sk = p384.randomPrivateKey();
|
||||
const pk = p384.getPublicKey(sk);
|
||||
const digest = sha384(new TextEncoder().encode("payload"));
|
||||
const sig = p384.sign(digest, sk);
|
||||
console.log(p384.verify(sig, digest, pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p384 from "@suaveplan/crypto-curve/p384";
|
||||
import { sha384 } from "@suaveplan/crypto-hash";
|
||||
import { zeroize } from "@suaveplan/crypto-utils";
|
||||
|
||||
function ephemeralSign(data: Uint8Array): { pk: Uint8Array; sig: Uint8Array } {
|
||||
const sk = p384.randomPrivateKey();
|
||||
try {
|
||||
return {
|
||||
pk: p384.getPublicKey(sk),
|
||||
sig: p384.sign(sha384(data), sk),
|
||||
};
|
||||
} finally {
|
||||
zeroize(sk);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
48-byte scalar → 49-byte compressed public key.
|
||||
|
||||
### `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
Pre-hashed input (48 bytes for SHA-384) → 96-byte compact (r ‖ s) signature.
|
||||
|
||||
### `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
|
||||
Returns `false` for malformed inputs rather than throwing.
|
||||
|
||||
### `randomPrivateKey(): Uint8Array`
|
||||
|
||||
Returns a valid P-384 scalar. Retries until `isValidPrivateKey` passes.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`sign` calls Noble's `.toBytes("compact")` to produce the flat 96-byte
|
||||
format; callers needing ASN.1 DER must convert externally. `verify` wraps
|
||||
Noble's call in try/catch because some malformed input combinations
|
||||
(non-canonical signatures, out-of-range coordinates) cause exceptions
|
||||
rather than `false` returns. The rejection loop in `randomPrivateKey` is
|
||||
statistically negligible — the curve order is approximately `2^384` so
|
||||
the rejection rate is essentially zero. NIST FIPS 186-4 / CAVP P-384
|
||||
test vectors pass through the noble underlying implementation.
|
||||
@@ -0,0 +1,81 @@
|
||||
# p384
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [p384.native](./impl/p384.native/p384.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384](./p384.md) — <!-- begin:auto-docs --> _(no submodules)_- p384.native — <!-- begin:auto-docs --> _(no submodul…
|
||||
- [p384.native](./impl/p384.native/p384.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384](./p384.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.native](./impl/p384.native/p384.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384](./p384.md) — <!-- begin:auto-docs --> _(no submodules)_- p384.native — <!-- begin:aut…
|
||||
- [p384.native](./impl/p384.native/p384.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384](./p384.md) — <!-- begin:auto-docs --> _(no submodules)_- p384.native — <!-- begin:auto-docs --> _(no submodu…
|
||||
- [p384.native](./impl/p384.native/p384.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p384.noble](./impl/p384.noble/p384.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p384.select](./p384.select/p384.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p384](./p384.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
`p384.ts` is the stable public entry point for the NIST P-384 elliptic-curve
|
||||
primitive in `@suaveplan/crypto-curve`. It re-exports everything from
|
||||
`p384.select.ts`, which forwards to either the Noble-backed or native
|
||||
implementation based on `crypto.config.ts`.
|
||||
|
||||
P-384 (also known as secp384r1, standardised in FIPS 186-4) is the higher-
|
||||
security NIST curve used for COSE `ES384` signatures (RFC 9053 §2.1), Apple
|
||||
WebAuthn attestation in some configurations, FIDO2 device certificates that
|
||||
opt out of P-256 for additional security margin, and US Government Suite B
|
||||
deployments that require ≥192-bit security strength. The curve has a 384-bit
|
||||
order so private keys are 48 bytes, compressed public keys are 49 bytes, and
|
||||
compact ECDSA signatures are 96 bytes.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — 49-byte compressed public key.
|
||||
- `sign(digest, privateKey)` — compact 96-byte ECDSA signature (r ‖ s).
|
||||
Caller pre-hashes (SHA-384 for ES384 wire compatibility).
|
||||
- `verify(signature, digest, publicKey)` — boolean; never throws on
|
||||
malformed inputs.
|
||||
- `randomPrivateKey()` — rejection-sampled 48-byte private key.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as p384 from "@suaveplan/crypto-curve/p384";
|
||||
import { sha384 } from "@suaveplan/crypto-hash";
|
||||
|
||||
const sk = p384.randomPrivateKey();
|
||||
const pk = p384.getPublicKey(sk);
|
||||
const msg = new TextEncoder().encode("hello P-384");
|
||||
const sig = p384.sign(sha384(msg), sk);
|
||||
console.log(p384.verify(sig, sha384(msg), pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p384 from "@suaveplan/crypto-curve/p384";
|
||||
import { sha384 } from "@suaveplan/crypto-hash";
|
||||
|
||||
// COSE_Sign1 verify path for an ES384-signed message.
|
||||
function verifyCoseSign1Es384(
|
||||
tbs: Uint8Array,
|
||||
signature: Uint8Array,
|
||||
publicKey: Uint8Array,
|
||||
): boolean {
|
||||
return p384.verify(signature, sha384(tbs), publicKey);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All symbols re-exported from `p384.select.ts`:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a zero-logic re-export barrel. Its sole purpose is the stable
|
||||
import path `@suaveplan/crypto-curve/p384`. Callers MUST pre-hash with
|
||||
SHA-384 (or another 384-bit digest) before calling `sign` — ECDSA does not
|
||||
hash internally. Use SHA-384 specifically for COSE ES384 / JWA ES384 wire
|
||||
compatibility. The `randomPrivateKey` rejection loop terminates after a
|
||||
constant expected number of iterations because nearly the full 48-byte
|
||||
space is a valid scalar (the curve order is `2^384 - 2^128 - …` so the
|
||||
rejection rate is negligible).
|
||||
@@ -0,0 +1,70 @@
|
||||
# p384.select
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`p384.select.ts` is the build-time primitive selector for NIST P-384 in
|
||||
`@suaveplan/crypto-curve`. Generated by `bun scripts/gen-crypto-selects.ts`,
|
||||
it re-exports one concrete backend — `p384.noble.ts` or `p384.native.ts` —
|
||||
according to the `p384` field in `crypto.config.ts`.
|
||||
|
||||
Callers import through the stable `p384.ts` entry point. This selector level
|
||||
insulates all call sites from backend changes: regenerating this file is the
|
||||
only step required to switch implementations. Currently the Noble backend is
|
||||
active because the native stub throws `PrimitiveNotImplemented`.
|
||||
|
||||
## Features
|
||||
|
||||
- Single re-export of `getPublicKey`, `sign`, `verify`, and `randomPrivateKey`.
|
||||
- Generated — never hand-edited.
|
||||
- Controlled by `crypto.config.ts`: set `p384` to `'noble'` or `'native'`.
|
||||
- Enables dead-code elimination of the inactive backend.
|
||||
- Maintains a stable API across backend switches.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as p384 from "@suaveplan/crypto-curve/p384";
|
||||
|
||||
const sk = p384.randomPrivateKey();
|
||||
const pk = p384.getPublicKey(sk);
|
||||
console.log(pk.length); // 49
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p384 from "@suaveplan/crypto-curve/p384";
|
||||
import { sha384 } from "@suaveplan/crypto-hash";
|
||||
|
||||
function roundTrip(message: Uint8Array): boolean {
|
||||
const sk = p384.randomPrivateKey();
|
||||
const pk = p384.getPublicKey(sk);
|
||||
const digest = sha384(message);
|
||||
const sig = p384.sign(digest, sk);
|
||||
return p384.verify(sig, digest, pk);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Re-exports from the active implementation:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array` — 49-byte compressed
|
||||
P-384 public key.
|
||||
- `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array` — 96-byte
|
||||
compact ECDSA signature.
|
||||
- `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array` — rejection-sampled 48-byte key.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The selector file contains a single `export * from` line and is marked
|
||||
`/* GENERATED — DO NOT EDIT */`. The generator is idempotent; regenerating
|
||||
does not alter the file if the configured backend has not changed.
|
||||
Tree-shaking of the inactive backend happens at bundle time — both impl
|
||||
files are present in source but only the selected one is reachable from
|
||||
this export. Adding a new backend (e.g. a WebCrypto path) is a one-line
|
||||
edit to the generator plus the new impl file.
|
||||
@@ -0,0 +1,89 @@
|
||||
# p521.native
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`p521.native.ts` is the native-backend placeholder for the NIST P-521
|
||||
primitive in `@suaveplan/crypto-curve`. It exports the complete P-521
|
||||
surface — `getPublicKey`, `sign`, `verify`, and `randomPrivateKey` —
|
||||
each throwing `PrimitiveNotImplemented("p521")`.
|
||||
|
||||
P-521 has native WebCrypto support in all modern browsers
|
||||
(`SubtleCrypto.sign` with `{ name: "ECDSA", hash: "SHA-512" }`) and
|
||||
Node.js 15+. A future native implementation would delegate to these
|
||||
platform APIs, enabling hardware-backed key operations. The stub keeps
|
||||
the layout complete until that work lands.
|
||||
|
||||
## Features
|
||||
|
||||
- Satisfies the full P-521 interface: `getPublicKey`, `sign`, `verify`,
|
||||
`randomPrivateKey`.
|
||||
- Throws `PrimitiveNotImplemented("p521")` from every function.
|
||||
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Active only when crypto.config.ts sets p521 to 'native'.
|
||||
import * as p521 from "@suaveplan/crypto-curve/p521";
|
||||
|
||||
try {
|
||||
p521.randomPrivateKey();
|
||||
} catch (err) {
|
||||
// PrimitiveNotImplemented: p521
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p521 from "@suaveplan/crypto-curve/p521";
|
||||
|
||||
async function nativeP521Available(): Promise<boolean> {
|
||||
try {
|
||||
p521.randomPrivateKey();
|
||||
return true;
|
||||
} catch {
|
||||
try {
|
||||
await crypto.subtle.generateKey(
|
||||
{ name: "ECDSA", namedCurve: "P-521" },
|
||||
false,
|
||||
["sign"]
|
||||
);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All functions throw `PrimitiveNotImplemented("p521")`:
|
||||
|
||||
- `getPublicKey(_privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(_digest: Uint8Array, _privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(_signature: Uint8Array, _digest: Uint8Array, _publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
P-521 native support varies across platforms — Safari historically lagged
|
||||
behind Chrome / Firefox in WebCrypto P-521 support and capabilities
|
||||
detection is essential for production rollouts. A native implementation
|
||||
would handle the byte-width quirks (66-byte coordinates left-padded to
|
||||
67-byte compressed pubkeys) that WebCrypto already encodes. Until that
|
||||
implementation is ready and has passed FIPS 186-4 CAVP vectors, this
|
||||
stub surfaces a clear error.
|
||||
|
||||
### Status
|
||||
|
||||
Placeholder native stub — throws `PrimitiveNotImplemented` until a
|
||||
WebCrypto `ECDSA P-521` bridge is wired through. Today the
|
||||
`p521.select` selector falls through to the Noble implementation in
|
||||
every engine mode; the production path is exercised by the noble
|
||||
roundtrip suite in `p521.test.ts`.
|
||||
@@ -0,0 +1,92 @@
|
||||
# p521.noble
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`p521.noble.ts` is the Noble-backed concrete implementation of the NIST
|
||||
P-521 primitive in `@suaveplan/crypto-curve`. It wraps the `p521` object
|
||||
from `@noble/curves/p521`, providing ECDSA signing and verification,
|
||||
public-key derivation, and rejection-sampled private-key generation.
|
||||
|
||||
P-521 (secp521r1, FIPS 186-4) is the highest-security NIST curve and is
|
||||
required by COSE `ES512` (RFC 9053 §2.1), some FIPS-mode deployments, and
|
||||
high-assurance long-lived identity certificates. Signatures are returned
|
||||
in compact 132-byte (r ‖ s) form, where r and s are each 66 bytes (the
|
||||
521-bit scalar left-padded). Callers must pre-hash the message; this
|
||||
module operates on digests.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — 67-byte compressed P-521 public key.
|
||||
- `sign(digest, privateKey)` — deterministic ECDSA (RFC 6979); compact
|
||||
132-byte output.
|
||||
- `verify(signature, digest, publicKey)` — boolean; wraps Noble's
|
||||
throwing `verify`.
|
||||
- `randomPrivateKey()` — rejection-sampled 66-byte key via
|
||||
`@suaveplan/crypto-utils/random`.
|
||||
- Noble's `p521.utils.isValidPrivateKey` gates the rejection loop.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as p521 from "@suaveplan/crypto-curve/p521";
|
||||
import { sha512 } from "@suaveplan/crypto-hash";
|
||||
|
||||
const sk = p521.randomPrivateKey();
|
||||
const pk = p521.getPublicKey(sk);
|
||||
const digest = sha512(new TextEncoder().encode("payload"));
|
||||
const sig = p521.sign(digest, sk);
|
||||
console.log(p521.verify(sig, digest, pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p521 from "@suaveplan/crypto-curve/p521";
|
||||
import { sha512 } from "@suaveplan/crypto-hash";
|
||||
import { zeroize } from "@suaveplan/crypto-utils";
|
||||
|
||||
function ephemeralSign(data: Uint8Array): { pk: Uint8Array; sig: Uint8Array } {
|
||||
const sk = p521.randomPrivateKey();
|
||||
try {
|
||||
return {
|
||||
pk: p521.getPublicKey(sk),
|
||||
sig: p521.sign(sha512(data), sk),
|
||||
};
|
||||
} finally {
|
||||
zeroize(sk);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
66-byte scalar → 67-byte compressed public key.
|
||||
|
||||
### `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
Pre-hashed input (64 bytes for SHA-512) → 132-byte compact (r ‖ s)
|
||||
signature.
|
||||
|
||||
### `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
|
||||
Returns `false` for malformed inputs rather than throwing.
|
||||
|
||||
### `randomPrivateKey(): Uint8Array`
|
||||
|
||||
Returns a valid P-521 scalar. Retries until `isValidPrivateKey` passes.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`sign` calls Noble's `.toBytes("compact")` to produce the flat 132-byte
|
||||
format. The 66-byte width comes from rounding 521 bits up to the next
|
||||
byte boundary; the high byte of every scalar / coordinate has only its
|
||||
low 9 bits populated for the maximum value. `verify` wraps Noble's call
|
||||
in try/catch because some malformed input combinations cause exceptions
|
||||
rather than `false` returns. The rejection rate for `randomPrivateKey`
|
||||
is approximately `1 − 2^(-7)` because only the low 9 bits of the high
|
||||
byte matter; the loop typically converges in one or two iterations.
|
||||
@@ -0,0 +1,80 @@
|
||||
# p521
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [p521.native](./impl/p521.native/p521.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521](./p521.md) — <!-- begin:auto-docs --> _(no submodules)_- p521.native — <!-- begin:auto-docs --> _(no submodul…
|
||||
- [p521.native](./impl/p521.native/p521.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521](./p521.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.native](./impl/p521.native/p521.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521](./p521.md) — <!-- begin:auto-docs --> _(no submodules)_- p521.native — <!-- begin:aut…
|
||||
- [p521.native](./impl/p521.native/p521.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521](./p521.md) — <!-- begin:auto-docs --> _(no submodules)_- p521.native — <!-- begin:auto-docs --> _(no submodu…
|
||||
- [p521.native](./impl/p521.native/p521.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p521.noble](./impl/p521.noble/p521.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [p521.select](./p521.select/p521.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [p521](./p521.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
`p521.ts` is the stable public entry point for the NIST P-521 elliptic-curve
|
||||
primitive in `@suaveplan/crypto-curve`. It re-exports everything from
|
||||
`p521.select.ts`, which forwards to either the Noble-backed or native
|
||||
implementation based on `crypto.config.ts`.
|
||||
|
||||
P-521 (also known as secp521r1, standardised in FIPS 186-4) is the highest-
|
||||
security NIST curve and is required by COSE `ES512` (RFC 9053 §2.1), some
|
||||
FIPS-mode deployments, and high-assurance long-lived identity certificates.
|
||||
The curve has a 521-bit order; private keys and coordinates are
|
||||
left-padded to 66 bytes so the entire data path stays byte-aligned.
|
||||
Compact ECDSA signatures are 132 bytes (2 × 66).
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — 67-byte compressed public key.
|
||||
- `sign(digest, privateKey)` — compact 132-byte ECDSA signature (r ‖ s).
|
||||
Caller pre-hashes (SHA-512 for ES512 wire compatibility).
|
||||
- `verify(signature, digest, publicKey)` — boolean; never throws on
|
||||
malformed inputs.
|
||||
- `randomPrivateKey()` — rejection-sampled 66-byte private key.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as p521 from "@suaveplan/crypto-curve/p521";
|
||||
import { sha512 } from "@suaveplan/crypto-hash";
|
||||
|
||||
const sk = p521.randomPrivateKey();
|
||||
const pk = p521.getPublicKey(sk);
|
||||
const msg = new TextEncoder().encode("hello P-521");
|
||||
const sig = p521.sign(sha512(msg), sk);
|
||||
console.log(p521.verify(sig, sha512(msg), pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p521 from "@suaveplan/crypto-curve/p521";
|
||||
import { sha512 } from "@suaveplan/crypto-hash";
|
||||
|
||||
// COSE_Sign1 verify path for an ES512-signed message.
|
||||
function verifyCoseSign1Es512(
|
||||
tbs: Uint8Array,
|
||||
signature: Uint8Array,
|
||||
publicKey: Uint8Array,
|
||||
): boolean {
|
||||
return p521.verify(signature, sha512(tbs), publicKey);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All symbols re-exported from `p521.select.ts`:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a zero-logic re-export barrel. Its sole purpose is the stable
|
||||
import path `@suaveplan/crypto-curve/p521`. Callers MUST pre-hash with
|
||||
SHA-512 (or another 512-bit digest) before calling `sign` — ECDSA does not
|
||||
hash internally. The 66-byte coordinate width comes from the 521-bit curve
|
||||
order rounded up to the next byte boundary; the high byte of each scalar
|
||||
is mostly zero (only the low 9 bits are populated for the maximum value).
|
||||
This matches the COSE wire format which also uses 66-byte left-padded
|
||||
coordinates for P-521 keys.
|
||||
@@ -0,0 +1,70 @@
|
||||
# p521.select
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`p521.select.ts` is the build-time primitive selector for NIST P-521 in
|
||||
`@suaveplan/crypto-curve`. Generated by `bun scripts/gen-crypto-selects.ts`,
|
||||
it re-exports one concrete backend — `p521.noble.ts` or `p521.native.ts` —
|
||||
according to the `p521` field in `crypto.config.ts`.
|
||||
|
||||
Callers import through the stable `p521.ts` entry point. This selector
|
||||
level insulates all call sites from backend changes: regenerating this
|
||||
file is the only step required to switch implementations. Currently the
|
||||
Noble backend is active because the native stub throws
|
||||
`PrimitiveNotImplemented`.
|
||||
|
||||
## Features
|
||||
|
||||
- Single re-export of `getPublicKey`, `sign`, `verify`, and `randomPrivateKey`.
|
||||
- Generated — never hand-edited.
|
||||
- Controlled by `crypto.config.ts`: set `p521` to `'noble'` or `'native'`.
|
||||
- Enables dead-code elimination of the inactive backend.
|
||||
- Maintains a stable API across backend switches.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as p521 from "@suaveplan/crypto-curve/p521";
|
||||
|
||||
const sk = p521.randomPrivateKey();
|
||||
const pk = p521.getPublicKey(sk);
|
||||
console.log(pk.length); // 67
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as p521 from "@suaveplan/crypto-curve/p521";
|
||||
import { sha512 } from "@suaveplan/crypto-hash";
|
||||
|
||||
function roundTrip(message: Uint8Array): boolean {
|
||||
const sk = p521.randomPrivateKey();
|
||||
const pk = p521.getPublicKey(sk);
|
||||
const digest = sha512(message);
|
||||
const sig = p521.sign(digest, sk);
|
||||
return p521.verify(sig, digest, pk);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Re-exports from the active implementation:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array` — 67-byte compressed
|
||||
P-521 public key.
|
||||
- `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array` — 132-byte
|
||||
compact ECDSA signature.
|
||||
- `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
- `randomPrivateKey(): Uint8Array` — rejection-sampled 66-byte key.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The selector file contains a single `export * from` line and is marked
|
||||
`/* GENERATED — DO NOT EDIT */`. P-521 keys carry an additional invariant
|
||||
that callers need to be aware of: the high byte of every scalar / coordinate
|
||||
must have the top 7 bits zero (the curve has a 521-bit order, byte-aligned
|
||||
to 66 bytes). The noble backend enforces this in `isValidPrivateKey`; the
|
||||
rejection loop in `randomPrivateKey` retries until the constraint holds.
|
||||
@@ -0,0 +1,65 @@
|
||||
# secp256k1.native
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`secp256k1.native.ts` is the native-backend placeholder for the secp256k1 primitive in `@suaveplan/crypto-curve`. It exports the complete secp256k1 surface — `getPublicKey`, `sign`, `verify`, `getSharedSecret`, and `randomPrivateKey` — each throwing `PrimitiveNotImplemented("secp256k1")`.
|
||||
|
||||
This stub keeps the two-implementation selector layout structurally complete. When the in-house secp256k1 implementation is ready and has passed the required test vectors, this file is replaced with the real code and the selector is regenerated to point here.
|
||||
|
||||
## Features
|
||||
|
||||
- Satisfies the full secp256k1 interface: `getPublicKey`, `sign`, `verify`, `getSharedSecret`, `randomPrivateKey`.
|
||||
- Throws `PrimitiveNotImplemented("secp256k1")` from every function.
|
||||
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
|
||||
- Provides clear, typed errors identifying which primitive is unimplemented.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Active only when crypto.config.ts sets secp256k1 to 'native'.
|
||||
import * as secp256k1 from "@suaveplan/crypto-curve/secp256k1";
|
||||
|
||||
try {
|
||||
secp256k1.randomPrivateKey();
|
||||
} catch (err) {
|
||||
// PrimitiveNotImplemented: secp256k1
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
// Capability detection — safe to call when backend may not be implemented.
|
||||
import * as secp256k1 from "@suaveplan/crypto-curve/secp256k1";
|
||||
|
||||
function isSecp256k1Available(): boolean {
|
||||
try {
|
||||
secp256k1.randomPrivateKey();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All functions throw `PrimitiveNotImplemented("secp256k1")`:
|
||||
|
||||
- `getPublicKey(_privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(_digest: Uint8Array, _privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(_signature: Uint8Array, _digest: Uint8Array, _publicKey: Uint8Array): boolean`
|
||||
- `getSharedSecret(_privateKey: Uint8Array, _peerPublicKey: Uint8Array): Uint8Array`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Each function has underscore-prefixed parameters to suppress unused-variable diagnostics without inline suppressions. `PrimitiveNotImplemented` from `@suaveplan/crypto-utils/errors` extends `SuaveplanError` and participates in the full error serialisation, cause-chain traversal, and OTel span-event system. When the native implementation lands, parameter prefixes are removed, the `throw` replaced, and the selector regenerated — no changes are required outside this file.
|
||||
|
||||
### Status
|
||||
|
||||
Placeholder native stub — throws `PrimitiveNotImplemented` until a libsecp256k1 binding is added to the `native` engine slot. Until then, `secp256k1.select` routes every call through `@noble/curves/secp256k1`, which is constant-time by construction and differentially fuzzed against `@noble/secp256k1` via the cross-impl harness in `@suaveplan/crypto-utils`.
|
||||
@@ -0,0 +1,91 @@
|
||||
# secp256k1.noble
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`secp256k1.noble.ts` is the Noble-backed concrete implementation of the secp256k1 primitive in `@suaveplan/crypto-curve`. It wraps the secp256k1 operations from `@noble/curves/secp256k1`, providing public-key derivation, ECDSA signing and verification, ECDH shared-secret computation, and random private-key generation.
|
||||
|
||||
secp256k1 is the domain parameter set used by Bitcoin, Ethereum, and a wide range of blockchain and Web3 protocols. Private keys are 32-byte scalars; public keys are 33-byte compressed points; signatures are compact 64-byte (r ‖ s) form. Callers are expected to pre-hash their messages before signing — this module operates on digests.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — 33-byte compressed public key via point multiplication.
|
||||
- `sign(digest, privateKey)` — deterministic ECDSA signature (RFC 6979); compact 64-byte output.
|
||||
- `verify(signature, digest, publicKey)` — boolean result; catches Noble exceptions for malformed inputs.
|
||||
- `getSharedSecret(privateKey, peerPublicKey)` — ECDH shared secret for key agreement.
|
||||
- `randomPrivateKey()` — rejection-sampled 32-byte key; retries until `isValidPrivateKey` passes.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as secp256k1 from "@suaveplan/crypto-curve/secp256k1";
|
||||
import { sha256 } from "@suaveplan/crypto-hash/hash";
|
||||
|
||||
const sk = secp256k1.randomPrivateKey();
|
||||
const pk = secp256k1.getPublicKey(sk);
|
||||
const msg = new TextEncoder().encode("hello secp256k1");
|
||||
const sig = secp256k1.sign(sha256(msg), sk);
|
||||
console.log(secp256k1.verify(sig, sha256(msg), pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as secp256k1 from "@suaveplan/crypto-curve/secp256k1";
|
||||
import { hkdf } from "@suaveplan/crypto-hash/kdf";
|
||||
|
||||
// ECDH + HKDF to derive an encryption key.
|
||||
function deriveSharedKey(
|
||||
localSk: Uint8Array,
|
||||
remotePk: Uint8Array,
|
||||
context: string
|
||||
): Uint8Array {
|
||||
const raw = secp256k1.getSharedSecret(localSk, remotePk);
|
||||
return hkdf("SHA-256", raw, {
|
||||
salt: new Uint8Array(32),
|
||||
info: new TextEncoder().encode(context),
|
||||
length: 32,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
Derives the 33-byte compressed public key for `privateKey`.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `privateKey` | `Uint8Array` | 32-byte scalar in [1, n−1]. |
|
||||
|
||||
**Returns:** 33-byte compressed public key.
|
||||
|
||||
### `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
Signs a pre-hashed digest. Uses RFC 6979 deterministic nonce generation.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `digest` | `Uint8Array` | 32-byte hash of the message. |
|
||||
| `privateKey` | `Uint8Array` | 32-byte private key. |
|
||||
|
||||
**Returns:** 64-byte compact (r ‖ s) signature.
|
||||
|
||||
### `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
|
||||
Verifies a signature. Returns `false` for malformed inputs instead of throwing.
|
||||
|
||||
### `getSharedSecret(privateKey: Uint8Array, peerPublicKey: Uint8Array): Uint8Array`
|
||||
|
||||
Computes the ECDH shared secret. The result must be passed through a KDF before use as a symmetric key.
|
||||
|
||||
### `randomPrivateKey(): Uint8Array`
|
||||
|
||||
Returns a uniformly random 32-byte valid private key. Uses rejection sampling.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`verify` wraps the Noble call in try/catch because Noble throws for some malformed input combinations (non-canonical encodings, out-of-range coordinates). The wrapper converts all exceptions to `false`, maintaining a uniform boolean contract. `randomPrivateKey` uses rejection sampling because not all 32-byte sequences are valid secp256k1 scalars — the probability of rejection is negligible (~1 in 2¹²⁸) so the loop almost never iterates more than once. `sign` uses Noble's `toBytes("compact")` to get the flat 64-byte form rather than DER; callers that need DER encoding must convert externally.
|
||||
@@ -0,0 +1,88 @@
|
||||
# secp256k1
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [secp256k1.native](./impl/secp256k1.native/secp256k1.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1](./secp256k1.md) — <!-- begin:auto-docs --> _(no submodules)_- secp256k1.native — <!-- begin:auto-docs --> _(no sub…
|
||||
- [secp256k1.native](./impl/secp256k1.native/secp256k1.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1](./secp256k1.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.native](./impl/secp256k1.native/secp256k1.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1](./secp256k1.md) — <!-- begin:auto-docs --> _(no submodules)_- secp256k1.native — <!-- begi…
|
||||
- [secp256k1.native](./impl/secp256k1.native/secp256k1.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1](./secp256k1.md) — <!-- begin:auto-docs --> _(no submodules)_- secp256k1.native — <!-- begin:auto-docs --> _(no su…
|
||||
- [secp256k1.native](./impl/secp256k1.native/secp256k1.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [secp256k1.noble](./impl/secp256k1.noble/secp256k1.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [secp256k1.select](./secp256k1.select/secp256k1.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [secp256k1](./secp256k1.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
`secp256k1.ts` is the stable public entry point for the secp256k1 elliptic-curve primitive in `@suaveplan/crypto-curve`. It re-exports everything from the generated selector file `secp256k1.select.ts`, which in turn forwards to either the Noble-backed or native implementation depending on `crypto.config.ts`.
|
||||
|
||||
secp256k1 is the Koblitz curve used by Bitcoin, Ethereum, and many other blockchain protocols. It supports ECDSA signing and ECDH key agreement. Callers import from this file and remain insulated from implementation changes.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — derives the 33-byte compressed public key.
|
||||
- `sign(digest, privateKey)` — produces a compact 64-byte ECDSA signature (r ‖ s). Caller must pre-hash the message.
|
||||
- `verify(signature, digest, publicKey)` — returns `boolean`, never throws on malformed inputs.
|
||||
- `getSharedSecret(privateKey, peerPublicKey)` — computes ECDH shared secret.
|
||||
- `randomPrivateKey()` — generates a cryptographically valid 32-byte private key via rejection sampling.
|
||||
- `signWithRecovery(digest, privateKey)` — produces a 65-byte signature (`64-byte compact || 1-byte recovery`). Recovery byte is `0` or `1`.
|
||||
- `recoverPublicKey(signatureWithRecovery, digest)` — recovers the 33-byte compressed public key from a 65-byte signature-with-recovery and the original digest. Throws `CryptoCurveError(RECOVERY_BYTE_MISSING)` for 64-byte inputs and `CryptoCurveError(RECOVERY_BYTE_INVALID)` when the recovery byte is not `0` or `1`.
|
||||
- `verifyWithRecovery(signatureWithRecovery, digest, expectedPublicKey)` — recovers the public key and compares it to `expectedPublicKey` in constant time. Returns `false` on mismatch (does not throw); structural errors (`RECOVERY_BYTE_MISSING`, `RECOVERY_BYTE_INVALID`) still throw.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as secp256k1 from "@suaveplan/crypto-curve/secp256k1";
|
||||
import { sha256 } from "@suaveplan/crypto-hash/hash";
|
||||
|
||||
const sk = secp256k1.randomPrivateKey();
|
||||
const pk = secp256k1.getPublicKey(sk);
|
||||
const msg = new TextEncoder().encode("transaction data");
|
||||
const digest = sha256(msg);
|
||||
const sig = secp256k1.sign(digest, sk);
|
||||
console.log(secp256k1.verify(sig, digest, pk)); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as secp256k1 from "@suaveplan/crypto-curve/secp256k1";
|
||||
import { hkdf } from "@suaveplan/crypto-hash/kdf";
|
||||
import { zeroize } from "@suaveplan/crypto-utils/zeroize";
|
||||
|
||||
// Ephemeral ECDH for a sealed message.
|
||||
function sealedEphemeralKey(recipientPk: Uint8Array): {
|
||||
ephemeralPk: Uint8Array;
|
||||
sharedKey: Uint8Array;
|
||||
} {
|
||||
const ephemeralSk = secp256k1.randomPrivateKey();
|
||||
try {
|
||||
const ephemeralPk = secp256k1.getPublicKey(ephemeralSk);
|
||||
const rawSecret = secp256k1.getSharedSecret(ephemeralSk, recipientPk);
|
||||
const sharedKey = hkdf("SHA-256", rawSecret, {
|
||||
salt: new Uint8Array(32),
|
||||
info: new TextEncoder().encode("secp256k1-sealed"),
|
||||
length: 32,
|
||||
});
|
||||
return { ephemeralPk, sharedKey };
|
||||
} finally {
|
||||
zeroize(ephemeralSk);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All symbols are re-exported from `secp256k1.select.ts`. See the active implementation (`secp256k1.noble.ts` or `secp256k1.native.ts`) for full parameter documentation.
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
- `getSharedSecret(privateKey: Uint8Array, peerPublicKey: Uint8Array): Uint8Array`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
- `signWithRecovery(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
- `recoverPublicKey(signatureWithRecovery: Uint8Array, digest: Uint8Array): Uint8Array`
|
||||
- `verifyWithRecovery(signatureWithRecovery: Uint8Array, digest: Uint8Array, expectedPublicKey: Uint8Array): boolean`
|
||||
|
||||
### Signature-with-recovery layout
|
||||
|
||||
The 65-byte form is `[r:32 | s:32 | recovery:1]` (big-endian). The recovery byte selects which of the two possible public keys derived from `(r, s, digest)` was the signer; valid values are `0` and `1`. Some libraries (for compressed-vs-uncompressed keys) emit `2` and `3` — those are explicitly rejected here because suaveplan always returns the compressed (33-byte) form, so the lift bit is implicit.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a zero-overhead re-export barrel. Its only role is to provide a stable import path (`@suaveplan/crypto-curve/secp256k1`) that survives future backend changes. The selector file underneath is generated; this file is not. Do not add logic here — it belongs in the concrete implementation files.
|
||||
@@ -0,0 +1,55 @@
|
||||
# secp256k1.select
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`secp256k1.select.ts` is the build-time primitive selector for the secp256k1 implementation in `@suaveplan/crypto-curve`. It is generated by `bun scripts/gen-crypto-selects.ts` and re-exports exactly one backend — either `secp256k1.noble.ts` or `secp256k1.native.ts` — based on the `secp256k1` entry in `crypto.config.ts`.
|
||||
|
||||
Callers import through the public entry point `secp256k1.ts`, which delegates here. This indirection means switching from the Noble backend to the native backend requires only regenerating this file; all import paths above it remain unchanged.
|
||||
|
||||
## Features
|
||||
|
||||
- Single-file re-export of all secp256k1 operations (`getPublicKey`, `sign`, `verify`, `getSharedSecret`, `randomPrivateKey`).
|
||||
- Generated by `bun scripts/gen-crypto-selects.ts` — never hand-edited.
|
||||
- Controlled by `crypto.config.ts`: set `secp256k1` to `'noble'` or `'native'`.
|
||||
- Enables static dead-code elimination of the unselected backend.
|
||||
- Maintains a stable API surface across backend switches.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Always import through the stable entry point, not this select file.
|
||||
import * as secp256k1 from "@suaveplan/crypto-curve/secp256k1";
|
||||
|
||||
const sk = secp256k1.randomPrivateKey();
|
||||
const pk = secp256k1.getPublicKey(sk);
|
||||
console.log(pk.length); // 33 (compressed)
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as secp256k1 from "@suaveplan/crypto-curve/secp256k1";
|
||||
import { sha256 } from "@suaveplan/crypto-hash/hash";
|
||||
|
||||
// Sign a pre-hashed digest (hash-then-sign pattern).
|
||||
function signDigest(message: Uint8Array, privateKey: Uint8Array): Uint8Array {
|
||||
return secp256k1.sign(sha256(message), privateKey);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Re-exports all symbols from the active implementation. At the Noble backend these are:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
- `sign(digest: Uint8Array, privateKey: Uint8Array): Uint8Array`
|
||||
- `verify(signature: Uint8Array, digest: Uint8Array, publicKey: Uint8Array): boolean`
|
||||
- `getSharedSecret(privateKey: Uint8Array, peerPublicKey: Uint8Array): Uint8Array`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file contains a single `export * from` line and is marked `/* GENERATED — DO NOT EDIT */`. The generator reads `crypto.config.ts`, looks up the backend for `secp256k1`, and writes the appropriate path. Currently the Noble backend is the only production-ready option; the native stub throws `PrimitiveNotImplemented("secp256k1")`. The selector enables tree-shaking of the inactive backend at the bundler level, keeping the deployed bundle minimal.
|
||||
@@ -0,0 +1,61 @@
|
||||
# x25519.native
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`x25519.native.ts` is the native-backend placeholder for the X25519 primitive in `@suaveplan/crypto-curve`. It exports `getPublicKey`, `getSharedSecret`, and `randomPrivateKey` — each throwing `PrimitiveNotImplemented("x25519")` — to maintain the two-implementation selector layout.
|
||||
|
||||
This stub signals that a native X25519 backend (for example, delegating to the host's WebCrypto `ECDH` with `X25519` or a native module) is planned but not yet active. The selector can be pointed here by changing `crypto.config.ts` and regenerating, at which point callers would get the implementation.
|
||||
|
||||
## Features
|
||||
|
||||
- Satisfies `getPublicKey`, `getSharedSecret`, and `randomPrivateKey` interfaces.
|
||||
- Throws `PrimitiveNotImplemented("x25519")` unconditionally from every function.
|
||||
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Active only when crypto.config.ts sets x25519 to 'native'.
|
||||
import * as x25519 from "@suaveplan/crypto-curve/x25519";
|
||||
|
||||
try {
|
||||
x25519.randomPrivateKey();
|
||||
} catch (err) {
|
||||
// PrimitiveNotImplemented: x25519
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
// Graceful degradation pattern.
|
||||
import * as x25519 from "@suaveplan/crypto-curve/x25519";
|
||||
|
||||
function tryGetPublicKey(sk: Uint8Array): Uint8Array | null {
|
||||
try {
|
||||
return x25519.getPublicKey(sk);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All functions throw `PrimitiveNotImplemented("x25519")`:
|
||||
|
||||
- `getPublicKey(_privateKey: Uint8Array): Uint8Array`
|
||||
- `getSharedSecret(_privateKey: Uint8Array, _peerPublicKey: Uint8Array): Uint8Array`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Underscore-prefixed parameters suppress unused-parameter diagnostics without inline suppressions. When the native implementation is ready and RFC 7748 §6.1 test vectors pass, the `throw` is replaced with the real algorithm and the selector regenerated. The native backend for X25519 is particularly valuable in browser environments where `SubtleCrypto.deriveBits` with `{ name: "X25519" }` is available natively in modern browsers (Chrome 113+, Firefox 117+), enabling hardware-accelerated key agreement without a JavaScript implementation.
|
||||
|
||||
### Status
|
||||
|
||||
Placeholder native stub — throws `PrimitiveNotImplemented` until a WebCrypto `ECDH X25519` / Node `crypto.diffieHellman` bridge is added. Today the `x25519.select` selector always lands on `@noble/curves/ed25519` (which also provides the Curve25519 implementation); RFC 7748 test vectors in the KAT harness cover that path end-to-end.
|
||||
@@ -0,0 +1,71 @@
|
||||
# x25519.noble
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`x25519.noble.ts` is the Noble-backed concrete implementation of X25519 in `@suaveplan/crypto-curve`. It wraps the `x25519` object from `@noble/curves/ed25519`, which shares the Curve25519 field arithmetic with Ed25519 but uses Montgomery form for Diffie-Hellman.
|
||||
|
||||
X25519 (RFC 7748 §5) computes a scalar multiplication on Curve25519 in Montgomery u-coordinate representation. The result is a 32-byte shared secret that both parties compute independently from their private key and the peer's public key. The output is a raw field element and must be processed through a KDF before use as a symmetric key.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — derives the 32-byte Montgomery public key (u-coordinate of base-point multiple).
|
||||
- `getSharedSecret(privateKey, peerPublicKey)` — ECDH shared secret; Noble handles RFC 7748 clamping internally.
|
||||
- `randomPrivateKey()` — 32 random bytes; no rejection loop needed since every byte sequence is valid.
|
||||
- Noble handles the RFC 7748 §5 private-key clamping (bits 0–2 of byte 0 cleared, bit 7 of byte 31 cleared, bit 6 set) internally during the scalar multiply.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as x25519 from "@suaveplan/crypto-curve/x25519";
|
||||
|
||||
const sk = x25519.randomPrivateKey();
|
||||
const pk = x25519.getPublicKey(sk);
|
||||
console.log(pk.length); // 32
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as x25519 from "@suaveplan/crypto-curve/x25519";
|
||||
import { hkdf } from "@suaveplan/crypto-hash/kdf";
|
||||
|
||||
// Derive a 32-byte AES key from an X25519 shared secret.
|
||||
function ecdheKey(localSk: Uint8Array, remotePk: Uint8Array): Uint8Array {
|
||||
const raw = x25519.getSharedSecret(localSk, remotePk);
|
||||
return hkdf("SHA-256", raw, {
|
||||
salt: new Uint8Array(32),
|
||||
info: new TextEncoder().encode("x25519-ecdhe"),
|
||||
length: 32,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `privateKey` | `Uint8Array` | Any 32 bytes. Noble applies RFC 7748 clamping. |
|
||||
|
||||
**Returns:** 32-byte Montgomery u-coordinate public key.
|
||||
|
||||
### `getSharedSecret(privateKey: Uint8Array, peerPublicKey: Uint8Array): Uint8Array`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `privateKey` | `Uint8Array` | 32-byte local private key. |
|
||||
| `peerPublicKey` | `Uint8Array` | 32-byte remote public key. |
|
||||
|
||||
**Returns:** 32-byte raw shared secret (must be KDF-processed before use).
|
||||
|
||||
### `randomPrivateKey(): Uint8Array`
|
||||
|
||||
Returns 32 cryptographically random bytes from `@suaveplan/crypto-utils/random`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Noble's X25519 implementation auto-clamps private keys according to RFC 7748 §5, which means the actual scalar used is deterministic from the 32-byte input but differs from it. This clamping is intentional (it forces the scalar to be in a safe sub-group range) and callers should not attempt to apply clamping themselves. The shared secret is not safe to use as a key directly — apply HKDF-SHA-256 or equivalent. Swapping `getPublicKey` base with something other than the Curve25519 generator is not supported at this layer.
|
||||
@@ -0,0 +1,74 @@
|
||||
# x25519
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [x25519.native](./impl/x25519.native/x25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519](./x25519.md) — <!-- begin:auto-docs --> _(no submodules)_- x25519.native — <!-- begin:auto-docs --> _(no submod…
|
||||
- [x25519.native](./impl/x25519.native/x25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519](./x25519.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.native](./impl/x25519.native/x25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519](./x25519.md) — <!-- begin:auto-docs --> _(no submodules)_- x25519.native — <!-- begin:a…
|
||||
- [x25519.native](./impl/x25519.native/x25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519](./x25519.md) — <!-- begin:auto-docs --> _(no submodules)_- x25519.native — <!-- begin:auto-docs --> _(no submo…
|
||||
- [x25519.native](./impl/x25519.native/x25519.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [x25519.noble](./impl/x25519.noble/x25519.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [x25519.select](./x25519.select/x25519.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [x25519](./x25519.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
`x25519.ts` is the stable public entry point for the X25519 elliptic-curve Diffie-Hellman primitive in `@suaveplan/crypto-curve`. It re-exports everything from `x25519.select.ts`, which forwards to either the Noble-backed or native implementation based on `crypto.config.ts`.
|
||||
|
||||
X25519 (RFC 7748) is the Diffie-Hellman function over Curve25519 in Montgomery form. It is the standard for ephemeral key agreement in TLS 1.3, Signal, Noise Protocol, and SSH. Its defining property is that any 32-byte value is a valid private key (no validity check required), and the output is a 32-byte shared secret that must be passed through a KDF before use as a symmetric key.
|
||||
|
||||
## Features
|
||||
|
||||
- `getPublicKey(privateKey)` — 32-byte Montgomery-form public key (u-coordinate).
|
||||
- `getSharedSecret(privateKey, peerPublicKey)` — 32-byte raw ECDH output. Must be KDF-processed.
|
||||
- `randomPrivateKey()` — 32 cryptographically random bytes (any 32-byte value is valid).
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as x25519 from "@suaveplan/crypto-curve/x25519";
|
||||
|
||||
const aliceSk = x25519.randomPrivateKey();
|
||||
const alicePk = x25519.getPublicKey(aliceSk);
|
||||
|
||||
const bobSk = x25519.randomPrivateKey();
|
||||
const bobPk = x25519.getPublicKey(bobSk);
|
||||
|
||||
const aliceShared = x25519.getSharedSecret(aliceSk, bobPk);
|
||||
const bobShared = x25519.getSharedSecret(bobSk, alicePk);
|
||||
// aliceShared and bobShared are identical
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as x25519 from "@suaveplan/crypto-curve/x25519";
|
||||
import { hkdf } from "@suaveplan/crypto-hash/kdf";
|
||||
import { zeroize } from "@suaveplan/crypto-utils/zeroize";
|
||||
|
||||
// Ephemeral ECDH with HKDF key derivation (Noise XX pattern excerpt).
|
||||
function deriveSessionKey(
|
||||
localSk: Uint8Array,
|
||||
remotePk: Uint8Array,
|
||||
handshakeHash: Uint8Array
|
||||
): Uint8Array {
|
||||
const rawSecret = x25519.getSharedSecret(localSk, remotePk);
|
||||
try {
|
||||
return hkdf("SHA-256", rawSecret, {
|
||||
salt: handshakeHash,
|
||||
info: new TextEncoder().encode("noise-session"),
|
||||
length: 32,
|
||||
});
|
||||
} finally {
|
||||
zeroize(rawSecret);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All symbols re-exported from `x25519.select.ts`:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array`
|
||||
- `getSharedSecret(privateKey: Uint8Array, peerPublicKey: Uint8Array): Uint8Array`
|
||||
- `randomPrivateKey(): Uint8Array`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a zero-logic re-export barrel. Its sole purpose is the stable import path `@suaveplan/crypto-curve/x25519`. Logic belongs in the implementation files. The raw ECDH output of X25519 is not safe to use directly as a symmetric key — always run it through HKDF or another KDF before using for encryption or MAC operations.
|
||||
@@ -0,0 +1,54 @@
|
||||
# x25519.select
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`x25519.select.ts` is the build-time primitive selector for X25519 in `@suaveplan/crypto-curve`. Generated by `bun scripts/gen-crypto-selects.ts`, it re-exports one backend — `x25519.noble.ts` or `x25519.native.ts` — based on the `x25519` entry in `crypto.config.ts`.
|
||||
|
||||
Callers import through `x25519.ts`, which delegates here. The selector isolates callers from backend changes: switching from Noble to a native implementation requires only regenerating this file. Currently the Noble backend is active because the native stub throws `PrimitiveNotImplemented`.
|
||||
|
||||
## Features
|
||||
|
||||
- Single re-export of `getPublicKey`, `getSharedSecret`, and `randomPrivateKey`.
|
||||
- Generated — never hand-edited.
|
||||
- Controlled by `crypto.config.ts`: set `x25519` to `'noble'` or `'native'`.
|
||||
- Enables dead-code elimination of the inactive backend.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import * as x25519 from "@suaveplan/crypto-curve/x25519";
|
||||
|
||||
const sk = x25519.randomPrivateKey();
|
||||
const pk = x25519.getPublicKey(sk);
|
||||
console.log(pk.length); // 32
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import * as x25519 from "@suaveplan/crypto-curve/x25519";
|
||||
|
||||
// Simple ECDH exchange.
|
||||
function exchange(localSk: Uint8Array, remotePk: Uint8Array): Uint8Array {
|
||||
return x25519.getSharedSecret(localSk, remotePk);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Re-exports from the active implementation:
|
||||
|
||||
- `getPublicKey(privateKey: Uint8Array): Uint8Array` — 32-byte public key.
|
||||
- `getSharedSecret(privateKey: Uint8Array, peerPublicKey: Uint8Array): Uint8Array` — 32-byte raw shared secret.
|
||||
- `randomPrivateKey(): Uint8Array` — 32 random bytes.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a generated one-liner: `export * from "./impl/x25519.noble.js"`. The generator resolves the configured backend and writes the appropriate path. The file is idempotent and safe to regenerate at any time. Do not add logic here — it will be overwritten.
|
||||
|
||||
### Selection Logic
|
||||
|
||||
At build time `crypto.config.ts` picks either the Noble or the native engine for every primitive; the selector exported here reads the generated `crypto-selects` map and routes calls to the chosen impl. When the selected engine is not yet implemented, the `PrimitiveNotImplemented` sentinel from `@suaveplan/crypto-utils` is thrown with the primitive-id baked in so logs identify the exact missing slot.
|
||||
Reference in New Issue
Block a user