@cello-protocol/crypto 0.0.19 → 0.0.20
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/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/package.json +1 -1
|
@@ -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 "../frost/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,mBAAmB,CAAC;AAI3B,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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
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 { ed25519_FROST } from "@noble/curves/ed25519.js";
|
|
17
|
+
import { randomBytes } from "@noble/hashes/utils.js";
|
|
18
|
+
// ─── InProcessDirectoryNodeStub ───────────────────────────────────────────────
|
|
19
|
+
export class InProcessDirectoryNodeStub {
|
|
20
|
+
id;
|
|
21
|
+
// Signing key — received via receiveShare() during bootstrap
|
|
22
|
+
#key = null;
|
|
23
|
+
// Behavior flags
|
|
24
|
+
// #unreachable: not reachable at initiation (isReachable() → false)
|
|
25
|
+
#unreachable = false;
|
|
26
|
+
// #unresponsive: reachable at initiation, but signRound returns null (timeout simulation)
|
|
27
|
+
#unresponsive = false;
|
|
28
|
+
// #invalidResponse: returns random bytes on signRound (malformed partial sig)
|
|
29
|
+
#invalidResponse = false;
|
|
30
|
+
constructor(id) {
|
|
31
|
+
this.id = id;
|
|
32
|
+
}
|
|
33
|
+
// ─── DirectoryNodeStub: receiveShare ─────────────────────────────────────────
|
|
34
|
+
/**
|
|
35
|
+
* Receive a FROST signing share from the bootstrap ceremony.
|
|
36
|
+
* Called by bootstrapKeyShares during setup.
|
|
37
|
+
*/
|
|
38
|
+
async receiveShare(secret, pub) {
|
|
39
|
+
this.#key = { secret, pub };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* TEST-ONLY: Return the stored FROST key pair for use by external test harnesses
|
|
43
|
+
* (e.g., to inject a share into a FrostDirectoryHandler via injectShareForTest).
|
|
44
|
+
*
|
|
45
|
+
* This deliberately exposes the raw FrostSecret for test setup purposes.
|
|
46
|
+
* MUST NOT be called outside of test code.
|
|
47
|
+
*/
|
|
48
|
+
getShareForTest() {
|
|
49
|
+
return this.#key;
|
|
50
|
+
}
|
|
51
|
+
// ─── Test control methods ────────────────────────────────────────────────────
|
|
52
|
+
/**
|
|
53
|
+
* Mark this node as unreachable at ceremony initiation.
|
|
54
|
+
* isReachable() will return false, causing the coordinator to exclude
|
|
55
|
+
* this node before any rounds begin.
|
|
56
|
+
*/
|
|
57
|
+
setUnreachable(flag) {
|
|
58
|
+
this.#unreachable = flag;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Mark this node as unresponsive during signing rounds.
|
|
62
|
+
* isReachable() returns true (node is connectable) but signRound returns null
|
|
63
|
+
* (simulates a timeout after connection is established).
|
|
64
|
+
*/
|
|
65
|
+
setUnresponsive(flag) {
|
|
66
|
+
this.#unresponsive = flag;
|
|
67
|
+
}
|
|
68
|
+
/** Make this stub return random bytes (simulate malformed partial sig) */
|
|
69
|
+
setInvalidResponse(flag) {
|
|
70
|
+
this.#invalidResponse = flag;
|
|
71
|
+
}
|
|
72
|
+
// ─── DirectoryNodeStub interface ─────────────────────────────────────────────
|
|
73
|
+
/**
|
|
74
|
+
* Returns whether this node is reachable at ceremony initiation.
|
|
75
|
+
* The coordinator uses this for the pre-ceremony availability check:
|
|
76
|
+
* if fewer than `threshold` nodes are reachable, fail immediately
|
|
77
|
+
* with DIRECTORY_BELOW_THRESHOLD.
|
|
78
|
+
*/
|
|
79
|
+
isReachable() {
|
|
80
|
+
return !this.#unreachable;
|
|
81
|
+
}
|
|
82
|
+
// Cache the pending nonce between generateCommitment() and signRound()
|
|
83
|
+
// Per RFC 9591: nonces are one-time-use — consumed after signRound
|
|
84
|
+
#pendingNonce = null;
|
|
85
|
+
/**
|
|
86
|
+
* Generate a nonce commitment for the next signing round.
|
|
87
|
+
* Called by the coordinator before collecting partial signatures.
|
|
88
|
+
* Caches the nonce scalars for use in signRound().
|
|
89
|
+
*/
|
|
90
|
+
async generateCommitment() {
|
|
91
|
+
if (!this.#key) {
|
|
92
|
+
throw new Error(`Stub ${this.id} has no key share (not bootstrapped)`);
|
|
93
|
+
}
|
|
94
|
+
const nonce = ed25519_FROST.commit(this.#key.secret);
|
|
95
|
+
// Cache nonce scalars so signRound can use them
|
|
96
|
+
this.#pendingNonce = nonce.nonces;
|
|
97
|
+
return {
|
|
98
|
+
nodeId: this.id,
|
|
99
|
+
nonceCommitment: nonce.commitments,
|
|
100
|
+
nonces: nonce.nonces,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Participate in a signing round.
|
|
105
|
+
*
|
|
106
|
+
* Uses the nonce cached from the preceding generateCommitment() call.
|
|
107
|
+
* Returns a never-resolving Promise to simulate timeout (the coordinator's
|
|
108
|
+
* per-node timer will fire first). Returns random bytes to simulate
|
|
109
|
+
* an invalid partial sig.
|
|
110
|
+
*
|
|
111
|
+
* @returns Partial signature bytes, or a hanging Promise (simulate timeout)
|
|
112
|
+
*/
|
|
113
|
+
async signRound(params) {
|
|
114
|
+
// Unresponsive: simulate a node that never responds.
|
|
115
|
+
// Return a Promise that never resolves — the coordinator's roundTimeoutMs
|
|
116
|
+
// timer will fire first, treating this as a timeout.
|
|
117
|
+
if (this.#unresponsive) {
|
|
118
|
+
return new Promise(() => {
|
|
119
|
+
// intentionally never resolve — coordinator timeout fires instead
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (!this.#key) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
// InvalidResponse: return random bytes (not a valid partial sig)
|
|
126
|
+
if (this.#invalidResponse) {
|
|
127
|
+
return new Uint8Array(randomBytes(32));
|
|
128
|
+
}
|
|
129
|
+
// Use the nonce cached from generateCommitment()
|
|
130
|
+
if (!this.#pendingNonce) {
|
|
131
|
+
// No pending nonce — should not happen in normal ceremony flow
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const nonces = this.#pendingNonce;
|
|
135
|
+
this.#pendingNonce = null; // consume nonce (one-time use per RFC 9591)
|
|
136
|
+
try {
|
|
137
|
+
const sig = ed25519_FROST.signShare(this.#key.secret, params.pub, nonces, params.commitmentList, params.msg);
|
|
138
|
+
return sig;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ─── createInProcessStubs ─────────────────────────────────────────────────────
|
|
146
|
+
/**
|
|
147
|
+
* Create n in-process directory node stubs.
|
|
148
|
+
* Each stub has a unique deterministic ID.
|
|
149
|
+
*/
|
|
150
|
+
export function createInProcessStubs(n) {
|
|
151
|
+
return Array.from({ length: n }, (_, i) => {
|
|
152
|
+
// Use a stable, deterministic ID that works as a FROST identifier input
|
|
153
|
+
const id = `cello-test-node-${i.toString().padStart(4, "0")}`;
|
|
154
|
+
return new InProcessDirectoryNodeStub(id);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=stubs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stubs.js","sourceRoot":"","sources":["../../src/frost/stubs.ts"],"names":[],"mappings":"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"}
|