@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,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"}
|
package/dist/hashing.js
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/merkle.d.ts
ADDED
|
@@ -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"}
|