@openzeppelin/miden-multisig-client 0.12.8 → 0.13.1

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 (237) hide show
  1. package/README.md +42 -4
  2. package/dist/account/builder.d.ts +1 -1
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +7 -5
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/account/builder.test.d.ts +2 -0
  7. package/dist/account/builder.test.d.ts.map +1 -0
  8. package/dist/account/builder.test.js +39 -0
  9. package/dist/account/builder.test.js.map +1 -0
  10. package/dist/account/masm.d.ts +3 -3
  11. package/dist/account/masm.d.ts.map +1 -1
  12. package/dist/account/masm.js +101 -98
  13. package/dist/account/masm.js.map +1 -1
  14. package/dist/account/storage.d.ts +1 -1
  15. package/dist/account/storage.d.ts.map +1 -1
  16. package/dist/account/storage.js +17 -7
  17. package/dist/account/storage.js.map +1 -1
  18. package/dist/client.d.ts +1 -1
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +2 -2
  21. package/dist/client.js.map +1 -1
  22. package/dist/client.test.js +4 -4
  23. package/dist/client.test.js.map +1 -1
  24. package/dist/index.d.ts +8 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +6 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/inspector.d.ts +1 -1
  29. package/dist/inspector.d.ts.map +1 -1
  30. package/dist/inspector.js +16 -10
  31. package/dist/inspector.js.map +1 -1
  32. package/dist/inspector.test.js +33 -22
  33. package/dist/inspector.test.js.map +1 -1
  34. package/dist/multisig/helpers.d.ts +1 -1
  35. package/dist/multisig/helpers.d.ts.map +1 -1
  36. package/dist/multisig/helpers.js +1 -1
  37. package/dist/multisig/helpers.js.map +1 -1
  38. package/dist/multisig/helpers.test.js +2 -2
  39. package/dist/multisig/helpers.test.js.map +1 -1
  40. package/dist/multisig/proposal/execution.d.ts +19 -0
  41. package/dist/multisig/proposal/execution.d.ts.map +1 -0
  42. package/dist/multisig/proposal/execution.js +158 -0
  43. package/dist/multisig/proposal/execution.js.map +1 -0
  44. package/dist/multisig/proposal/parser.d.ts +17 -0
  45. package/dist/multisig/proposal/parser.d.ts.map +1 -0
  46. package/dist/multisig/proposal/parser.js +147 -0
  47. package/dist/multisig/proposal/parser.js.map +1 -0
  48. package/dist/multisig/signing.d.ts +5 -0
  49. package/dist/multisig/signing.d.ts.map +1 -0
  50. package/dist/multisig/signing.js +14 -0
  51. package/dist/multisig/signing.js.map +1 -0
  52. package/dist/multisig.d.ts +10 -13
  53. package/dist/multisig.d.ts.map +1 -1
  54. package/dist/multisig.js +128 -337
  55. package/dist/multisig.js.map +1 -1
  56. package/dist/multisig.test.js +8 -4
  57. package/dist/multisig.test.js.map +1 -1
  58. package/dist/proposal/exported.d.ts +12 -0
  59. package/dist/proposal/exported.d.ts.map +1 -0
  60. package/dist/proposal/exported.js +88 -0
  61. package/dist/proposal/exported.js.map +1 -0
  62. package/dist/proposal/metadata.d.ts +15 -0
  63. package/dist/proposal/metadata.d.ts.map +1 -0
  64. package/dist/proposal/metadata.js +212 -0
  65. package/dist/proposal/metadata.js.map +1 -0
  66. package/dist/signer.d.ts +2 -20
  67. package/dist/signer.d.ts.map +1 -1
  68. package/dist/signer.js +2 -82
  69. package/dist/signer.js.map +1 -1
  70. package/dist/signer.test.js +22 -16
  71. package/dist/signer.test.js.map +1 -1
  72. package/dist/signers/ecdsa.d.ts +12 -0
  73. package/dist/signers/ecdsa.d.ts.map +1 -0
  74. package/dist/signers/ecdsa.js +30 -0
  75. package/dist/signers/ecdsa.js.map +1 -0
  76. package/dist/signers/falcon.d.ts +12 -0
  77. package/dist/signers/falcon.d.ts.map +1 -0
  78. package/dist/signers/falcon.js +31 -0
  79. package/dist/signers/falcon.js.map +1 -0
  80. package/dist/signers/falcon.test.d.ts +2 -0
  81. package/dist/signers/falcon.test.d.ts.map +1 -0
  82. package/dist/signers/falcon.test.js +113 -0
  83. package/dist/signers/falcon.test.js.map +1 -0
  84. package/dist/signers/index.d.ts +5 -0
  85. package/dist/signers/index.d.ts.map +1 -0
  86. package/dist/signers/index.js +5 -0
  87. package/dist/signers/index.js.map +1 -0
  88. package/dist/signers/miden-wallet.d.ts +16 -0
  89. package/dist/signers/miden-wallet.d.ts.map +1 -0
  90. package/dist/signers/miden-wallet.js +35 -0
  91. package/dist/signers/miden-wallet.js.map +1 -0
  92. package/dist/signers/miden-wallet.test.d.ts +2 -0
  93. package/dist/signers/miden-wallet.test.d.ts.map +1 -0
  94. package/dist/signers/miden-wallet.test.js +107 -0
  95. package/dist/signers/miden-wallet.test.js.map +1 -0
  96. package/dist/signers/para.d.ts +21 -0
  97. package/dist/signers/para.d.ts.map +1 -0
  98. package/dist/signers/para.js +43 -0
  99. package/dist/signers/para.js.map +1 -0
  100. package/dist/signers/para.test.d.ts +2 -0
  101. package/dist/signers/para.test.d.ts.map +1 -0
  102. package/dist/signers/para.test.js +88 -0
  103. package/dist/signers/para.test.js.map +1 -0
  104. package/dist/transaction/consumeNotes.d.ts +3 -3
  105. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  106. package/dist/transaction/consumeNotes.js +11 -7
  107. package/dist/transaction/consumeNotes.js.map +1 -1
  108. package/dist/transaction/options.d.ts +2 -2
  109. package/dist/transaction/options.d.ts.map +1 -1
  110. package/dist/transaction/p2id.d.ts +1 -1
  111. package/dist/transaction/p2id.d.ts.map +1 -1
  112. package/dist/transaction/p2id.interop.test.d.ts +2 -0
  113. package/dist/transaction/p2id.interop.test.d.ts.map +1 -0
  114. package/dist/transaction/p2id.interop.test.js +36 -0
  115. package/dist/transaction/p2id.interop.test.js.map +1 -0
  116. package/dist/transaction/p2id.js +5 -5
  117. package/dist/transaction/p2id.js.map +1 -1
  118. package/dist/transaction/p2id.test.d.ts +2 -0
  119. package/dist/transaction/p2id.test.d.ts.map +1 -0
  120. package/dist/transaction/p2id.test.js +138 -0
  121. package/dist/transaction/p2id.test.js.map +1 -0
  122. package/dist/transaction/rpoRandomCoin.d.ts +7 -0
  123. package/dist/transaction/rpoRandomCoin.d.ts.map +1 -0
  124. package/dist/transaction/rpoRandomCoin.js +17 -0
  125. package/dist/transaction/rpoRandomCoin.js.map +1 -0
  126. package/dist/transaction/rpoRandomCoin.test.d.ts +2 -0
  127. package/dist/transaction/rpoRandomCoin.test.d.ts.map +1 -0
  128. package/dist/transaction/rpoRandomCoin.test.js +52 -0
  129. package/dist/transaction/rpoRandomCoin.test.js.map +1 -0
  130. package/dist/transaction/summary.d.ts +1 -1
  131. package/dist/transaction/summary.d.ts.map +1 -1
  132. package/dist/transaction/summary.js +1 -1
  133. package/dist/transaction/summary.js.map +1 -1
  134. package/dist/transaction/updatePsm.d.ts +1 -1
  135. package/dist/transaction/updatePsm.d.ts.map +1 -1
  136. package/dist/transaction/updatePsm.js +5 -5
  137. package/dist/transaction/updatePsm.js.map +1 -1
  138. package/dist/transaction/updateSigners.d.ts +1 -1
  139. package/dist/transaction/updateSigners.d.ts.map +1 -1
  140. package/dist/transaction/updateSigners.js +5 -5
  141. package/dist/transaction/updateSigners.js.map +1 -1
  142. package/dist/transaction.test.js +1 -1
  143. package/dist/transaction.test.js.map +1 -1
  144. package/dist/types.d.ts +1 -1
  145. package/dist/types.d.ts.map +1 -1
  146. package/dist/utils/digest.d.ts +6 -0
  147. package/dist/utils/digest.d.ts.map +1 -0
  148. package/dist/utils/digest.js +22 -0
  149. package/dist/utils/digest.js.map +1 -0
  150. package/dist/utils/ecdsa.d.ts +8 -0
  151. package/dist/utils/ecdsa.d.ts.map +1 -0
  152. package/dist/utils/ecdsa.js +60 -0
  153. package/dist/utils/ecdsa.js.map +1 -0
  154. package/dist/utils/ecdsa.test.d.ts +2 -0
  155. package/dist/utils/ecdsa.test.d.ts.map +1 -0
  156. package/dist/utils/ecdsa.test.js +85 -0
  157. package/dist/utils/ecdsa.test.js.map +1 -0
  158. package/dist/utils/index.d.ts +1 -0
  159. package/dist/utils/index.d.ts.map +1 -1
  160. package/dist/utils/index.js +1 -0
  161. package/dist/utils/index.js.map +1 -1
  162. package/dist/utils/key.d.ts +10 -0
  163. package/dist/utils/key.d.ts.map +1 -0
  164. package/dist/utils/key.js +38 -0
  165. package/dist/utils/key.js.map +1 -0
  166. package/dist/utils/key.test.d.ts +2 -0
  167. package/dist/utils/key.test.d.ts.map +1 -0
  168. package/dist/utils/key.test.js +80 -0
  169. package/dist/utils/key.test.js.map +1 -0
  170. package/dist/utils/random.d.ts +1 -1
  171. package/dist/utils/random.d.ts.map +1 -1
  172. package/dist/utils/random.js +1 -1
  173. package/dist/utils/random.js.map +1 -1
  174. package/dist/utils/signature.d.ts +2 -1
  175. package/dist/utils/signature.d.ts.map +1 -1
  176. package/dist/utils/signature.js +6 -3
  177. package/dist/utils/signature.js.map +1 -1
  178. package/dist/utils/signature.test.js +11 -11
  179. package/dist/utils/signature.test.js.map +1 -1
  180. package/dist/utils/state-validation.d.ts +5 -0
  181. package/dist/utils/state-validation.d.ts.map +1 -0
  182. package/dist/utils/state-validation.js +30 -0
  183. package/dist/utils/state-validation.js.map +1 -0
  184. package/dist/utils/validation.d.ts +6 -0
  185. package/dist/utils/validation.d.ts.map +1 -0
  186. package/dist/utils/validation.js +52 -0
  187. package/dist/utils/validation.js.map +1 -0
  188. package/dist/utils/word.d.ts +6 -1
  189. package/dist/utils/word.d.ts.map +1 -1
  190. package/dist/utils/word.js +11 -0
  191. package/dist/utils/word.js.map +1 -1
  192. package/dist/utils/word.test.js +54 -3
  193. package/dist/utils/word.test.js.map +1 -1
  194. package/package.json +4 -5
  195. package/src/account/builder.ts +7 -5
  196. package/src/account/masm.ts +103 -99
  197. package/src/account/storage.ts +19 -7
  198. package/src/client.test.ts +4 -4
  199. package/src/client.ts +3 -3
  200. package/src/index.ts +8 -1
  201. package/src/inspector.test.ts +38 -25
  202. package/src/inspector.ts +18 -10
  203. package/src/multisig/helpers.test.ts +2 -2
  204. package/src/multisig/helpers.ts +2 -1
  205. package/src/multisig/proposal/execution.ts +364 -0
  206. package/src/multisig/proposal/parser.ts +191 -0
  207. package/src/multisig/signing.ts +24 -0
  208. package/src/multisig.test.ts +8 -4
  209. package/src/multisig.ts +176 -434
  210. package/src/signer.ts +2 -95
  211. package/src/signers/ecdsa.ts +35 -0
  212. package/src/{signer.test.ts → signers/falcon.test.ts} +29 -33
  213. package/src/signers/falcon.ts +36 -0
  214. package/src/signers/index.ts +4 -0
  215. package/src/signers/miden-wallet.test.ts +119 -0
  216. package/src/signers/miden-wallet.ts +49 -0
  217. package/src/signers/para.test.ts +103 -0
  218. package/src/signers/para.ts +60 -0
  219. package/src/transaction/consumeNotes.ts +14 -10
  220. package/src/transaction/options.ts +2 -2
  221. package/src/transaction/p2id.ts +3 -8
  222. package/src/transaction/summary.ts +2 -2
  223. package/src/transaction/updatePsm.ts +5 -6
  224. package/src/transaction/updateSigners.ts +5 -6
  225. package/src/transaction.test.ts +1 -1
  226. package/src/types.ts +1 -1
  227. package/src/utils/digest.ts +25 -0
  228. package/src/utils/ecdsa.test.ts +104 -0
  229. package/src/utils/ecdsa.ts +77 -0
  230. package/src/utils/index.ts +1 -0
  231. package/src/utils/key.test.ts +89 -0
  232. package/src/utils/key.ts +49 -0
  233. package/src/utils/random.ts +1 -1
  234. package/src/utils/signature.test.ts +11 -11
  235. package/src/utils/signature.ts +10 -3
  236. package/src/utils/word.test.ts +57 -3
  237. package/src/utils/word.ts +13 -1
