@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.
- package/dist/checkpoint.d.ts +52 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +70 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/ed25519.d.ts +25 -0
- package/dist/ed25519.d.ts.map +1 -0
- package/dist/ed25519.js +120 -0
- package/dist/ed25519.js.map +1 -0
- package/dist/frost/frost-threshold-signer.d.ts +178 -0
- package/dist/frost/frost-threshold-signer.d.ts.map +1 -0
- package/dist/frost/frost-threshold-signer.js +478 -0
- package/dist/frost/frost-threshold-signer.js.map +1 -0
- package/dist/frost/index.d.ts +23 -0
- package/dist/frost/index.d.ts.map +1 -0
- package/dist/frost/index.js +22 -0
- package/dist/frost/index.js.map +1 -0
- package/dist/frost/stubs.d.ts +82 -0
- package/dist/frost/stubs.d.ts.map +1 -0
- package/dist/frost/stubs.js +157 -0
- package/dist/frost/stubs.js.map +1 -0
- package/dist/frost/types.d.ts +173 -0
- package/dist/frost/types.d.ts.map +1 -0
- package/dist/frost/types.js +21 -0
- package/dist/frost/types.js.map +1 -0
- package/dist/hashing.d.ts +17 -0
- package/dist/hashing.d.ts.map +1 -0
- package/dist/hashing.js +50 -0
- package/dist/hashing.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/merkle.d.ts +162 -0
- package/dist/merkle.d.ts.map +1 -0
- package/dist/merkle.js +240 -0
- package/dist/merkle.js.map +1 -0
- package/dist/ml-dsa.d.ts +100 -0
- package/dist/ml-dsa.d.ts.map +1 -0
- package/dist/ml-dsa.js +257 -0
- package/dist/ml-dsa.js.map +1 -0
- package/dist/relay-registration.d.ts +62 -0
- package/dist/relay-registration.d.ts.map +1 -0
- package/dist/relay-registration.js +87 -0
- package/dist/relay-registration.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,478 @@
|
|
|
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 { ed25519_FROST } from "@noble/curves/ed25519.js";
|
|
87
|
+
import { CONTEXT_SESSION_ESTABLISHMENT, CONTEXT_SEAL } from "./types.js";
|
|
88
|
+
// Module-level store for local key shares, keyed by agent pubkey hex.
|
|
89
|
+
// This keeps the signing share within the package boundary.
|
|
90
|
+
// Private (#) module-level map — not exported.
|
|
91
|
+
const _localShares = new Map();
|
|
92
|
+
/**
|
|
93
|
+
* Clear all local key shares stored in the module-level map.
|
|
94
|
+
*
|
|
95
|
+
* TEST-ONLY: guards against accumulation of key material across test cases.
|
|
96
|
+
* Call in afterEach/afterAll to evict shares seeded by bootstrapKeyShares.
|
|
97
|
+
*
|
|
98
|
+
* Throws if called outside NODE_ENV=test so it can never be misused in
|
|
99
|
+
* production (where it would collapse multi-party FROST to zero-share state).
|
|
100
|
+
*/
|
|
101
|
+
export function clearTestShares() {
|
|
102
|
+
if (process.env.NODE_ENV !== "test") {
|
|
103
|
+
throw new Error("clearTestShares is test-only and must not be called in production");
|
|
104
|
+
}
|
|
105
|
+
_localShares.clear();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Store the client's local DKG key share after a real DKG ceremony completes.
|
|
109
|
+
*
|
|
110
|
+
* Production path: called after runNetworkDkg completes successfully.
|
|
111
|
+
* No NODE_ENV guard — this is the production key storage path (unlike bootstrapKeyShares).
|
|
112
|
+
*
|
|
113
|
+
* SECURITY: validates the secret against the public before storing.
|
|
114
|
+
* The FrostSecret is stored in the module-level map keyed by agentPubkeyHex.
|
|
115
|
+
*/
|
|
116
|
+
export function storeDkgResult(agentPubkeyHex, secret, pub) {
|
|
117
|
+
ed25519_FROST.validateSecret(secret, pub);
|
|
118
|
+
_localShares.set(agentPubkeyHex, { secret, pub });
|
|
119
|
+
}
|
|
120
|
+
// ─── Message framing for domain separation ────────────────────────────────────
|
|
121
|
+
/**
|
|
122
|
+
* Frame a TBS with a context string for domain separation.
|
|
123
|
+
*
|
|
124
|
+
* Encoding: `<context>\0<tbs>`
|
|
125
|
+
* The null byte separator ensures unambiguous parsing: a context string
|
|
126
|
+
* cannot contain \0, so there is no collision between different contexts
|
|
127
|
+
* even when TBS bytes have the same content.
|
|
128
|
+
*/
|
|
129
|
+
function frameMessage(context, tbs) {
|
|
130
|
+
const enc = new TextEncoder();
|
|
131
|
+
const ctxBytes = enc.encode(context);
|
|
132
|
+
// context + 0x00 separator + tbs
|
|
133
|
+
const framed = new Uint8Array(ctxBytes.length + 1 + tbs.length);
|
|
134
|
+
framed.set(ctxBytes, 0);
|
|
135
|
+
framed[ctxBytes.length] = 0x00; // null separator
|
|
136
|
+
framed.set(tbs, ctxBytes.length + 1);
|
|
137
|
+
return framed;
|
|
138
|
+
}
|
|
139
|
+
// ─── Agent pubkey serialization ───────────────────────────────────────────────
|
|
140
|
+
function toHex(bytes) {
|
|
141
|
+
return Buffer.from(bytes).toString("hex");
|
|
142
|
+
}
|
|
143
|
+
// ─── bootstrapKeyShares ───────────────────────────────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* Test-harness shortcut for key share bootstrap.
|
|
146
|
+
*
|
|
147
|
+
* GUARD: Only callable in NODE_ENV=test. This is NOT a real multi-round DKG
|
|
148
|
+
* (that comes in M3). It uses trustedDealer to pre-populate shares for testing.
|
|
149
|
+
*
|
|
150
|
+
* Behavior:
|
|
151
|
+
* 1. Generate FROST shares via trustedDealer for all n stub nodes
|
|
152
|
+
* 2. Distribute each stub its signing share
|
|
153
|
+
* 3. One of the identifiers is the "client" (coordinator) — its share is stored locally
|
|
154
|
+
* 4. primary_pubkey = commitments[0] from the shared public package
|
|
155
|
+
* 5. Return { primaryPubkey } — no key material beyond the group public key
|
|
156
|
+
*/
|
|
157
|
+
export async function bootstrapKeyShares(agentPubkey, config) {
|
|
158
|
+
if (process.env.NODE_ENV !== "test") {
|
|
159
|
+
throw new Error("bootstrapKeyShares is a test-only shortcut. " +
|
|
160
|
+
"Real DKG (M3) is required in production.");
|
|
161
|
+
}
|
|
162
|
+
const stubs = config.directoryNodeStubs;
|
|
163
|
+
if (!stubs || stubs.length === 0) {
|
|
164
|
+
throw new Error("bootstrapKeyShares requires directoryNodeStubs in test mode");
|
|
165
|
+
}
|
|
166
|
+
const { threshold, participants } = config;
|
|
167
|
+
// Identifiers: one per stub node + one for the client (coordinator)
|
|
168
|
+
// The client acts as participant "client:<agentPubkeyHex>"
|
|
169
|
+
const clientIdStr = `client:${toHex(agentPubkey)}`;
|
|
170
|
+
const clientIdentifier = ed25519_FROST.Identifier.derive(clientIdStr);
|
|
171
|
+
const stubIdentifiers = stubs.map((stub) => ed25519_FROST.Identifier.derive(stub.id));
|
|
172
|
+
const allIdentifiers = [clientIdentifier, ...stubIdentifiers];
|
|
173
|
+
// trustedDealer: produces one FrostPublic + per-participant FrostSecret shares
|
|
174
|
+
// The dealer polynomial's constant term commitment is the group public key.
|
|
175
|
+
const deal = ed25519_FROST.trustedDealer({ min: threshold, max: participants + 1 }, // +1 for the client
|
|
176
|
+
allIdentifiers);
|
|
177
|
+
// Distribute shares to stubs
|
|
178
|
+
for (const stub of stubs) {
|
|
179
|
+
const stubId = ed25519_FROST.Identifier.derive(stub.id);
|
|
180
|
+
const stubShare = deal.secretShares[stubId];
|
|
181
|
+
if (!stubShare) {
|
|
182
|
+
throw new Error(`No share generated for stub ${stub.id}`);
|
|
183
|
+
}
|
|
184
|
+
await stub.receiveShare(stubShare, deal.public);
|
|
185
|
+
}
|
|
186
|
+
// Store client's local share
|
|
187
|
+
const clientShare = deal.secretShares[clientIdentifier];
|
|
188
|
+
if (!clientShare) {
|
|
189
|
+
throw new Error("No share generated for client");
|
|
190
|
+
}
|
|
191
|
+
_localShares.set(toHex(agentPubkey), {
|
|
192
|
+
secret: clientShare,
|
|
193
|
+
pub: deal.public,
|
|
194
|
+
});
|
|
195
|
+
// primary_pubkey = commitments[0] = the group public key (32-byte Ed25519 point)
|
|
196
|
+
const primaryPubkey = deal.public.commitments[0];
|
|
197
|
+
// Return ONLY primaryPubkey — no key material
|
|
198
|
+
return { primaryPubkey: new Uint8Array(primaryPubkey) };
|
|
199
|
+
}
|
|
200
|
+
// ─── FrostThresholdSigner ─────────────────────────────────────────────────────
|
|
201
|
+
export class FrostThresholdSigner {
|
|
202
|
+
#config;
|
|
203
|
+
#agentPubkey;
|
|
204
|
+
static #DEFAULT_ROUND_TIMEOUT_MS = 3000;
|
|
205
|
+
static #DEFAULT_CEREMONY_TIMEOUT_MS = 30000;
|
|
206
|
+
static #DEFAULT_MAX_RETRIES = 3;
|
|
207
|
+
constructor(config, agentPubkey) {
|
|
208
|
+
this.#config = {
|
|
209
|
+
threshold: config.threshold,
|
|
210
|
+
participants: config.participants,
|
|
211
|
+
directoryNodes: config.directoryNodes,
|
|
212
|
+
directoryNodeStubs: config.directoryNodeStubs,
|
|
213
|
+
roundTimeoutMs: config.roundTimeoutMs ?? FrostThresholdSigner.#DEFAULT_ROUND_TIMEOUT_MS,
|
|
214
|
+
ceremonyTimeoutMs: config.ceremonyTimeoutMs ??
|
|
215
|
+
FrostThresholdSigner.#DEFAULT_CEREMONY_TIMEOUT_MS,
|
|
216
|
+
maxRetries: config.maxRetries ?? FrostThresholdSigner.#DEFAULT_MAX_RETRIES,
|
|
217
|
+
};
|
|
218
|
+
this.#agentPubkey = agentPubkey;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get the group public key (primary_pubkey) for this signer.
|
|
222
|
+
* Returns the key from the stored local share (populated by bootstrapKeyShares).
|
|
223
|
+
*/
|
|
224
|
+
getPrimaryPubkey() {
|
|
225
|
+
const localShare = _localShares.get(toHex(this.#agentPubkey));
|
|
226
|
+
if (!localShare) {
|
|
227
|
+
throw new Error("Not bootstrapped: call bootstrapKeyShares before using FrostThresholdSigner");
|
|
228
|
+
}
|
|
229
|
+
return new Uint8Array(localShare.pub.commitments[0]);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Verify a threshold signature against a public key and TBS with context.
|
|
233
|
+
* Used by callers (and tests) to verify signatures produced by participateInCeremony.
|
|
234
|
+
*/
|
|
235
|
+
verifySignature(signature, tbs, context, publicKey) {
|
|
236
|
+
try {
|
|
237
|
+
const msg = frameMessage(context, tbs);
|
|
238
|
+
return ed25519_FROST.verify(signature, msg, publicKey);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Participate in a FROST threshold signing ceremony as coordinator.
|
|
246
|
+
*
|
|
247
|
+
* RFC 9591 Section 5 coordinator flow with in-process stubs.
|
|
248
|
+
* Full round coordination, timeout handling, and participant exclusion.
|
|
249
|
+
*/
|
|
250
|
+
async participateInCeremony(ceremonyId, tbs, context, onProgress) {
|
|
251
|
+
const localShare = _localShares.get(toHex(this.#agentPubkey));
|
|
252
|
+
if (!localShare) {
|
|
253
|
+
throw new Error("Not bootstrapped: call bootstrapKeyShares before using FrostThresholdSigner");
|
|
254
|
+
}
|
|
255
|
+
const stubs = this.#config.directoryNodeStubs ?? [];
|
|
256
|
+
const { threshold, roundTimeoutMs, ceremonyTimeoutMs, maxRetries } = this.#config;
|
|
257
|
+
// Frame message for domain separation: context\0tbs
|
|
258
|
+
const msg = frameMessage(context, tbs);
|
|
259
|
+
const { pub } = localShare;
|
|
260
|
+
const clientId = ed25519_FROST.Identifier.derive(`client:${toHex(this.#agentPubkey)}`);
|
|
261
|
+
// Ceremony-level timeout: a deadline across all retry attempts
|
|
262
|
+
const ceremonyDeadline = Date.now() + ceremonyTimeoutMs;
|
|
263
|
+
// Pre-ceremony availability check (RFC 9591 §5 coordinator pre-check):
|
|
264
|
+
// Count nodes reachable at initiation. If fewer than (threshold - 1) are
|
|
265
|
+
// reachable, fail immediately — the ceremony cannot possibly succeed.
|
|
266
|
+
// (threshold - 1 because the client itself is one of the threshold signers)
|
|
267
|
+
const reachableAtInitiation = stubs.filter((s) => s.isReachable());
|
|
268
|
+
if (reachableAtInitiation.length < threshold - 1) {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
error: { reason: "DIRECTORY_BELOW_THRESHOLD" },
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// Within-ceremony exclusion: stubs excluded during THIS ceremony.
|
|
275
|
+
// A stub is excluded (within this ceremony) when it: (a) times out or
|
|
276
|
+
// (b) returns an invalid partial signature (treated identically per story spec).
|
|
277
|
+
// On each new participateInCeremony call, the exclusion set resets.
|
|
278
|
+
const ceremonyExcluded = new Set();
|
|
279
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
280
|
+
// Check ceremony-level timeout at the start of each attempt
|
|
281
|
+
if (Date.now() >= ceremonyDeadline) {
|
|
282
|
+
return {
|
|
283
|
+
ok: false,
|
|
284
|
+
error: { reason: "CEREMONY_TIMEOUT" },
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
// Available stubs: reachable at initiation and not excluded in this ceremony
|
|
288
|
+
const available = reachableAtInitiation.filter((s) => !ceremonyExcluded.has(s.id));
|
|
289
|
+
// Select (threshold - 1) stubs for this round (client counts as one signer)
|
|
290
|
+
const selected = available.slice(0, threshold - 1);
|
|
291
|
+
if (selected.length < threshold - 1) {
|
|
292
|
+
// Not enough non-excluded nodes remain for this attempt (fewer than
|
|
293
|
+
// threshold - 1 stubs available after excluding failed nodes). Since the
|
|
294
|
+
// client counts as one of the threshold signers, fewer than (threshold - 1)
|
|
295
|
+
// stub participants means the ceremony cannot possibly succeed regardless of
|
|
296
|
+
// further retries. Return DIRECTORY_BELOW_THRESHOLD immediately — same as
|
|
297
|
+
// the pre-ceremony check — rather than sleeping and timing out with
|
|
298
|
+
// CEREMONY_TIMEOUT (which would diverge from the spec).
|
|
299
|
+
return {
|
|
300
|
+
ok: false,
|
|
301
|
+
error: { reason: "DIRECTORY_BELOW_THRESHOLD" },
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
// Round 1: generate fresh nonces + commitment list
|
|
305
|
+
const localNonce = ed25519_FROST.commit(localShare.secret);
|
|
306
|
+
const stubCommits = await Promise.all(selected.map((s) => s.generateCommitment()));
|
|
307
|
+
const commitmentList = [
|
|
308
|
+
localNonce.commitments,
|
|
309
|
+
...stubCommits.map((c) => c.nonceCommitment),
|
|
310
|
+
];
|
|
311
|
+
// total = selected stubs + 1 client
|
|
312
|
+
const totalParticipants = selected.length + 1;
|
|
313
|
+
for (let ci = 0; ci < totalParticipants; ci++) {
|
|
314
|
+
onProgress?.({ type: "commit_collected", index: ci + 1, total: totalParticipants });
|
|
315
|
+
}
|
|
316
|
+
// Round 2: collect partial signatures with per-node timeout
|
|
317
|
+
const sigShares = {};
|
|
318
|
+
let anyFailedThisRound = false;
|
|
319
|
+
let partialSigCount = 0;
|
|
320
|
+
for (let i = 0; i < selected.length; i++) {
|
|
321
|
+
const stub = selected[i];
|
|
322
|
+
// Check ceremony timeout before each node
|
|
323
|
+
if (Date.now() >= ceremonyDeadline) {
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
error: { reason: "CEREMONY_TIMEOUT" },
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
// Race stub response against per-node timeout
|
|
330
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), roundTimeoutMs));
|
|
331
|
+
let partialSig;
|
|
332
|
+
try {
|
|
333
|
+
partialSig = await Promise.race([
|
|
334
|
+
stub.signRound({ pub, commitmentList, msg, ceremonyId }),
|
|
335
|
+
timeoutPromise,
|
|
336
|
+
]);
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
partialSig = null;
|
|
340
|
+
}
|
|
341
|
+
if (partialSig === null) {
|
|
342
|
+
// Timeout — exclude this stub for the rest of this ceremony;
|
|
343
|
+
// pick replacement (different stub) in the next attempt
|
|
344
|
+
ceremonyExcluded.add(stub.id);
|
|
345
|
+
anyFailedThisRound = true;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
// Verify partial sig — invalid treated identically to timeout (story spec §AC-006)
|
|
349
|
+
// The invalid partial is NEVER passed to the combiner/aggregator.
|
|
350
|
+
let isValid;
|
|
351
|
+
try {
|
|
352
|
+
const stubId = ed25519_FROST.Identifier.derive(stub.id);
|
|
353
|
+
isValid = ed25519_FROST.verifyShare(pub, commitmentList, msg, stubId, partialSig);
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
isValid = false;
|
|
357
|
+
}
|
|
358
|
+
if (!isValid) {
|
|
359
|
+
// Invalid partial sig — exclude for rest of this ceremony (same path as timeout)
|
|
360
|
+
ceremonyExcluded.add(stub.id);
|
|
361
|
+
anyFailedThisRound = true;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const stubId = ed25519_FROST.Identifier.derive(stub.id);
|
|
365
|
+
// String(stubId) makes the bigint→string coercion explicit: @noble/curves
|
|
366
|
+
// Identifier is a bigint, and Record<string,…> keys are strings. Using
|
|
367
|
+
// String() here documents the conversion rather than relying on implicit
|
|
368
|
+
// JS coercion, and must match how aggregate() looks up shares.
|
|
369
|
+
sigShares[String(stubId)] = partialSig;
|
|
370
|
+
partialSigCount++;
|
|
371
|
+
onProgress?.({ type: "partial_sig_collected", index: partialSigCount, total: totalParticipants });
|
|
372
|
+
}
|
|
373
|
+
// If any stub failed, this round's commitment list is inconsistent —
|
|
374
|
+
// the failed stubs committed but did not sign valid shares.
|
|
375
|
+
// Restart with a fresh participant set on the next attempt.
|
|
376
|
+
if (anyFailedThisRound) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
// All selected stubs provided valid partial sigs. Compute client's partial sig.
|
|
380
|
+
let clientSig;
|
|
381
|
+
try {
|
|
382
|
+
clientSig = ed25519_FROST.signShare(localShare.secret, pub, localNonce.nonces, commitmentList, msg);
|
|
383
|
+
sigShares[String(clientId)] = clientSig;
|
|
384
|
+
partialSigCount++;
|
|
385
|
+
onProgress?.({ type: "partial_sig_collected", index: partialSigCount, total: totalParticipants });
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (Object.keys(sigShares).length < threshold) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
// Aggregate
|
|
394
|
+
let signature;
|
|
395
|
+
try {
|
|
396
|
+
signature = ed25519_FROST.aggregate(pub, commitmentList, msg, sigShares);
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
return { ok: true, signature };
|
|
402
|
+
}
|
|
403
|
+
// Check if we timed out or exhausted retries
|
|
404
|
+
if (Date.now() >= ceremonyDeadline) {
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
error: { reason: "CEREMONY_TIMEOUT" },
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
ok: false,
|
|
412
|
+
error: { reason: "CEREMONY_EXHAUSTED" },
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// ─── Standalone FROST signature verification ─────────────────────────────────
|
|
417
|
+
/**
|
|
418
|
+
* Verify a FROST threshold signature without a signer instance.
|
|
419
|
+
*
|
|
420
|
+
* Used by the client when it is the counterparty (participant B) and has no
|
|
421
|
+
* IThresholdSigner injected. Verifies the domain-separated FROST signature
|
|
422
|
+
* against the provided public key.
|
|
423
|
+
*
|
|
424
|
+
* @param signature - 64-byte threshold signature from the SessionAssignment
|
|
425
|
+
* @param tbs - to-be-signed bytes (from buildSessionEstablishmentTbs)
|
|
426
|
+
* @param context - domain context string (e.g. CONTEXT_SESSION_ESTABLISHMENT)
|
|
427
|
+
* @param publicKey - 32-byte Ed25519 group public key to verify against
|
|
428
|
+
* @returns true if signature verifies; false otherwise (never throws)
|
|
429
|
+
*/
|
|
430
|
+
export function verifyFrostSignature(signature, tbs, context, publicKey) {
|
|
431
|
+
try {
|
|
432
|
+
const msg = frameMessage(context, tbs);
|
|
433
|
+
return ed25519_FROST.verify(signature, msg, publicKey);
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// ─── MockThresholdSigner ──────────────────────────────────────────────────────
|
|
440
|
+
/**
|
|
441
|
+
* MockThresholdSigner: confirms the IThresholdSigner swap point is real.
|
|
442
|
+
*
|
|
443
|
+
* Implements IThresholdSigner without any FROST crypto. Used by AC-010 to
|
|
444
|
+
* verify that callers work correctly against the interface without knowing
|
|
445
|
+
* the concrete implementation.
|
|
446
|
+
*
|
|
447
|
+
* Returns a deterministic 64-byte signature (not cryptographically valid,
|
|
448
|
+
* but structurally correct for interface tests).
|
|
449
|
+
*/
|
|
450
|
+
export class MockThresholdSigner {
|
|
451
|
+
async participateInCeremony(_ceremonyId, tbs, _context, _onProgress) {
|
|
452
|
+
// Return a 64-byte mock signature derived from the TBS
|
|
453
|
+
// Not cryptographically valid — only for interface shape testing
|
|
454
|
+
const sig = new Uint8Array(64);
|
|
455
|
+
for (let i = 0; i < Math.min(tbs.length, 32); i++) {
|
|
456
|
+
sig[i] = tbs[i];
|
|
457
|
+
}
|
|
458
|
+
sig[63] = 0x42; // marker
|
|
459
|
+
return { ok: true, signature: sig };
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Return a deterministic 32-byte mock primary pubkey.
|
|
463
|
+
* Not a real Ed25519 point — only for interface shape testing.
|
|
464
|
+
*/
|
|
465
|
+
getPrimaryPubkey() {
|
|
466
|
+
const key = new Uint8Array(32);
|
|
467
|
+
key[0] = 0x02; // marker byte indicating mock
|
|
468
|
+
key[31] = 0x4d; // 'M' for Mock
|
|
469
|
+
return key;
|
|
470
|
+
}
|
|
471
|
+
verifySignature(_signature, _tbs, _context, _publicKey) {
|
|
472
|
+
// Mock: always returns false (not a real verifier)
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// Re-export context constants for convenience
|
|
477
|
+
export { CONTEXT_SESSION_ESTABLISHMENT, CONTEXT_SEAL };
|
|
478
|
+
//# sourceMappingURL=frost-threshold-signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frost-threshold-signer.js","sourceRoot":"","sources":["../../src/frost/frost-threshold-signer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FROST threshold signing public API — CELLO-CRYPTO-003
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* - IThresholdSigner: the swap point interface
|
|
6
|
+
* - ThresholdSignature: result type
|
|
7
|
+
* - FrostThresholdSigner: FROST/Ed25519 implementation
|
|
8
|
+
* - MockThresholdSigner: test double confirming the swap point
|
|
9
|
+
* - CONTEXT_SESSION_ESTABLISHMENT / CONTEXT_SEAL: domain context constants
|
|
10
|
+
*
|
|
11
|
+
* Note: bootstrapKeyShares and clearTestShares are test-only — NOT exported
|
|
12
|
+
* from this barrel. Test files import them directly from
|
|
13
|
+
* ./frost-threshold-signer.js to prevent accidental use in production.
|
|
14
|
+
*
|
|
15
|
+
* Note: createInProcessStubs and stub types (DirectoryNodeStub, StubSignParams,
|
|
16
|
+
* StubCommitment) are NOT exported from this barrel. Test files import them
|
|
17
|
+
* directly from ./stubs.js and ./types.js to keep test infrastructure out of
|
|
18
|
+
* the production package surface.
|
|
19
|
+
*/
|
|
20
|
+
export type { IThresholdSigner, ThresholdSignature, ThresholdSignatureOk, ThresholdSignatureError, FrostThresholdSignerConfig, FrostContext, BootstrapResult, } from "./types.js";
|
|
21
|
+
export { CONTEXT_SESSION_ESTABLISHMENT, CONTEXT_SEAL, } from "./types.js";
|
|
22
|
+
export { FrostThresholdSigner, MockThresholdSigner, } from "./frost-threshold-signer.js";
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/frost/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,0BAA0B,EAC1B,YAAY,EACZ,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,6BAA6B,EAC7B,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FROST threshold signing public API — CELLO-CRYPTO-003
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* - IThresholdSigner: the swap point interface
|
|
6
|
+
* - ThresholdSignature: result type
|
|
7
|
+
* - FrostThresholdSigner: FROST/Ed25519 implementation
|
|
8
|
+
* - MockThresholdSigner: test double confirming the swap point
|
|
9
|
+
* - CONTEXT_SESSION_ESTABLISHMENT / CONTEXT_SEAL: domain context constants
|
|
10
|
+
*
|
|
11
|
+
* Note: bootstrapKeyShares and clearTestShares are test-only — NOT exported
|
|
12
|
+
* from this barrel. Test files import them directly from
|
|
13
|
+
* ./frost-threshold-signer.js to prevent accidental use in production.
|
|
14
|
+
*
|
|
15
|
+
* Note: createInProcessStubs and stub types (DirectoryNodeStub, StubSignParams,
|
|
16
|
+
* StubCommitment) are NOT exported from this barrel. Test files import them
|
|
17
|
+
* directly from ./stubs.js and ./types.js to keep test infrastructure out of
|
|
18
|
+
* the production package surface.
|
|
19
|
+
*/
|
|
20
|
+
export { CONTEXT_SESSION_ESTABLISHMENT, CONTEXT_SEAL, } from "./types.js";
|
|
21
|
+
export { FrostThresholdSigner, MockThresholdSigner, } from "./frost-threshold-signer.js";
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/frost/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAYH,OAAO,EACL,6BAA6B,EAC7B,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process directory node stubs for FROST tests.
|
|
3
|
+
*
|
|
4
|
+
* CELLO-CRYPTO-003
|
|
5
|
+
*
|
|
6
|
+
* These stubs mimic the behavior of a real directory node's /cello/frost/1.0.0
|
|
7
|
+
* protocol handler. They exercise the same ceremony protocol logic as real nodes
|
|
8
|
+
* but run in-process for fast, deterministic tests.
|
|
9
|
+
*
|
|
10
|
+
* Stub configuration options:
|
|
11
|
+
* - Normal: responds with valid partial signatures
|
|
12
|
+
* - Unreachable: node is not reachable at initiation (caught by pre-ceremony check)
|
|
13
|
+
* - Unresponsive: reachable at initiation, but signRound times out (returns null)
|
|
14
|
+
* - InvalidResponse: returns random bytes (simulates malformed partial sig)
|
|
15
|
+
*/
|
|
16
|
+
import type { FrostPublic, FrostSecret } from "@noble/curves/abstract/frost.js";
|
|
17
|
+
import type { DirectoryNodeStub, StubSignParams, StubCommitment } from "./types.js";
|
|
18
|
+
export declare class InProcessDirectoryNodeStub implements DirectoryNodeStub {
|
|
19
|
+
#private;
|
|
20
|
+
readonly id: string;
|
|
21
|
+
constructor(id: string);
|
|
22
|
+
/**
|
|
23
|
+
* Receive a FROST signing share from the bootstrap ceremony.
|
|
24
|
+
* Called by bootstrapKeyShares during setup.
|
|
25
|
+
*/
|
|
26
|
+
receiveShare(secret: FrostSecret, pub: FrostPublic): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* TEST-ONLY: Return the stored FROST key pair for use by external test harnesses
|
|
29
|
+
* (e.g., to inject a share into a FrostDirectoryHandler via injectShareForTest).
|
|
30
|
+
*
|
|
31
|
+
* This deliberately exposes the raw FrostSecret for test setup purposes.
|
|
32
|
+
* MUST NOT be called outside of test code.
|
|
33
|
+
*/
|
|
34
|
+
getShareForTest(): {
|
|
35
|
+
secret: FrostSecret;
|
|
36
|
+
pub: FrostPublic;
|
|
37
|
+
} | null;
|
|
38
|
+
/**
|
|
39
|
+
* Mark this node as unreachable at ceremony initiation.
|
|
40
|
+
* isReachable() will return false, causing the coordinator to exclude
|
|
41
|
+
* this node before any rounds begin.
|
|
42
|
+
*/
|
|
43
|
+
setUnreachable(flag: boolean): void;
|
|
44
|
+
/**
|
|
45
|
+
* Mark this node as unresponsive during signing rounds.
|
|
46
|
+
* isReachable() returns true (node is connectable) but signRound returns null
|
|
47
|
+
* (simulates a timeout after connection is established).
|
|
48
|
+
*/
|
|
49
|
+
setUnresponsive(flag: boolean): void;
|
|
50
|
+
/** Make this stub return random bytes (simulate malformed partial sig) */
|
|
51
|
+
setInvalidResponse(flag: boolean): void;
|
|
52
|
+
/**
|
|
53
|
+
* Returns whether this node is reachable at ceremony initiation.
|
|
54
|
+
* The coordinator uses this for the pre-ceremony availability check:
|
|
55
|
+
* if fewer than `threshold` nodes are reachable, fail immediately
|
|
56
|
+
* with DIRECTORY_BELOW_THRESHOLD.
|
|
57
|
+
*/
|
|
58
|
+
isReachable(): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Generate a nonce commitment for the next signing round.
|
|
61
|
+
* Called by the coordinator before collecting partial signatures.
|
|
62
|
+
* Caches the nonce scalars for use in signRound().
|
|
63
|
+
*/
|
|
64
|
+
generateCommitment(): Promise<StubCommitment>;
|
|
65
|
+
/**
|
|
66
|
+
* Participate in a signing round.
|
|
67
|
+
*
|
|
68
|
+
* Uses the nonce cached from the preceding generateCommitment() call.
|
|
69
|
+
* Returns a never-resolving Promise to simulate timeout (the coordinator's
|
|
70
|
+
* per-node timer will fire first). Returns random bytes to simulate
|
|
71
|
+
* an invalid partial sig.
|
|
72
|
+
*
|
|
73
|
+
* @returns Partial signature bytes, or a hanging Promise (simulate timeout)
|
|
74
|
+
*/
|
|
75
|
+
signRound(params: StubSignParams): Promise<Uint8Array | null>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create n in-process directory node stubs.
|
|
79
|
+
* Each stub has a unique deterministic ID.
|
|
80
|
+
*/
|
|
81
|
+
export declare function createInProcessStubs(n: number): InProcessDirectoryNodeStub[];
|
|
82
|
+
//# sourceMappingURL=stubs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stubs.d.ts","sourceRoot":"","sources":["../../src/frost/stubs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EAEZ,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,cAAc,EACf,MAAM,YAAY,CAAC;AAIpB,qBAAa,0BAA2B,YAAW,iBAAiB;;IAClE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAaR,EAAE,EAAE,MAAM;IAMtB;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxE;;;;;;OAMG;IACH,eAAe,IAAI;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,GAAG,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI;IAMnE;;;;OAIG;IACH,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAInC;;;;OAIG;IACH,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAIpC,0EAA0E;IAC1E,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAMvC;;;;;OAKG;IACH,WAAW,IAAI,OAAO;IAQtB;;;;OAIG;IACG,kBAAkB,IAAI,OAAO,CAAC,cAAc,CAAC;IAcnD;;;;;;;;;OASG;IACG,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;CAyCpE;AAID;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,0BAA0B,EAAE,CAM5E"}
|