@cello-protocol/crypto 0.0.2

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.
Files changed (49) hide show
  1. package/dist/checkpoint.d.ts +52 -0
  2. package/dist/checkpoint.d.ts.map +1 -0
  3. package/dist/checkpoint.js +70 -0
  4. package/dist/checkpoint.js.map +1 -0
  5. package/dist/ed25519.d.ts +25 -0
  6. package/dist/ed25519.d.ts.map +1 -0
  7. package/dist/ed25519.js +120 -0
  8. package/dist/ed25519.js.map +1 -0
  9. package/dist/frost/frost-threshold-signer.d.ts +178 -0
  10. package/dist/frost/frost-threshold-signer.d.ts.map +1 -0
  11. package/dist/frost/frost-threshold-signer.js +478 -0
  12. package/dist/frost/frost-threshold-signer.js.map +1 -0
  13. package/dist/frost/index.d.ts +23 -0
  14. package/dist/frost/index.d.ts.map +1 -0
  15. package/dist/frost/index.js +22 -0
  16. package/dist/frost/index.js.map +1 -0
  17. package/dist/frost/stubs.d.ts +82 -0
  18. package/dist/frost/stubs.d.ts.map +1 -0
  19. package/dist/frost/stubs.js +157 -0
  20. package/dist/frost/stubs.js.map +1 -0
  21. package/dist/frost/types.d.ts +173 -0
  22. package/dist/frost/types.d.ts.map +1 -0
  23. package/dist/frost/types.js +21 -0
  24. package/dist/frost/types.js.map +1 -0
  25. package/dist/hashing.d.ts +17 -0
  26. package/dist/hashing.d.ts.map +1 -0
  27. package/dist/hashing.js +50 -0
  28. package/dist/hashing.js.map +1 -0
  29. package/dist/index.d.ts +14 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +14 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/merkle.d.ts +162 -0
  34. package/dist/merkle.d.ts.map +1 -0
  35. package/dist/merkle.js +240 -0
  36. package/dist/merkle.js.map +1 -0
  37. package/dist/ml-dsa.d.ts +100 -0
  38. package/dist/ml-dsa.d.ts.map +1 -0
  39. package/dist/ml-dsa.js +257 -0
  40. package/dist/ml-dsa.js.map +1 -0
  41. package/dist/relay-registration.d.ts +62 -0
  42. package/dist/relay-registration.d.ts.map +1 -0
  43. package/dist/relay-registration.js +87 -0
  44. package/dist/relay-registration.js.map +1 -0
  45. package/dist/types.d.ts +11 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +2 -0
  48. package/dist/types.js.map +1 -0
  49. package/package.json +49 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * FEDERATION-002 — Canonical checkpoint TBS (to-be-signed) serialization.
