@openzeppelin/miden-multisig-client 0.13.3 → 0.14.3

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 (145) hide show
  1. package/README.md +10 -13
  2. package/dist/account/builder.d.ts +3 -3
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +10 -10
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/account/builder.test.js +16 -14
  7. package/dist/account/builder.test.js.map +1 -1
  8. package/dist/account/masm/account-components/auth.d.ts +4 -4
  9. package/dist/account/masm/account-components/auth.d.ts.map +1 -1
  10. package/dist/account/masm/account-components/auth.js +8 -12
  11. package/dist/account/masm/account-components/auth.js.map +1 -1
  12. package/dist/account/masm/auth.d.ts +4 -4
  13. package/dist/account/masm/auth.d.ts.map +1 -1
  14. package/dist/account/masm/auth.js +226 -124
  15. package/dist/account/masm/auth.js.map +1 -1
  16. package/dist/account/storage.d.ts.map +1 -1
  17. package/dist/account/storage.js +24 -6
  18. package/dist/account/storage.js.map +1 -1
  19. package/dist/account/storage.test.d.ts +2 -0
  20. package/dist/account/storage.test.d.ts.map +1 -0
  21. package/dist/account/storage.test.js +73 -0
  22. package/dist/account/storage.test.js.map +1 -0
  23. package/dist/client.d.ts +9 -9
  24. package/dist/client.d.ts.map +1 -1
  25. package/dist/client.js +24 -14
  26. package/dist/client.js.map +1 -1
  27. package/dist/client.test.js +45 -8
  28. package/dist/client.test.js.map +1 -1
  29. package/dist/index.d.ts +6 -12
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +6 -12
  32. package/dist/index.js.map +1 -1
  33. package/dist/multisig/proposal/execution.d.ts +3 -2
  34. package/dist/multisig/proposal/execution.d.ts.map +1 -1
  35. package/dist/multisig/proposal/execution.js +8 -12
  36. package/dist/multisig/proposal/execution.js.map +1 -1
  37. package/dist/multisig.d.ts +10 -4
  38. package/dist/multisig.d.ts.map +1 -1
  39. package/dist/multisig.js +97 -71
  40. package/dist/multisig.js.map +1 -1
  41. package/dist/multisig.test.js +481 -29
  42. package/dist/multisig.test.js.map +1 -1
  43. package/dist/procedures.d.ts +8 -8
  44. package/dist/procedures.js +8 -8
  45. package/dist/raw-client.d.ts +13 -0
  46. package/dist/raw-client.d.ts.map +1 -0
  47. package/dist/raw-client.js +42 -0
  48. package/dist/raw-client.js.map +1 -0
  49. package/dist/raw-client.test.d.ts +2 -0
  50. package/dist/raw-client.test.d.ts.map +1 -0
  51. package/dist/raw-client.test.js +89 -0
  52. package/dist/raw-client.test.js.map +1 -0
  53. package/dist/signers/ecdsa.d.ts +3 -1
  54. package/dist/signers/ecdsa.d.ts.map +1 -1
  55. package/dist/signers/ecdsa.js +15 -1
  56. package/dist/signers/ecdsa.js.map +1 -1
  57. package/dist/signers/ecdsa.test.js +27 -4
  58. package/dist/signers/ecdsa.test.js.map +1 -1
  59. package/dist/signers/falcon.d.ts +3 -1
  60. package/dist/signers/falcon.d.ts.map +1 -1
  61. package/dist/signers/falcon.js +15 -1
  62. package/dist/signers/falcon.js.map +1 -1
  63. package/dist/signers/falcon.test.js +39 -3
  64. package/dist/signers/falcon.test.js.map +1 -1
  65. package/dist/transaction/consumeNotes.d.ts +2 -2
  66. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  67. package/dist/transaction/consumeNotes.js +5 -4
  68. package/dist/transaction/consumeNotes.js.map +1 -1
  69. package/dist/transaction/options.d.ts +1 -0
  70. package/dist/transaction/options.d.ts.map +1 -1
  71. package/dist/transaction/p2id.d.ts.map +1 -1
  72. package/dist/transaction/p2id.js +7 -10
  73. package/dist/transaction/p2id.js.map +1 -1
  74. package/dist/transaction/p2id.test.js +19 -9
  75. package/dist/transaction/p2id.test.js.map +1 -1
  76. package/dist/transaction/summary.d.ts +2 -2
  77. package/dist/transaction/summary.d.ts.map +1 -1
  78. package/dist/transaction/summary.js +4 -2
  79. package/dist/transaction/summary.js.map +1 -1
  80. package/dist/transaction/updateGuardian.d.ts +2 -2
  81. package/dist/transaction/updateGuardian.d.ts.map +1 -1
  82. package/dist/transaction/updateGuardian.js +8 -11
  83. package/dist/transaction/updateGuardian.js.map +1 -1
  84. package/dist/transaction/updateProcedureThreshold.d.ts +2 -2
  85. package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
  86. package/dist/transaction/updateProcedureThreshold.js +8 -10
  87. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  88. package/dist/transaction/updateSigners.d.ts +2 -2
  89. package/dist/transaction/updateSigners.d.ts.map +1 -1
  90. package/dist/transaction/updateSigners.js +8 -10
  91. package/dist/transaction/updateSigners.js.map +1 -1
  92. package/dist/transaction.test.js +6 -6
  93. package/dist/transaction.test.js.map +1 -1
  94. package/dist/utils/key.d.ts.map +1 -1
  95. package/dist/utils/key.js +3 -3
  96. package/dist/utils/key.js.map +1 -1
  97. package/dist/utils/key.test.js +4 -2
  98. package/dist/utils/key.test.js.map +1 -1
  99. package/dist/utils/signature.d.ts +2 -0
  100. package/dist/utils/signature.d.ts.map +1 -1
  101. package/dist/utils/signature.js +10 -5
  102. package/dist/utils/signature.js.map +1 -1
  103. package/dist/utils/signature.test.js +9 -9
  104. package/dist/utils/signature.test.js.map +1 -1
  105. package/masm/account_components/auth/multisig.masm +2 -3
  106. package/masm/account_components/auth/multisig_ecdsa.masm +2 -3
  107. package/masm/account_components/auth/multisig_guardian.masm +2 -3
  108. package/masm/account_components/auth/multisig_guardian_ecdsa.masm +2 -3
  109. package/masm/auth/guardian.masm +30 -8
  110. package/masm/auth/guardian_ecdsa.masm +24 -7
  111. package/masm/auth/multisig.masm +86 -55
  112. package/masm/auth/multisig_ecdsa.masm +86 -54
  113. package/package.json +3 -3
  114. package/src/account/builder.test.ts +16 -14
  115. package/src/account/builder.ts +13 -12
  116. package/src/account/masm/account-components/auth.ts +8 -12
  117. package/src/account/masm/auth.ts +226 -124
  118. package/src/account/storage.test.ts +90 -0
  119. package/src/account/storage.ts +27 -6
  120. package/src/client.test.ts +52 -8
  121. package/src/client.ts +39 -16
  122. package/src/index.ts +6 -12
  123. package/src/multisig/proposal/execution.ts +18 -14
  124. package/src/multisig.test.ts +572 -29
  125. package/src/multisig.ts +121 -83
  126. package/src/procedures.ts +8 -8
  127. package/src/raw-client.test.ts +133 -0
  128. package/src/raw-client.ts +75 -0
  129. package/src/signers/ecdsa.test.ts +35 -4
  130. package/src/signers/ecdsa.ts +18 -2
  131. package/src/signers/falcon.test.ts +50 -3
  132. package/src/signers/falcon.ts +18 -2
  133. package/src/transaction/consumeNotes.ts +16 -5
  134. package/src/transaction/options.ts +1 -0
  135. package/src/transaction/p2id.test.ts +19 -9
  136. package/src/transaction/p2id.ts +8 -13
  137. package/src/transaction/summary.ts +11 -4
  138. package/src/transaction/updateGuardian.ts +22 -14
  139. package/src/transaction/updateProcedureThreshold.ts +20 -15
  140. package/src/transaction/updateSigners.ts +22 -14
  141. package/src/transaction.test.ts +6 -6
  142. package/src/utils/key.test.ts +4 -2
  143. package/src/utils/key.ts +7 -3
  144. package/src/utils/signature.test.ts +10 -10
  145. package/src/utils/signature.ts +12 -5
