@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.
- package/README.md +10 -13
- package/dist/account/builder.d.ts +3 -3
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +10 -10
- package/dist/account/builder.js.map +1 -1
- package/dist/account/builder.test.js +16 -14
- package/dist/account/builder.test.js.map +1 -1
- package/dist/account/masm/account-components/auth.d.ts +4 -4
- package/dist/account/masm/account-components/auth.d.ts.map +1 -1
- package/dist/account/masm/account-components/auth.js +8 -12
- package/dist/account/masm/account-components/auth.js.map +1 -1
- package/dist/account/masm/auth.d.ts +4 -4
- package/dist/account/masm/auth.d.ts.map +1 -1
- package/dist/account/masm/auth.js +226 -124
- package/dist/account/masm/auth.js.map +1 -1
- package/dist/account/storage.d.ts.map +1 -1
- package/dist/account/storage.js +24 -6
- package/dist/account/storage.js.map +1 -1
- package/dist/account/storage.test.d.ts +2 -0
- package/dist/account/storage.test.d.ts.map +1 -0
- package/dist/account/storage.test.js +73 -0
- package/dist/account/storage.test.js.map +1 -0
- package/dist/client.d.ts +9 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +24 -14
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +45 -8
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +6 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -12
- package/dist/index.js.map +1 -1
- package/dist/multisig/proposal/execution.d.ts +3 -2
- package/dist/multisig/proposal/execution.d.ts.map +1 -1
- package/dist/multisig/proposal/execution.js +8 -12
- package/dist/multisig/proposal/execution.js.map +1 -1
- package/dist/multisig.d.ts +10 -4
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +97 -71
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +481 -29
- package/dist/multisig.test.js.map +1 -1
- package/dist/procedures.d.ts +8 -8
- package/dist/procedures.js +8 -8
- package/dist/raw-client.d.ts +13 -0
- package/dist/raw-client.d.ts.map +1 -0
- package/dist/raw-client.js +42 -0
- package/dist/raw-client.js.map +1 -0
- package/dist/raw-client.test.d.ts +2 -0
- package/dist/raw-client.test.d.ts.map +1 -0
- package/dist/raw-client.test.js +89 -0
- package/dist/raw-client.test.js.map +1 -0
- package/dist/signers/ecdsa.d.ts +3 -1
- package/dist/signers/ecdsa.d.ts.map +1 -1
- package/dist/signers/ecdsa.js +15 -1
- package/dist/signers/ecdsa.js.map +1 -1
- package/dist/signers/ecdsa.test.js +27 -4
- package/dist/signers/ecdsa.test.js.map +1 -1
- package/dist/signers/falcon.d.ts +3 -1
- package/dist/signers/falcon.d.ts.map +1 -1
- package/dist/signers/falcon.js +15 -1
- package/dist/signers/falcon.js.map +1 -1
- package/dist/signers/falcon.test.js +39 -3
- package/dist/signers/falcon.test.js.map +1 -1
- package/dist/transaction/consumeNotes.d.ts +2 -2
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +5 -4
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/options.d.ts +1 -0
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +7 -10
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/p2id.test.js +19 -9
- package/dist/transaction/p2id.test.js.map +1 -1
- package/dist/transaction/summary.d.ts +2 -2
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +4 -2
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/updateGuardian.d.ts +2 -2
- package/dist/transaction/updateGuardian.d.ts.map +1 -1
- package/dist/transaction/updateGuardian.js +8 -11
- package/dist/transaction/updateGuardian.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.d.ts +2 -2
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +8 -10
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +2 -2
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +8 -10
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.test.js +6 -6
- package/dist/transaction.test.js.map +1 -1
- package/dist/utils/key.d.ts.map +1 -1
- package/dist/utils/key.js +3 -3
- package/dist/utils/key.js.map +1 -1
- package/dist/utils/key.test.js +4 -2
- package/dist/utils/key.test.js.map +1 -1
- package/dist/utils/signature.d.ts +2 -0
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +10 -5
- package/dist/utils/signature.js.map +1 -1
- package/dist/utils/signature.test.js +9 -9
- package/dist/utils/signature.test.js.map +1 -1
- package/masm/account_components/auth/multisig.masm +2 -3
- package/masm/account_components/auth/multisig_ecdsa.masm +2 -3
- package/masm/account_components/auth/multisig_guardian.masm +2 -3
- package/masm/account_components/auth/multisig_guardian_ecdsa.masm +2 -3
- package/masm/auth/guardian.masm +30 -8
- package/masm/auth/guardian_ecdsa.masm +24 -7
- package/masm/auth/multisig.masm +86 -55
- package/masm/auth/multisig_ecdsa.masm +86 -54
- package/package.json +3 -3
- package/src/account/builder.test.ts +16 -14
- package/src/account/builder.ts +13 -12
- package/src/account/masm/account-components/auth.ts +8 -12
- package/src/account/masm/auth.ts +226 -124
- package/src/account/storage.test.ts +90 -0
- package/src/account/storage.ts +27 -6
- package/src/client.test.ts +52 -8
- package/src/client.ts +39 -16
- package/src/index.ts +6 -12
- package/src/multisig/proposal/execution.ts +18 -14
- package/src/multisig.test.ts +572 -29
- package/src/multisig.ts +121 -83
- package/src/procedures.ts +8 -8
- package/src/raw-client.test.ts +133 -0
- package/src/raw-client.ts +75 -0
- package/src/signers/ecdsa.test.ts +35 -4
- package/src/signers/ecdsa.ts +18 -2
- package/src/signers/falcon.test.ts +50 -3
- package/src/signers/falcon.ts +18 -2
- package/src/transaction/consumeNotes.ts +16 -5
- package/src/transaction/options.ts +1 -0
- package/src/transaction/p2id.test.ts +19 -9
- package/src/transaction/p2id.ts +8 -13
- package/src/transaction/summary.ts +11 -4
- package/src/transaction/updateGuardian.ts +22 -14
- package/src/transaction/updateProcedureThreshold.ts +20 -15
- package/src/transaction/updateSigners.ts +22 -14
- package/src/transaction.test.ts +6 -6
- package/src/utils/key.test.ts +4 -2
- package/src/utils/key.ts +7 -3
- package/src/utils/signature.test.ts +10 -10
- package/src/utils/signature.ts +12 -5
package/dist/multisig.test.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 = {
|