package/src/signer.ts CHANGED
@@ -1,95 +1,2 @@
1
- import { SecretKey, Word, AccountId, Felt, FeltArray, Rpo256 } from '@demox-labs/miden-sdk';
2
- import type { Signer, SignatureScheme } from './types.js';
3
- import { bytesToHex } from './utils/encoding.js';
4
-
5
- export class FalconSigner implements Signer {
6
- readonly commitment: string;
7
- readonly publicKey: string;
8
- readonly scheme: SignatureScheme = 'falcon';
9
- private readonly secretKey: SecretKey;
10
-
11
- constructor(secretKey: SecretKey) {
12
- this.secretKey = secretKey;
13
- const pubKey = secretKey.publicKey();
14
- this.commitment = pubKey.toCommitment().toHex();
15
- const serialized = pubKey.serialize();
16
- const falconPubKey = serialized.slice(1);
17
- this.publicKey = bytesToHex(falconPubKey);
18
- }
19
-
20
- signAccountIdWithTimestamp(accountId: string, timestamp: number): string {
21
- const paddedHex = accountId.startsWith('0x') ? accountId : `0x${accountId}`;
22
- const parsedAccountId = AccountId.fromHex(paddedHex);
23
- const prefix = parsedAccountId.prefix();
24
- const suffix = parsedAccountId.suffix();
25
-
26
- const feltArray = new FeltArray([
27
- prefix,
28
- suffix,
29
- new Felt(BigInt(timestamp)),
30
- new Felt(BigInt(0)),
31
- ]);
32
-
33
- const digest = Rpo256.hashElements(feltArray);
34
- const signature = this.secretKey.sign(digest);
35
- const signatureBytes = signature.serialize();
36
- const falconSignature = signatureBytes.slice(1);
37
- return bytesToHex(falconSignature);
38
- }
39
-
40
- signCommitment(commitmentHex: string): string {
41
- const paddedHex = commitmentHex.startsWith('0x') ? commitmentHex : `0x${commitmentHex}`;
42
- const cleanHex = paddedHex.slice(2).padStart(64, '0');
43
- const word = Word.fromHex(`0x${cleanHex}`);
44
- const signature = this.secretKey.sign(word);
45
- const signatureBytes = signature.serialize();
46
- const falconSignature = signatureBytes.slice(1);
47
- return bytesToHex(falconSignature);
48
- }
49
- }
50
-
51
- export class EcdsaSigner implements Signer {
52
- readonly commitment: string;
53
- readonly publicKey: string;
54
- readonly scheme: SignatureScheme = 'ecdsa';
55
- private readonly secretKey: SecretKey;
56
-
57
- constructor(secretKey: SecretKey) {
58
- this.secretKey = secretKey;
59
- const pubKey = secretKey.publicKey();
60
- this.commitment = pubKey.toCommitment().toHex();
61
- const serialized = pubKey.serialize();
62
- const ecdsaPubKey = serialized.slice(1);
63
- this.publicKey = bytesToHex(ecdsaPubKey);
64
- }
65
-
66
- signAccountIdWithTimestamp(accountId: string, timestamp: number): string {
67
- const paddedHex = accountId.startsWith('0x') ? accountId : `0x${accountId}`;
68
- const parsedAccountId = AccountId.fromHex(paddedHex);
69
- const prefix = parsedAccountId.prefix();
70
- const suffix = parsedAccountId.suffix();
71
-
72
- const feltArray = new FeltArray([
73
- prefix,
74
- suffix,
75
- new Felt(BigInt(timestamp)),
76
- new Felt(BigInt(0)),
77
- ]);
78
-
79
- const digest = Rpo256.hashElements(feltArray);
80
- const signature = this.secretKey.sign(digest);
81
- const signatureBytes = signature.serialize();
82
- const ecdsaSignature = signatureBytes.slice(1);
83
- return bytesToHex(ecdsaSignature);
84
- }
85
-
86
- signCommitment(commitmentHex: string): string {
87
- const paddedHex = commitmentHex.startsWith('0x') ? commitmentHex : `0x${commitmentHex}`;
88
- const cleanHex = paddedHex.slice(2).padStart(64, '0');
89
- const word = Word.fromHex(`0x${cleanHex}`);
90
- const signature = this.secretKey.sign(word);
91
- const signatureBytes = signature.serialize();
92
- const ecdsaSignature = signatureBytes.slice(1);
93
- return bytesToHex(ecdsaSignature);
94
- }
95
- }
1
+ export { FalconSigner } from './signers/falcon.js';
2
+ export { EcdsaSigner } from './signers/ecdsa.js';
@@ -0,0 +1,35 @@
1
+ import { AuthSecretKey } from '@miden-sdk/miden-sdk';
2
+ import type { Signer, SignatureScheme } from '../types.js';
3
+ import { bytesToHex, normalizeHexWord } from '../utils/encoding.js';
4
+ import { AuthDigest } from '../utils/digest.js';
5
+
6
+ export class EcdsaSigner implements Signer {
7
+ readonly commitment: string;
8
+ readonly publicKey: string;
9
+ readonly scheme: SignatureScheme = 'ecdsa';
10
+ private readonly secretKey: AuthSecretKey;
11
+
12
+ constructor(secretKey: AuthSecretKey) {
13
+ this.secretKey = secretKey;
14
+ const pubKey = secretKey.publicKey();
15
+ const serialized = pubKey.serialize();
16
+ this.publicKey = bytesToHex(serialized.slice(1));
17
+ this.commitment = normalizeHexWord(pubKey.toCommitment().toHex());
18
+ }
19
+
20
+ async signAccountIdWithTimestamp(accountId: string, timestamp: number): Promise<string> {
21
+ const digest = AuthDigest.fromAccountIdWithTimestamp(accountId, timestamp);
22
+ const signature = this.secretKey.sign(digest);
23
+ const signatureBytes = signature.serialize();
24
+ const ecdsaSignature = signatureBytes.slice(1);
25
+ return bytesToHex(ecdsaSignature);
26
+ }
27
+
28
+ async signCommitment(commitmentHex: string): Promise<string> {
29
+ const word = AuthDigest.fromCommitmentHex(commitmentHex);
30
+ const signature = this.secretKey.sign(word);
31
+ const signatureBytes = signature.serialize();
32
+ const ecdsaSignature = signatureBytes.slice(1);
33
+ return bytesToHex(ecdsaSignature);
34
+ }
35
+ }
@@ -1,8 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { FalconSigner } from './signer.js';
2
+ import { FalconSigner } from './falcon.js';
3
3
 
