@unlink-xyz/multisig 0.1.0

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 (70) hide show
  1. package/dist/browser/index.js +29418 -0
  2. package/dist/browser/index.js.map +1 -0
  3. package/dist/src/frost/account.d.ts +39 -0
  4. package/dist/src/frost/account.d.ts.map +1 -0
  5. package/dist/src/frost/account.js +156 -0
  6. package/dist/src/frost/coordinator.d.ts +60 -0
  7. package/dist/src/frost/coordinator.d.ts.map +1 -0
  8. package/dist/src/frost/coordinator.js +211 -0
  9. package/dist/src/frost/crypto.d.ts +48 -0
  10. package/dist/src/frost/crypto.d.ts.map +1 -0
  11. package/dist/src/frost/crypto.js +79 -0
  12. package/dist/src/frost/curve.d.ts +97 -0
  13. package/dist/src/frost/curve.d.ts.map +1 -0
  14. package/dist/src/frost/curve.js +143 -0
  15. package/dist/src/frost/dkg.d.ts +36 -0
  16. package/dist/src/frost/dkg.d.ts.map +1 -0
  17. package/dist/src/frost/dkg.js +242 -0
  18. package/dist/src/frost/index.d.ts +16 -0
  19. package/dist/src/frost/index.d.ts.map +1 -0
  20. package/dist/src/frost/index.js +15 -0
  21. package/dist/src/frost/listener.d.ts +31 -0
  22. package/dist/src/frost/listener.d.ts.map +1 -0
  23. package/dist/src/frost/listener.js +65 -0
  24. package/dist/src/frost/mock-coordinator.d.ts +11 -0
  25. package/dist/src/frost/mock-coordinator.d.ts.map +1 -0
  26. package/dist/src/frost/mock-coordinator.js +288 -0
  27. package/dist/src/frost/serialization.d.ts +86 -0
  28. package/dist/src/frost/serialization.d.ts.map +1 -0
  29. package/dist/src/frost/serialization.js +193 -0
  30. package/dist/src/frost/signing.d.ts +64 -0
  31. package/dist/src/frost/signing.d.ts.map +1 -0
  32. package/dist/src/frost/signing.js +225 -0
  33. package/dist/src/frost/test-utils.d.ts +2 -0
  34. package/dist/src/frost/test-utils.d.ts.map +1 -0
  35. package/dist/src/frost/test-utils.js +1 -0
  36. package/dist/src/frost/types.d.ts +154 -0
  37. package/dist/src/frost/types.d.ts.map +1 -0
  38. package/dist/src/frost/types.js +4 -0
  39. package/dist/src/index.d.ts +9 -0
  40. package/dist/src/index.d.ts.map +1 -0
  41. package/dist/src/index.js +8 -0
  42. package/dist/src/node.d.ts +2 -0
  43. package/dist/src/node.d.ts.map +1 -0
  44. package/dist/src/node.js +1 -0
  45. package/dist/src/wallet/fs-store.d.ts +16 -0
  46. package/dist/src/wallet/fs-store.d.ts.map +1 -0
  47. package/dist/src/wallet/fs-store.js +62 -0
  48. package/dist/src/wallet/index.d.ts +5 -0
  49. package/dist/src/wallet/index.d.ts.map +1 -0
  50. package/dist/src/wallet/index.js +2 -0
  51. package/dist/src/wallet/indexeddb-store.d.ts +12 -0
  52. package/dist/src/wallet/indexeddb-store.d.ts.map +1 -0
  53. package/dist/src/wallet/indexeddb-store.js +69 -0
  54. package/dist/src/wallet/store.d.ts +14 -0
  55. package/dist/src/wallet/store.d.ts.map +1 -0
  56. package/dist/src/wallet/store.js +1 -0
  57. package/dist/src/wallet/types.d.ts +36 -0
  58. package/dist/src/wallet/types.d.ts.map +1 -0
  59. package/dist/src/wallet/types.js +1 -0
  60. package/dist/src/wallet/wallet.d.ts +3 -0
  61. package/dist/src/wallet/wallet.d.ts.map +1 -0
  62. package/dist/src/wallet/wallet.js +42 -0
  63. package/dist/tsconfig.tsbuildinfo +1 -0
  64. package/dist/tsup.browser.config.d.ts +7 -0
  65. package/dist/tsup.browser.config.d.ts.map +1 -0
  66. package/dist/tsup.browser.config.js +34 -0
  67. package/dist/vitest.config.d.ts +3 -0
  68. package/dist/vitest.config.d.ts.map +1 -0
  69. package/dist/vitest.config.js +20 -0
  70. package/package.json +59 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Generalized m-of-n FROST signing protocol.