@@ -41,7 +41,7 @@ vi.mock('@miden-sdk/miden-sdk', () => ({
41
41
  insert: vi.fn(),
42
42
  })),
43
43
  FeltArray: vi.fn().mockImplementation((arr) => arr),
44
- Rpo256: {
44
+ Poseidon2: {
45
45
  hashElements: vi.fn().mockReturnValue({
46
46
  toHex: () => '0x' + 'e'.repeat(64),
47
47
  }),
@@ -108,6 +108,9 @@ function mockedAccount(commitmentHex, nonce = 0) {
108
108
  commitment: () => ({
109
109
  toHex: () => commitmentHex,
110
110
  }),
111
+ to_commitment: () => ({
112
+ toHex: () => commitmentHex,
113
+ }),
111
114
  nonce: () => ({
112
115
  asInt: () => BigInt(nonce),
113
116
  }),
@@ -167,6 +170,11 @@ describe('Multisig', () => {
167
170
  proveTransaction: vi.fn(),
168
171
  submitProvenTransaction: vi.fn(),
169
172
  applyTransaction: vi.fn(),
173
+ submitNewTransaction: vi.fn(),
174
+ submitNewTransactionWithProver: vi.fn(),
175
+ transactions: {
176
+ submit: vi.fn(),
177
+ },
170
178
  getConsumableNotes: vi.fn().mockResolvedValue([]),
171
179
  syncState: vi.fn(),
172
180
  getAccount: vi.fn().mockResolvedValue(null),
@@ -310,11 +318,7 @@ describe('Multisig', () => {
310
318
  guardianCommitment: '0x' + 'c'.repeat(64),
311
319
  };
312
320
  const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, undefined, 'https://rpc.devnet.miden.io');
313
- mockWebClient.getAccount.mockResolvedValueOnce({
314
- commitment: () => ({
315
- toHex: () => '0x' + 'b'.repeat(64),
316
- }),
317
- });
321
+ mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'b'.repeat(64), 0));
318
322
  mockFetch.mockResolvedValueOnce({
319
323
  ok: true,
320
324
  json: async () => ({
@@ -444,11 +448,7 @@ describe('Multisig', () => {
444
448
  signerCommitments: ['0x' + 'a'.repeat(64)],
445
449
  guardianCommitment: '0x' + 'c'.repeat(64),
446
450
  };
447
- mockWebClient.getAccount.mockResolvedValueOnce({
448
- commitment: () => ({
449
- toHex: () => '0x' + 'b'.repeat(64),
450
- }),
451
- });
451
+ mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'b'.repeat(64), 0));
452
452
  const multisigWithRpc = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, undefined, 'https://rpc.devnet.miden.io');
453
453
  await expect(multisigWithRpc.verifyStateCommitment()).resolves.toMatchObject({
454
454
  accountId: multisigWithRpc.accountId,
@@ -470,11 +470,7 @@ describe('Multisig', () => {
470
470
  signerCommitments: ['0x' + 'a'.repeat(64)],
471
471
  guardianCommitment: '0x' + 'c'.repeat(64),
472
472
  };
473
- mockWebClient.getAccount.mockResolvedValueOnce({
474
- commitment: () => ({
475
- toHex: () => '0x' + 'f'.repeat(64),
476
- }),
477
- });
473
+ mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'f'.repeat(64), 0));
478
474
  mockRpcGetAccountDetails.mockResolvedValueOnce({
479
475
  commitment: () => ({
480
476
  toHex: () => '0x' + 'b'.repeat(64),
@@ -1638,6 +1634,470 @@ describe('Multisig', () => {
1638
1634
  expect(mockSigner.signCommitment).not.toHaveBeenCalled();
1639
1635
  });
1640
1636
  });
1637
+ describe('createTransactionProposalRequest', () => {
1638
+ it('should return a ready non-switch_guardian request without executing it', async () => {
1639
+ const { buildSignatureAdviceEntry, signatureHexToBytes } = await import('./utils/signature.js');
1640
+ vi.mocked(signatureHexToBytes).mockClear();
1641
+ vi.mocked(buildSignatureAdviceEntry).mockClear();
1642
+ const config = {
1643
+ threshold: 1,
1644
+ signerCommitments: ['0x' + 'a'.repeat(64)],
1645
+ guardianCommitment: '0x' + 'c'.repeat(64),
1646
+ guardianPublicKey: '0x' + '1'.repeat(66),
1647
+ };
1648
+ const ecdsaSigner = {
1649
+ ...mockSigner,
1650
+ scheme: 'ecdsa',
1651
+ publicKey: '0x' + '2'.repeat(66),
1652
+ };
1653
+ const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
1654
+ const cachedProposalId = '0x' + 'c'.repeat(64);
1655
+ const requestedProposalId = '0x' + 'C'.repeat(64);
1656
+ const cosignerPubkey = '0x' + '3'.repeat(66);
1657
+ const ackPubkey = '0x' + '4'.repeat(66);
1658
+ const cosignerSignature = '0x' + '5'.repeat(130);
1659
+ const ackSignature = '0x' + '6'.repeat(130);
1660
+ const finalRequest = { kind: 'final-change-threshold-request' };
1661
+ vi.mocked(buildUpdateSignersTransactionRequest)
1662
+ .mockResolvedValueOnce({
1663
+ request: { kind: 'verify-change-threshold-request' },
1664
+ salt: { toHex: () => '0x' + 'd'.repeat(64) },
1665
+ configHash: { toHex: () => '0x' + 'e'.repeat(64) },
1666
+ })
1667
+ .mockResolvedValueOnce({
1668
+ request: finalRequest,
1669
+ salt: { toHex: () => '0x' + 'd'.repeat(64) },
1670
+ configHash: { toHex: () => '0x' + 'e'.repeat(64) },
1671
+ });
1672
+ multisig.proposals.set(cachedProposalId, {
1673
+ id: cachedProposalId,
1674
+ accountId: multisig.accountId,
1675
+ nonce: 1,
1676
+ status: 'ready',
1677
+ txSummary: 'AQID',
1678
+ signatures: [
1679
+ {
1680
+ signerId: '0x' + 'a'.repeat(64),
1681
+ signature: {
1682
+ scheme: 'ecdsa',
1683
+ signature: cosignerSignature,
1684
+ publicKey: cosignerPubkey,
1685
+ },
1686
+ timestamp: '2024-01-01T00:00:00Z',
1687
+ },
1688
+ ],
1689
+ metadata: {
1690
+ proposalType: 'change_threshold',
1691
+ targetThreshold: 1,
1692
+ targetSignerCommitments: ['0x' + 'a'.repeat(64)],
1693
+ description: '',
1694
+ },
1695
+ });
1696
+ mockFetch.mockResolvedValueOnce({
1697
+ ok: true,
1698
+ json: async () => ({
1699
+ account_id: multisig.accountId,
1700
+ nonce: 1,
1701
+ prev_commitment: '0x' + 'b'.repeat(64),
1702
+ delta_payload: {
1703
+ tx_summary: { data: 'AQID' },
1704
+ signatures: [],
1705
+ metadata: {
1706
+ proposal_type: 'change_threshold',
1707
+ target_threshold: 1,
1708
+ signer_commitments: ['0x' + 'a'.repeat(64)],
1709
+ },
1710
+ },
1711
+ status: {
1712
+ status: 'pending',
1713
+ timestamp: '2024-01-01T00:00:00Z',
1714
+ proposer_id: '0x' + 'a'.repeat(64),
1715
+ cosigner_sigs: [],
1716
+ },
1717
+ }),
1718
+ });
1719
+ mockFetch.mockResolvedValueOnce({
1720
+ ok: true,
1721
+ json: async () => ({
1722
+ account_id: multisig.accountId,
1723
+ nonce: 1,
1724
+ ack_sig: ackSignature,
1725
+ ack_pubkey: ackPubkey,
1726
+ ack_scheme: 'ecdsa',
1727
+ }),
1728
+ });
1729
+ await expect(multisig.createTransactionProposalRequest(requestedProposalId)).resolves.toBe(finalRequest);
1730
+ expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(1, cosignerSignature, 'ecdsa');
1731
+ expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(2, ackSignature, 'ecdsa');
1732
+ expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(1, expect.anything(), expect.anything(), expect.anything(), cosignerPubkey, cosignerSignature);
1733
+ expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(2, expect.anything(), expect.anything(), expect.anything(), ackPubkey, ackSignature);
1734
+ expect(mockWebClient.executeTransaction).not.toHaveBeenCalled();
1735
+ expect(mockWebClient.proveTransaction).not.toHaveBeenCalled();
1736
+ expect(mockWebClient.submitProvenTransaction).not.toHaveBeenCalled();
1737
+ expect(mockWebClient.applyTransaction).not.toHaveBeenCalled();
1738
+ });
1739
+ it('should return a ready switch_guardian request without executing it', async () => {
1740
+ const config = {
1741
+ threshold: 1,
1742
+ signerCommitments: ['0x' + 'a'.repeat(64)],
1743
+ guardianCommitment: '0x' + 'c'.repeat(64),
1744
+ };
1745
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1746
+ const proposalId = '0x' + 'c'.repeat(64);
1747
+ const newGuardianPubkey = '0x' + '1'.repeat(64);
1748
+ const finalRequest = { kind: 'final-switch-guardian-request' };
1749
+ vi.mocked(buildUpdateGuardianTransactionRequest)
1750
+ .mockResolvedValueOnce({
1751
+ request: { kind: 'verify-switch-guardian-request' },
1752
+ salt: { toHex: () => '0x' + 'd'.repeat(64) },
1753
+ })
1754
+ .mockResolvedValueOnce({
1755
+ request: finalRequest,
1756
+ salt: { toHex: () => '0x' + 'd'.repeat(64) },
1757
+ });
1758
+ multisig.proposals.set(proposalId, {
1759
+ id: proposalId,
1760
+ accountId: multisig.accountId,
1761
+ nonce: 1,
1762
+ status: 'ready',
1763
+ txSummary: 'AQID',
1764
+ signatures: [
1765
+ {
1766
+ signerId: '0x' + 'a'.repeat(64),
1767
+ signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
1768
+ timestamp: '2024-01-01T00:00:00Z',
1769
+ },
1770
+ ],
1771
+ metadata: {
1772
+ proposalType: 'switch_guardian',
1773
+ newGuardianPubkey,
1774
+ newGuardianEndpoint: 'http://new-guardian.com',
1775
+ description: '',
1776
+ },
1777
+ });
1778
+ mockFetch.mockResolvedValueOnce({
1779
+ ok: true,
1780
+ json: async () => ({ commitment: newGuardianPubkey }),
1781
+ });
1782
+ await expect(multisig.createTransactionProposalRequest(proposalId)).resolves.toBe(finalRequest);
1783
+ expect(mockFetch).toHaveBeenCalledTimes(1);
1784
+ expect(mockWebClient.executeTransaction).not.toHaveBeenCalled();
1785
+ expect(mockWebClient.proveTransaction).not.toHaveBeenCalled();
1786
+ expect(mockWebClient.submitProvenTransaction).not.toHaveBeenCalled();
1787
+ expect(mockWebClient.applyTransaction).not.toHaveBeenCalled();
1788
+ });
1789
+ it('should throw if proposal not found locally', async () => {
1790
+ const config = {
1791
+ threshold: 1,
1792
+ signerCommitments: ['0x' + 'a'.repeat(64)],
1793
+ guardianCommitment: '0x' + 'c'.repeat(64),
1794
+ };
1795
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1796
+ await expect(multisig.createTransactionProposalRequest('0x' + 'nonexistent'.repeat(5))).rejects.toThrow('Proposal not found');
1797
+ });
1798
+ it('should throw if proposal is still pending', async () => {
1799
+ const config = {
1800
+ threshold: 2,
1801
+ signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1802
+ guardianCommitment: '0x' + 'c'.repeat(64),
1803
+ };
1804
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1805
+ const mockProposals = [
1806
+ {
1807
+ account_id: '0x' + 'a'.repeat(30),
1808
+ nonce: 1,
1809
+ prev_commitment: '0x' + 'b'.repeat(64),
1810
+ delta_payload: {
1811
+ tx_summary: { data: 'AQID' },
1812
+ signatures: [],
1813
+ metadata: {
1814
+ proposal_type: 'add_signer',
1815
+ description: '',
1816
+ target_threshold: 2,
1817
+ signer_commitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1818
+ },
1819
+ },
1820
+ status: {
1821
+ status: 'pending',
1822
+ timestamp: '2024-01-01T00:00:00Z',
1823
+ proposer_id: '0x' + 'c'.repeat(64),
1824
+ cosigner_sigs: [
1825
+ {
1826
+ signer_id: '0x' + 'a'.repeat(64),
1827
+ signature: { scheme: 'falcon', signature: '0x' + 'e'.repeat(128) },
1828
+ timestamp: '2024-01-01T00:00:00Z',
1829
+ },
1830
+ ],
1831
+ },
1832
+ },
1833
+ ];
1834
+ mockFetch.mockResolvedValueOnce({
1835
+ ok: true,
1836
+ json: async () => ({ proposals: mockProposals }),
1837
+ });
1838
+ await multisig.syncProposals();
1839
+ await expect(multisig.createTransactionProposalRequest('0x' + 'c'.repeat(64))).rejects.toThrow('not ready for execution');
1840
+ });
1841
+ it('should throw when proposal metadata does not match tx_summary', async () => {
1842
+ const config = {
1843
+ threshold: 1,
1844
+ signerCommitments: ['0x' + 'a'.repeat(64)],
1845
+ guardianCommitment: '0x' + 'c'.repeat(64),
1846
+ };
1847
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1848
+ const proposalId = '0x' + 'c'.repeat(64);
1849
+ vi.mocked(executeForSummary).mockResolvedValueOnce({
1850
+ toCommitment: () => ({
1851
+ toHex: () => '0x' + 'd'.repeat(64),
1852
+ }),
1853
+ });
1854
+ multisig.proposals.set(proposalId, {
1855
+ id: proposalId,
1856
+ accountId: multisig.accountId,
1857
+ nonce: 1,
1858
+ status: 'ready',
1859
+ txSummary: 'AQID',
1860
+ signatures: [
1861
+ {
1862
+ signerId: '0x' + 'a'.repeat(64),
1863
+ signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
1864
+ timestamp: '2024-01-01T00:00:00Z',
1865
+ },
1866
+ ],
1867
+ metadata: {
1868
+ proposalType: 'change_threshold',
1869
+ targetThreshold: 1,
1870
+ targetSignerCommitments: ['0x' + 'a'.repeat(64)],
1871
+ description: '',
1872
+ },
1873
+ });
1874
+ await expect(multisig.createTransactionProposalRequest(proposalId)).rejects.toThrow(`Invalid proposal: metadata does not match tx_summary for ${proposalId}`);
1875
+ expect(mockWebClient.executeTransaction).not.toHaveBeenCalled();
1876
+ });
1877
+ it('should reject switch_guardian requests when endpoint commitment mismatches', async () => {
1878
+ const config = {
1879
+ threshold: 1,
1880
+ signerCommitments: ['0x' + 'a'.repeat(64)],
1881
+ guardianCommitment: '0x' + 'c'.repeat(64),
1882
+ };
1883
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1884
+ const proposalId = '0x' + 'c'.repeat(64);
1885
+ multisig.proposals.set(proposalId, {
1886
+ id: proposalId,
1887
+ accountId: multisig.accountId,
1888
+ nonce: 1,
1889
+ status: 'ready',
1890
+ txSummary: 'AQID',
1891
+ signatures: [
1892
+ {
1893
+ signerId: '0x' + 'a'.repeat(64),
1894
+ signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
1895
+ timestamp: '2024-01-01T00:00:00Z',
1896
+ },
1897
+ ],
1898
+ metadata: {
1899
+ proposalType: 'switch_guardian',
1900
+ newGuardianPubkey: '0x' + '1'.repeat(64),
1901
+ newGuardianEndpoint: 'http://new-guardian.com',
1902
+ description: '',
1903
+ },
1904
+ });
1905
+ mockFetch.mockResolvedValueOnce({
1906
+ ok: true,
1907
+ json: async () => ({ commitment: '0x' + '2'.repeat(64) }),
1908
+ });
1909
+ await expect(multisig.createTransactionProposalRequest(proposalId)).rejects.toThrow('Refusing to use GUARDIAN endpoint');
1910
+ expect(mockWebClient.executeTransaction).not.toHaveBeenCalled();
1911
+ });
1912
+ it('should reject duplicate normalized signer IDs during request creation', async () => {
1913
+ const config = {
1914
+ threshold: 2,
1915
+ signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1916
+ guardianCommitment: '0x' + 'c'.repeat(64),
1917
+ };
1918
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1919
+ const proposalId = '0x' + 'c'.repeat(64);
1920
+ multisig.proposals.set(proposalId, {
1921
+ id: proposalId,
1922
+ accountId: multisig.accountId,
1923
+ nonce: 1,
1924
+ status: 'ready',
1925
+ txSummary: 'AQID',
1926
+ signatures: [
1927
+ {
1928
+ signerId: '0x' + 'a'.repeat(64),
1929
+ signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
1930
+ timestamp: '2024-01-01T00:00:00Z',
1931
+ },
1932
+ {
1933
+ signerId: '0x' + 'A'.repeat(64),
1934
+ signature: { scheme: 'falcon', signature: '0x' + 'c'.repeat(128) },
1935
+ timestamp: '2024-01-01T00:00:01Z',
1936
+ },
1937
+ ],
1938
+ metadata: {
1939
+ proposalType: 'switch_guardian',
1940
+ newGuardianPubkey: '0x' + '1'.repeat(64),
1941
+ newGuardianEndpoint: 'http://new-guardian.com',
1942
+ description: '',
1943
+ },
1944
+ });
1945
+ await expect(multisig.createTransactionProposalRequest(proposalId)).rejects.toThrow('duplicate signatures for signer');
1946
+ });
1947
+ it('should build a fresh tx commitment word for each advice entry during request creation', async () => {
1948
+ const { buildSignatureAdviceEntry } = await import('./utils/signature.js');
1949
+ const { Word } = await import('@miden-sdk/miden-sdk');
1950
+ const originalAdviceImpl = vi.mocked(buildSignatureAdviceEntry).getMockImplementation();
1951
+ const originalWordFromHexImpl = vi.mocked(Word.fromHex).getMockImplementation();
1952
+ try {
1953
+ vi.mocked(Word.fromHex).mockImplementation((hex) => {
1954
+ let consumed = false;
1955
+ return {
1956
+ toHex: () => hex,
1957
+ toFelts: () => {
1958
+ if (consumed) {
1959
+ throw new Error('Word already consumed');
1960
+ }
1961
+ consumed = true;
1962
+ return [1, 2, 3, 4];
1963
+ },
1964
+ };
1965
+ });
1966
+ vi.mocked(buildSignatureAdviceEntry).mockImplementation((signerCommitment, message) => {
1967
+ message.toFelts();
1968
+ return {
1969
+ key: { toHex: () => signerCommitment.toHex() },
1970
+ values: [1, 2, 3],
1971
+ };
1972
+ });
1973
+ const config = {
1974
+ threshold: 1,
1975
+ signerCommitments: ['0x' + 'a'.repeat(64)],
1976
+ guardianCommitment: '0x' + 'c'.repeat(64),
1977
+ };
1978
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1979
+ const proposalId = '0x' + 'c'.repeat(64);
1980
+ const finalRequest = { kind: 'fresh-message-word-request' };
1981
+ vi.mocked(buildUpdateSignersTransactionRequest)
1982
+ .mockResolvedValueOnce({
1983
+ request: { kind: 'verify-change-threshold-request' },
1984
+ salt: { toHex: () => '0x' + 'd'.repeat(64) },
1985
+ configHash: { toHex: () => '0x' + 'e'.repeat(64) },
1986
+ })
1987
+ .mockResolvedValueOnce({
1988
+ request: finalRequest,
1989
+ salt: { toHex: () => '0x' + 'd'.repeat(64) },
1990
+ configHash: { toHex: () => '0x' + 'e'.repeat(64) },
1991
+ });
1992
+ multisig.proposals.set(proposalId, {
1993
+ id: proposalId,
1994
+ accountId: multisig.accountId,
1995
+ nonce: 1,
1996
+ status: 'ready',
1997
+ txSummary: 'AQID',
1998
+ signatures: [
1999
+ {
2000
+ signerId: '0x' + 'a'.repeat(64),
2001
+ signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
2002
+ timestamp: '2024-01-01T00:00:00Z',
2003
+ },
2004
+ ],
2005
+ metadata: {
2006
+ proposalType: 'change_threshold',
2007
+ targetThreshold: 1,
2008
+ targetSignerCommitments: ['0x' + 'a'.repeat(64)],
2009
+ description: '',
2010
+ },
2011
+ });
2012
+ mockFetch.mockResolvedValueOnce({
2013
+ ok: true,
2014
+ json: async () => ({
2015
+ account_id: multisig.accountId,
2016
+ nonce: 1,
2017
+ prev_commitment: '0x' + 'b'.repeat(64),
2018
+ delta_payload: {
2019
+ tx_summary: { data: 'AQID' },
2020
+ signatures: [],
2021
+ metadata: {
2022
+ proposal_type: 'change_threshold',
2023
+ target_threshold: 1,
2024
+ signer_commitments: ['0x' + 'a'.repeat(64)],
2025
+ },
2026
+ },
2027
+ status: {
2028
+ status: 'pending',
2029
+ timestamp: '2024-01-01T00:00:00Z',
2030
+ proposer_id: '0x' + 'a'.repeat(64),
2031
+ cosigner_sigs: [],
2032
+ },
2033
+ }),
2034
+ });
2035
+ mockFetch.mockResolvedValueOnce({
2036
+ ok: true,
2037
+ json: async () => ({
2038
+ account_id: multisig.accountId,
2039
+ nonce: 1,
2040
+ ack_sig: '0x' + 'f'.repeat(128),
2041
+ ack_scheme: 'falcon',
2042
+ }),
2043
+ });
2044
+ await expect(multisig.createTransactionProposalRequest(proposalId)).resolves.toBe(finalRequest);
2045
+ }
2046
+ finally {
2047
+ if (originalAdviceImpl) {
2048
+ vi.mocked(buildSignatureAdviceEntry).mockImplementation(originalAdviceImpl);
2049
+ }
2050
+ if (originalWordFromHexImpl) {
2051
+ vi.mocked(Word.fromHex).mockImplementation(originalWordFromHexImpl);
2052
+ }
2053
+ }
2054
+ });
2055
+ it('should reject advice-map key collisions during request creation', async () => {
2056
+ const { buildSignatureAdviceEntry } = await import('./utils/signature.js');
2057
+ vi.mocked(buildSignatureAdviceEntry)
2058
+ .mockImplementationOnce(() => ({
2059
+ key: { toHex: () => '0x' + 'f'.repeat(64) },
2060
+ values: [1, 2, 3],
2061
+ }))
2062
+ .mockImplementationOnce(() => ({
2063
+ key: { toHex: () => '0x' + 'f'.repeat(64) },
2064
+ values: [1, 2, 3],
2065
+ }));
2066
+ const config = {
2067
+ threshold: 2,
2068
+ signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2069
+ guardianCommitment: '0x' + 'c'.repeat(64),
2070
+ };
2071
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2072
+ const proposalId = '0x' + 'c'.repeat(64);
2073
+ multisig.proposals.set(proposalId, {
2074
+ id: proposalId,
2075
+ accountId: multisig.accountId,
2076
+ nonce: 1,
2077
+ status: 'ready',
2078
+ txSummary: 'AQID',
2079
+ signatures: [
2080
+ {
2081
+ signerId: '0x' + 'a'.repeat(64),
2082
+ signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
2083
+ timestamp: '2024-01-01T00:00:00Z',
2084
+ },
2085
+ {
2086
+ signerId: '0x' + 'b'.repeat(64),
2087
+ signature: { scheme: 'falcon', signature: '0x' + 'c'.repeat(128) },
2088
+ timestamp: '2024-01-01T00:00:01Z',
2089
+ },
2090
+ ],
2091
+ metadata: {
2092
+ proposalType: 'switch_guardian',
2093
+ newGuardianPubkey: '0x' + '1'.repeat(64),
2094
+ newGuardianEndpoint: 'http://new-guardian.com',
2095
+ description: '',
2096
+ },
2097
+ });
2098
+ await expect(multisig.createTransactionProposalRequest(proposalId)).rejects.toThrow('Duplicate advice-map key detected');
2099
+ });
2100
+ });
1641
2101
  describe('executeProposal', () => {
1642
2102
  it('should throw if proposal not found locally', async () => {
1643
2103
  const config = {
@@ -1824,11 +2284,9 @@ describe('Multisig', () => {
1824
2284
  ack_scheme: 'ecdsa',
1825
2285
  }),
1826
2286
  });
1827
- mockWebClient.executeTransaction.mockResolvedValueOnce({});
1828
- mockWebClient.proveTransaction.mockResolvedValueOnce({});
1829
- mockWebClient.submitProvenTransaction.mockResolvedValueOnce(1n);
1830
- mockWebClient.applyTransaction.mockResolvedValueOnce(undefined);
2287
+ mockWebClient.transactions.submit.mockResolvedValueOnce({});
1831
2288
  await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
2289
+ expect(mockWebClient.transactions.submit).toHaveBeenCalledTimes(1);
1832
2290
  expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(1, cosignerSignature, 'ecdsa');
1833
2291
  expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(2, ackSignature, 'ecdsa');
1834
2292
  expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(1, expect.anything(), expect.anything(), expect.anything(), cosignerPubkey, cosignerSignature);
@@ -1909,10 +2367,7 @@ describe('Multisig', () => {
1909
2367
  ack_scheme: 'ecdsa',
1910
2368
  }),
1911
2369
  });
1912
- mockWebClient.executeTransaction.mockResolvedValueOnce({});
1913
- mockWebClient.proveTransaction.mockResolvedValueOnce({});
1914
- mockWebClient.submitProvenTransaction.mockResolvedValueOnce(1n);
1915
- mockWebClient.applyTransaction.mockResolvedValueOnce(undefined);
2370
+ mockWebClient.transactions.submit.mockResolvedValueOnce({});
1916
2371
  await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
1917
2372
  expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(1, cosignerSignature, 'ecdsa');
1918
2373
  expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(2, ackSignature, 'ecdsa');
@@ -1959,12 +2414,9 @@ describe('Multisig', () => {
1959
2414
  ok: true,
1960
2415
  json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
1961
2416
  });
1962
- mockWebClient.executeTransaction.mockResolvedValueOnce({});
1963
- mockWebClient.proveTransaction.mockResolvedValueOnce({});
1964
- mockWebClient.submitProvenTransaction.mockResolvedValueOnce(1n);
1965
- mockWebClient.applyTransaction.mockResolvedValueOnce(undefined);
2417
+ mockWebClient.transactions.submit.mockResolvedValueOnce({});
1966
2418
  await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
1967
- expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
2419
+ expect(mockWebClient.transactions.submit).toHaveBeenCalledTimes(1);
1968
2420
  });
1969
2421
  it('should reject switch_guardian execution when endpoint commitment mismatches', async () => {
1970
2422
  const config = {