4
- // Mock the Miden SDK
5
- vi.mock('@demox-labs/miden-sdk', () => {
4
+ vi.mock('@miden-sdk/miden-sdk', () => {
6
5
  const mockSignature = {
7
6
  serialize: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
8
7
  };
@@ -20,7 +19,7 @@ vi.mock('@demox-labs/miden-sdk', () => {
20
19
  };
21
20
 
22
21
  return {
23
- SecretKey: {
22
+ AuthSecretKey: {
24
23
  rpoFalconWithRNG: vi.fn().mockReturnValue(mockSecretKey),
25
24
  },
26
25
  Word: {
@@ -51,8 +50,8 @@ describe('FalconSigner', () => {
51
50
 
52
51
  beforeEach(async () => {
53
52
  vi.clearAllMocks();
54
- const { SecretKey } = await import('@demox-labs/miden-sdk');
55
- mockSecretKey = SecretKey.rpoFalconWithRNG(new Uint8Array(32));
53
+ const { AuthSecretKey } = await import('@miden-sdk/miden-sdk');
54
+ mockSecretKey = AuthSecretKey.rpoFalconWithRNG(new Uint8Array(32));
56
55
  signer = new FalconSigner(mockSecretKey);
57
56
  });
58
57
 
@@ -62,7 +61,6 @@ describe('FalconSigner', () => {
62
61
  });
63
62
 
64
63
  it('extracts public key without first byte', () => {
65
- // Public key serialized is [0, 1, 2, ...15], slice(1) removes first byte
66
64
  expect(signer.publicKey).toBe('0x0102030405060708090a0b0c0d0e0f');
67
65
  });
68
66
 
@@ -72,63 +70,61 @@ describe('FalconSigner', () => {
72
70
  });
73
71
 
74
72
  describe('signAccountIdWithTimestamp', () => {
75
- it('handles account ID with 0x prefix', () => {
76
- const signature = signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
73
+ it('handles account ID with 0x prefix', async () => {
74
+ const signature = await signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
77
75
  expect(signature).toMatch(/^0x[a-f0-9]+$/);
78
76
  });
79
77
 
80
- it('handles account ID without 0x prefix', () => {
81
- const signature = signer.signAccountIdWithTimestamp('a'.repeat(30), 1700000000);
78
+ it('handles account ID without 0x prefix', async () => {
79
+ const signature = await signer.signAccountIdWithTimestamp('a'.repeat(30), 1700000000);
82
80
  expect(signature).toMatch(/^0x[a-f0-9]+$/);
83
81
  });
84
82
 
85
- it('calls sign method with hashed digest', () => {
86
- signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
83
+ it('calls sign method with hashed digest', async () => {
84
+ await signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
87
85
  expect(mockSecretKey.sign).toHaveBeenCalled();
88
86
  });
89
87
 
90
- it('returns signature without first byte', () => {
91
- // Signature serialized is [0, 1, 2, ...9], slice(1) returns [1, 2, ...9]
92
- const signature = signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
88
+ it('returns signature without first byte', async () => {
89
+ const signature = await signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
93
90
  expect(signature).toBe('0x010203040506070809');
94
91
  });
95
92
 
96
93
  it('includes timestamp in signed payload', async () => {
97
- const { Felt, FeltArray } = await import('@demox-labs/miden-sdk');
98
- signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
99
- // Verify FeltArray was called with timestamp in the third position
94
+ const { Felt, FeltArray } = await import('@miden-sdk/miden-sdk');
95
+ await signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
100
96
  expect(FeltArray).toHaveBeenCalledWith(expect.arrayContaining([
101
- expect.anything(), // prefix
102
- expect.anything(), // suffix
103
- expect.anything(), // timestamp
104
- expect.anything(), // zero padding
97
+ expect.anything(),
98
+ expect.anything(),
99
+ expect.anything(),
100
+ expect.anything(),
105
101
  ]));
106
102
  });
107
103
  });
108
104
 
109
105
  describe('signCommitment', () => {
110
- it('handles commitment with 0x prefix', () => {
111
- const signature = signer.signCommitment('0x' + 'c'.repeat(64));
106
+ it('handles commitment with 0x prefix', async () => {
107
+ const signature = await signer.signCommitment('0x' + 'c'.repeat(64));
112
108
  expect(signature).toMatch(/^0x[a-f0-9]+$/);
113
109
  });
114
110
 
115
- it('handles commitment without 0x prefix', () => {
116
- const signature = signer.signCommitment('c'.repeat(64));
111
+ it('handles commitment without 0x prefix', async () => {
112
+ const signature = await signer.signCommitment('c'.repeat(64));
117
113
  expect(signature).toMatch(/^0x[a-f0-9]+$/);
118
114
  });
119
115
 
120
- it('pads short commitment hex to 64 characters', () => {
121
- const signature = signer.signCommitment('abc');
116
+ it('pads short commitment hex to 64 characters', async () => {
117
+ const signature = await signer.signCommitment('abc');
122
118
  expect(signature).toMatch(/^0x[a-f0-9]+$/);
123
119
  });
124
120
 
125
- it('calls sign method with word from hex', () => {
126
- signer.signCommitment('0x' + 'c'.repeat(64));
121
+ it('calls sign method with word from hex', async () => {
122
+ await signer.signCommitment('0x' + 'c'.repeat(64));
127
123
  expect(mockSecretKey.sign).toHaveBeenCalled();
128
124
  });
129
125
 
130
- it('returns signature without first byte', () => {
131
- const signature = signer.signCommitment('0x' + 'c'.repeat(64));
126
+ it('returns signature without first byte', async () => {
127
+ const signature = await signer.signCommitment('0x' + 'c'.repeat(64));
132
128
  expect(signature).toBe('0x010203040506070809');
133
129
  });
134
130
  });
@@ -0,0 +1,36 @@
1
+ import { AuthSecretKey } from '@miden-sdk/miden-sdk';
2
+ import type { Signer, SignatureScheme } from '../types.js';
3
+ import { bytesToHex } from '../utils/encoding.js';
4
+ import { AuthDigest } from '../utils/digest.js';
5
+
6
+ export class FalconSigner implements Signer {
7
+ readonly commitment: string;
8
+ readonly publicKey: string;
9
+ readonly scheme: SignatureScheme = 'falcon';
10
+ private readonly secretKey: AuthSecretKey;
11
+
12
+ constructor(secretKey: AuthSecretKey) {
13
+ this.secretKey = secretKey;
14
+ const pubKey = secretKey.publicKey();
15
+ this.commitment = pubKey.toCommitment().toHex();
16
+ const serialized = pubKey.serialize();
17
+ const falconPubKey = serialized.slice(1);
18
+ this.publicKey = bytesToHex(falconPubKey);
19
+ }
20
+
21
+ async signAccountIdWithTimestamp(accountId: string, timestamp: number): Promise<string> {
22
+ const digest = AuthDigest.fromAccountIdWithTimestamp(accountId, timestamp);
23
+ const signature = this.secretKey.sign(digest);
24
+ const signatureBytes = signature.serialize();
25
+ const falconSignature = signatureBytes.slice(1);
26
+ return bytesToHex(falconSignature);
27
+ }
28
+
29
+ async signCommitment(commitmentHex: string): Promise<string> {
30
+ const word = AuthDigest.fromCommitmentHex(commitmentHex);
31
+ const signature = this.secretKey.sign(word);
32
+ const signatureBytes = signature.serialize();
33
+ const falconSignature = signatureBytes.slice(1);
34
+ return bytesToHex(falconSignature);
35
+ }
36
+ }
@@ -0,0 +1,4 @@
1
+ export { FalconSigner } from './falcon.js';
2
+ export { EcdsaSigner } from './ecdsa.js';
3
+ export { ParaSigner, type ParaSigningContext } from './para.js';
4
+ export { MidenWalletSigner, type WalletSigningContext } from './miden-wallet.js';
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { MidenWalletSigner } from './miden-wallet.js';
3
+ import type { WalletSigningContext } from './miden-wallet.js';
4
+ import type { Signer } from '../types.js';
5
+
6
+ vi.mock('@miden-sdk/miden-sdk', () => ({
7
+ Word: {
8
+ fromHex: vi.fn((hex: string) => ({
9
+ toHex: () => hex,
10
+ toFelts: () => [
11
+ { asInt: () => 1n },
12
+ { asInt: () => 2n },
13
+ { asInt: () => 3n },
14
+ { asInt: () => 4n },
15
+ ],
16
+ })),
17
+ },
18
+ AccountId: {
19
+ fromHex: vi.fn(() => ({
20
+ prefix: () => ({ asInt: () => BigInt(1) }),
21
+ suffix: () => ({ asInt: () => BigInt(2) }),
22
+ })),
23
+ },
24
+ Felt: vi.fn().mockImplementation((v: bigint) => ({ value: v })),
25
+ FeltArray: vi.fn().mockImplementation((arr: any[]) => arr),
26
+ Rpo256: {
27
+ hashElements: vi.fn().mockReturnValue({
28
+ toHex: () => '0x' + 'bb'.repeat(32),
29
+ toFelts: () => [
30
+ { asInt: () => 10n },
31
+ { asInt: () => 20n },
32
+ { asInt: () => 30n },
33
+ { asInt: () => 40n },
34
+ ],
35
+ }),
36
+ },
37
+ }));
38
+
39
+ describe('MidenWalletSigner', () => {
40
+ let mockWallet: WalletSigningContext;
41
+
42
+ beforeEach(() => {
43
+ vi.clearAllMocks();
44
+ mockWallet = {
45
+ signBytes: vi.fn().mockResolvedValue(new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04])),
46
+ };
47
+ });
48
+
49
+ describe('constructor', () => {
50
+ it('should set commitment, scheme, and publicKey', () => {
51
+ const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'falcon');
52
+ expect(signer.commitment).toBe('0xcommitment');
53
+ expect(signer.scheme).toBe('falcon');
54
+ expect(signer.publicKey).toBe('0xcommitment');
55
+ });
56
+
57
+ it('should use localAuthSigner publicKey when provided', () => {
58
+ const localSigner: Signer = {
59
+ commitment: '0xlocal',
60
+ publicKey: '0xlocalpubkey',
61
+ scheme: 'ecdsa',
62
+ signAccountIdWithTimestamp: vi.fn(),
63
+ signCommitment: vi.fn(),
64
+ };
65
+ const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'ecdsa', localSigner);
66
+ expect(signer.publicKey).toBe('0xlocalpubkey');
67
+ });
68
+ });
69
+
70
+ describe('signAccountIdWithTimestamp', () => {
71
+ it('should delegate to localAuthSigner when present', async () => {
72
+ const localSigner: Signer = {
73
+ commitment: '0xlocal',
74
+ publicKey: '0xlocalpubkey',
75
+ scheme: 'ecdsa',
76
+ signAccountIdWithTimestamp: vi.fn().mockResolvedValue('0xlocalsig'),
77
+ signCommitment: vi.fn(),
78
+ };
79
+ const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'ecdsa', localSigner);
80
+ const result = await signer.signAccountIdWithTimestamp('0x' + 'aa'.repeat(15), 1700000000);
81
+ expect(result).toBe('0xlocalsig');
82
+ expect(localSigner.signAccountIdWithTimestamp).toHaveBeenCalledWith('0x' + 'aa'.repeat(15), 1700000000);
83
+ expect(mockWallet.signBytes).not.toHaveBeenCalled();
84
+ });
85
+
86
+ it('should use wallet signing when no localAuthSigner', async () => {
87
+ const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'falcon');
88
+ const result = await signer.signAccountIdWithTimestamp('0x' + 'aa'.repeat(15), 1700000000);
89
+ expect(mockWallet.signBytes).toHaveBeenCalled();
90
+ expect(result).toMatch(/^0x/);
91
+ });
92
+ });
93
+
94
+ describe('signCommitment', () => {
95
+ it('should always use wallet signing', async () => {
96
+ const localSigner: Signer = {
97
+ commitment: '0xlocal',
98
+ publicKey: '0xlocalpubkey',
99
+ scheme: 'ecdsa',
100
+ signAccountIdWithTimestamp: vi.fn(),
101
+ signCommitment: vi.fn(),
102
+ };
103
+ const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'ecdsa', localSigner);
104
+ await signer.signCommitment('0x' + 'cc'.repeat(32));
105
+ expect(mockWallet.signBytes).toHaveBeenCalled();
106
+ });
107
+ });
108
+
109
+ describe('signature byte stripping', () => {
110
+ it('should strip the first byte from wallet signature response', async () => {
111
+ (mockWallet.signBytes as ReturnType<typeof vi.fn>).mockResolvedValue(
112
+ new Uint8Array([0x00, 0xaa, 0xbb, 0xcc]),
113
+ );
114
+ const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'falcon');
115
+ const result = await signer.signCommitment('0x' + 'cc'.repeat(32));
116
+ expect(result).toBe('0xaabbcc');
117
+ });
118
+ });
119
+ });
@@ -0,0 +1,49 @@
1
+ import type { Signer, SignatureScheme } from '../types.js';
2
+ import { AuthDigest } from '../utils/digest.js';
3
+ import { bytesToHex } from '../utils/encoding.js';
4
+ import { wordToBytes } from '../utils/word.js';
5
+
6
+ export interface WalletSigningContext {
7
+ signBytes(data: Uint8Array, kind: 'word' | 'signingInputs'): Promise<Uint8Array>;
8
+ }
9
+
10
+ export class MidenWalletSigner implements Signer {
11
+ readonly commitment: string;
12
+ readonly publicKey: string;
13
+ readonly scheme: SignatureScheme;
14
+ private readonly wallet: WalletSigningContext;
15
+ private readonly localAuthSigner: Signer | null;
16
+
17
+ constructor(
18
+ wallet: WalletSigningContext,
19
+ commitment: string,
20
+ scheme: SignatureScheme,
21
+ localAuthSigner?: Signer,
22
+ ) {
23
+ this.wallet = wallet;
24
+ this.commitment = commitment;
25
+ this.scheme = scheme;
26
+ this.localAuthSigner = localAuthSigner ?? null;
27
+ this.publicKey = localAuthSigner?.publicKey ?? commitment;
28
+ }
29
+
30
+ async signAccountIdWithTimestamp(accountId: string, timestamp: number): Promise<string> {
31
+ if (this.localAuthSigner) {
32
+ return this.localAuthSigner.signAccountIdWithTimestamp(accountId, timestamp);
33
+ }
34
+ const word = AuthDigest.fromAccountIdWithTimestamp(accountId, timestamp);
35
+ return this.signWord(word);
36
+ }
37
+
38
+ async signCommitment(commitmentHex: string): Promise<string> {
39
+ const word = AuthDigest.fromCommitmentHex(commitmentHex);
40
+ return this.signWord(word);
41
+ }
42
+
43
+ private async signWord(word: { toFelts: () => Array<{ asInt: () => bigint }> }): Promise<string> {
44
+ const bytes = wordToBytes(word);
45
+ const signatureBytes = await this.wallet.signBytes(bytes, 'word');
46
+ const rawSignature = signatureBytes.slice(1);
47
+ return bytesToHex(rawSignature);
48
+ }
49
+ }
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { ParaSigner } from './para.js';
3
+ import type { ParaSigningContext } from './para.js';
4
+
5
+ vi.mock('@miden-sdk/miden-sdk', () => ({
6
+ Word: {
7
+ fromHex: vi.fn((hex: string) => ({
8
+ toHex: () => hex,
9
+ toFelts: () => [
10
+ { asInt: () => 1n },
11
+ { asInt: () => 2n },
12
+ { asInt: () => 3n },
13
+ { asInt: () => 4n },
14
+ ],
15
+ })),
16
+ },
17
+ AccountId: {
18
+ fromHex: vi.fn(() => ({
19
+ prefix: () => ({ asInt: () => BigInt(1) }),
20
+ suffix: () => ({ asInt: () => BigInt(2) }),
21
+ })),
22
+ },
23
+ Felt: vi.fn().mockImplementation((v: bigint) => ({ value: v })),
24
+ FeltArray: vi.fn().mockImplementation((arr: any[]) => arr),
25
+ Rpo256: {
26
+ hashElements: vi.fn().mockReturnValue({
27
+ toHex: () => '0x' + 'bb'.repeat(32),
28
+ toFelts: () => [
29
+ { asInt: () => 10n },
30
+ { asInt: () => 20n },
31
+ { asInt: () => 30n },
32
+ { asInt: () => 40n },
33
+ ],
34
+ }),
35
+ },
36
+ }));
37
+
38
+ describe('ParaSigner', () => {
39
+ let mockPara: ParaSigningContext;
40
+ const validCompressedKey = '0x02' + 'aa'.repeat(32);
41
+ const validUncompressedKey = '0x04' + 'aa'.repeat(32) + 'bb'.repeat(31) + 'b0';
42
+
43
+ beforeEach(() => {
44
+ vi.clearAllMocks();
45
+ mockPara = {
46
+ signMessage: vi.fn().mockResolvedValue({
47
+ signature: '0x' + 'cc'.repeat(64),
48
+ }),
49
+ };
50
+ });
51
+
52
+ describe('constructor', () => {
53
+ it('should accept a valid compressed public key', () => {
54
+ const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validCompressedKey);
55
+ expect(signer.publicKey).toBe(validCompressedKey);
56
+ expect(signer.commitment).toBe('0xcommitment');
57
+ expect(signer.scheme).toBe('ecdsa');
58
+ });
59
+
60
+ it('should compress an uncompressed public key', () => {
61
+ const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validUncompressedKey);
62
+ expect(signer.publicKey).toBe('0x02' + 'aa'.repeat(32));
63
+ });
64
+
65
+ it('should throw on invalid public key', () => {
66
+ expect(
67
+ () => new ParaSigner(mockPara, 'wallet-1', '0xcommitment', '0xinvalid'),
68
+ ).toThrow('Invalid ECDSA public key for ParaSigner');
69
+ });
70
+ });
71
+
72
+ describe('signAccountIdWithTimestamp', () => {
73
+ it('should call para signMessage and return normalized signature', async () => {
74
+ const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validCompressedKey);
75
+ const result = await signer.signAccountIdWithTimestamp('0x' + 'aa'.repeat(15), 1700000000);
76
+ expect(mockPara.signMessage).toHaveBeenCalledWith(
77
+ expect.objectContaining({
78
+ walletId: 'wallet-1',
79
+ messageBase64: expect.any(String),
80
+ }),
81
+ );
82
+ expect(result).toMatch(/^0x/);
83
+ });
84
+ });
85
+
86
+ describe('signCommitment', () => {
87
+ it('should call para signMessage', async () => {
88
+ const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validCompressedKey);
89
+ await signer.signCommitment('0x' + 'cc'.repeat(32));
90
+ expect(mockPara.signMessage).toHaveBeenCalled();
91
+ });
92
+ });
93
+
94
+ describe('signing denial', () => {
95
+ it('should throw when para returns no signature', async () => {
96
+ (mockPara.signMessage as ReturnType<typeof vi.fn>).mockResolvedValue({ signature: undefined });
97
+ const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validCompressedKey);
98
+ await expect(signer.signCommitment('0x' + 'cc'.repeat(32))).rejects.toThrow(
99
+ 'Para signing was denied by user',
100
+ );
101
+ });
102
+ });
103
+ });
@@ -0,0 +1,60 @@
1
+ import type { Signer, SignatureScheme } from '../types.js';
2
+ import { AuthDigest } from '../utils/digest.js';
3
+ import { EcdsaFormat } from '../utils/ecdsa.js';
4
+ import { hexToBytes, uint8ArrayToBase64 } from '../utils/encoding.js';
5
+ import { wordToBytes } from '../utils/word.js';
6
+
7
+ export interface ParaSigningContext {
8
+ signMessage(params: { walletId: string; messageBase64: string }): Promise<{ signature?: string }>;
9
+ }
10
+
11
+ export class ParaSigner implements Signer {
12
+ readonly commitment: string;
13
+ readonly publicKey: string;
14
+ readonly scheme: SignatureScheme = 'ecdsa';
15
+ private readonly para: ParaSigningContext;
16
+ private readonly walletId: string;
17
+
18
+ constructor(
19
+ para: ParaSigningContext,
20
+ walletId: string,
21
+ commitment: string,
22
+ publicKey: string,
23
+ ) {
24
+ if (!EcdsaFormat.validatePublicKeyHex(publicKey)) {
25
+ throw new Error('Invalid ECDSA public key for ParaSigner');
26
+ }
27
+ this.para = para;
28
+ this.walletId = walletId;
29
+ this.commitment = commitment;
30
+ this.publicKey = EcdsaFormat.compressPublicKey(publicKey);
31
+ }
32
+
33
+ async signAccountIdWithTimestamp(accountId: string, timestamp: number): Promise<string> {
34
+ const digest = AuthDigest.fromAccountIdWithTimestamp(accountId, timestamp);
35
+ return this.signWord(digest);
36
+ }
37
+
38
+ async signCommitment(commitmentHex: string): Promise<string> {
39
+ const word = AuthDigest.fromCommitmentHex(commitmentHex);
40
+ return this.signWord(word);
41
+ }
42
+
43
+ private async signWord(word: { toHex: () => string; toFelts: () => Array<{ asInt: () => bigint }> }): Promise<string> {
44
+ const wordBytes = wordToBytes(word);
45
+ const hashedHex = EcdsaFormat.keccakDigestHex(wordBytes);
46
+ const cleanHex = hashedHex.slice(2);
47
+ const messageBase64 = uint8ArrayToBase64(hexToBytes(cleanHex));
48
+
49
+ const res = await this.para.signMessage({
50
+ walletId: this.walletId,
51
+ messageBase64,
52
+ });
53
+
54
+ if (!res.signature) {
55
+ throw new Error('Para signing was denied by user');
56
+ }
57
+
58
+ return EcdsaFormat.normalizeRecoveryByte(res.signature);
59
+ }
60
+ }