@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,97 @@
1
+ /**
2
+ * BabyJubJub curve wrapper for FROST operations.
3
+ *
4
+ * FROST requires:
5
+ * - Generator point (Base8)
6
+ * - Scalar multiplication
7
+ * - Point addition
8
+ * - Field arithmetic (modular inverse, etc.)
9
+ *
10
+ * We wrap @zk-kit/baby-jubjub to provide a consistent interface.
11
+ */
12
+ export type Point = [bigint, bigint];
13
+ /**
14
+ * BabyJubJub curve operations for FROST.
15
+ */
16
+ export declare const babyjubjub: {
17
+ /**
18
+ * Generator point (Base8 from baby-jubjub).
19
+ * This is the standard generator for EdDSA on BabyJubJub.
20
+ */
21
+ generator: Point;
22
+ /**
23
+ * Group order - the order of the subgroup generated by Base8.
24
+ * All scalars should be reduced modulo this value.
25
+ */
26
+ order: bigint;
27
+ /**
28
+ * Prime field order (the full curve order).
29
+ */
30
+ primeOrder: bigint;
31
+ /**
32
+ * Field prime (r) - used for coordinate arithmetic.
33
+ */
34
+ fieldPrime: bigint;
35
+ /**
36
+ * Identity point (neutral element for addition).
37
+ * On twisted Edwards curves, this is (0, 1).
38
+ */
39
+ identity: Point;
40
+ /**
41
+ * Add two points on the curve.
42
+ */
43
+ add(p1: Point, p2: Point): Point;
44
+ /**
45
+ * Scalar multiplication: base * scalar.
46
+ */
47
+ scalarMult(base: Point, scalar: bigint): Point;
48
+ /**
49
+ * Multiply generator by scalar: G * scalar.
50
+ */
51
+ scalarMultGen(scalar: bigint): Point;
52
+ /**
53
+ * Check if a point is on the curve.
54
+ */
55
+ isOnCurve(p: Point): boolean;
56
+ /**
57
+ * Check if a point is the identity.
58
+ */
59
+ isIdentity(p: Point): boolean;
60
+ /**
61
+ * Check if two points are equal.
62
+ */
63
+ equals(p1: Point, p2: Point): boolean;
64
+ };
65
+ /**
66
+ * Modular arithmetic helpers for scalars.
67
+ */
68
+ /**
69
+ * Modular reduction: a mod n.
70
+ * Handles negative numbers correctly.
71
+ */
72
+ export declare function mod(a: bigint, n: bigint): bigint;
73
+ /**
74
+ * Extended Euclidean Algorithm for modular inverse.
75
+ * Returns x such that (a * x) mod n = 1.
76
+ */
77
+ export declare function modInverse(a: bigint, n: bigint): bigint;
78
+ /**
79
+ * Modular addition: (a + b) mod n.
80
+ */
81
+ export declare function modAdd(a: bigint, b: bigint, n: bigint): bigint;
82
+ /**
83
+ * Modular multiplication: (a * b) mod n.
84
+ */
85
+ export declare function modMul(a: bigint, b: bigint, n: bigint): bigint;
86
+ /**
87
+ * Modular division: (a / b) mod n = (a * b^-1) mod n.
88
+ */
89
+ export declare function modDiv(a: bigint, b: bigint, n: bigint): bigint;
90
+ /**
91
+ * Generate a random scalar in [1, subOrder-1] using rejection sampling.
92
+ *
93
+ * subOrder is ~251 bits, so P(reject) ≈ 1 - subOrder/2^256 ≈ 0.003.
94
+ * Virtually always succeeds on first try.
95
+ */
96
+ export declare function randomScalar(): bigint;
97
+ //# sourceMappingURL=curve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curve.d.ts","sourceRoot":"","sources":["../../../src/frost/curve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAYH,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAErC;;GAEG;AACH,eAAO,MAAM,UAAU;IACrB;;;OAGG;eACiB,KAAK;IAEzB;;;OAGG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;;OAGG;cACmB,KAAK;IAE3B;;OAEG;YACK,KAAK,MAAM,KAAK,GAAG,KAAK;IAIhC;;OAEG;qBACc,KAAK,UAAU,MAAM,GAAG,KAAK;IAM9C;;OAEG;0BACmB,MAAM,GAAG,KAAK;IAIpC;;OAEG;iBACU,KAAK,GAAG,OAAO;IAI5B;;OAEG;kBACW,KAAK,GAAG,OAAO;IAI7B;;OAEG;eACQ,KAAK,MAAM,KAAK,GAAG,OAAO;CAGtC,CAAC;AAEF;;GAEG;AAEH;;;GAGG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAevD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAYrC"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * BabyJubJub curve wrapper for FROST operations.
3
+ *
4
+ * FROST requires:
5
+ * - Generator point (Base8)
6
+ * - Scalar multiplication
7
+ * - Point addition
8
+ * - Field arithmetic (modular inverse, etc.)
9
+ *
10
+ * We wrap @zk-kit/baby-jubjub to provide a consistent interface.
11
+ */
12
+ import { addPoint, Base8, inCurve, mulPointEscalar, order, r, subOrder, } from "@zk-kit/baby-jubjub";
13
+ /**
14
+ * BabyJubJub curve operations for FROST.
15
+ */
16
+ export const babyjubjub = {
17
+ /**
18
+ * Generator point (Base8 from baby-jubjub).
19
+ * This is the standard generator for EdDSA on BabyJubJub.
20
+ */
21
+ generator: Base8,
22
+ /**
23
+ * Group order - the order of the subgroup generated by Base8.
24
+ * All scalars should be reduced modulo this value.
25
+ */
26
+ order: subOrder,
27
+ /**
28
+ * Prime field order (the full curve order).
29
+ */
30
+ primeOrder: order,
31
+ /**
32
+ * Field prime (r) - used for coordinate arithmetic.
33
+ */
34
+ fieldPrime: r,
35
+ /**
36
+ * Identity point (neutral element for addition).
37
+ * On twisted Edwards curves, this is (0, 1).
38
+ */
39
+ identity: [0n, 1n],
40
+ /**
41
+ * Add two points on the curve.
42
+ */
43
+ add(p1, p2) {
44
+ return addPoint(p1, p2);
45
+ },
46
+ /**
47
+ * Scalar multiplication: base * scalar.
48
+ */
49
+ scalarMult(base, scalar) {
50
+ // Reduce scalar modulo order
51
+ const reducedScalar = mod(scalar, subOrder);
52
+ return mulPointEscalar(base, reducedScalar);
53
+ },
54
+ /**
55
+ * Multiply generator by scalar: G * scalar.
56
+ */
57
+ scalarMultGen(scalar) {
58
+ return this.scalarMult(this.generator, scalar);
59
+ },
60
+ /**
61
+ * Check if a point is on the curve.
62
+ */
63
+ isOnCurve(p) {
64
+ return inCurve(p);
65
+ },
66
+ /**
67
+ * Check if a point is the identity.
68
+ */
69
+ isIdentity(p) {
70
+ return p[0] === 0n && p[1] === 1n;
71
+ },
72
+ /**
73
+ * Check if two points are equal.
74
+ */
75
+ equals(p1, p2) {
76
+ return p1[0] === p2[0] && p1[1] === p2[1];
77
+ },
78
+ };
79
+ /**
80
+ * Modular arithmetic helpers for scalars.
81
+ */
82
+ /**
83
+ * Modular reduction: a mod n.
84
+ * Handles negative numbers correctly.
85
+ */
86
+ export function mod(a, n) {
87
+ const result = a % n;
88
+ return result >= 0n ? result : result + n;
89
+ }
90
+ /**
91
+ * Extended Euclidean Algorithm for modular inverse.
92
+ * Returns x such that (a * x) mod n = 1.
93
+ */
94
+ export function modInverse(a, n) {
95
+ let [oldR, r] = [mod(a, n), n];
96
+ let [oldS, s] = [1n, 0n];
97
+ while (r !== 0n) {
98
+ const quotient = oldR / r;
99
+ [oldR, r] = [r, oldR - quotient * r];
100
+ [oldS, s] = [s, oldS - quotient * s];
101
+ }
102
+ if (oldR !== 1n) {
103
+ throw new Error(`Modular inverse does not exist for ${a} mod ${n}`);
104
+ }
105
+ return mod(oldS, n);
106
+ }
107
+ /**
108
+ * Modular addition: (a + b) mod n.
109
+ */
110
+ export function modAdd(a, b, n) {
111
+ return mod(a + b, n);
112
+ }
113
+ /**
114
+ * Modular multiplication: (a * b) mod n.
115
+ */
116
+ export function modMul(a, b, n) {
117
+ return mod(a * b, n);
118
+ }
119
+ /**
120
+ * Modular division: (a / b) mod n = (a * b^-1) mod n.
121
+ */
122
+ export function modDiv(a, b, n) {
123
+ return modMul(a, modInverse(b, n), n);
124
+ }
125
+ /**
126
+ * Generate a random scalar in [1, subOrder-1] using rejection sampling.
127
+ *
128
+ * subOrder is ~251 bits, so P(reject) ≈ 1 - subOrder/2^256 ≈ 0.003.
129
+ * Virtually always succeeds on first try.
130
+ */
131
+ export function randomScalar() {
132
+ const bytes = new Uint8Array(32);
133
+ for (;;) {
134
+ globalThis.crypto.getRandomValues(bytes);
135
+ const hex = Array.from(bytes)
136
+ .map((b) => b.toString(16).padStart(2, "0"))
137
+ .join("");
138
+ const scalar = BigInt("0x" + hex);
139
+ if (scalar > 0n && scalar < subOrder) {
140
+ return scalar;
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Distributed Key Generation (Feldman's Verifiable Secret Sharing).
3
+ *
4
+ * Participants generate key shares cooperatively — no single party
5
+ * ever holds the full private key.
6
+ *
7
+ * Protocol:
8
+ * 1. Round 1: Generate polynomial, commit to coefficients, prove knowledge
9
+ * 2. Round 2: Evaluate polynomial at peers, encrypt shares via ECDH
10
+ * 3. Finalize: Verify shares against commitments, compute key share + group key
11
+ */
12
+ import type { DkgResult, DkgRound1Package, DkgRound1Secret, DkgRound2Package, FrostConfig, ViewingKeyPair } from "./types.js";
13
+ /**
14
+ * Round 1: Generate polynomial, coefficient commitments, and Schnorr PoK.
15
+ */
16
+ export declare function dkgRound1(config: FrostConfig, participantIndex: number): {
17
+ secret: DkgRound1Secret;
18
+ package: DkgRound1Package;
19
+ };
20
+ /**
21
+ * Round 1 from deterministic seed: derive coefficients via HKDF.
22
+ */
23
+ export declare function dkgRound1FromSeed(config: FrostConfig, participantIndex: number, seed: Uint8Array): {
24
+ secret: DkgRound1Secret;
25
+ package: DkgRound1Package;
26
+ };
27
+ /**
28
+ * Round 2: Verify PoK proofs, evaluate polynomial at peers, encrypt shares.
29
+ */
30
+ export declare function dkgRound2(secret: DkgRound1Secret, allRound1Packages: DkgRound1Package[], viewingKeyPair?: ViewingKeyPair): DkgRound2Package;
31
+ /**
32
+ * Finalize DKG: decrypt shares, verify against commitments,
33
+ * compute secret share and group public key.
34
+ */
35
+ export declare function dkgFinalize(secret: DkgRound1Secret, allRound1Packages: DkgRound1Package[], allRound2Packages: DkgRound2Package[], viewingKeyPair?: ViewingKeyPair): DkgResult;
36
+ //# sourceMappingURL=dkg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dkg.d.ts","sourceRoot":"","sources":["../../../src/frost/dkg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAuBH,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAEhB,WAAW,EAEX,cAAc,EACf,MAAM,YAAY,CAAC;AAiGpB;;GAEG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,WAAW,EACnB,gBAAgB,EAAE,MAAM,GACvB;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAOxD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,UAAU,GACf;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CASxD;AA4BD;;GAEG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,eAAe,EACvB,iBAAiB,EAAE,gBAAgB,EAAE,EACrC,cAAc,CAAC,EAAE,cAAc,GAC9B,gBAAgB,CA8DlB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,eAAe,EACvB,iBAAiB,EAAE,gBAAgB,EAAE,EACrC,iBAAiB,EAAE,gBAAgB,EAAE,EACrC,cAAc,CAAC,EAAE,cAAc,GAC9B,SAAS,CA0GX"}
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Distributed Key Generation (Feldman's Verifiable Secret Sharing).
3
+ *
4
+ * Participants generate key shares cooperatively — no single party
5
+ * ever holds the full private key.
6
+ *
7
+ * Protocol:
8
+ * 1. Round 1: Generate polynomial, commit to coefficients, prove knowledge
9
+ * 2. Round 2: Evaluate polynomial at peers, encrypt shares via ECDH
10
+ * 3. Finalize: Verify shares against commitments, compute key share + group key
11
+ */
12
+ import { hkdf } from "@noble/hashes/hkdf.js";
13
+ import { sha256 } from "@noble/hashes/sha2.js";
14
+ import { poseidon } from "@unlink-xyz/core";
15
+ import { bytesToBigint, decryptBytes, decryptShare, deriveSymmetricKey, ecdhSharedSecret, encryptBytes, encryptShare, } from "./crypto.js";
16
+ import { babyjubjub, mod, modAdd, modMul, randomScalar, } from "./curve.js";
17
+ /**
18
+ * Evaluate polynomial at x: f(x) = sum(coefficients[k] * x^k).
19
+ */
20
+ function evaluatePolynomial(coefficients, x) {
21
+ const n = babyjubjub.order;
22
+ let result = 0n;
23
+ let power = 1n;
24
+ for (const coeff of coefficients) {
25
+ result = modAdd(result, modMul(coeff, power, n), n);
26
+ power = modMul(power, x, n);
27
+ }
28
+ return result;
29
+ }
30
+ /**
31
+ * Generate a Schnorr proof of knowledge for secret behind public key.
32
+ * Proves: "I know x such that X = x*G"
33
+ */
34
+ function generateSchnorrProof(participantIndex, secret, publicKey) {
35
+ const k = randomScalar();
36
+ const R = babyjubjub.scalarMultGen(k);
37
+ const challenge = mod(poseidon([
38
+ BigInt(participantIndex),
39
+ R[0],
40
+ R[1],
41
+ publicKey[0],
42
+ publicKey[1],
43
+ ]), babyjubjub.order);
44
+ const response = modAdd(k, modMul(challenge, secret, babyjubjub.order), babyjubjub.order);
45
+ return { commitment: R, response };
46
+ }
47
+ /**
48
+ * Verify a Schnorr proof of knowledge: z*G == R + c*PK
49
+ */
50
+ function verifySchnorrProof(participantIndex, publicKey, proof) {
51
+ const challenge = mod(poseidon([
52
+ BigInt(participantIndex),
53
+ proof.commitment[0],
54
+ proof.commitment[1],
55
+ publicKey[0],
56
+ publicKey[1],
57
+ ]), babyjubjub.order);
58
+ const lhs = babyjubjub.scalarMultGen(proof.response);
59
+ const cPk = babyjubjub.scalarMult(publicKey, challenge);
60
+ const rhs = babyjubjub.add(proof.commitment, cPk);
61
+ return babyjubjub.equals(lhs, rhs);
62
+ }
63
+ /**
64
+ * Verify a share against coefficient commitments.
65
+ * s*G == sum(C_k * i^k) for k = 0..t-1
66
+ */
67
+ function verifyShareCommitment(share, senderCommitments, recipientIndex) {
68
+ const lhs = babyjubjub.scalarMultGen(share);
69
+ let rhs = babyjubjub.identity;
70
+ let power = 1n;
71
+ const idx = BigInt(recipientIndex);
72
+ for (const commitment of senderCommitments) {
73
+ const term = babyjubjub.scalarMult(commitment, power);
74
+ rhs = babyjubjub.add(rhs, term);
75
+ power = modMul(power, idx, babyjubjub.order);
76
+ }
77
+ return babyjubjub.equals(lhs, rhs);
78
+ }
79
+ /**
80
+ * Round 1: Generate polynomial, coefficient commitments, and Schnorr PoK.
81
+ */
82
+ export function dkgRound1(config, participantIndex) {
83
+ const coefficients = [];
84
+ for (let i = 0; i < config.threshold; i++) {
85
+ coefficients.push(randomScalar());
86
+ }
87
+ return dkgRound1WithCoefficients(config, participantIndex, coefficients);
88
+ }
89
+ /**
90
+ * Round 1 from deterministic seed: derive coefficients via HKDF.
91
+ */
92
+ export function dkgRound1FromSeed(config, participantIndex, seed) {
93
+ const coefficients = [];
94
+ for (let i = 0; i < config.threshold; i++) {
95
+ const info = new TextEncoder().encode(`frost-dkg-coeff-${i}`);
96
+ const derived = hkdf(sha256, seed, undefined, info, 32);
97
+ coefficients.push(mod(bytesToBigint(derived), babyjubjub.order));
98
+ }
99
+ return dkgRound1WithCoefficients(config, participantIndex, coefficients);
100
+ }
101
+ /**
102
+ * Shared Round 1 logic given coefficients.
103
+ */
104
+ function dkgRound1WithCoefficients(_config, participantIndex, coefficients) {
105
+ const commitments = coefficients.map((c) => babyjubjub.scalarMultGen(c));
106
+ const proof = generateSchnorrProof(participantIndex, coefficients[0], commitments[0]);
107
+ return {
108
+ secret: { participantIndex, coefficients },
109
+ package: {
110
+ participantIndex,
111
+ coefficientCommitments: commitments,
112
+ proof,
113
+ },
114
+ };
115
+ }
116
+ /**
117
+ * Round 2: Verify PoK proofs, evaluate polynomial at peers, encrypt shares.
118
+ */
119
+ export function dkgRound2(secret, allRound1Packages, viewingKeyPair) {
120
+ // Verify all PoK proofs
121
+ for (const pkg of allRound1Packages) {
122
+ if (pkg.participantIndex === secret.participantIndex)
123
+ continue;
124
+ const pk = pkg.coefficientCommitments[0];
125
+ if (!verifySchnorrProof(pkg.participantIndex, pk, pkg.proof)) {
126
+ throw new Error(`Invalid proof of knowledge from participant ${pkg.participantIndex}`);
127
+ }
128
+ }
129
+ // Evaluate polynomial at each other participant and encrypt
130
+ const encryptedShares = [];
131
+ let encryptedViewingKeys;
132
+ // If viewingKeyPair provided, prepare the 64-byte payload
133
+ let vkPayload;
134
+ if (viewingKeyPair) {
135
+ vkPayload = new Uint8Array(64);
136
+ vkPayload.set(viewingKeyPair.privateKey, 0);
137
+ vkPayload.set(viewingKeyPair.pubkey, 32);
138
+ encryptedViewingKeys = [];
139
+ }
140
+ for (const pkg of allRound1Packages) {
141
+ if (pkg.participantIndex === secret.participantIndex)
142
+ continue;
143
+ const shareValue = evaluatePolynomial(secret.coefficients, BigInt(pkg.participantIndex));
144
+ // ECDH: use own a_i0 as private key, recipient's C_j0 as public key
145
+ const recipientPk = pkg.coefficientCommitments[0];
146
+ const sharedPoint = ecdhSharedSecret(secret.coefficients[0], recipientPk);
147
+ const symKey = deriveSymmetricKey(sharedPoint);
148
+ const { ciphertext, nonce } = encryptShare(shareValue, symKey);
149
+ encryptedShares.push({
150
+ fromIndex: secret.participantIndex,
151
+ toIndex: pkg.participantIndex,
152
+ ciphertext,
153
+ nonce,
154
+ });
155
+ // Encrypt viewing key for this peer using the same ECDH channel
156
+ if (vkPayload && encryptedViewingKeys) {
157
+ const evk = encryptBytes(vkPayload, symKey);
158
+ encryptedViewingKeys.push({
159
+ toIndex: pkg.participantIndex,
160
+ ciphertext: evk.ciphertext,
161
+ nonce: evk.nonce,
162
+ });
163
+ }
164
+ }
165
+ return {
166
+ participantIndex: secret.participantIndex,
167
+ encryptedShares,
168
+ encryptedViewingKeys,
169
+ };
170
+ }
171
+ /**
172
+ * Finalize DKG: decrypt shares, verify against commitments,
173
+ * compute secret share and group public key.
174
+ */
175
+ export function dkgFinalize(secret, allRound1Packages, allRound2Packages, viewingKeyPair) {
176
+ const n = babyjubjub.order;
177
+ const myIndex = secret.participantIndex;
178
+ // Start with own polynomial evaluation at own index
179
+ let secretShare = evaluatePolynomial(secret.coefficients, BigInt(myIndex));
180
+ // Track decrypted viewing key from creator's round 2 package
181
+ let decryptedViewingKeyPair = viewingKeyPair;
182
+ // Decrypt and verify shares from each other participant
183
+ for (const round2Pkg of allRound2Packages) {
184
+ if (round2Pkg.participantIndex === myIndex)
185
+ continue;
186
+ const encShare = round2Pkg.encryptedShares.find((s) => s.toIndex === myIndex);
187
+ if (!encShare) {
188
+ throw new Error(`No share from participant ${round2Pkg.participantIndex} for participant ${myIndex}`);
189
+ }
190
+ // Decrypt using ECDH: own a_i0 as private, sender's C_j0 as public
191
+ const senderPkg = allRound1Packages.find((p) => p.participantIndex === round2Pkg.participantIndex);
192
+ const senderPk = senderPkg.coefficientCommitments[0];
193
+ const sharedPoint = ecdhSharedSecret(secret.coefficients[0], senderPk);
194
+ const symKey = deriveSymmetricKey(sharedPoint);
195
+ let decryptedShare;
196
+ try {
197
+ decryptedShare = decryptShare(encShare.ciphertext, encShare.nonce, symKey);
198
+ }
199
+ catch {
200
+ throw new Error(`Failed to decrypt share from participant ${round2Pkg.participantIndex}`);
201
+ }
202
+ // Verify against commitments: s*G == sum(C_k * i^k)
203
+ if (!verifyShareCommitment(decryptedShare, senderPkg.coefficientCommitments, myIndex)) {
204
+ throw new Error(`Invalid share from participant ${round2Pkg.participantIndex}: commitment verification failed`);
205
+ }
206
+ // Accumulate into secret share
207
+ secretShare = modAdd(secretShare, decryptedShare, n);
208
+ // Decrypt viewing key if present in this round 2 package
209
+ if (!decryptedViewingKeyPair && round2Pkg.encryptedViewingKeys) {
210
+ const evk = round2Pkg.encryptedViewingKeys.find((k) => k.toIndex === myIndex);
211
+ if (evk) {
212
+ const payload = decryptBytes(evk.ciphertext, evk.nonce, symKey);
213
+ decryptedViewingKeyPair = {
214
+ privateKey: payload.slice(0, 32),
215
+ pubkey: payload.slice(32, 64),
216
+ };
217
+ }
218
+ }
219
+ }
220
+ // Compute verification share
221
+ const verificationShare = babyjubjub.scalarMultGen(secretShare);
222
+ // Compute group public key: Y = sum(C_j0) for all j
223
+ let groupPublicKey = babyjubjub.identity;
224
+ for (const pkg of allRound1Packages) {
225
+ groupPublicKey = babyjubjub.add(groupPublicKey, pkg.coefficientCommitments[0]);
226
+ }
227
+ // Collect coefficient commitments
228
+ const coefficientCommitments = new Map();
229
+ for (const pkg of allRound1Packages) {
230
+ coefficientCommitments.set(pkg.participantIndex, pkg.coefficientCommitments);
231
+ }
232
+ return {
233
+ keyShare: {
234
+ participantIndex: myIndex,
235
+ secretShare,
236
+ verificationShare,
237
+ },
238
+ groupPublicKey,
239
+ coefficientCommitments,
240
+ viewingKeyPair: decryptedViewingKeyPair,
241
+ };
242
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * FROST threshold signatures for BabyJubJub EdDSA.
3
+ *
4
+ * This module provides m-of-n threshold signing using the FROST protocol
5
+ * (RFC 9591) adapted for BabyJubJub curve with Poseidon hash.
6
+ */
7
+ export * from "./types.js";
8
+ export * from "./curve.js";
9
+ export * from "./crypto.js";
10
+ export * from "./dkg.js";
11
+ export * from "./signing.js";
12
+ export * from "./serialization.js";
13
+ export * from "./coordinator.js";
14
+ export * from "./account.js";
15
+ export * from "./listener.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/frost/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * FROST threshold signatures for BabyJubJub EdDSA.
3
+ *
4
+ * This module provides m-of-n threshold signing using the FROST protocol
5
+ * (RFC 9591) adapted for BabyJubJub curve with Poseidon hash.
6
+ */
7
+ export * from "./types.js";
8
+ export * from "./curve.js";
9
+ export * from "./crypto.js";
10
+ export * from "./dkg.js";
11
+ export * from "./signing.js";
12
+ export * from "./serialization.js";
13
+ export * from "./coordinator.js";
14
+ export * from "./account.js";
15
+ export * from "./listener.js";
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Signing listener — polls for new signing sessions and auto-joins them.
3
+ *
4
+ * Co-signers run this to automatically participate in FROST signing
5
+ * sessions created by the initiator's reusable signer.
6
+ */
7
+ import type { MultisigAccount } from "./types.js";
8
+ export type SigningListenerParams = {
9
+ account: MultisigAccount;
10
+ /** Abort to stop the listener loop. */
11
+ signal?: AbortSignal;
12
+ /** Called when a new session is discovered. */
13
+ onSession?: (session: {
14
+ code: string;
15
+ message: bigint;
16
+ }) => void;
17
+ /** Called when signing a session fails. Return true to continue, false to stop. */
18
+ onError?: (error: unknown, session: {
19
+ code: string;
20
+ message: bigint;
21
+ }) => boolean;
22
+ /** Polling interval in ms (default 1000). */
23
+ pollIntervalMs?: number;
24
+ };
25
+ /**
26
+ * Poll for signing sessions targeting this account's group and co-sign them.
27
+ *
28
+ * Runs until the AbortSignal fires or an unrecoverable error occurs.
29
+ */
30
+ export declare function runSigningListener(params: SigningListenerParams): Promise<void>;
31
+ //# sourceMappingURL=listener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../../src/frost/listener.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,eAAe,CAAC;IACzB,uCAAuC;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjE,mFAAmF;IACnF,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KACvC,OAAO,CAAC;IACb,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAqDf"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Signing listener — polls for new signing sessions and auto-joins them.
3
+ *
4
+ * Co-signers run this to automatically participate in FROST signing
5
+ * sessions created by the initiator's reusable signer.
6
+ */
7
+ import { signMultisig } from "./account.js";
8
+ import { FrostCoordinator } from "./coordinator.js";
9
+ /**
10
+ * Poll for signing sessions targeting this account's group and co-sign them.
11
+ *
12
+ * Runs until the AbortSignal fires or an unrecoverable error occurs.
13
+ */
14
+ export async function runSigningListener(params) {
15
+ const { account, signal, onSession, onError } = params;
16
+ const pollIntervalMs = params.pollIntervalMs ?? 1000;
17
+ const coordinator = new FrostCoordinator({
18
+ baseUrl: `${account.gatewayUrl.replace(/\/$/, "")}/frost`,
19
+ pollIntervalMs,
20
+ });
21
+ const seen = new Set();
22
+ while (!signal?.aborted) {
23
+ try {
24
+ const sessions = await coordinator.listSigningSessions(account.groupId);
25
+ const ordered = [...sessions].reverse();
26
+ for (const session of ordered) {
27
+ if (seen.has(session.code))
28
+ continue;
29
+ if (!session.participants.includes(account.participantIndex))
30
+ continue;
31
+ if (session.status !== "waiting_for_commitments") {
32
+ // Signing is non-idempotent and resumability is not supported.
33
+ // Skip stale phases to avoid repeatedly attempting hopeless sessions.
34
+ seen.add(session.code);
35
+ continue;
36
+ }
37
+ // Mark as seen BEFORE signing — FROST signing is non-idempotent
38
+ // (nonce published on submitCommitment), so retrying is never safe.
39
+ seen.add(session.code);
40
+ const info = { code: session.code, message: session.message };
41
+ onSession?.(info);
42
+ try {
43
+ await signMultisig({
44
+ account,
45
+ message: session.message,
46
+ signingSessionCode: session.code,
47
+ });
48
+ }
49
+ catch (err) {
50
+ const shouldContinue = onError?.(err, info) ?? true;
51
+ if (!shouldContinue)
52
+ return;
53
+ }
54
+ }
55
+ }
56
+ catch (err) {
57
+ // Poll-level errors (e.g. coordinator unreachable) should not kill
58
+ // the listener — report via onError and continue the loop.
59
+ const shouldContinue = onError?.(err, { code: "", message: 0n }) ?? true;
60
+ if (!shouldContinue)
61
+ return;
62
+ }
63
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
64
+ }
65
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * In-memory mock of the frost HTTP server for tests.
3
+ *
4
+ * Implements the same state machine as `backend/frost/src/state.rs`
5
+ * without requiring a Rust toolchain.
6
+ */
7
+ export declare function startMockCoordinator(): Promise<{
8
+ url: string;
9
+ close: () => Promise<void>;
10
+ }>;
11
+ //# sourceMappingURL=mock-coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-coordinator.d.ts","sourceRoot":"","sources":["../../../src/frost/mock-coordinator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmDH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC,CAiRD"}