3
+ *
4
+ * buildCheckpointTbs produces the canonical byte representation for the checkpoint hash:
5
+ * SHA-256(mmr_peaks_serialized || identity_merkle_root || checkpoint_id)
6
+ *
7
+ * where mmr_peaks_serialized is a canonical JSON array of hex-encoded peak hashes
8
+ * ordered by MMR position ascending — e.g. '["aabb...","ccdd..."]' with no
9
+ * whitespace and no object keys, UTF-8 encoded.
10
+ *
11
+ * This function MUST live in @cello-protocol/crypto so both:
12
+ * - the coordinator (which computes the hash to broadcast)
13
+ * - the non-coordinator (which independently recomputes to verify)
14
+ * import the same serialization. AC-010-canonical-tbs: no inline serialization
15
+ * of mmr_peaks exists in packages/directory.
16
+ *
17
+ * References: FIPS 180-4 (SHA-256), RFC 8032 (Ed25519).
18
+ */
19
+ /**
20
+ * Produce the canonical TBS bytes for a checkpoint hash.
21
+ *
22
+ * Pseudocode:
23
+ * 1. Serialize mmr_peaks as a compact JSON array: JSON.stringify(mmrPeaks)
24
+ * mmrPeaks must be an array of hex strings ordered by MMR position ascending.
25
+ * JSON.stringify produces no whitespace: '["aabb...","ccdd..."]' — FIPS 180-4.
26
+ * 2. Encode mmr_peaks_serialized as UTF-8 bytes.
27
+ * 3. Encode identity_merkle_root as UTF-8 bytes.
28
+ * 4. Encode checkpoint_id as UTF-8 bytes.
29
+ * 5. Concatenate: mmr_peaks_serialized_bytes || identity_merkle_root_bytes || checkpoint_id_bytes
30
+ * 6. Compute SHA-256(concatenation) — FIPS 180-4.
31
+ * 7. Return the 32-byte digest as a Uint8Array.
32
+ *
33
+ * @param mmrPeaks - Hex-encoded peak hashes ordered by MMR position ascending.
34
+ * Example: ["aabb...", "ccdd..."] for a 3-leaf MMR with 2 peaks.
35
+ * @param identityMerkleRoot - Hex-encoded identity Merkle root (32 bytes as hex).
36
+ * @param checkpointId - UUID string for this checkpoint (as issued by the coordinator).
37
+ * @returns 32-byte SHA-256 digest — FIPS 180-4.
38
+ */
39
+ export declare function buildCheckpointTbs(mmrPeaks: readonly string[], identityMerkleRoot: string, checkpointId: string): Uint8Array;
40
+ /**
41
+ * Compute the hex-encoded checkpoint hash from canonical TBS.
42
+ *
43
+ * Convenience wrapper around buildCheckpointTbs that returns a hex string
44
+ * (matching the `checkpoint_hash` column type in directory_checkpoints).
45
+ *
46
+ * @param mmrPeaks - Hex-encoded peak hashes ordered by MMR position ascending.
47
+ * @param identityMerkleRoot - Hex-encoded identity Merkle root.
48
+ * @param checkpointId - UUID string for this checkpoint.
49
+ * @returns 64-character lowercase hex string (32-byte SHA-256 digest).
50
+ */
51
+ export declare function computeCheckpointHash(mmrPeaks: readonly string[], identityMerkleRoot: string, checkpointId: string): string;
52
+ //# sourceMappingURL=checkpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint.d.ts","sourceRoot":"","sources":["../src/checkpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,kBAAkB,EAAE,MAAM,EAC1B,YAAY,EAAE,MAAM,GACnB,UAAU,CAkBZ;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,kBAAkB,EAAE,MAAM,EAC1B,YAAY,EAAE,MAAM,GACnB,MAAM,CAER"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * FEDERATION-002 — Canonical checkpoint TBS (to-be-signed) serialization.
3
+ *
4
+ * buildCheckpointTbs produces the canonical byte representation for the checkpoint hash:
5
+ * SHA-256(mmr_peaks_serialized || identity_merkle_root || checkpoint_id)
6
+ *
7
+ * where mmr_peaks_serialized is a canonical JSON array of hex-encoded peak hashes
8
+ * ordered by MMR position ascending — e.g. '["aabb...","ccdd..."]' with no
9
+ * whitespace and no object keys, UTF-8 encoded.
10
+ *
11
+ * This function MUST live in @cello-protocol/crypto so both:
12
+ * - the coordinator (which computes the hash to broadcast)
13
+ * - the non-coordinator (which independently recomputes to verify)
14
+ * import the same serialization. AC-010-canonical-tbs: no inline serialization
15
+ * of mmr_peaks exists in packages/directory.
16
+ *
17
+ * References: FIPS 180-4 (SHA-256), RFC 8032 (Ed25519).
18
+ */
19
+ import { createHash } from "node:crypto";
20
+ /**
21
+ * Produce the canonical TBS bytes for a checkpoint hash.
22
+ *
23
+ * Pseudocode:
24
+ * 1. Serialize mmr_peaks as a compact JSON array: JSON.stringify(mmrPeaks)
25
+ * mmrPeaks must be an array of hex strings ordered by MMR position ascending.
26
+ * JSON.stringify produces no whitespace: '["aabb...","ccdd..."]' — FIPS 180-4.
27
+ * 2. Encode mmr_peaks_serialized as UTF-8 bytes.
28
+ * 3. Encode identity_merkle_root as UTF-8 bytes.
29
+ * 4. Encode checkpoint_id as UTF-8 bytes.
30
+ * 5. Concatenate: mmr_peaks_serialized_bytes || identity_merkle_root_bytes || checkpoint_id_bytes
31
+ * 6. Compute SHA-256(concatenation) — FIPS 180-4.
32
+ * 7. Return the 32-byte digest as a Uint8Array.
33
+ *
34
+ * @param mmrPeaks - Hex-encoded peak hashes ordered by MMR position ascending.
35
+ * Example: ["aabb...", "ccdd..."] for a 3-leaf MMR with 2 peaks.
36
+ * @param identityMerkleRoot - Hex-encoded identity Merkle root (32 bytes as hex).
37
+ * @param checkpointId - UUID string for this checkpoint (as issued by the coordinator).
38
+ * @returns 32-byte SHA-256 digest — FIPS 180-4.
39
+ */
40
+ export function buildCheckpointTbs(mmrPeaks, identityMerkleRoot, checkpointId) {
41
+ // Step 1+2: canonical JSON serialization of the peak array — no whitespace (FIPS 180-4 §6.2)
42
+ const mmrPeaksSerialized = JSON.stringify(mmrPeaks);
43
+ // Steps 3-5: UTF-8 encode and concatenate components
44
+ const encoder = new TextEncoder();
45
+ const peakBytes = encoder.encode(mmrPeaksSerialized);
46
+ const identityBytes = encoder.encode(identityMerkleRoot);
47
+ const checkpointBytes = encoder.encode(checkpointId);
48
+ // Step 6: SHA-256(mmr_peaks_serialized || identity_merkle_root || checkpoint_id)
49
+ const combined = Buffer.concat([
50
+ Buffer.from(peakBytes),
51
+ Buffer.from(identityBytes),
52
+ Buffer.from(checkpointBytes),
53
+ ]);
54
+ return new Uint8Array(createHash("sha256").update(combined).digest());
55
+ }
56
+ /**
57
+ * Compute the hex-encoded checkpoint hash from canonical TBS.
58
+ *
59
+ * Convenience wrapper around buildCheckpointTbs that returns a hex string
60
+ * (matching the `checkpoint_hash` column type in directory_checkpoints).
61
+ *
62
+ * @param mmrPeaks - Hex-encoded peak hashes ordered by MMR position ascending.
63
+ * @param identityMerkleRoot - Hex-encoded identity Merkle root.
64
+ * @param checkpointId - UUID string for this checkpoint.
65
+ * @returns 64-character lowercase hex string (32-byte SHA-256 digest).
66
+ */
67
+ export function computeCheckpointHash(mmrPeaks, identityMerkleRoot, checkpointId) {
68
+ return Buffer.from(buildCheckpointTbs(mmrPeaks, identityMerkleRoot, checkpointId)).toString("hex");
69
+ }
70
+ //# sourceMappingURL=checkpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../src/checkpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAA2B,EAC3B,kBAA0B,EAC1B,YAAoB;IAEpB,6FAA6F;IAC7F,MAAM,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEpD,qDAAqD;IACrD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACrD,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACzD,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAErD,iFAAiF;IACjF,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC;KAC7B,CAAC,CAAC;IAEH,OAAO,IAAI,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAA2B,EAC3B,kBAA0B,EAC1B,YAAoB;IAEpB,OAAO,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACrG,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { KeyProvider, PublicKey, Signature } from "./types.js";
2
+ declare const INSPECT: unique symbol;
3
+ export declare class InMemoryKeyProvider implements KeyProvider {
4
+ #private;
5
+ constructor(seed: Uint8Array);
6
+ getPublicKey(): Promise<PublicKey>;
7
+ sign(data: Uint8Array): Promise<Signature>;
8
+ toJSON(): Record<string, string>;
9
+ toString(): string;
10
+ [INSPECT](): string;
11
+ }
12
+ export declare class FileKeyProvider implements KeyProvider {
13
+ #private;
14
+ private constructor();
15
+ static load(path: string): Promise<FileKeyProvider>;
16
+ getPublicKey(): Promise<PublicKey>;
17
+ sign(data: Uint8Array): Promise<Signature>;
18
+ toJSON(): Record<string, unknown>;
19
+ toString(): string;
20
+ [INSPECT](): string;
21
+ }
22
+ export declare function generateKeypair(): InMemoryKeyProvider;
23
+ export declare function verify(publicKey: PublicKey, data: Uint8Array, signature: Signature): boolean;
24
+ export {};
25
+ //# sourceMappingURL=ed25519.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ed25519.d.ts","sourceRoot":"","sources":["../src/ed25519.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEpE,QAAA,MAAM,OAAO,eAA2C,CAAC;AAOzD,qBAAa,mBAAoB,YAAW,WAAW;;gBAIzC,IAAI,EAAE,UAAU;IAMtB,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAIlC,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAIhD,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAIhC,QAAQ,IAAI,MAAM;IAIlB,CAAC,OAAO,CAAC,IAAI,MAAM;CAGpB;AAED,qBAAa,eAAgB,YAAW,WAAW;;IAGjD,OAAO;WAIM,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAuDnD,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAIlC,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAIhD,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIjC,QAAQ,IAAI,MAAM;IAIlB,CAAC,OAAO,CAAC,IAAI,MAAM;CAGpB;AAED,wBAAgB,eAAe,IAAI,mBAAmB,CAErD;AAED,wBAAgB,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAM5F"}
@@ -0,0 +1,120 @@
1
+ import { ed25519 } from "@noble/curves/ed25519.js";
2
+ import { randomBytes } from "@noble/hashes/utils.js";
3
+ import { readFile, rename, mkdir, open as fsOpen } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ const INSPECT = Symbol.for("nodejs.util.inspect.custom");
6
+ const KEY_FILE_MAGIC = new Uint8Array([0xce, 0x11, 0x0e, 0x01]); // "CELLO\x01"
7
+ const KEY_FILE_VERSION = 1;
8
+ const SEED_BYTES = 32;
9
+ // Magic(4) + version(1) + seed(32) = 37 bytes
10
+ const KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + SEED_BYTES;
11
+ export class InMemoryKeyProvider {
12
+ #seed;
13
+ #publicKey;
14
+ constructor(seed) {
15
+ if (seed.length !== SEED_BYTES)
16
+ throw new Error("seed must be 32 bytes");
17
+ this.#seed = seed;
18
+ this.#publicKey = ed25519.getPublicKey(seed);
19
+ }
20
+ async getPublicKey() {
21
+ return this.#publicKey;
22
+ }
23
+ async sign(data) {
24
+ return ed25519.sign(data, this.#seed);
25
+ }
26
+ toJSON() {
27
+ return { type: "InMemoryKeyProvider", publicKey: Buffer.from(this.#publicKey).toString("hex") };
28
+ }
29
+ toString() {
30
+ return `InMemoryKeyProvider(pubkey=${Buffer.from(this.#publicKey).toString("hex")})`;
31
+ }
32
+ [INSPECT]() {
33
+ return this.toString();
34
+ }
35
+ }
36
+ export class FileKeyProvider {
37
+ #inner;
38
+ constructor(inner) {
39
+ this.#inner = inner;
40
+ }
41
+ static async load(path) {
42
+ // Attempt to read without existsSync to avoid TOCTOU: if the file disappears
43
+ // between a check and a read, readFile throws ENOENT — treated as "not found".
44
+ let raw = null;
45
+ try {
46
+ raw = await readFile(path);
47
+ }
48
+ catch (err) {
49
+ const code = err.code;
50
+ if (code !== "ENOENT") {
51
+ throw { reason: "key_file_corrupt", message: `cannot read key file: ${err.message}` };
52
+ }
53
+ // ENOENT → fall through to generation
54
+ }
55
+ if (raw !== null) {
56
+ if (raw.length !== KEY_FILE_SIZE) {
57
+ throw { reason: "key_file_corrupt", message: `key file has wrong length: expected ${KEY_FILE_SIZE}, got ${raw.length}` };
58
+ }
59
+ for (let i = 0; i < KEY_FILE_MAGIC.length; i++) {
60
+ if (raw[i] !== KEY_FILE_MAGIC[i]) {
61
+ throw { reason: "key_file_corrupt", message: "key file has invalid magic bytes" };
62
+ }
63
+ }
64
+ if (raw[KEY_FILE_MAGIC.length] !== KEY_FILE_VERSION) {
65
+ throw { reason: "key_file_corrupt", message: `key file has unsupported version: ${raw[KEY_FILE_MAGIC.length]}` };
66
+ }
67
+ const seed = raw.slice(KEY_FILE_MAGIC.length + 1, KEY_FILE_MAGIC.length + 1 + SEED_BYTES);
68
+ return new FileKeyProvider(new InMemoryKeyProvider(seed));
69
+ }
70
+ // Generate and atomically write a new key file.
71
+ // Tmp file lives in the same directory as the target so rename() is same-filesystem.
72
+ const seed = randomBytes(SEED_BYTES);
73
+ const buf = Buffer.alloc(KEY_FILE_SIZE);
74
+ KEY_FILE_MAGIC.forEach((b, i) => { buf[i] = b; });
75
+ buf[KEY_FILE_MAGIC.length] = KEY_FILE_VERSION;
76
+ seed.forEach((b, i) => { buf[KEY_FILE_MAGIC.length + 1 + i] = b; });
77
+ const dir = dirname(path);
78
+ await mkdir(dir, { recursive: true });
79
+ const tmp = join(dir, `.cello-key-tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`);
80
+ // Write to tmp fd with O_CREAT|O_EXCL, fchmod before close, then rename — so the file
81
+ // arrives at its final path with 0o600 already set (no post-rename chmod race window).
82
+ const fd = await fsOpen(tmp, "wx", 0o600);
83
+ try {
84
+ await fd.write(buf);
85
+ await fd.chmod(0o600);
86
+ }
87
+ finally {
88
+ await fd.close();
89
+ }
90
+ await rename(tmp, path);
91
+ return new FileKeyProvider(new InMemoryKeyProvider(seed));
92
+ }
93
+ async getPublicKey() {
94
+ return this.#inner.getPublicKey();
95
+ }
96
+ async sign(data) {
97
+ return this.#inner.sign(data);
98
+ }
99
+ toJSON() {
100
+ return { type: "FileKeyProvider", publicKey: this.#inner.toJSON().publicKey };
101
+ }
102
+ toString() {
103
+ return `FileKeyProvider(pubkey=${this.#inner.toJSON().publicKey})`;
104
+ }
105
+ [INSPECT]() {
106
+ return this.toString();
107
+ }
108
+ }
109
+ export function generateKeypair() {
110
+ return new InMemoryKeyProvider(randomBytes(SEED_BYTES));
111
+ }
112
+ export function verify(publicKey, data, signature) {
113
+ try {
114
+ return ed25519.verify(signature, data, publicKey);
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ }
120
+ //# sourceMappingURL=ed25519.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ed25519.js","sourceRoot":"","sources":["../src/ed25519.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;AACzD,MAAM,cAAc,GAAG,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc;AAC/E,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAC3B,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,8CAA8C;AAC9C,MAAM,aAAa,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC;AAE7D,MAAM,OAAO,mBAAmB;IACrB,KAAK,CAAa;IAClB,UAAU,CAAY;IAE/B,YAAY,IAAgB;QAC1B,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACzE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAgB;QACzB,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IAED,MAAM;QACJ,OAAO,EAAE,IAAI,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IAClG,CAAC;IAED,QAAQ;QACN,OAAO,8BAA8B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC;IACvF,CAAC;IAED,CAAC,OAAO,CAAC;QACP,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;CACF;AAED,MAAM,OAAO,eAAe;IACjB,MAAM,CAAsB;IAErC,YAAoB,KAA0B;QAC5C,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAY;QAC5B,6EAA6E;QAC7E,+EAA+E;QAC/E,IAAI,GAAG,GAAkB,IAAI,CAAC;QAC9B,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;YACjD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtB,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,yBAA0B,GAAa,CAAC,OAAO,EAAE,EAAE,CAAC;YACnG,CAAC;YACD,sCAAsC;QACxC,CAAC;QAED,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,IAAI,GAAG,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;gBACjC,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,uCAAuC,aAAa,SAAS,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3H,CAAC;YACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/C,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;oBACjC,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,kCAAkC,EAAE,CAAC;gBACpF,CAAC;YACH,CAAC;YACD,IAAI,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,gBAAgB,EAAE,CAAC;gBACpD,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,qCAAqC,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACnH,CAAC;YACD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,cAAc,CAAC,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC;YAC1F,OAAO,IAAI,eAAe,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,gDAAgD;QAChD,qFAAqF;QACrF,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACxC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,gBAAgB,CAAC;QAC9C,IAAI,CAAC,OAAO,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,GAAG,GAAG,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAEpF,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,kBAAkB,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7F,sFAAsF;QACtF,uFAAuF;QACvF,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACpB,MAAM,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QACD,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAExB,OAAO,IAAI,eAAe,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAgB;QACzB,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,MAAM;QACJ,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,CAAC;IAChF,CAAC;IAED,QAAQ;QACN,OAAO,0BAA0B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,SAAS,GAAG,CAAC;IACrE,CAAC;IAED,CAAC,OAAO,CAAC;QACP,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;CACF;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,mBAAmB,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,SAAoB,EAAE,IAAgB,EAAE,SAAoB;IACjF,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * FrostThresholdSigner — FROST threshold signing over Ed25519 (RFC 9591)
3
+ *
4
+ * CELLO-CRYPTO-003
5
+ *
6
+ * Crypto reference: FROST / RFC 9591, Ed25519 / RFC 8032
7
+ * Implementation: @noble/curves/ed25519 — ed25519_FROST
8
+ *
9
+ * ─── Phase P: Pseudocode ─────────────────────────────────────────────────────
10
+ *
11
+ * bootstrapKeyShares(agentPubkey, config):
12
+ * // TEST-ONLY shortcut — NOT real multi-round DKG (that's M3)
13
+ * // Guard: only callable in NODE_ENV=test
14
+ * assert NODE_ENV === 'test'
15
+ * identifiers = [derive(stub.id) for stub in stubs]
16
+ * dealerShares = trustedDealer({ min: threshold, max: participants }, identifiers)
17
+ * // Distribute each stub its share (persists in stub.#key)
18
+ * for each stub, share in dealerShares.secretShares:
19
+ * stub.receiveShare(share, dealerShares.public)
20
+ * // primary_pubkey = commitments[0] from the shared public package
21
+ * primary_pubkey = dealerShares.public.commitments[0]
22
+ * // Persist client's local key share (stored in module-level map keyed by agentPubkey)
23
+ * storeLocalShare(agentPubkey, { share: agentShare, pub: dealerShares.public })
24
+ * return { primaryPubkey: primary_pubkey }
25
+ * // NOTE: signing share NEVER returned or logged
26
+ *
27
+ * FrostThresholdSigner.participateInCeremony(ceremonyId, tbs, context):
28
+ * // Coordinator flow — RFC 9591 Section 5
29
+ * frame(tbs, context) → msg = encode(context + '\0' + tbs) // domain separation
30
+ * localShare = loadLocalShare(agentPubkey)
31
+ * if localShare is null: throw "not bootstrapped"
32
+ * availableStubs = stubs (or directoryNodes via stream in production)
33
+ *
34
+ * // Attempt loop — up to maxRetries
35
+ * for attempt in 1..maxRetries (honoring ceremonyTimeout):
36
+ * reachableStubs = stubs where not excluded
37
+ * if |reachableStubs| < threshold: return DIRECTORY_BELOW_THRESHOLD
38
+ * selectedStubs = reachableStubs[0..threshold-1]
39
+ *
40
+ * // Round 1 — collect commitments (coordinator generates local nonce too)
41
+ * localNonce = commit(localShare.secret)
42
+ * commitments = [localNonce.commitments]
43
+ * for each stub in selectedStubs:
44
+ * c = stub.generateCommitment()
45
+ * commitments.push(c.commitment)
46
+ * commitmentList = sort(commitments, by identifier)
47
+ *
48
+ * // Round 2 — collect partial signatures (with per-node timeout)
49
+ * validShares = {}
50
+ * excludedInThisRound = []
51
+ * for each (stub, stubCommitment) in selectedStubs (with roundTimeout):
52
+ * partialSig = await stub.signRound(pub, commitmentList, msg) // timeout → null
53
+ * if partialSig is null: excludedInThisRound.push(stub); continue
54
+ * if !verifyShare(pub, commitmentList, msg, stub.id, partialSig):
55
+ * excludedInThisRound.push(stub); continue // invalid partial — same as timeout
56
+ * validShares[stub.id] = partialSig
57
+ *
58
+ * // Also compute coordinator's own partial sig
59
+ * coordinatorPartialSig = signShare(localShare.secret, pub, localNonce.nonces, commitmentList, msg)
60
+ * validShares[coordinatorId] = coordinatorPartialSig
61
+ *
62
+ * if |validShares| >= threshold:
63
+ * signature = aggregate(pub, commitmentList, msg, validShares)
64
+ * return { ok: true, signature }
65
+ * else:
66
+ * // Not enough valid shares — retry with different participants
67
+ * continue
68
+ *
69
+ * return { ok: false, error: { reason: 'CEREMONY_EXHAUSTED' } }
70
+ *
71
+ * primary_pubkey derivation:
72
+ * // In trustedDealer mode: commitments[0] is the group public key
73
+ * // This is the constant term of the dealer's Shamir polynomial commitment
74
+ * // It is deterministic given the dealer's secret polynomial
75
+ * // All participants' public shares verify against the same commitments[0]
76
+ * primary_pubkey = dealerShares.public.commitments[0]
77
+ *
78
+ * Round timeout / node replacement:
79
+ * // Each stub.signRound wraps in Promise.race([real, timeoutPromise])
80
+ * // If timeout wins → stub treated as excluded (same path as invalid sig)
81
+ * // On retry, excluded stubs are filtered from available pool
82
+ * // If available < threshold → DIRECTORY_BELOW_THRESHOLD (not retried)
83
+ *
84
+ * ─── End Pseudocode ──────────────────────────────────────────────────────────
85
+ */
86
+ import type { FrostPublic, FrostSecret } from "@noble/curves/abstract/frost.js";
87
+ import type { IThresholdSigner, ThresholdSignature, FrostThresholdSignerConfig, BootstrapResult, FrostContext } from "./types.js";
88
+ import { CONTEXT_SESSION_ESTABLISHMENT, CONTEXT_SEAL } from "./types.js";
89
+ /**
90
+ * Clear all local key shares stored in the module-level map.
91
+ *
92
+ * TEST-ONLY: guards against accumulation of key material across test cases.
93
+ * Call in afterEach/afterAll to evict shares seeded by bootstrapKeyShares.
94
+ *
95
+ * Throws if called outside NODE_ENV=test so it can never be misused in
96
+ * production (where it would collapse multi-party FROST to zero-share state).
97
+ */
98
+ export declare function clearTestShares(): void;
99
+ /**
100
+ * Store the client's local DKG key share after a real DKG ceremony completes.
101
+ *
102
+ * Production path: called after runNetworkDkg completes successfully.
103
+ * No NODE_ENV guard — this is the production key storage path (unlike bootstrapKeyShares).
104
+ *
105
+ * SECURITY: validates the secret against the public before storing.
106
+ * The FrostSecret is stored in the module-level map keyed by agentPubkeyHex.
107
+ */
108
+ export declare function storeDkgResult(agentPubkeyHex: string, secret: FrostSecret, pub: FrostPublic): void;
109
+ /**
110
+ * Test-harness shortcut for key share bootstrap.
111
+ *
112
+ * GUARD: Only callable in NODE_ENV=test. This is NOT a real multi-round DKG
113
+ * (that comes in M3). It uses trustedDealer to pre-populate shares for testing.
114
+ *
115
+ * Behavior:
116
+ * 1. Generate FROST shares via trustedDealer for all n stub nodes
117
+ * 2. Distribute each stub its signing share
118
+ * 3. One of the identifiers is the "client" (coordinator) — its share is stored locally
119
+ * 4. primary_pubkey = commitments[0] from the shared public package
120
+ * 5. Return { primaryPubkey } — no key material beyond the group public key
121
+ */
122
+ export declare function bootstrapKeyShares(agentPubkey: Uint8Array, config: FrostThresholdSignerConfig): Promise<BootstrapResult>;
123
+ export declare class FrostThresholdSigner implements IThresholdSigner {
124
+ #private;
125
+ constructor(config: FrostThresholdSignerConfig, agentPubkey: Uint8Array);
126
+ /**
127
+ * Get the group public key (primary_pubkey) for this signer.
128
+ * Returns the key from the stored local share (populated by bootstrapKeyShares).
129
+ */
130
+ getPrimaryPubkey(): Uint8Array;
131
+ /**
132
+ * Verify a threshold signature against a public key and TBS with context.
133
+ * Used by callers (and tests) to verify signatures produced by participateInCeremony.
134
+ */
135
+ verifySignature(signature: Uint8Array, tbs: Uint8Array, context: string, publicKey: Uint8Array): boolean;
136
+ /**
137
+ * Participate in a FROST threshold signing ceremony as coordinator.
138
+ *
139
+ * RFC 9591 Section 5 coordinator flow with in-process stubs.
140
+ * Full round coordination, timeout handling, and participant exclusion.
141
+ */
142
+ participateInCeremony(ceremonyId: string, tbs: Uint8Array, context: FrostContext, onProgress?: import("./types.js").CeremonyProgressCallback): Promise<ThresholdSignature>;
143
+ }
144
+ /**
145
+ * Verify a FROST threshold signature without a signer instance.
146
+ *
147
+ * Used by the client when it is the counterparty (participant B) and has no
148
+ * IThresholdSigner injected. Verifies the domain-separated FROST signature
149
+ * against the provided public key.
150
+ *
151
+ * @param signature - 64-byte threshold signature from the SessionAssignment
152
+ * @param tbs - to-be-signed bytes (from buildSessionEstablishmentTbs)
153
+ * @param context - domain context string (e.g. CONTEXT_SESSION_ESTABLISHMENT)
154
+ * @param publicKey - 32-byte Ed25519 group public key to verify against
155
+ * @returns true if signature verifies; false otherwise (never throws)
156
+ */
157
+ export declare function verifyFrostSignature(signature: Uint8Array, tbs: Uint8Array, context: string, publicKey: Uint8Array): boolean;
158
+ /**
159
+ * MockThresholdSigner: confirms the IThresholdSigner swap point is real.
160
+ *
161
+ * Implements IThresholdSigner without any FROST crypto. Used by AC-010 to
162
+ * verify that callers work correctly against the interface without knowing
163
+ * the concrete implementation.
164
+ *
165
+ * Returns a deterministic 64-byte signature (not cryptographically valid,
166
+ * but structurally correct for interface tests).
167
+ */
168
+ export declare class MockThresholdSigner implements IThresholdSigner {
169
+ participateInCeremony(_ceremonyId: string, tbs: Uint8Array, _context: FrostContext, _onProgress?: import("./types.js").CeremonyProgressCallback): Promise<ThresholdSignature>;
170
+ /**
171
+ * Return a deterministic 32-byte mock primary pubkey.
172
+ * Not a real Ed25519 point — only for interface shape testing.
173
+ */
174
+ getPrimaryPubkey(): Uint8Array;
175
+ verifySignature(_signature: Uint8Array, _tbs: Uint8Array, _context: FrostContext, _publicKey: Uint8Array): boolean;
176
+ }
177
+ export { CONTEXT_SESSION_ESTABLISHMENT, CONTEXT_SEAL };
178
+ //# sourceMappingURL=frost-threshold-signer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frost-threshold-signer.d.ts","sourceRoot":"","sources":["../../src/frost/frost-threshold-signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AAGH,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACZ,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,0BAA0B,EAE1B,eAAe,EACf,YAAY,EACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,6BAA6B,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAgBzE;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,WAAW,GACf,IAAI,CAGN;AA+BD;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,UAAU,EACvB,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,eAAe,CAAC,CAwD1B;AAID,qBAAa,oBAAqB,YAAW,gBAAgB;;gBAa/C,MAAM,EAAE,0BAA0B,EAAE,WAAW,EAAE,UAAU;IAgBvE;;;OAGG;IACH,gBAAgB,IAAI,UAAU;IAU9B;;;OAGG;IACH,eAAe,CACb,SAAS,EAAE,UAAU,EACrB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,UAAU,GACpB,OAAO;IASV;;;;;OAKG;IACG,qBAAqB,CACzB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,YAAY,EACrB,UAAU,CAAC,EAAE,OAAO,YAAY,EAAE,wBAAwB,GACzD,OAAO,CAAC,kBAAkB,CAAC;CA2M/B;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,UAAU,EACrB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,UAAU,GACpB,OAAO,CAOT;AAID;;;;;;;;;GASG;AACH,qBAAa,mBAAoB,YAAW,gBAAgB;IACpD,qBAAqB,CACzB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,YAAY,EACtB,WAAW,CAAC,EAAE,OAAO,YAAY,EAAE,wBAAwB,GAC1D,OAAO,CAAC,kBAAkB,CAAC;IAW9B;;;OAGG;IACH,gBAAgB,IAAI,UAAU;IAO9B,eAAe,CACb,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,UAAU,GACrB,OAAO;CAIX;AAGD,OAAO,EAAE,6BAA6B,EAAE,YAAY,EAAE,CAAC"}