Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 0 additions & 176 deletions .claude/skills/apply-pr-feedback/SKILL.md

This file was deleted.

3 changes: 0 additions & 3 deletions .claude/skills/implement-plan-linear/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,6 @@ After manual confirmation for a phase, run these steps in order:
3. **Submit and publish the stack:**
- `gt submit --publish` — pushes all branches in the stack and creates/updates PRs for each.

4. **Trigger automated review:**
- `gh pr comment <PR-number> --body "@codex review"`

Rules:

- Always run `gt sync` before `gt create` — it is safe on stack branches and keeps the stack rebased on latest trunk.
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ build/
# plans

thoughts/shared/plans/*
thoughts/shared/research/*
11 changes: 11 additions & 0 deletions packages/pq-key-fingerprint/test-data/test-keys/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Test Key Fixtures

This directory contains package-local test fixtures used by `pq-key-fingerprint` TypeScript tests.

Source provenance:
- Copied from `packages/pq-key-encoder/test-data/test-keys/`
- Files currently mirrored:
- `ml_kem_512_pub.der`
- `ml_kem_512_pub.pem`

These fixtures are duplicated intentionally to avoid runtime coupling to another package's test-data layout.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN PUBLIC KEY-----
MIIDMjALBglghkgBZQMEBAEDggMhACjwCgBjmsLKiitKIXfMCbMGdCWnRlDUELz4
H4nGER+Dx2y6jewoTBdRQz6YIsApn1p0Z/uXVHoDn0FAUDdgHv+BuV/RluHjHBsL
iRsYip5TOCNHD4ekkI0UxAuRzkBmFUzCm5yVqCaZvcUnVRWRp56kQ9UcL5sRi33l
WqKHwuGGfJUCwy35Vezps7O5otEJzuuXko5mgXrQunY6yhpLmlwVcUY7x85AkrbX
sPtpFB1WwOCbON6TCdo4Bn42WyFGh1zlIc5oYpArTHYywWSMVwd8bWPoNmFxdFyK
oE/TdumWJGy4fBzWZwooVSpxZuTsxpgJSmMLafMil8Z2A/GzO5WAm/D3W5kBGKfi
QZehG8PovLjTl/+RE1WYsaFTVygVfM98q6wKtXInixYjPtzGfgPiCXLLGUa5Chmr
dK7TUOhYxfbZGCVTelxbuMJ0Hg4GjksWG9Q3L3HRxHVBm2EFi13MSNHjyMzwFovM
zxFsWDo8J94UfWL6FqPCn+bDflJoFsRgFVs3zuXqd1rheWUVXavHONoMTLaCUAlT
nwsWBd48QqehNP+7TGfpM7ErvaOAabdmip3QKE/objK8cRD7LEGzmW5JWYq1H9i8
rQnQFFF5kBKECNLCZ4qQrqb0g0opCWfqGXcIkQA9WqPEZe/Qdc7aVQURQj+aJVim
U+06ezvWJzT3lNURc9vLt8TlEpEzaa5pfQPWm57FIMDWoB/Uut8YXfOJHxQyead4
rU5BQUtTDmI8uAxnFEacpavSuGGGqLDGyqWmkyJEklNgVdjEWvdEvufSt8oSGmco
nsQWKQVSuYnTldqRskdiiEZZy9VBwgrkJjJsTsdTFr1Vja43laq1VJO0u3q5wB7A
CxRyFHRKzkMMSQ1spHGVPmxYAf63B/n4WcgDvT4zGb/2kANIAr5GhkITAZaYqrwp
QEsZF4RsK4Bgw2SzXSTAqz2rAxkkw84kBn/WtjgzudSZgH1wZYm0ZfMwgtA0Rgsi
KxQRXUdLWa/CRcEWNc6govJFEtDWepLuxdfD9f5AjiQWykctmlpYvknoSh9fSz9I
uin1mrhx
-----END PUBLIC KEY-----
49 changes: 44 additions & 5 deletions packages/pq-key-fingerprint/ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,60 @@ interface FingerprintOptions {

type FingerprintResult = string | Uint8Array;

function fingerprintPublicKey(input: PublicKeyInput, options?: FingerprintOptions): Promise<FingerprintResult>;
function fingerprintPublicKey(
input: PublicKeyInput,
options: FingerprintOptions & { encoding: 'bytes' },
): Promise<Uint8Array>;
function fingerprintPublicKey(input: PublicKeyInput, options?: FingerprintOptions): Promise<string>;
function fingerprintPublicKeyBytes(
bytes: Uint8Array,
alg: AlgorithmName,
options: FingerprintOptions & { encoding: 'bytes' },
): Promise<Uint8Array>;
function fingerprintPublicKeyBytes(
bytes: Uint8Array,
alg: AlgorithmName,
options?: FingerprintOptions,
): Promise<FingerprintResult>;
function fingerprintSPKI(spki: Uint8Array, options?: FingerprintOptions): Promise<FingerprintResult>;
function fingerprintPEM(pem: string, options?: FingerprintOptions): Promise<FingerprintResult>;
function fingerprintJWK(jwk: PQJwk, options?: FingerprintOptions): Promise<FingerprintResult>;
): Promise<string>;
function fingerprintSPKI(
spki: Uint8Array,
options: FingerprintOptions & { encoding: 'bytes' },
): Promise<Uint8Array>;
function fingerprintSPKI(spki: Uint8Array, options?: FingerprintOptions): Promise<string>;
function fingerprintPEM(
pem: string,
options: FingerprintOptions & { encoding: 'bytes' },
): Promise<Uint8Array>;
function fingerprintPEM(pem: string, options?: FingerprintOptions): Promise<string>;
function fingerprintJWK(
jwk: PQPublicJwk,
options: FingerprintOptions & { encoding: 'bytes' },
): Promise<Uint8Array>;
function fingerprintJWK(jwk: PQPublicJwk, options?: FingerprintOptions): Promise<string>;
```

## Compatibility Note

Canonical fingerprint identity for interoperability is `SHA-256` with `hex` output (the default). Alternate digest or encoding choices are intended for advanced use-cases where both producer and consumer explicitly agree on format.

All exported fingerprint entrypoints enforce a strict local error boundary by design. Upstream parser/validation failures from `pq-key-encoder` are translated into `pq-key-fingerprint` error classes (subclasses of `FingerprintError`) before they leave this package. This behavior is intentional and part of the package contract.

`options` must be a plain object when provided and only supports `digest` plus `encoding`. Unknown option keys and invalid option values (for example, empty `digest`/`encoding` strings) are rejected rather than silently defaulting.

The fingerprint preimage format is stable and versioned as:

`UTF8("pq-key-fingerprint:v1") || 0x00 || UTF8(alg) || 0x00 || publicKeyBytes`

`alg` uses canonical algorithm names emitted by `pq-key-encoder`; that canonicalization is part of this package contract. Algorithm names containing NUL bytes are rejected.

Unexpected runtime/internal failures are wrapped as `FingerprintError` with the original failure attached via `cause` when available.

Fingerprint digests are algorithm-scoped: the digest input is domain-separated and includes both the algorithm name and public key bytes. The same byte sequence under different algorithms yields different fingerprints.

Runtime requirement: a WebCrypto `subtle.digest` implementation and `TextEncoder` must be available in the current runtime.

Supported runtime baseline: Node.js 18+, Bun 1+, and modern browsers that expose global WebCrypto plus `TextEncoder`.

## License

MIT
11 changes: 11 additions & 0 deletions packages/pq-key-fingerprint/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"engines": {
"node": ">=18"
},
"sideEffects": false,
"files": [
"dist"
],
Expand Down
20 changes: 10 additions & 10 deletions packages/pq-key-fingerprint/ts/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
/** Base error for pq-key-fingerprint failures. */
export class FingerprintError extends Error {
constructor(message: string) {
super(message);
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = 'FingerprintError';
Object.setPrototypeOf(this, new.target.prototype);
}
}

/** Error for invalid or missing fingerprint input values. */
export class InvalidFingerprintInputError extends FingerprintError {
constructor(message: string) {
super(message);
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = 'InvalidFingerprintInputError';
}
}

/** Error for key-type mismatches, such as passing private keys. */
export class InvalidKeyTypeError extends FingerprintError {
constructor(message: string) {
super(message);
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = 'InvalidKeyTypeError';
}
}

/** Error for unsupported digest algorithm selections. */
export class UnsupportedDigestError extends FingerprintError {
constructor(message: string) {
super(message);
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = 'UnsupportedDigestError';
}
}

/** Error for missing runtime cryptographic capabilities. */
export class RuntimeCapabilityError extends FingerprintError {
constructor(message: string) {
super(message);
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = 'RuntimeCapabilityError';
}
}
Loading