3
+ *
4
+ * Reimplements core signing from poc.ts with:
5
+ * - Any m-of-n threshold support
6
+ * - Mandatory per-share verification (for ROAST compatibility)
7
+ * - Nonce reuse protection
8
+ *
9
+ * References:
10
+ * - RFC 9591: https://datatracker.ietf.org/doc/html/rfc9591
11
+ * - FROST Paper: https://eprint.iacr.org/2020/852.pdf
12
+ */
13
+ import { poseidon } from "@unlink-xyz/core";
14
+ import { babyjubjub, mod, modAdd, modInverse, modMul, randomScalar, } from "./curve.js";
15
+ /**
16
+ * Compute Lagrange coefficient for participant i at x=0.
17
+ *
18
+ * lambda_i = product_{j in participants, j != i} (0 - j) / (i - j)
19
+ */
20
+ export function lagrangeCoefficient(participantIndex, allParticipants) {
21
+ const n = babyjubjub.order;
22
+ let numerator = 1n;
23
+ let denominator = 1n;
24
+ for (const j of allParticipants) {
25
+ if (j === participantIndex)
26
+ continue;
27
+ numerator = modMul(numerator, mod(-BigInt(j), n), n);
28
+ denominator = modMul(denominator, mod(BigInt(participantIndex) - BigInt(j), n), n);
29
+ }
30
+ return modMul(numerator, modInverse(denominator, n), n);
31
+ }
32
+ /**
33
+ * Round 1: Generate nonces and commitments.
34
+ * Caller MUST store nonces securely and never reuse them.
35
+ */
36
+ export function generateCommitment(participantIndex) {
37
+ const hidingNonce = randomScalar();
38
+ const bindingNonce = randomScalar();
39
+ const hidingCommitment = babyjubjub.scalarMultGen(hidingNonce);
40
+ const bindingCommitment = babyjubjub.scalarMultGen(bindingNonce);
41
+ return {
42
+ nonces: {
43
+ hiding: hidingNonce,
44
+ binding: bindingNonce,
45
+ },
46
+ commitment: {
47
+ participantIndex,
48
+ hiding: hidingCommitment,
49
+ binding: bindingCommitment,
50
+ },
51
+ };
52
+ }
53
+ /**
54
+ * Compute binding factor for a participant.
55
+ * rho_i = H(i || message || encoded_commitments)
56
+ */
57
+ export function computeBindingFactor(participantIndex, message, commitments) {
58
+ const sorted = [...commitments].sort((a, b) => a.participantIndex - b.participantIndex);
59
+ // Hash each commitment individually then combine.
60
+ // Poseidon has a 16-input arity limit — stage the hashing.
61
+ const commitmentHashes = [];
62
+ for (const c of sorted) {
63
+ commitmentHashes.push(poseidon([
64
+ BigInt(c.participantIndex),
65
+ c.hiding[0],
66
+ c.hiding[1],
67
+ c.binding[0],
68
+ c.binding[1],
69
+ ]));
70
+ }
71
+ const hash = poseidon([
72
+ BigInt(participantIndex),
73
+ message,
74
+ ...commitmentHashes,
75
+ ]);
76
+ return mod(hash, babyjubjub.order);
77
+ }
78
+ /**
79
+ * Compute the group commitment R from all participants' commitments.
80
+ * R = sum(D_i + rho_i * E_i) for all participants
81
+ */
82
+ export function computeGroupCommitment(commitments, bindingFactors) {
83
+ let R = babyjubjub.identity;
84
+ for (const c of commitments) {
85
+ const rho = bindingFactors.get(c.participantIndex);
86
+ const bindingContrib = babyjubjub.scalarMult(c.binding, rho);
87
+ const contribution = babyjubjub.add(c.hiding, bindingContrib);
88
+ R = babyjubjub.add(R, contribution);
89
+ }
90
+ return R;
91
+ }
92
+ /**
93
+ * Round 2: Generate signature share.
94
+ * z_i = d_i + rho_i * e_i + 8 * c * lambda_i * s_i
95
+ *
96
+ * Nonces are zeroed after use to prevent reuse.
97
+ */
98
+ export function generateSignatureShare(keyShare, nonces, message, commitments, groupPublicKey) {
99
+ if (nonces.hiding === 0n || nonces.binding === 0n) {
100
+ throw new Error("Nonce reuse detected: nonces have already been consumed");
101
+ }
102
+ const n = babyjubjub.order;
103
+ const participants = commitments.map((c) => c.participantIndex);
104
+ // Compute binding factors
105
+ const bindingFactors = new Map();
106
+ for (const c of commitments) {
107
+ const rho = computeBindingFactor(c.participantIndex, message, commitments);
108
+ bindingFactors.set(c.participantIndex, rho);
109
+ }
110
+ // Group commitment R
111
+ const R = computeGroupCommitment(commitments, bindingFactors);
112
+ // Challenge c = H(R, Y, M)
113
+ const challenge = poseidon([
114
+ R[0],
115
+ R[1],
116
+ groupPublicKey[0],
117
+ groupPublicKey[1],
118
+ message,
119
+ ]);
120
+ const c = mod(challenge, n);
121
+ const rho = bindingFactors.get(keyShare.participantIndex);
122
+ const lambda = lagrangeCoefficient(keyShare.participantIndex, participants);
123
+ // z_i = d_i + rho_i * e_i + 8 * c * lambda_i * s_i
124
+ const cofactor = 8n;
125
+ let z = nonces.hiding;
126
+ z = modAdd(z, modMul(rho, nonces.binding, n), n);
127
+ z = modAdd(z, modMul(cofactor, modMul(c, modMul(lambda, keyShare.secretShare, n), n), n), n);
128
+ // Zero nonces to prevent reuse
129
+ nonces.hiding = 0n;
130
+ nonces.binding = 0n;
131
+ return {
132
+ participantIndex: keyShare.participantIndex,
133
+ share: z,
134
+ };
135
+ }
136
+ /**
137
+ * Verify a single signature share before aggregation.
138
+ * z_i * G == D_i + rho_i * E_i + 8 * c * lambda_i * V_i
139
+ */
140
+ export function verifySignatureShare(share, commitment, verificationShare, message, allCommitments, groupPublicKey) {
141
+ const n = babyjubjub.order;
142
+ const participants = allCommitments.map((c) => c.participantIndex);
143
+ // Compute binding factors
144
+ const bindingFactors = new Map();
145
+ for (const c of allCommitments) {
146
+ const rho = computeBindingFactor(c.participantIndex, message, allCommitments);
147
+ bindingFactors.set(c.participantIndex, rho);
148
+ }
149
+ // Group commitment R
150
+ const R = computeGroupCommitment(allCommitments, bindingFactors);
151
+ // Challenge
152
+ const challenge = poseidon([
153
+ R[0],
154
+ R[1],
155
+ groupPublicKey[0],
156
+ groupPublicKey[1],
157
+ message,
158
+ ]);
159
+ const c = mod(challenge, n);
160
+ const rho = bindingFactors.get(share.participantIndex);
161
+ const lambda = lagrangeCoefficient(share.participantIndex, participants);
162
+ // LHS: z_i * G
163
+ const lhs = babyjubjub.scalarMultGen(share.share);
164
+ // RHS: D_i + rho_i * E_i + 8 * c * lambda_i * V_i
165
+ const cofactor = 8n;
166
+ const bindingContrib = babyjubjub.scalarMult(commitment.binding, rho);
167
+ const nonceContrib = babyjubjub.add(commitment.hiding, bindingContrib);
168
+ const keyContrib = babyjubjub.scalarMult(verificationShare, modMul(cofactor, modMul(c, lambda, n), n));
169
+ const rhs = babyjubjub.add(nonceContrib, keyContrib);
170
+ return babyjubjub.equals(lhs, rhs);
171
+ }
172
+ /**
173
+ * Aggregate signature shares into final FROST signature.
174
+ * Optionally verifies each share before aggregation.
175
+ */
176
+ export function aggregateSignatures(commitments, shares, message, options) {
177
+ const n = babyjubjub.order;
178
+ // Optional per-share verification
179
+ if (options?.verificationShares) {
180
+ if (!options.groupPublicKey) {
181
+ throw new Error("groupPublicKey required when verificationShares provided");
182
+ }
183
+ for (const share of shares) {
184
+ const commitment = commitments.find((c) => c.participantIndex === share.participantIndex);
185
+ if (!commitment) {
186
+ throw new Error(`No commitment found for participant ${share.participantIndex}`);
187
+ }
188
+ const vk = options.verificationShares.get(share.participantIndex);
189
+ if (!vk) {
190
+ throw new Error(`No verification share for participant ${share.participantIndex}`);
191
+ }
192
+ const valid = verifySignatureShare(share, commitment, vk, message, commitments, options.groupPublicKey);
193
+ if (!valid) {
194
+ throw new Error(`Invalid signature share from participant ${share.participantIndex}`);
195
+ }
196
+ }
197
+ }
198
+ // Compute binding factors
199
+ const bindingFactors = new Map();
200
+ for (const c of commitments) {
201
+ const rho = computeBindingFactor(c.participantIndex, message, commitments);
202
+ bindingFactors.set(c.participantIndex, rho);
203
+ }
204
+ // Group commitment R
205
+ const R = computeGroupCommitment(commitments, bindingFactors);
206
+ // Aggregate: S = sum(z_i)
207
+ let S = 0n;
208
+ for (const share of shares) {
209
+ S = modAdd(S, share.share, n);
210
+ }
211
+ return { R8: R, S };
212
+ }
213
+ /**
214
+ * Verify a FROST signature.
215
+ * S * G == R + 8 * c * Y
216
+ */
217
+ export function verifyFrostSignature(signature, message, groupPublicKey) {
218
+ const { R8: R, S } = signature;
219
+ const c = mod(poseidon([R[0], R[1], groupPublicKey[0], groupPublicKey[1], message]), babyjubjub.order);
220
+ const cofactor = 8n;
221
+ const lhs = babyjubjub.scalarMultGen(S);
222
+ const c8Y = babyjubjub.scalarMult(groupPublicKey, modMul(cofactor, c, babyjubjub.order));
223
+ const rhs = babyjubjub.add(R, c8Y);
224
+ return babyjubjub.equals(lhs, rhs);
225
+ }
@@ -0,0 +1,2 @@
1
+ export { startMockCoordinator } from "./mock-coordinator.js";
2
+ //# sourceMappingURL=test-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../src/frost/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1 @@
1
+ export { startMockCoordinator } from "./mock-coordinator.js";
@@ -0,0 +1,154 @@
1
+ /**
2
+ * FROST data types for threshold signatures.
3
+ */
4
+ import type { ViewingKeyPair } from "@unlink-xyz/core";
5
+ import type { Point } from "./curve.js";
6
+ export type { ViewingKeyPair };
7
+ /**
8
+ * FROST configuration for a multisig account.
9
+ */
10
+ export type FrostConfig = {
11
+ /** Minimum signers required (threshold) */
12
+ threshold: number;
13
+ /** Total number of shares */
14
+ totalShares: number;
15
+ };
16
+ /**
17
+ * A participant's key share.
18
+ */
19
+ export type KeyShare = {
20
+ /** Participant index (1-based, per FROST spec) */
21
+ participantIndex: number;
22
+ /** Secret share scalar */
23
+ secretShare: bigint;
24
+ /** Verification share (public key for this participant) */
25
+ verificationShare: Point;
26
+ };
27
+ /**
28
+ * Group public key derived from all participants.
29
+ */
30
+ export type GroupPublicKey = Point;
31
+ /**
32
+ * Round 1 commitment (hiding and binding nonces).
33
+ */
34
+ export type SigningCommitment = {
35
+ /** Participant index */
36
+ participantIndex: number;
37
+ /** Hiding nonce commitment D_i = d_i * G */
38
+ hiding: Point;
39
+ /** Binding nonce commitment E_i = e_i * G */
40
+ binding: Point;
41
+ };
42
+ /**
43
+ * Round 1 nonces (kept secret by participant).
44
+ */
45
+ export type SigningNonces = {
46
+ /** Hiding nonce d_i */
47
+ hiding: bigint;
48
+ /** Binding nonce e_i */
49
+ binding: bigint;
50
+ };
51
+ /**
52
+ * Round 2 signature share.
53
+ */
54
+ export type SignatureShare = {
55
+ /** Participant index */
56
+ participantIndex: number;
57
+ /** Signature share z_i */
58
+ share: bigint;
59
+ };
60
+ /**
61
+ * Final FROST signature (compatible with EdDSA format).
62
+ */
63
+ export type FrostSignature = {
64
+ /** R point (aggregated nonce commitment) */
65
+ R8: Point;
66
+ /** S scalar (aggregated signature) */
67
+ S: bigint;
68
+ };
69
+ /**
70
+ * Schnorr proof of knowledge for a_i0 (prevents rogue key attacks).
71
+ */
72
+ export type SchnorrProof = {
73
+ /** R = k*G */
74
+ commitment: Point;
75
+ /** z = k + challenge * a_i0 */
76
+ response: bigint;
77
+ };
78
+ /**
79
+ * Round 1 output broadcast by each participant.
80
+ */
81
+ export type DkgRound1Package = {
82
+ participantIndex: number;
83
+ /** Commitments to polynomial coefficients: C_k = a_ik * G */
84
+ coefficientCommitments: Point[];
85
+ /** Proof of knowledge for a_i0 */
86
+ proof: SchnorrProof;
87
+ };
88
+ /**
89
+ * A participant's secret state from Round 1 (kept private).
90
+ */
91
+ export type DkgRound1Secret = {
92
+ participantIndex: number;
93
+ /** Polynomial coefficients a_i0, a_i1, ..., a_i(t-1) */
94
+ coefficients: bigint[];
95
+ };
96
+ /**
97
+ * An encrypted share sent from participant i to participant j.
98
+ */
99
+ export type EncryptedShare = {
100
+ fromIndex: number;
101
+ toIndex: number;
102
+ /** ChaCha20-Poly1305 ciphertext */
103
+ ciphertext: Uint8Array;
104
+ /** 12-byte nonce */
105
+ nonce: Uint8Array;
106
+ };
107
+ /**
108
+ * Encrypted viewing key sent from creator to a peer during round 2.
109
+ */
110
+ export type EncryptedViewingKey = {
111
+ toIndex: number;
112
+ /** ChaCha20-Poly1305 ciphertext (64 bytes plaintext + 16 byte tag) */
113
+ ciphertext: Uint8Array;
114
+ /** 12-byte nonce */
115
+ nonce: Uint8Array;
116
+ };
117
+ /**
118
+ * Round 2 output broadcast by each participant.
119
+ */
120
+ export type DkgRound2Package = {
121
+ participantIndex: number;
122
+ encryptedShares: EncryptedShare[];
123
+ /** Encrypted viewing keys, set by the participant that provides the viewing key. */
124
+ encryptedViewingKeys?: EncryptedViewingKey[];
125
+ };
126
+ /**
127
+ * Complete DKG result stored by each participant.
128
+ */
129
+ export type DkgResult = {
130
+ keyShare: KeyShare;
131
+ groupPublicKey: GroupPublicKey;
132
+ /** All participants' coefficient commitments (for share verification). */
133
+ coefficientCommitments: Map<number, Point[]>;
134
+ /** Viewing key pair, present when distributed during DKG. */
135
+ viewingKeyPair?: ViewingKeyPair;
136
+ };
137
+ /**
138
+ * High-level multisig account with derived keys and address.
139
+ */
140
+ export type MultisigAccount = {
141
+ groupId: string;
142
+ config: FrostConfig;
143
+ participantIndex: number;
144
+ keyShare: KeyShare;
145
+ groupPublicKey: Point;
146
+ /** All participants' coefficient commitments (for per-share verification). */
147
+ coefficientCommitments: Map<number, Point[]>;
148
+ viewingKeyPair: ViewingKeyPair;
149
+ nullifyingKey: bigint;
150
+ masterPublicKey: bigint;
151
+ gatewayUrl: string;
152
+ address: string;
153
+ };
154
+ //# 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,4 @@
1
+ /**
2
+ * FROST data types for threshold signatures.
3
+ */
4
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @unlink-xyz/multisig - FROST threshold signatures for Unlink
3
+ *
4
+ * This SDK enables m-of-n multisig accounts that produce EdDSA signatures
5
+ * compatible with the existing JoinSplit circuit (no circuit changes).
6
+ */
7
+ export * from "./frost/index.js";
8
+ export * from "./wallet/index.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @unlink-xyz/multisig - FROST threshold signatures for Unlink
3
+ *
4
+ * This SDK enables m-of-n multisig accounts that produce EdDSA signatures
5
+ * compatible with the existing JoinSplit circuit (no circuit changes).
6
+ */
7
+ export * from "./frost/index.js";
8
+ export * from "./wallet/index.js";
@@ -0,0 +1,2 @@
1
+ export { FsStore } from "./wallet/fs-store.js";
2
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export { FsStore } from "./wallet/fs-store.js";
@@ -0,0 +1,16 @@
1
+ import type { MultisigAccount } from "../frost/types.js";
2
+ import type { MultisigAccountStore } from "./store.js";
3
+ /**
4
+ * Filesystem-backed MultisigAccountStore for Node/CLI environments.
5
+ * Stores one JSON file per account: `<dir>/<groupId>.json`.
6
+ */
7
+ export declare class FsStore implements MultisigAccountStore {
8
+ private readonly dir;
9
+ constructor(dir: string);
10
+ save(account: MultisigAccount): Promise<void>;
11
+ load(groupId: string): Promise<MultisigAccount | null>;
12
+ delete(groupId: string): Promise<void>;
13
+ list(): Promise<string[]>;
14
+ private path;
15
+ }
16
+ //# sourceMappingURL=fs-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-store.d.ts","sourceRoot":"","sources":["../../../src/wallet/fs-store.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;GAGG;AACH,qBAAa,OAAQ,YAAW,oBAAoB;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,MAAM;IAElC,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAUtD,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAStC,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAY/B,OAAO,CAAC,IAAI;CAOb"}
@@ -0,0 +1,62 @@
1
+ import { mkdir, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { deserializeMultisigAccount, serializeMultisigAccount, } from "../frost/serialization.js";
4
+ /**
5
+ * Filesystem-backed MultisigAccountStore for Node/CLI environments.
6
+ * Stores one JSON file per account: `<dir>/<groupId>.json`.
7
+ */
8
+ export class FsStore {
9
+ dir;
10
+ constructor(dir) {
11
+ this.dir = dir;
12
+ }
13
+ async save(account) {
14
+ await mkdir(this.dir, { recursive: true });
15
+ const data = serializeMultisigAccount(account);
16
+ const dest = this.path(account.groupId);
17
+ const tmp = `${dest}.tmp`;
18
+ await writeFile(tmp, JSON.stringify(data), "utf-8");
19
+ await rename(tmp, dest);
20
+ }
21
+ async load(groupId) {
22
+ try {
23
+ const raw = await readFile(this.path(groupId), "utf-8");
24
+ return deserializeMultisigAccount(JSON.parse(raw));
25
+ }
26
+ catch (err) {
27
+ if (err.code === "ENOENT")
28
+ return null;
29
+ throw err;
30
+ }
31
+ }
32
+ async delete(groupId) {
33
+ try {
34
+ await rm(this.path(groupId));
35
+ }
36
+ catch (err) {
37
+ if (err.code === "ENOENT")
38
+ return;
39
+ throw err;
40
+ }
41
+ }
42
+ async list() {
43
+ try {
44
+ const files = await readdir(this.dir);
45
+ return files
46
+ .filter((f) => f.endsWith(".json"))
47
+ .map((f) => f.slice(0, -5));
48
+ }
49
+ catch (err) {
50
+ if (err.code === "ENOENT")
51
+ return [];
52
+ throw err;
53
+ }
54
+ }
55
+ path(groupId) {
56
+ const resolved = resolve(this.dir, `${groupId}.json`);
57
+ if (!resolved.startsWith(resolve(this.dir) + "/")) {
58
+ throw new Error(`invalid groupId: path traversal detected`);
59
+ }
60
+ return resolved;
61
+ }
62
+ }
@@ -0,0 +1,5 @@
1
+ export { createMultisigWallet } from "./wallet.js";
2
+ export type * from "./types.js";
3
+ export type { MultisigAccountStore } from "./store.js";
4
+ export { IndexedDbStore } from "./indexeddb-store.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/wallet/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,mBAAmB,YAAY,CAAC;AAChC,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { createMultisigWallet } from "./wallet.js";
2
+ export { IndexedDbStore } from "./indexeddb-store.js";
@@ -0,0 +1,12 @@
1
+ import type { MultisigAccount } from "../frost/types.js";
2
+ import type { MultisigAccountStore } from "./store.js";
3
+ /**
4
+ * IndexedDB-backed MultisigAccountStore for browser environments.
5
+ */
6
+ export declare class IndexedDbStore implements MultisigAccountStore {
7
+ save(account: MultisigAccount): Promise<void>;
8
+ load(groupId: string): Promise<MultisigAccount | null>;
9
+ delete(groupId: string): Promise<void>;
10
+ list(): Promise<string[]>;
11
+ }
12
+ //# sourceMappingURL=indexeddb-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb-store.d.ts","sourceRoot":"","sources":["../../../src/wallet/indexeddb-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAkCvD;;GAEG;AACH,qBAAa,cAAe,YAAW,oBAAoB;IACnD,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAW7C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAUtD,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAStC,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CAShC"}
@@ -0,0 +1,69 @@
1
+ import { deserializeMultisigAccount, serializeMultisigAccount, } from "../frost/serialization.js";
2
+ const DB_NAME = "unlink-multisig";
3
+ const DB_VERSION = 1;
4
+ const STORE_NAME = "accounts";
5
+ function openDb() {
6
+ return new Promise((resolve, reject) => {
7
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
8
+ req.onupgradeneeded = () => {
9
+ const db = req.result;
10
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
11
+ db.createObjectStore(STORE_NAME, { keyPath: "groupId" });
12
+ }
13
+ };
14
+ req.onsuccess = () => resolve(req.result);
15
+ req.onerror = () => reject(req.error);
16
+ });
17
+ }
18
+ function tx(db, mode, fn) {
19
+ return new Promise((resolve, reject) => {
20
+ const transaction = db.transaction(STORE_NAME, mode);
21
+ const store = transaction.objectStore(STORE_NAME);
22
+ const req = fn(store);
23
+ req.onsuccess = () => resolve(req.result);
24
+ req.onerror = () => reject(req.error);
25
+ });
26
+ }
27
+ /**
28
+ * IndexedDB-backed MultisigAccountStore for browser environments.
29
+ */
30
+ export class IndexedDbStore {
31
+ async save(account) {
32
+ const db = await openDb();
33
+ try {
34
+ await tx(db, "readwrite", (store) => store.put(serializeMultisigAccount(account)));
35
+ }
36
+ finally {
37
+ db.close();
38
+ }
39
+ }
40
+ async load(groupId) {
41
+ const db = await openDb();
42
+ try {
43
+ const data = await tx(db, "readonly", (store) => store.get(groupId));
44
+ return data ? deserializeMultisigAccount(data) : null;
45
+ }
46
+ finally {
47
+ db.close();
48
+ }
49
+ }
50
+ async delete(groupId) {
51
+ const db = await openDb();
52
+ try {
53
+ await tx(db, "readwrite", (store) => store.delete(groupId));
54
+ }
55
+ finally {
56
+ db.close();
57
+ }
58
+ }
59
+ async list() {
60
+ const db = await openDb();
61
+ try {
62
+ const keys = await tx(db, "readonly", (store) => store.getAllKeys());
63
+ return keys.map(String);
64
+ }
65
+ finally {
66
+ db.close();
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,14 @@
1
+ import type { MultisigAccount } from "../frost/types.js";
2
+ /**
3
+ * Persistence interface for MultisigAccount instances.
4
+ *
5
+ * Implementations handle platform-specific storage (IndexedDB, filesystem, etc.)
6
+ * and use serialization internally.
7
+ */
8
+ export interface MultisigAccountStore {
9
+ save(account: MultisigAccount): Promise<void>;
10
+ load(groupId: string): Promise<MultisigAccount | null>;
11
+ delete(groupId: string): Promise<void>;
12
+ list(): Promise<string[]>;
13
+ }
14
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/wallet/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC3B"}
@@ -0,0 +1 @@
1
+ export {};