@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,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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"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * FROST Threshold Signing Abstraction — Type Definitions
3
+ *
4
+ * CELLO-CRYPTO-003
5
+ *
6
+ * Design decisions:
7
+ * - `IThresholdSigner` is separate from `KeyProvider`. `KeyProvider` handles K_local
8
+ * envelope signing; `IThresholdSigner` handles the multi-party threshold ceremony.
9
+ * - Two domain context strings are defined (M2 only):
10
+ * - "cello-frost-session-establishment-v1" — session establishment TBS
11
+ * - "cello-frost-seal-v1" — conversation seal TBS
12
+ * - Context is prepended to the TBS before signing to achieve domain separation.
13
+ * FROST over ed25519 does not support a native context parameter, so we use
14
+ * a framed encoding: `<context>\0<tbs>`.
15
+ * - `primary_pubkey` is the group public key (commitments[0] from FROST public package).
16
+ * It is derived deterministically from all n nodes' share commitments. Per-agent.
17
+ */
18
+ export declare const CONTEXT_SESSION_ESTABLISHMENT: "cello-frost-session-establishment-v1";
19
+ export declare const CONTEXT_SEAL: "cello-frost-seal-v1";
20
+ export type FrostContext = typeof CONTEXT_SESSION_ESTABLISHMENT | typeof CONTEXT_SEAL;
21
+ export type ThresholdSignatureOk = {
22
+ readonly ok: true;
23
+ readonly signature: Uint8Array;
24
+ };
25
+ export type ThresholdSignatureError = {
26
+ readonly ok: false;
27
+ readonly error: {
28
+ readonly reason: "DIRECTORY_BELOW_THRESHOLD" | "CEREMONY_TIMEOUT" | "CEREMONY_EXHAUSTED";
29
+ };
30
+ };
31
+ export type ThresholdSignature = ThresholdSignatureOk | ThresholdSignatureError;
32
+ export type CeremonyProgressEvent = {
33
+ type: "commit_collected";
34
+ index: number;
35
+ total: number;
36
+ } | {
37
+ type: "partial_sig_collected";
38
+ index: number;
39
+ total: number;
40
+ };
41
+ export type CeremonyProgressCallback = (event: CeremonyProgressEvent) => void;
42
+ /**
43
+ * IThresholdSigner: the swap point for all threshold signing in the protocol.
44
+ *
45
+ * Implementations MUST:
46
+ * - Keep the local FROST key share within the crypto package boundary
47
+ * - Never log or return key share material in any error or diagnostic path
48
+ * - Use the context string for domain separation (framed: `<context>\0<tbs>`)
49
+ */
50
+ export interface IThresholdSigner {
51
+ /**
52
+ * Participate in a threshold signing ceremony as coordinator.
53
+ *
54
+ * Opens streams to `threshold` directory nodes, runs the FROST protocol
55
+ * with the given `context` string for domain separation, collects partial
56
+ * signature shares, combines them, and returns the aggregated signature.
57
+ *
58
+ * @param ceremonyId - Unique identifier for this ceremony instance
59
+ * @param tbs - The to-be-signed bytes (domain-specific payload)
60
+ * @param context - Domain context string for separation (e.g. CONTEXT_SESSION_ESTABLISHMENT)
61
+ */
62
+ participateInCeremony(ceremonyId: string, tbs: Uint8Array, context: FrostContext, onProgress?: CeremonyProgressCallback): Promise<ThresholdSignature>;
63
+ /**
64
+ * Return the group public key (primary_pubkey) for this threshold signer.
65
+ *
66
+ * This is commitments[0] from the FROST public package — the constant term
67
+ * of the dealer's Shamir polynomial commitment. It is the same 32-byte
68
+ * Ed25519 point used to verify threshold signatures produced by participateInCeremony.
69
+ *
70
+ * Called by the directory to embed primary_pubkey in the SessionAssignment
71
+ * (signer_pubkey field) so the counterparty can verify without a round-trip.
72
+ *
73
+ * Throws if not bootstrapped (no local share available).
74
+ */
75
+ getPrimaryPubkey(): Uint8Array;
76
+ /**
77
+ * Verify a combined FROST threshold signature.
78
+ *
79
+ * Used by the seal verifier (both initiator and counterparty) to confirm
80
+ * that a session_sealed FROST signature is valid before transitioning to sealed.
81
+ *
82
+ * @param signature - 64-byte combined FROST signature
83
+ * @param tbs - The to-be-signed bytes (same as passed to participateInCeremony)
84
+ * @param context - Domain context string used during signing
85
+ * @param publicKey - 32-byte group public key (primary_pubkey) to verify against
86
+ * @returns true if the signature is valid
87
+ */
88
+ verifySignature(signature: Uint8Array, tbs: Uint8Array, context: FrostContext, publicKey: Uint8Array): boolean;
89
+ }
90
+ /**
91
+ * Configuration for FrostThresholdSigner.
92
+ *
93
+ * `directoryNodes` is used in production; `directoryNodeStubs` is an injection
94
+ * point for in-process test stubs that exercise the same ceremony logic.
95
+ */
96
+ export interface FrostThresholdSignerConfig {
97
+ /** t in t-of-n: minimum signers required */
98
+ readonly threshold: number;
99
+ /** n: total participants */
100
+ readonly participants: number;
101
+ /** Production: multiaddr strings pointing to directory nodes */
102
+ readonly directoryNodes?: string[];
103
+ /** Test injection: in-process stubs that mimic directory node behavior */
104
+ readonly directoryNodeStubs?: DirectoryNodeStub[];
105
+ /** Timeout for a single round (one node response), default 3000ms */
106
+ readonly roundTimeoutMs?: number;
107
+ /** Timeout for the entire ceremony across all retries, default 30000ms */
108
+ readonly ceremonyTimeoutMs?: number;
109
+ /** Maximum ceremony attempts before CEREMONY_EXHAUSTED, default 3 */
110
+ readonly maxRetries?: number;
111
+ }
112
+ /**
113
+ * DirectoryNodeStub: in-process stand-in for a real directory node's
114
+ * /cello/frost/1.0.0 protocol handler.
115
+ *
116
+ * The stub holds its own FROST secret share and responds to ceremony rounds.
117
+ * Stubs can be configured to timeout or return invalid partial signatures.
118
+ */
119
+ export interface DirectoryNodeStub {
120
+ /** Stable identifier for this stub node */
121
+ readonly id: string;
122
+ /**
123
+ * Returns whether this node is reachable at ceremony initiation time.
124
+ * An unreachable node is excluded before any rounds begin.
125
+ * Used by the coordinator's pre-ceremony availability check.
126
+ */
127
+ isReachable(): boolean;
128
+ /**
129
+ * Participate in a signing round.
130
+ *
131
+ * Returns a partial signature share for the given commitment list and message,
132
+ * or null to simulate a timeout.
133
+ *
134
+ * @param pub - The shared FROST public package
135
+ * @param commitmentList - Nonce commitments from all signers in this round
136
+ * @param msg - The framed message (context + tbs)
137
+ * @returns Partial signature bytes, or null to simulate timeout
138
+ */
139
+ signRound(params: StubSignParams): Promise<Uint8Array | null>;
140
+ /**
141
+ * Generate nonce commitment for a round.
142
+ * Called by the coordinator before collecting partial signatures.
143
+ * Returns a Promise to support both in-process stubs and network nodes.
144
+ */
145
+ generateCommitment(): Promise<StubCommitment>;
146
+ /**
147
+ * Receive a FROST signing share from the bootstrap ceremony.
148
+ * Called by bootstrapKeyShares during setup.
149
+ * Returns a Promise to support both in-process stubs and network nodes.
150
+ */
151
+ receiveShare(secret: import("@noble/curves/abstract/frost.js").FrostSecret, pub: import("@noble/curves/abstract/frost.js").FrostPublic): Promise<void>;
152
+ }
153
+ export interface StubSignParams {
154
+ pub: import("@noble/curves/abstract/frost.js").FrostPublic;
155
+ commitmentList: import("@noble/curves/abstract/frost.js").NonceCommitments[];
156
+ msg: Uint8Array;
157
+ /** Unique ID for this ceremony instance — passed through so network nodes can use it for conflict detection. */
158
+ ceremonyId: string;
159
+ }
160
+ export interface StubCommitment {
161
+ nodeId: string;
162
+ nonceCommitment: import("@noble/curves/abstract/frost.js").NonceCommitments;
163
+ nonces: import("@noble/curves/abstract/frost.js").Nonces;
164
+ }
165
+ export interface BootstrapResult {
166
+ /**
167
+ * The group public key derived deterministically from all n nodes' share commitments.
168
+ * 32-byte Ed25519 point. Stored alongside K_local.
169
+ * This is the only key material returned from bootstrapKeyShares.
170
+ */
171
+ readonly primaryPubkey: Uint8Array;
172
+ }
173
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/frost/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * FROST Threshold Signing Abstraction — Type Definitions
3
+ *
4
+ * CELLO-CRYPTO-003
5
+ *
6
+ * Design decisions:
7
+ * - `IThresholdSigner` is separate from `KeyProvider`. `KeyProvider` handles K_local
8
+ * envelope signing; `IThresholdSigner` handles the multi-party threshold ceremony.
9
+ * - Two domain context strings are defined (M2 only):
10
+ * - "cello-frost-session-establishment-v1" — session establishment TBS
11
+ * - "cello-frost-seal-v1" — conversation seal TBS
12
+ * - Context is prepended to the TBS before signing to achieve domain separation.
13
+ * FROST over ed25519 does not support a native context parameter, so we use
14
+ * a framed encoding: `<context>\0<tbs>`.
15
+ * - `primary_pubkey` is the group public key (commitments[0] from FROST public package).
16
+ * It is derived deterministically from all n nodes' share commitments. Per-agent.
17
+ */
18
+ // ─── Domain context strings ──────────────────────────────────────────────────
19
+ export const CONTEXT_SESSION_ESTABLISHMENT = "cello-frost-session-establishment-v1";
20
+ export const CONTEXT_SEAL = "cello-frost-seal-v1";
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/frost/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,gFAAgF;AAEhF,MAAM,CAAC,MAAM,6BAA6B,GACxC,sCAA+C,CAAC;AAClD,MAAM,CAAC,MAAM,YAAY,GAAG,qBAA8B,CAAC"}
@@ -0,0 +1,17 @@
1
+ export declare function hash(data: Uint8Array): Uint8Array;
2
+ export declare function msgLeafHash(data: Uint8Array): Uint8Array;
3
+ export declare function nodeHash(left: Uint8Array, right: Uint8Array): Uint8Array;
4
+ export declare function ctrlLeafHash(data: Uint8Array): Uint8Array;
5
+ /**
6
+ * Build the to-be-signed bytes for a relay hash-submit ACK.
7
+ *
8
+ * TBS = SHA-256(hash_bytes || seq_BE4 || ts_BE8)
9
+ * hash_bytes: 32 raw bytes (the Structure 1 content_hash — NOT hex-encoded)
10
+ * seq_BE4: sequence_number as 4-byte big-endian uint32
11
+ * ts_BE8: timestamp as 8-byte big-endian uint64
12
+ *
13
+ * Both the relay (signer) and the client (verifier) must use this function so
14
+ * they cannot diverge. RFC 8032 (Ed25519), FIPS 180-4 (SHA-256).
15
+ */
16
+ export declare function buildRelayAckTbs(hashBytes: Uint8Array, sequenceNumber: number, timestamp: number): Uint8Array;
17
+ //# sourceMappingURL=hashing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashing.d.ts","sourceRoot":"","sources":["../src/hashing.ts"],"names":[],"mappings":"AAcA,wBAAgB,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAEjD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAExD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CASxE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,UAAU,EACrB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,GAChB,UAAU,CASZ"}
@@ -0,0 +1,50 @@
1
+ import { createHash } from "node:crypto";
2
+ import { sha256 } from "@noble/hashes/sha2.js";
3
+ const MSG_LEAF = 0x00;
4
+ const INTERNAL_NODE = 0x01;
5
+ const CTRL_LEAF = 0x02;
6
+ function prefixed(prefix, data) {
7
+ const buf = new Uint8Array(1 + data.length);
8
+ buf[0] = prefix;
9
+ buf.set(data, 1);
10
+ return buf;
11
+ }
12
+ export function hash(data) {
13
+ return sha256(data);
14
+ }
15
+ export function msgLeafHash(data) {
16
+ return sha256(prefixed(MSG_LEAF, data));
17
+ }
18
+ export function nodeHash(left, right) {
19
+ if (left.length !== 32 || right.length !== 32) {
20
+ throw new Error(`nodeHash: expected 32-byte inputs, got left=${left.length} right=${right.length}`);
21
+ }
22
+ const buf = new Uint8Array(1 + left.length + right.length);
23
+ buf[0] = INTERNAL_NODE;
24
+ buf.set(left, 1);
25
+ buf.set(right, 1 + left.length);
26
+ return sha256(buf);
27
+ }
28
+ export function ctrlLeafHash(data) {
29
+ return sha256(prefixed(CTRL_LEAF, data));
30
+ }
31
+ /**
32
+ * Build the to-be-signed bytes for a relay hash-submit ACK.
33
+ *
34
+ * TBS = SHA-256(hash_bytes || seq_BE4 || ts_BE8)
35
+ * hash_bytes: 32 raw bytes (the Structure 1 content_hash — NOT hex-encoded)
36
+ * seq_BE4: sequence_number as 4-byte big-endian uint32
37
+ * ts_BE8: timestamp as 8-byte big-endian uint64
38
+ *
39
+ * Both the relay (signer) and the client (verifier) must use this function so
40
+ * they cannot diverge. RFC 8032 (Ed25519), FIPS 180-4 (SHA-256).
41
+ */
42
+ export function buildRelayAckTbs(hashBytes, sequenceNumber, timestamp) {
43
+ const seqBuf = Buffer.allocUnsafe(4);
44
+ seqBuf.writeUInt32BE(sequenceNumber >>> 0, 0);
45
+ const tsBuf = Buffer.allocUnsafe(8);
46
+ tsBuf.writeBigUInt64BE(BigInt(timestamp), 0);
47
+ const preimage = Buffer.concat([Buffer.from(hashBytes), seqBuf, tsBuf]);
48
+ return new Uint8Array(createHash("sha256").update(preimage).digest());
49
+ }
50
+ //# sourceMappingURL=hashing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashing.js","sourceRoot":"","sources":["../src/hashing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAE/C,MAAM,QAAQ,GAAG,IAAI,CAAC;AACtB,MAAM,aAAa,GAAG,IAAI,CAAC;AAC3B,MAAM,SAAS,GAAG,IAAI,CAAC;AAEvB,SAAS,QAAQ,CAAC,MAAc,EAAE,IAAgB;IAChD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;IAChB,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACjB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,IAAgB;IACnC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAgB;IAC1C,OAAO,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAgB,EAAE,KAAiB;IAC1D,IAAI,IAAI,CAAC,MAAM,KAAK,EAAE,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,+CAA+C,IAAI,CAAC,MAAM,UAAU,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACtG,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3D,GAAG,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC;IACvB,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACjB,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAgB;IAC3C,OAAO,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAC9B,SAAqB,EACrB,cAAsB,EACtB,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACrC,MAAM,CAAC,aAAa,CAAC,cAAc,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAE9C,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACpC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;IAE7C,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IACxE,OAAO,IAAI,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AACxE,CAAC"}
@@ -0,0 +1,14 @@
1
+ export type { KeyProvider, PublicKey, Signature, KeyFileCorruptError } from "./types.js";
2
+ export { InMemoryKeyProvider, FileKeyProvider, generateKeypair, verify } from "./ed25519.js";
3
+ export type { MlDsaPublicKey, MlDsaSignature, MlDsaKeyPair, MlDsaKeyProvider } from "./ml-dsa.js";
4
+ export { InMemoryMlDsaKeyProvider, FileMlDsaKeyProvider, mlDsaKeygen, mlDsaSign, mlDsaVerify, mlDsaEnsureLoaded, } from "./ml-dsa.js";
5
+ export { hash, msgLeafHash, nodeHash, ctrlLeafHash, buildRelayAckTbs } from "./hashing.js";
6
+ export type { MerkleTree, LeafInput } from "./merkle.js";
7
+ export { buildMerkleTree, merkleRoot, inclusionProof, verifyInclusion } from "./merkle.js";
8
+ export type { IThresholdSigner, ThresholdSignature, ThresholdSignatureOk, ThresholdSignatureError, FrostThresholdSignerConfig, FrostContext, BootstrapResult, } from "./frost/index.js";
9
+ export { CONTEXT_SESSION_ESTABLISHMENT, CONTEXT_SEAL, FrostThresholdSigner, MockThresholdSigner, } from "./frost/index.js";
10
+ export { verifyFrostSignature } from "./frost/frost-threshold-signer.js";
11
+ export { ed25519_FROST } from "@noble/curves/ed25519.js";
12
+ export { buildCheckpointTbs, computeCheckpointHash } from "./checkpoint.js";
13
+ export { buildRelayRegistrationTbs, verifyRelayRegistrationSignature } from "./relay-registration.js";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC7F,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClG,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,WAAW,EACX,SAAS,EACT,WAAW,EACX,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC3F,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3F,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,0BAA0B,EAC1B,YAAY,EACZ,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,6BAA6B,EAC7B,YAAY,EACZ,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAGzE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGzD,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAG5E,OAAO,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ export { InMemoryKeyProvider, FileKeyProvider, generateKeypair, verify } from "./ed25519.js";
2
+ export { InMemoryMlDsaKeyProvider, FileMlDsaKeyProvider, mlDsaKeygen, mlDsaSign, mlDsaVerify, mlDsaEnsureLoaded, } from "./ml-dsa.js";
3
+ export { hash, msgLeafHash, nodeHash, ctrlLeafHash, buildRelayAckTbs } from "./hashing.js";
4
+ export { buildMerkleTree, merkleRoot, inclusionProof, verifyInclusion } from "./merkle.js";
5
+ export { CONTEXT_SESSION_ESTABLISHMENT, CONTEXT_SEAL, FrostThresholdSigner, MockThresholdSigner, } from "./frost/index.js";
6
+ // SESSION-004: standalone FROST verify (no signer instance needed — used by counterparty client)
7
+ export { verifyFrostSignature } from "./frost/frost-threshold-signer.js";
8
+ // REG-001: re-export ed25519_FROST for DKG coordinator in @cello-protocol/client
9
+ export { ed25519_FROST } from "@noble/curves/ed25519.js";
10
+ // FEDERATION-002: canonical checkpoint TBS serialization and hash computation
11
+ export { buildCheckpointTbs, computeCheckpointHash } from "./checkpoint.js";
12
+ // FEDERATION-003: relay registration TBS and signature verification
13
+ export { buildRelayRegistrationTbs, verifyRelayRegistrationSignature } from "./relay-registration.js";
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE7F,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,WAAW,EACX,SAAS,EACT,WAAW,EACX,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAE3F,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAU3F,OAAO,EACL,6BAA6B,EAC7B,YAAY,EACZ,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAE1B,iGAAiG;AACjG,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,iFAAiF;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,8EAA8E;AAC9E,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE5E,oEAAoE;AACpE,OAAO,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * RFC 6962 Merkle Tree Primitives
3
+ *
4
+ * PSEUDOCODE (Phase P)
5
+ * ====================
6
+ * References:
7
+ * RFC 6962 §2.1 — Merkle Hash Trees: construction
8
+ * RFC 6962 §2.1.1 — Merkle Audit Paths (inclusion proofs)
9
+ * FIPS 180-4 — SHA-256 specification
10
+ *
11
+ * --- build(leaves: Uint8Array[]) ---
12
+ * if leaves is empty:
13
+ * return SHA-256("") // RFC 6962 §2.1: D[0] = {}
14
+ * level := [leafHash(l) for l in leaves] // SHA-256(0x00 || l)
15
+ * while level.length > 1:
16
+ * next := []
17
+ * for i in 0..level.length step 2:
18
+ * if i+1 < level.length:
19
+ * next.push(nodeHash(level[i], level[i+1])) // SHA-256(0x01 || left || right)
20
+ * else:
21
+ * next.push(level[i]) // promote odd node — RFC 6962 §2.1: left-balanced
22
+ * level := next
23
+ * return level[0] // root
24
+ *
25
+ * --- inclusionProof(tree, index) ---
26
+ * // RFC 6962 §2.1.1: PATH(m, D[n]) = [] if n=1
27
+ * // otherwise siblings from leaf level up to root
28
+ * level := tree.levelHashes[0] // leaf hashes
29
+ * proof := []
30
+ * idx := index
31
+ * while level.length > 1:
32
+ * sibling := idx XOR 1 // flips last bit: 0→1 (right sibling), 1→0 (left sibling)
33
+ * if sibling < level.length:
34
+ * proof.push(level[sibling])
35
+ * // else: odd node was promoted — no sibling hash in proof
36
+ * next := ... (pair-and-promote)
37
+ * idx := idx >> 1
38
+ * level := next
39
+ * return proof
40
+ *
41
+ * --- verify(leafHash, index, treeSize, proof, expectedRoot) ---
42
+ * if treeSize == 0 or index >= treeSize: return false
43
+ * if treeSize == 1 and proof.empty: return leafHash == expectedRoot
44
+ * cur := leafHash
45
+ * idx := index; sz := treeSize; pi := 0
46
+ * while sz > 1:
47
+ * if idx is last at this level AND idx is even (promoted, no sibling):
48
+ * // cur carries up unchanged
49
+ * else:
50
+ * if pi >= proof.length: return false
51
+ * if proof[pi].length != 32: return false // AC-008: 31-byte sibling
52
+ * if idx is odd: cur := nodeHash(proof[pi], cur) // sibling is on the left
53
+ * else: cur := nodeHash(cur, proof[pi]) // sibling is on the right
54
+ * pi++
55
+ * idx := idx >> 1
56
+ * sz := ceil(sz / 2)
57
+ * if pi != proof.length: return false // wrong proof length (extra elements)
58
+ * return cur == expectedRoot
59
+ *
60
+ * --- edge cases ---
61
+ * index >= treeSize → false, no throw
62
+ * wrong proof length → false, no throw
63
+ * 31-byte sibling hash → false, no throw
64
+ * empty tree → SHA-256("") per RFC 6962 §2.1
65
+ *
66
+ * --- second-preimage protection (SI-001) ---
67
+ * Leaves are hashed as SHA-256(0x00 || data) — the 0x00 prefix ensures a crafted
68
+ * leaf payload that begins with 0x01 cannot collide with an internal nodeHash,
69
+ * because nodeHash uses 0x01 prefix on the already-hashed children, not the raw data.
70
+ */
71
+ /**
72
+ * Internal representation of a built Merkle tree.
73
+ * Stores all level hashes from leaf level (index 0) to root level (last index).
74
+ * Root is levelHashes[levelHashes.length - 1][0].
75
+ */
76
+ export interface MerkleTree {
77
+ /** The number of leaves in this tree. */
78
+ readonly size: number;
79
+ /**
80
+ * All levels of the tree.
81
+ * levelHashes[0] = leaf hashes (SHA-256(prefix || data) for each leaf)
82
+ * levelHashes[k] = hashes at level k (pairs merged with nodeHash, odd promoted)
83
+ * levelHashes[levelHashes.length - 1] = [root]
84
+ *
85
+ * Empty tree: levelHashes = [] and root is SHA-256("").
86
+ */
87
+ readonly levelHashes: readonly (readonly Uint8Array[])[];
88
+ }
89
+ /**
90
+ * A leaf input with an explicit kind for domain separation.
91
+ * - "msg": hashed as SHA-256(0x00 || data) — message leaves
92
+ * - "ctrl": hashed as SHA-256(0x02 || data) — control leaves (SEAL, etc.)
93
+ * - "hash": data is already a 32-byte leaf hash; used directly (no prefix applied)
94
+ */
95
+ export type LeafInput = {
96
+ kind: "msg";
97
+ data: Uint8Array;
98
+ } | {
99
+ kind: "ctrl";
100
+ data: Uint8Array;
101
+ } | {
102
+ kind: "hash";
103
+ data: Uint8Array;
104
+ };
105
+ /**
106
+ * Build a left-balanced Merkle tree from an ordered list of leaf inputs.
107
+ *
108
+ * Per RFC 6962 §2.1:
109
+ * - "msg" leaves: SHA-256(0x00 || data) using msgLeafHash from hashing.ts
110
+ * - "ctrl" leaves: SHA-256(0x02 || data) using ctrlLeafHash from hashing.ts
111
+ * - "hash" leaves: data used as-is (caller pre-computed the leaf hash)
112
+ * - Internal nodes: SHA-256(0x01 || left || right) using nodeHash from hashing.ts
113
+ * - Odd nodes at each level are promoted unchanged (not duplicated)
114
+ * - Empty tree root = SHA-256("") (SHA-256 of empty byte string)
115
+ *
116
+ * @param leaves - Leaf inputs with explicit kind for correct prefix application.
117
+ * @returns MerkleTree with all level hashes stored for O(log n) proof generation.
118
+ */
119
+ export declare function buildMerkleTree(leaves: LeafInput[]): MerkleTree;
120
+ /**
121
+ * Return the Merkle root of a built tree.
122
+ *
123
+ * Per RFC 6962 §2.1:
124
+ * - Empty tree → SHA-256("") (SHA-256 of empty byte string)
125
+ * - Otherwise → the single hash at the top level of levelHashes
126
+ *
127
+ * @param tree - A MerkleTree returned by buildMerkleTree.
128
+ * @returns 32-byte root hash.
129
+ */
130
+ export declare function merkleRoot(tree: MerkleTree): Uint8Array;
131
+ /**
132
+ * Produce an RFC 6962 §2.1.1 inclusion proof for leaf at index.
133
+ *
134
+ * The proof is an ordered list of sibling hashes from the leaf level to the root.
135
+ * For each level, the sibling of the current node is included. Promoted (odd, last)
136
+ * nodes have no sibling at their level, so no hash is added.
137
+ *
138
+ * @param tree - A MerkleTree returned by buildMerkleTree.
139
+ * @param index - Zero-based index of the target leaf.
140
+ * @returns Array of 32-byte sibling hashes (may be empty for a single-leaf tree).
141
+ * @throws If index is out of range.
142
+ */
143
+ export declare function inclusionProof(tree: MerkleTree, index: number): Uint8Array[];
144
+ /**
145
+ * Verify an RFC 6962 §2.1.1 inclusion proof.
146
+ *
147
+ * Reconstructs the root by traversing the proof from leaf to root and compares
148
+ * byte-for-byte to expectedRoot. Returns false (never throws) for all invalid inputs:
149
+ * - index out of range
150
+ * - wrong proof length
151
+ * - any sibling hash not exactly 32 bytes
152
+ * - reconstructed root doesn't match expectedRoot
153
+ *
154
+ * @param leafHash - Pre-computed leaf hash (SHA-256(0x00 || data)).
155
+ * @param index - Zero-based index of the leaf in the tree.
156
+ * @param treeSize - Total number of leaves in the tree.
157
+ * @param proof - Ordered sibling hashes from inclusionProof.
158
+ * @param expectedRoot - The expected 32-byte Merkle root.
159
+ * @returns true iff the proof is valid for the given leaf and root.
160
+ */
161
+ export declare function verifyInclusion(leafHash: Uint8Array, index: number, treeSize: number, proof: Uint8Array[], expectedRoot: Uint8Array): boolean;
162
+ //# sourceMappingURL=merkle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merkle.d.ts","sourceRoot":"","sources":["../src/merkle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AAIH;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,SAAS,UAAU,EAAE,CAAC,EAAE,CAAC;CAC1D;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEvC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,UAAU,CA4B/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAMvD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,CAsB5E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,UAAU,EAAE,EACnB,YAAY,EAAE,UAAU,GACvB,OAAO,CAkDT"}