@zanii/pq 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -56,7 +56,7 @@ export declare function verifyDual(obj: Record<string, unknown>, did: string, bi
56
56
  * receipt — a PQ binding is public by design, and the receipt's `payload_hash` must be
57
57
  * recomputable by any verifier. Suggested target: `pq.transition`.
58
58
  */
59
- export declare function transitionPayload(binding: PqBinding): string;
59
+ export declare function transitionPayload(binding: PqBinding): Uint8Array;
60
60
  export interface TransitionEvidence {
61
61
  sth: SignedTreeHead;
62
62
  index: number;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA+BA,OAAO,EAQL,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,MAAM,EAAG,WAAoB,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,CAG9D;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,YAAY,CAAC;IACnB,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,OAAO,MAAM,CAAC;IACnB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAC5D,YAAY,EAAE,UAAU,EACxB,WAAW,EAAE,UAAU,GACtB,SAAS,CAiBX;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAqCvE;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAEtD;AAED,MAAM,WAAW,aAAa;IAC5B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,GAAG,aAAa,CAMvH;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,SAAS,EAClB,IAAI,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,SAAS,GACvC,OAAO,CAiCT;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAI5D;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,cAAc,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAwBxH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA+BA,OAAO,EASL,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,MAAM,EAAG,WAAoB,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,CAG9D;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,YAAY,CAAC;IACnB,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,OAAO,MAAM,CAAC;IACnB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAC5D,YAAY,EAAE,UAAU,EACxB,WAAW,EAAE,UAAU,GACtB,SAAS,CAiBX;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAqCvE;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAEtD;AAED,MAAM,WAAW,aAAa;IAC5B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,GAAG,aAAa,CAMvH;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,SAAS,EAClB,IAAI,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,SAAS,GACvC,OAAO,CAiCT;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,GAAG,UAAU,CAMhE;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,cAAc,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAwBxH"}
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@
29
29
  import { ed25519 } from '@noble/curves/ed25519';
30
30
  import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
31
31
  import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
32
- import { canonicalBytes, hashPayload, jcsHash, leafHash, publicKeyFromDid, verifyInclusion, verifySTH, } from '@zanii/core';
32
+ import { canonicalBytes, hashPayload, payloadBytes, jcsHash, leafHash, publicKeyFromDid, verifyInclusion, verifySTH, } from '@zanii/core';
33
33
  export const PQ_ALG = 'ML-DSA-65';
34
34
  /** Generate an ML-DSA-65 keypair. Pass a 32-byte seed for deterministic derivation. */
35
35
  export function generatePqKeypair(seed) {
@@ -167,7 +167,9 @@ export function transitionPayload(binding) {
167
167
  const check = verifyPqBinding(binding);
168
168
  if (!check.ok)
169
169
  throw new Error(`refusing to anchor an invalid binding: ${check.reasons.join('; ')}`);
170
- return JSON.stringify(binding);
170
+ // JCS, matching what record() now commits to — a JSON.stringify here would make the
171
+ // recomputed hash disagree with the receipt for any non-sorted or non-ASCII binding.
172
+ return payloadBytes(binding);
171
173
  }
172
174
  /**
173
175
  * Verify the transition: the receipt commits to EXACTLY this binding
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zanii/pq",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Post-quantum migration rails - hybrid dual-signing (Ed25519 + ML-DSA-65, both must verify), a PQ key binding signed by BOTH keys (proves possession of each), and a transition record anchored into the Merkle log so the binding provably predates any future break of Ed25519. Old receipts are not re-signed: their post-quantum protection is the anchored timestamp, and we say so.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://ledger.zanii.agency",
@@ -34,7 +34,7 @@
34
34
  "@noble/post-quantum": "^0.7.0",
35
35
  "@noble/curves": "^1.8.1",
36
36
  "@noble/hashes": "^1.7.1",
37
- "@zanii/core": "0.4.0"
37
+ "@zanii/core": "^0.5.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc -p tsconfig.build.json",
package/src/index.ts CHANGED
@@ -1,258 +1,261 @@
1
- /**
2
- * @zanii/pq — post-quantum migration rails: dual-sign today, stay provable after Ed25519.
3
- *
4
- * Everything in Zanii signs with Ed25519, which a large quantum computer breaks. The
5
- * cheap, correct move is not to wait: it is a **hybrid** that a government or hospital
6
- * buyer can adopt now. Three pieces:
7
- *
8
- * 1. **A PQ key binding** — an agent binds an ML-DSA-65 (FIPS 204) public key to its
9
- * did:key, signed by **both** keys over the same body. Both signatures prove
10
- * possession of both keys: an attacker holding only one cannot forge the binding.
11
- * 2. **Dual-signing** (`dualSign`/`verifyDual`) — any protocol object signed by both
12
- * algorithms, and verification requires **BOTH** to pass. Hybrid security: forging
13
- * needs Ed25519 AND ML-DSA broken at once.
14
- * 3. **The transition record** — anchor the binding into the Merkle log NOW. The log
15
- * is SHA-256 (not broken by known quantum algorithms in any practical sense), so
16
- * the anchored, timestamped inclusion of the binding proves it existed BEFORE any
17
- * future Ed25519 break — "this PQ key was this agent's key all along" survives the
18
- * thing it is defending against.
19
- *
20
- * ## The limit, stated up front
21
- *
22
- * Pre-migration receipts are **not re-signed** and never will be — their post-quantum
23
- * protection is the anchored log timestamp (an Ed25519 signature that was included in
24
- * an anchored tree BEFORE the break is evidence of when it was made; the same
25
- * signature made AFTER the break proves nothing). Hybrid verification is only hybrid
26
- * when the verifier demands both signatures — `verifyDual` refuses a missing
27
- * ML-DSA signature rather than quietly falling back to Ed25519-only.
28
- */
29
- import { ed25519 } from '@noble/curves/ed25519';
30
- import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
31
- import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
32
- import {
33
- canonicalBytes,
34
- hashPayload,
35
- jcsHash,
36
- leafHash,
37
- publicKeyFromDid,
38
- verifyInclusion,
39
- verifySTH,
40
- type Receipt,
41
- type SignedTreeHead,
42
- } from '@zanii/core';
43
-
44
- export const PQ_ALG = 'ML-DSA-65' as const;
45
-
46
- export interface PqKeypair {
47
- publicKey: Uint8Array;
48
- secretKey: Uint8Array;
49
- }
50
-
51
- /** Generate an ML-DSA-65 keypair. Pass a 32-byte seed for deterministic derivation. */
52
- export function generatePqKeypair(seed?: Uint8Array): PqKeypair {
53
- const keys = seed ? ml_dsa65.keygen(seed) : ml_dsa65.keygen();
54
- return { publicKey: keys.publicKey, secretKey: keys.secretKey };
55
- }
56
-
57
- export interface PqBinding {
58
- v: 1;
59
- type: 'pq.binding';
60
- /** The agent's classical identity — the Ed25519 did:key everything else uses. */
61
- did: string;
62
- alg: typeof PQ_ALG;
63
- /** ML-DSA-65 public key, hex (1952 bytes). */
64
- pq_pub: string;
65
- ts: string;
66
- /** `ed25519:<hex>` by the did's key — the classical key vouches for the PQ key. */
67
- sig_ed?: string;
68
- /** `mldsa65:<hex>` by the PQ key — proves possession; not just a claimed key. */
69
- sig_pq?: string;
70
- }
71
-
72
- /**
73
- * Bind a PQ public key to a did:key. BOTH keys sign the same unsigned body — an
74
- * attacker holding only the Ed25519 key (or only the PQ key) cannot produce this.
75
- */
76
- export function bindPqKey(
77
- fields: { did: string; pqPublicKey: Uint8Array; ts: string },
78
- edPrivateKey: Uint8Array,
79
- pqSecretKey: Uint8Array,
80
- ): PqBinding {
81
- if (!fields.did) throw new Error('did is required');
82
- if (Number.isNaN(Date.parse(fields.ts))) throw new Error('ts must be an ISO timestamp');
83
- const unsigned = {
84
- v: 1 as const,
85
- type: 'pq.binding' as const,
86
- did: fields.did,
87
- alg: PQ_ALG,
88
- pq_pub: bytesToHex(fields.pqPublicKey),
89
- ts: fields.ts,
90
- };
91
- const bytes = canonicalBytes(unsigned);
92
- return {
93
- ...unsigned,
94
- sig_ed: `ed25519:${bytesToHex(ed25519.sign(bytes, edPrivateKey))}`,
95
- sig_pq: `mldsa65:${bytesToHex(ml_dsa65.sign(bytes, pqSecretKey))}`,
96
- };
97
- }
98
-
99
- export interface PqCheck {
100
- ok: boolean;
101
- reasons: string[];
102
- }
103
-
104
- /** Verify a binding: structure + BOTH signatures over the same body. */
105
- export function verifyPqBinding(binding: PqBinding | undefined): PqCheck {
106
- const reasons: string[] = [];
107
- const b = binding ?? ({} as PqBinding);
108
- if (b.v !== 1 || b.type !== 'pq.binding') reasons.push('not a pq.binding (v1)');
109
- if (b.alg !== PQ_ALG) reasons.push(`unsupported alg '${b.alg}'`);
110
- if (!b.did) reasons.push('missing did');
111
- if (typeof b.pq_pub !== 'string' || !/^[0-9a-f]+$/.test(b.pq_pub)) reasons.push('missing/invalid pq_pub');
112
- if (typeof b.ts !== 'string' || Number.isNaN(Date.parse(b.ts))) reasons.push('missing/invalid ts');
113
- if (reasons.length > 0) return { ok: false, reasons };
114
-
115
- const { sig_ed, sig_pq, ...unsigned } = b;
116
- const bytes = canonicalBytes(unsigned as unknown as Record<string, unknown>);
117
-
118
- const pub = publicKeyFromDid(b.did);
119
- const edHex = typeof sig_ed === 'string' && sig_ed.startsWith('ed25519:') ? sig_ed.slice(8) : '';
120
- let edOk = false;
121
- if (pub && /^[0-9a-f]{128}$/.test(edHex)) {
122
- try {
123
- edOk = ed25519.verify(hexToBytes(edHex), bytes, pub);
124
- } catch {
125
- edOk = false;
126
- }
127
- }
128
- if (!edOk) reasons.push('Ed25519 signature does not verify against the did');
129
-
130
- const pqHex = typeof sig_pq === 'string' && sig_pq.startsWith('mldsa65:') ? sig_pq.slice(8) : '';
131
- let pqOk = false;
132
- if (/^[0-9a-f]+$/.test(pqHex) && pqHex.length > 0) {
133
- try {
134
- pqOk = ml_dsa65.verify(hexToBytes(pqHex), bytes, hexToBytes(b.pq_pub));
135
- } catch {
136
- pqOk = false;
137
- }
138
- }
139
- if (!pqOk) reasons.push('ML-DSA signature does not verify against pq_pub (possession not proven)');
140
-
141
- return { ok: reasons.length === 0, reasons };
142
- }
143
-
144
- /** Canonical hash of the full binding (both sigs included) — its stable id. */
145
- export function bindingHash(binding: PqBinding): string {
146
- return jcsHash(binding as unknown as Record<string, unknown>);
147
- }
148
-
149
- export interface DualSignature {
150
- /** `ed25519:<hex>`. */
151
- sig_ed: string;
152
- /** `mldsa65:<hex>`. */
153
- sig_pq: string;
154
- }
155
-
156
- /** Sign any protocol object with both algorithms over its canonical (JCS) bytes. */
157
- export function dualSign(obj: Record<string, unknown>, edPrivateKey: Uint8Array, pqSecretKey: Uint8Array): DualSignature {
158
- const bytes = canonicalBytes(obj);
159
- return {
160
- sig_ed: `ed25519:${bytesToHex(ed25519.sign(bytes, edPrivateKey))}`,
161
- sig_pq: `mldsa65:${bytesToHex(ml_dsa65.sign(bytes, pqSecretKey))}`,
162
- };
163
- }
164
-
165
- /**
166
- * Verify a dual signature. BOTH must pass, and the PQ public key comes from a
167
- * **verified binding for the same did** — never from the message. A missing ML-DSA
168
- * signature is a failure, not a fallback: hybrid that degrades silently isn't hybrid.
169
- */
170
- export function verifyDual(
171
- obj: Record<string, unknown>,
172
- did: string,
173
- binding: PqBinding,
174
- sigs: Partial<DualSignature> | undefined,
175
- ): PqCheck {
176
- const reasons: string[] = [];
177
- const bind = verifyPqBinding(binding);
178
- if (!bind.ok) return { ok: false, reasons: bind.reasons.map((r) => `binding: ${r}`) };
179
- if (binding.did !== did) return { ok: false, reasons: [`binding is for ${binding.did}, not ${did}`] };
180
-
181
- const bytes = canonicalBytes(obj);
182
- const s = sigs ?? {};
183
-
184
- const pub = publicKeyFromDid(did);
185
- const edHex = typeof s.sig_ed === 'string' && s.sig_ed.startsWith('ed25519:') ? s.sig_ed.slice(8) : '';
186
- let edOk = false;
187
- if (pub && /^[0-9a-f]{128}$/.test(edHex)) {
188
- try {
189
- edOk = ed25519.verify(hexToBytes(edHex), bytes, pub);
190
- } catch {
191
- edOk = false;
192
- }
193
- }
194
- if (!edOk) reasons.push('Ed25519 signature missing or invalid');
195
-
196
- const pqHex = typeof s.sig_pq === 'string' && s.sig_pq.startsWith('mldsa65:') ? s.sig_pq.slice(8) : '';
197
- let pqOk = false;
198
- if (/^[0-9a-f]+$/.test(pqHex) && pqHex.length > 0) {
199
- try {
200
- pqOk = ml_dsa65.verify(hexToBytes(pqHex), bytes, hexToBytes(binding.pq_pub));
201
- } catch {
202
- pqOk = false;
203
- }
204
- }
205
- if (!pqOk) reasons.push('ML-DSA signature missing or invalid — refusing Ed25519-only fallback');
206
-
207
- return { ok: reasons.length === 0, reasons };
208
- }
209
-
210
- /**
211
- * The payload to `record()` UNSALTED (`salt: false` / `salt=False`) as the transition
212
- * receipt — a PQ binding is public by design, and the receipt's `payload_hash` must be
213
- * recomputable by any verifier. Suggested target: `pq.transition`.
214
- */
215
- export function transitionPayload(binding: PqBinding): string {
216
- const check = verifyPqBinding(binding);
217
- if (!check.ok) throw new Error(`refusing to anchor an invalid binding: ${check.reasons.join('; ')}`);
218
- return JSON.stringify(binding);
219
- }
220
-
221
- export interface TransitionEvidence {
222
- sth: SignedTreeHead;
223
- index: number;
224
- /** Audit path, hex-encoded. */
225
- proof: string[];
226
- }
227
-
228
- /**
229
- * Verify the transition: the receipt commits to EXACTLY this binding
230
- * (`payload_hash` matches, unsalted) and the receipt is Merkle-proven in the log the
231
- * STH commits to. The STH's anchored timestamp then bounds when the binding existed —
232
- * the piece that survives a future Ed25519 break.
233
- */
234
- export function verifyTransition(binding: PqBinding, receipt: Receipt, evidence: TransitionEvidence | undefined): PqCheck {
235
- const reasons: string[] = [];
236
- const bind = verifyPqBinding(binding);
237
- if (!bind.ok) return { ok: false, reasons: bind.reasons.map((r) => `binding: ${r}`) };
238
- if (receipt.agent_id !== binding.did) reasons.push(`transition receipt by ${receipt.agent_id}, but the binding is for ${binding.did}`);
239
- if (receipt.payload_hash !== hashPayload(transitionPayload(binding))) {
240
- reasons.push('receipt payload_hash does not commit to this binding (was it recorded salted?)');
241
- }
242
- if (!evidence) {
243
- reasons.push('no inclusion evidence — an unanchored binding proves nothing about when it existed');
244
- } else if (!verifySTH(evidence.sth)) {
245
- reasons.push('inclusion evidence STH signature is invalid');
246
- } else {
247
- const root = hexToBytes(evidence.sth.root.replace('sha256:', ''));
248
- const ok = verifyInclusion(
249
- leafHash(canonicalBytes(receipt as unknown as Record<string, unknown>)),
250
- evidence.index,
251
- evidence.sth.size,
252
- evidence.proof.map((h) => hexToBytes(h)),
253
- root,
254
- );
255
- if (!ok) reasons.push('inclusion proof FAILED — the transition is not in the log the STH commits to');
256
- }
257
- return { ok: reasons.length === 0, reasons };
258
- }
1
+ /**
2
+ * @zanii/pq — post-quantum migration rails: dual-sign today, stay provable after Ed25519.
3
+ *
4
+ * Everything in Zanii signs with Ed25519, which a large quantum computer breaks. The
5
+ * cheap, correct move is not to wait: it is a **hybrid** that a government or hospital
6
+ * buyer can adopt now. Three pieces:
7
+ *
8
+ * 1. **A PQ key binding** — an agent binds an ML-DSA-65 (FIPS 204) public key to its
9
+ * did:key, signed by **both** keys over the same body. Both signatures prove
10
+ * possession of both keys: an attacker holding only one cannot forge the binding.
11
+ * 2. **Dual-signing** (`dualSign`/`verifyDual`) — any protocol object signed by both
12
+ * algorithms, and verification requires **BOTH** to pass. Hybrid security: forging
13
+ * needs Ed25519 AND ML-DSA broken at once.
14
+ * 3. **The transition record** — anchor the binding into the Merkle log NOW. The log
15
+ * is SHA-256 (not broken by known quantum algorithms in any practical sense), so
16
+ * the anchored, timestamped inclusion of the binding proves it existed BEFORE any
17
+ * future Ed25519 break — "this PQ key was this agent's key all along" survives the
18
+ * thing it is defending against.
19
+ *
20
+ * ## The limit, stated up front
21
+ *
22
+ * Pre-migration receipts are **not re-signed** and never will be — their post-quantum
23
+ * protection is the anchored log timestamp (an Ed25519 signature that was included in
24
+ * an anchored tree BEFORE the break is evidence of when it was made; the same
25
+ * signature made AFTER the break proves nothing). Hybrid verification is only hybrid
26
+ * when the verifier demands both signatures — `verifyDual` refuses a missing
27
+ * ML-DSA signature rather than quietly falling back to Ed25519-only.
28
+ */
29
+ import { ed25519 } from '@noble/curves/ed25519';
30
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
31
+ import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
32
+ import {
33
+ canonicalBytes,
34
+ hashPayload,
35
+ payloadBytes,
36
+ jcsHash,
37
+ leafHash,
38
+ publicKeyFromDid,
39
+ verifyInclusion,
40
+ verifySTH,
41
+ type Receipt,
42
+ type SignedTreeHead,
43
+ } from '@zanii/core';
44
+
45
+ export const PQ_ALG = 'ML-DSA-65' as const;
46
+
47
+ export interface PqKeypair {
48
+ publicKey: Uint8Array;
49
+ secretKey: Uint8Array;
50
+ }
51
+
52
+ /** Generate an ML-DSA-65 keypair. Pass a 32-byte seed for deterministic derivation. */
53
+ export function generatePqKeypair(seed?: Uint8Array): PqKeypair {
54
+ const keys = seed ? ml_dsa65.keygen(seed) : ml_dsa65.keygen();
55
+ return { publicKey: keys.publicKey, secretKey: keys.secretKey };
56
+ }
57
+
58
+ export interface PqBinding {
59
+ v: 1;
60
+ type: 'pq.binding';
61
+ /** The agent's classical identity — the Ed25519 did:key everything else uses. */
62
+ did: string;
63
+ alg: typeof PQ_ALG;
64
+ /** ML-DSA-65 public key, hex (1952 bytes). */
65
+ pq_pub: string;
66
+ ts: string;
67
+ /** `ed25519:<hex>` by the did's key — the classical key vouches for the PQ key. */
68
+ sig_ed?: string;
69
+ /** `mldsa65:<hex>` by the PQ key — proves possession; not just a claimed key. */
70
+ sig_pq?: string;
71
+ }
72
+
73
+ /**
74
+ * Bind a PQ public key to a did:key. BOTH keys sign the same unsigned body — an
75
+ * attacker holding only the Ed25519 key (or only the PQ key) cannot produce this.
76
+ */
77
+ export function bindPqKey(
78
+ fields: { did: string; pqPublicKey: Uint8Array; ts: string },
79
+ edPrivateKey: Uint8Array,
80
+ pqSecretKey: Uint8Array,
81
+ ): PqBinding {
82
+ if (!fields.did) throw new Error('did is required');
83
+ if (Number.isNaN(Date.parse(fields.ts))) throw new Error('ts must be an ISO timestamp');
84
+ const unsigned = {
85
+ v: 1 as const,
86
+ type: 'pq.binding' as const,
87
+ did: fields.did,
88
+ alg: PQ_ALG,
89
+ pq_pub: bytesToHex(fields.pqPublicKey),
90
+ ts: fields.ts,
91
+ };
92
+ const bytes = canonicalBytes(unsigned);
93
+ return {
94
+ ...unsigned,
95
+ sig_ed: `ed25519:${bytesToHex(ed25519.sign(bytes, edPrivateKey))}`,
96
+ sig_pq: `mldsa65:${bytesToHex(ml_dsa65.sign(bytes, pqSecretKey))}`,
97
+ };
98
+ }
99
+
100
+ export interface PqCheck {
101
+ ok: boolean;
102
+ reasons: string[];
103
+ }
104
+
105
+ /** Verify a binding: structure + BOTH signatures over the same body. */
106
+ export function verifyPqBinding(binding: PqBinding | undefined): PqCheck {
107
+ const reasons: string[] = [];
108
+ const b = binding ?? ({} as PqBinding);
109
+ if (b.v !== 1 || b.type !== 'pq.binding') reasons.push('not a pq.binding (v1)');
110
+ if (b.alg !== PQ_ALG) reasons.push(`unsupported alg '${b.alg}'`);
111
+ if (!b.did) reasons.push('missing did');
112
+ if (typeof b.pq_pub !== 'string' || !/^[0-9a-f]+$/.test(b.pq_pub)) reasons.push('missing/invalid pq_pub');
113
+ if (typeof b.ts !== 'string' || Number.isNaN(Date.parse(b.ts))) reasons.push('missing/invalid ts');
114
+ if (reasons.length > 0) return { ok: false, reasons };
115
+
116
+ const { sig_ed, sig_pq, ...unsigned } = b;
117
+ const bytes = canonicalBytes(unsigned as unknown as Record<string, unknown>);
118
+
119
+ const pub = publicKeyFromDid(b.did);
120
+ const edHex = typeof sig_ed === 'string' && sig_ed.startsWith('ed25519:') ? sig_ed.slice(8) : '';
121
+ let edOk = false;
122
+ if (pub && /^[0-9a-f]{128}$/.test(edHex)) {
123
+ try {
124
+ edOk = ed25519.verify(hexToBytes(edHex), bytes, pub);
125
+ } catch {
126
+ edOk = false;
127
+ }
128
+ }
129
+ if (!edOk) reasons.push('Ed25519 signature does not verify against the did');
130
+
131
+ const pqHex = typeof sig_pq === 'string' && sig_pq.startsWith('mldsa65:') ? sig_pq.slice(8) : '';
132
+ let pqOk = false;
133
+ if (/^[0-9a-f]+$/.test(pqHex) && pqHex.length > 0) {
134
+ try {
135
+ pqOk = ml_dsa65.verify(hexToBytes(pqHex), bytes, hexToBytes(b.pq_pub));
136
+ } catch {
137
+ pqOk = false;
138
+ }
139
+ }
140
+ if (!pqOk) reasons.push('ML-DSA signature does not verify against pq_pub (possession not proven)');
141
+
142
+ return { ok: reasons.length === 0, reasons };
143
+ }
144
+
145
+ /** Canonical hash of the full binding (both sigs included) — its stable id. */
146
+ export function bindingHash(binding: PqBinding): string {
147
+ return jcsHash(binding as unknown as Record<string, unknown>);
148
+ }
149
+
150
+ export interface DualSignature {
151
+ /** `ed25519:<hex>`. */
152
+ sig_ed: string;
153
+ /** `mldsa65:<hex>`. */
154
+ sig_pq: string;
155
+ }
156
+
157
+ /** Sign any protocol object with both algorithms over its canonical (JCS) bytes. */
158
+ export function dualSign(obj: Record<string, unknown>, edPrivateKey: Uint8Array, pqSecretKey: Uint8Array): DualSignature {
159
+ const bytes = canonicalBytes(obj);
160
+ return {
161
+ sig_ed: `ed25519:${bytesToHex(ed25519.sign(bytes, edPrivateKey))}`,
162
+ sig_pq: `mldsa65:${bytesToHex(ml_dsa65.sign(bytes, pqSecretKey))}`,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Verify a dual signature. BOTH must pass, and the PQ public key comes from a
168
+ * **verified binding for the same did** — never from the message. A missing ML-DSA
169
+ * signature is a failure, not a fallback: hybrid that degrades silently isn't hybrid.
170
+ */
171
+ export function verifyDual(
172
+ obj: Record<string, unknown>,
173
+ did: string,
174
+ binding: PqBinding,
175
+ sigs: Partial<DualSignature> | undefined,
176
+ ): PqCheck {
177
+ const reasons: string[] = [];
178
+ const bind = verifyPqBinding(binding);
179
+ if (!bind.ok) return { ok: false, reasons: bind.reasons.map((r) => `binding: ${r}`) };
180
+ if (binding.did !== did) return { ok: false, reasons: [`binding is for ${binding.did}, not ${did}`] };
181
+
182
+ const bytes = canonicalBytes(obj);
183
+ const s = sigs ?? {};
184
+
185
+ const pub = publicKeyFromDid(did);
186
+ const edHex = typeof s.sig_ed === 'string' && s.sig_ed.startsWith('ed25519:') ? s.sig_ed.slice(8) : '';
187
+ let edOk = false;
188
+ if (pub && /^[0-9a-f]{128}$/.test(edHex)) {
189
+ try {
190
+ edOk = ed25519.verify(hexToBytes(edHex), bytes, pub);
191
+ } catch {
192
+ edOk = false;
193
+ }
194
+ }
195
+ if (!edOk) reasons.push('Ed25519 signature missing or invalid');
196
+
197
+ const pqHex = typeof s.sig_pq === 'string' && s.sig_pq.startsWith('mldsa65:') ? s.sig_pq.slice(8) : '';
198
+ let pqOk = false;
199
+ if (/^[0-9a-f]+$/.test(pqHex) && pqHex.length > 0) {
200
+ try {
201
+ pqOk = ml_dsa65.verify(hexToBytes(pqHex), bytes, hexToBytes(binding.pq_pub));
202
+ } catch {
203
+ pqOk = false;
204
+ }
205
+ }
206
+ if (!pqOk) reasons.push('ML-DSA signature missing or invalid — refusing Ed25519-only fallback');
207
+
208
+ return { ok: reasons.length === 0, reasons };
209
+ }
210
+
211
+ /**
212
+ * The payload to `record()` UNSALTED (`salt: false` / `salt=False`) as the transition
213
+ * receipt — a PQ binding is public by design, and the receipt's `payload_hash` must be
214
+ * recomputable by any verifier. Suggested target: `pq.transition`.
215
+ */
216
+ export function transitionPayload(binding: PqBinding): Uint8Array {
217
+ const check = verifyPqBinding(binding);
218
+ if (!check.ok) throw new Error(`refusing to anchor an invalid binding: ${check.reasons.join('; ')}`);
219
+ // JCS, matching what record() now commits to — a JSON.stringify here would make the
220
+ // recomputed hash disagree with the receipt for any non-sorted or non-ASCII binding.
221
+ return payloadBytes(binding);
222
+ }
223
+
224
+ export interface TransitionEvidence {
225
+ sth: SignedTreeHead;
226
+ index: number;
227
+ /** Audit path, hex-encoded. */
228
+ proof: string[];
229
+ }
230
+
231
+ /**
232
+ * Verify the transition: the receipt commits to EXACTLY this binding
233
+ * (`payload_hash` matches, unsalted) and the receipt is Merkle-proven in the log the
234
+ * STH commits to. The STH's anchored timestamp then bounds when the binding existed —
235
+ * the piece that survives a future Ed25519 break.
236
+ */
237
+ export function verifyTransition(binding: PqBinding, receipt: Receipt, evidence: TransitionEvidence | undefined): PqCheck {
238
+ const reasons: string[] = [];
239
+ const bind = verifyPqBinding(binding);
240
+ if (!bind.ok) return { ok: false, reasons: bind.reasons.map((r) => `binding: ${r}`) };
241
+ if (receipt.agent_id !== binding.did) reasons.push(`transition receipt by ${receipt.agent_id}, but the binding is for ${binding.did}`);
242
+ if (receipt.payload_hash !== hashPayload(transitionPayload(binding))) {
243
+ reasons.push('receipt payload_hash does not commit to this binding (was it recorded salted?)');
244
+ }
245
+ if (!evidence) {
246
+ reasons.push('no inclusion evidence — an unanchored binding proves nothing about when it existed');
247
+ } else if (!verifySTH(evidence.sth)) {
248
+ reasons.push('inclusion evidence STH signature is invalid');
249
+ } else {
250
+ const root = hexToBytes(evidence.sth.root.replace('sha256:', ''));
251
+ const ok = verifyInclusion(
252
+ leafHash(canonicalBytes(receipt as unknown as Record<string, unknown>)),
253
+ evidence.index,
254
+ evidence.sth.size,
255
+ evidence.proof.map((h) => hexToBytes(h)),
256
+ root,
257
+ );
258
+ if (!ok) reasons.push('inclusion proof FAILED — the transition is not in the log the STH commits to');
259
+ }
260
+ return { ok: reasons.length === 0, reasons };
261
+ }