@openzeppelin/miden-multisig-client 0.13.1 → 0.13.2
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 +98 -119
- package/dist/account/builder.d.ts +18 -0
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +59 -29
- package/dist/account/builder.js.map +1 -1
- package/dist/account/builder.test.js +108 -32
- package/dist/account/builder.test.js.map +1 -1
- package/dist/account/index.d.ts +1 -1
- package/dist/account/index.d.ts.map +1 -1
- package/dist/account/index.js +1 -1
- package/dist/account/index.js.map +1 -1
- package/dist/account/masm/account-components/auth.d.ts +5 -0
- package/dist/account/masm/account-components/auth.d.ts.map +1 -0
- package/dist/account/masm/account-components/auth.js +67 -0
- package/dist/account/masm/account-components/auth.js.map +1 -0
- package/dist/account/masm/auth.d.ts +5 -0
- package/dist/account/masm/auth.d.ts.map +1 -0
- package/dist/account/masm/auth.js +1395 -0
- package/dist/account/masm/auth.js.map +1 -0
- package/dist/account/masm/index.d.ts +3 -0
- package/dist/account/masm/index.d.ts.map +1 -0
- package/dist/account/masm/index.js +5 -0
- package/dist/account/masm/index.js.map +1 -0
- package/dist/account/masm.d.ts +1 -4
- package/dist/account/masm.d.ts.map +1 -1
- package/dist/account/masm.js +3 -1034
- package/dist/account/masm.js.map +1 -1
- package/dist/account/storage.d.ts +2 -2
- package/dist/account/storage.d.ts.map +1 -1
- package/dist/account/storage.js +18 -16
- package/dist/account/storage.js.map +1 -1
- package/dist/client.d.ts +67 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +73 -22
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +31 -32
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +54 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -9
- package/dist/index.js.map +1 -1
- package/dist/inspector.d.ts +32 -6
- package/dist/inspector.d.ts.map +1 -1
- package/dist/inspector.js +58 -23
- package/dist/inspector.js.map +1 -1
- package/dist/inspector.test.js +18 -18
- package/dist/inspector.test.js.map +1 -1
- package/dist/multisig/proposal/execution.d.ts +4 -4
- package/dist/multisig/proposal/execution.d.ts.map +1 -1
- package/dist/multisig/proposal/execution.js +42 -31
- package/dist/multisig/proposal/execution.js.map +1 -1
- package/dist/multisig/proposal/parser.d.ts +3 -3
- package/dist/multisig/proposal/parser.d.ts.map +1 -1
- package/dist/multisig/proposal/parser.js +23 -23
- package/dist/multisig/proposal/parser.js.map +1 -1
- package/dist/multisig/signing.d.ts +3 -3
- package/dist/multisig/signing.d.ts.map +1 -1
- package/dist/multisig/signing.js +3 -3
- package/dist/multisig/signing.js.map +1 -1
- package/dist/multisig.d.ts +188 -48
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +650 -308
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +1680 -241
- package/dist/multisig.test.js.map +1 -1
- package/dist/procedures.d.ts +46 -24
- package/dist/procedures.d.ts.map +1 -1
- package/dist/procedures.js +46 -21
- package/dist/procedures.js.map +1 -1
- package/dist/proposal/factory.d.ts +18 -0
- package/dist/proposal/factory.d.ts.map +1 -0
- package/dist/proposal/factory.js +99 -0
- package/dist/proposal/factory.js.map +1 -0
- package/dist/proposal/metadata.d.ts +5 -12
- package/dist/proposal/metadata.d.ts.map +1 -1
- package/dist/proposal/metadata.js +118 -174
- package/dist/proposal/metadata.js.map +1 -1
- package/dist/proposal/signatures.d.ts +10 -0
- package/dist/proposal/signatures.d.ts.map +1 -0
- package/dist/proposal/signatures.js +54 -0
- package/dist/proposal/signatures.js.map +1 -0
- package/dist/signer.d.ts +2 -0
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +2 -0
- package/dist/signer.js.map +1 -1
- package/dist/signers/ecdsa.d.ts +3 -0
- package/dist/signers/ecdsa.d.ts.map +1 -1
- package/dist/signers/ecdsa.js +8 -4
- package/dist/signers/ecdsa.js.map +1 -1
- package/dist/signers/ecdsa.test.d.ts +2 -0
- package/dist/signers/ecdsa.test.d.ts.map +1 -0
- package/dist/signers/ecdsa.test.js +65 -0
- package/dist/signers/ecdsa.test.js.map +1 -0
- package/dist/signers/falcon.d.ts +3 -0
- package/dist/signers/falcon.d.ts.map +1 -1
- package/dist/signers/falcon.js +8 -4
- package/dist/signers/falcon.js.map +1 -1
- package/dist/signers/falcon.test.js +12 -0
- package/dist/signers/falcon.test.js.map +1 -1
- package/dist/signers/miden-wallet.d.ts +3 -1
- package/dist/signers/miden-wallet.d.ts.map +1 -1
- package/dist/signers/miden-wallet.js +11 -2
- package/dist/signers/miden-wallet.js.map +1 -1
- package/dist/signers/miden-wallet.test.js +26 -0
- package/dist/signers/miden-wallet.test.js.map +1 -1
- package/dist/signers/para.d.ts +3 -3
- package/dist/signers/para.d.ts.map +1 -1
- package/dist/signers/para.js +15 -6
- package/dist/signers/para.js.map +1 -1
- package/dist/signers/para.test.js +39 -0
- package/dist/signers/para.test.js.map +1 -1
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +1 -0
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/index.d.ts +2 -1
- package/dist/transaction/index.d.ts.map +1 -1
- package/dist/transaction/index.js +2 -1
- package/dist/transaction/index.js.map +1 -1
- package/dist/transaction/options.d.ts +1 -1
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/p2id.d.ts +1 -0
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +9 -3
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/updateGuardian.d.ts +7 -0
- package/dist/transaction/updateGuardian.d.ts.map +1 -0
- package/dist/transaction/{updatePsm.js → updateGuardian.js} +15 -29
- package/dist/transaction/updateGuardian.js.map +1 -0
- package/dist/transaction/updateProcedureThreshold.d.ts +9 -0
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -0
- package/dist/transaction/updateProcedureThreshold.js +55 -0
- package/dist/transaction/updateProcedureThreshold.js.map +1 -0
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +5 -23
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.d.ts +2 -1
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +2 -1
- package/dist/transaction.js.map +1 -1
- package/dist/types/proposal.d.ts +32 -15
- package/dist/types/proposal.d.ts.map +1 -1
- package/dist/types.d.ts +24 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/digest.d.ts +5 -0
- package/dist/utils/digest.d.ts.map +1 -1
- package/dist/utils/digest.js +34 -1
- package/dist/utils/digest.js.map +1 -1
- package/dist/utils/digest.test.d.ts +2 -0
- package/dist/utils/digest.test.d.ts.map +1 -0
- package/dist/utils/digest.test.js +48 -0
- package/dist/utils/digest.test.js.map +1 -0
- package/dist/utils/signature.d.ts +3 -1
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +31 -6
- package/dist/utils/signature.js.map +1 -1
- package/dist/utils/word.test.js +1 -52
- package/dist/utils/word.test.js.map +1 -1
- package/masm/account_components/auth/multisig.masm +13 -0
- package/masm/account_components/auth/multisig_ecdsa.masm +13 -0
- package/masm/account_components/auth/multisig_guardian.masm +17 -0
- package/masm/account_components/auth/multisig_guardian_ecdsa.masm +17 -0
- package/masm/auth/guardian.masm +177 -0
- package/masm/auth/guardian_ecdsa.masm +178 -0
- package/masm/{multisig.masm → auth/multisig.masm} +149 -55
- package/masm/{multisig_ecdsa.masm → auth/multisig_ecdsa.masm} +148 -56
- package/package.json +11 -9
- package/src/account/builder.test.ts +165 -0
- package/src/account/builder.ts +71 -34
- package/src/account/index.ts +1 -1
- package/src/account/masm/account-components/auth.ts +70 -0
- package/src/account/masm/auth.ts +1398 -0
- package/src/account/masm/index.ts +5 -0
- package/src/account/masm.ts +3 -1040
- package/src/account/storage.ts +18 -16
- package/src/client.test.ts +31 -35
- package/src/client.ts +98 -26
- package/src/index.ts +84 -22
- package/src/inspector.test.ts +17 -17
- package/src/inspector.ts +63 -29
- package/src/multisig/proposal/execution.ts +66 -49
- package/src/multisig/proposal/parser.ts +26 -26
- package/src/multisig/signing.ts +4 -4
- package/src/multisig.test.ts +2097 -248
- package/src/multisig.ts +882 -388
- package/src/procedures.ts +50 -26
- package/src/proposal/factory.ts +171 -0
- package/src/proposal/metadata.ts +165 -0
- package/src/proposal/signatures.ts +68 -0
- package/src/signer.ts +2 -0
- package/src/signers/ecdsa.test.ts +80 -0
- package/src/signers/ecdsa.ts +15 -4
- package/src/signers/falcon.test.ts +19 -0
- package/src/signers/falcon.ts +16 -5
- package/src/signers/miden-wallet.test.ts +47 -0
- package/src/signers/miden-wallet.ts +18 -1
- package/src/signers/para.test.ts +57 -0
- package/src/signers/para.ts +26 -7
- package/src/transaction/consumeNotes.ts +1 -0
- package/src/transaction/index.ts +2 -2
- package/src/transaction/options.ts +1 -2
- package/src/transaction/p2id.test.ts +190 -0
- package/src/transaction/p2id.ts +11 -5
- package/src/transaction/rpoRandomCoin.test.ts +64 -0
- package/src/transaction/rpoRandomCoin.ts +24 -0
- package/src/transaction/{updatePsm.ts → updateGuardian.ts} +19 -32
- package/src/transaction/updateProcedureThreshold.ts +97 -0
- package/src/transaction/updateSigners.ts +12 -27
- package/src/transaction.ts +5 -3
- package/src/types/proposal.ts +43 -11
- package/src/types.ts +29 -11
- package/src/utils/digest.test.ts +55 -0
- package/src/utils/digest.ts +51 -1
- package/src/utils/signature.ts +47 -9
- package/src/utils/word.test.ts +1 -55
- package/dist/account.d.ts +0 -104
- package/dist/account.d.ts.map +0 -1
- package/dist/account.js +0 -136
- package/dist/account.js.map +0 -1
- package/dist/builder.d.ts +0 -54
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js +0 -78
- package/dist/builder.js.map +0 -1
- package/dist/http.d.ts +0 -64
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -162
- package/dist/http.js.map +0 -1
- package/dist/keystore.d.ts +0 -65
- package/dist/keystore.d.ts.map +0 -1
- package/dist/keystore.js +0 -121
- package/dist/keystore.js.map +0 -1
- package/dist/miden.d.ts +0 -9
- package/dist/miden.d.ts.map +0 -1
- package/dist/miden.js +0 -26
- package/dist/miden.js.map +0 -1
- package/dist/multisig/metadata.d.ts +0 -18
- package/dist/multisig/metadata.d.ts.map +0 -1
- package/dist/multisig/metadata.js +0 -100
- package/dist/multisig/metadata.js.map +0 -1
- package/dist/multisig/metadata.test.d.ts +0 -2
- package/dist/multisig/metadata.test.d.ts.map +0 -1
- package/dist/multisig/metadata.test.js +0 -323
- package/dist/multisig/metadata.test.js.map +0 -1
- package/dist/proposal/exported.d.ts +0 -12
- package/dist/proposal/exported.d.ts.map +0 -1
- package/dist/proposal/exported.js +0 -88
- package/dist/proposal/exported.js.map +0 -1
- package/dist/signer.test.d.ts +0 -2
- package/dist/signer.test.d.ts.map +0 -1
- package/dist/signer.test.js +0 -123
- package/dist/signer.test.js.map +0 -1
- package/dist/transaction/p2id.interop.test.d.ts +0 -2
- package/dist/transaction/p2id.interop.test.d.ts.map +0 -1
- package/dist/transaction/p2id.interop.test.js +0 -36
- package/dist/transaction/p2id.interop.test.js.map +0 -1
- package/dist/transaction/updatePsm.d.ts +0 -7
- package/dist/transaction/updatePsm.d.ts.map +0 -1
- package/dist/transaction/updatePsm.js.map +0 -1
- package/dist/transport/http.d.ts +0 -64
- package/dist/transport/http.d.ts.map +0 -1
- package/dist/transport/http.js +0 -162
- package/dist/transport/http.js.map +0 -1
- package/dist/transport/index.d.ts +0 -5
- package/dist/transport/index.d.ts.map +0 -1
- package/dist/transport/index.js +0 -5
- package/dist/transport/index.js.map +0 -1
- package/dist/utils/state-validation.d.ts +0 -5
- package/dist/utils/state-validation.d.ts.map +0 -1
- package/dist/utils/state-validation.js +0 -30
- package/dist/utils/state-validation.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -6
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -52
- package/dist/utils/validation.js.map +0 -1
- package/masm/psm.masm +0 -178
- package/masm/psm_ecdsa.masm +0 -179
package/src/multisig.test.ts
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { Multisig } from './multisig.js';
|
|
3
|
-
import {
|
|
3
|
+
import { GuardianHttpClient, type Signer } from '@openzeppelin/guardian-client';
|
|
4
|
+
import {
|
|
5
|
+
buildUpdateProcedureThresholdTransactionRequest,
|
|
6
|
+
buildUpdateGuardianTransactionRequest,
|
|
7
|
+
buildUpdateSignersTransactionRequest,
|
|
8
|
+
executeForSummary,
|
|
9
|
+
} from './transaction.js';
|
|
10
|
+
|
|
11
|
+
const { mockRpcGetAccountDetails, mockAccountDeserialize, mockDetectConfig } = vi.hoisted(() => ({
|
|
12
|
+
mockRpcGetAccountDetails: vi.fn(),
|
|
13
|
+
mockAccountDeserialize: vi.fn(),
|
|
14
|
+
mockDetectConfig: vi.fn(),
|
|
15
|
+
}));
|
|
4
16
|
|
|
5
17
|
// Mock the Miden SDK
|
|
6
18
|
vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
19
|
+
Account: {
|
|
20
|
+
deserialize: mockAccountDeserialize,
|
|
21
|
+
},
|
|
7
22
|
AccountId: {
|
|
8
23
|
fromHex: vi.fn((hex: string) => ({ toString: () => hex })),
|
|
9
24
|
},
|
|
@@ -38,6 +53,10 @@ vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
|
38
53
|
toHex: () => '0x' + 'e'.repeat(64),
|
|
39
54
|
}),
|
|
40
55
|
},
|
|
56
|
+
Endpoint: vi.fn().mockImplementation((url: string) => ({ url })),
|
|
57
|
+
RpcClient: vi.fn().mockImplementation(() => ({
|
|
58
|
+
getAccountDetails: mockRpcGetAccountDetails,
|
|
59
|
+
})),
|
|
41
60
|
}));
|
|
42
61
|
|
|
43
62
|
// Mock transaction module
|
|
@@ -48,7 +67,12 @@ vi.mock('./transaction.js', () => ({
|
|
|
48
67
|
salt: { toHex: () => '0x' + 'd'.repeat(64) },
|
|
49
68
|
configHash: { toHex: () => '0x' + 'e'.repeat(64) },
|
|
50
69
|
}),
|
|
51
|
-
|
|
70
|
+
buildUpdateProcedureThresholdTransactionRequest: vi.fn().mockResolvedValue({
|
|
71
|
+
request: {},
|
|
72
|
+
salt: { toHex: () => '0x' + 'd'.repeat(64) },
|
|
73
|
+
configHash: { toHex: () => '0x' + 'e'.repeat(64) },
|
|
74
|
+
}),
|
|
75
|
+
buildUpdateGuardianTransactionRequest: vi.fn().mockResolvedValue({
|
|
52
76
|
request: {},
|
|
53
77
|
salt: { toHex: () => '0x' + 'd'.repeat(64) },
|
|
54
78
|
}),
|
|
@@ -62,50 +86,92 @@ vi.mock('./transaction.js', () => ({
|
|
|
62
86
|
}),
|
|
63
87
|
}));
|
|
64
88
|
|
|
65
|
-
vi.mock('./utils/signature.js', () =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
vi.mock('./utils/signature.js', async () => {
|
|
90
|
+
const actual = await vi.importActual<typeof import('./utils/signature.js')>('./utils/signature.js');
|
|
91
|
+
return {
|
|
92
|
+
...actual,
|
|
93
|
+
buildSignatureAdviceEntry: vi.fn().mockImplementation((signerCommitment: { toHex?: () => string }) => ({
|
|
94
|
+
key: { toHex: () => signerCommitment.toHex ? signerCommitment.toHex() : '0x' + 'f'.repeat(64) },
|
|
95
|
+
values: [1, 2, 3],
|
|
96
|
+
})),
|
|
97
|
+
signatureHexToBytes: vi.fn((hex: string) => new Uint8Array([0, 1, 2, 3])),
|
|
98
|
+
};
|
|
99
|
+
});
|
|
72
100
|
|
|
73
101
|
vi.mock('./utils/encoding.js', async () => {
|
|
74
102
|
const actual = await vi.importActual<typeof import('./utils/encoding.js')>('./utils/encoding.js');
|
|
75
103
|
return {
|
|
76
|
-
|
|
77
|
-
normalizeHexWord: vi.fn((hex: string) => '0x' + hex.replace(/^0x/i, '').padStart(64, '0')),
|
|
78
|
-
bytesToHex: actual.bytesToHex,
|
|
79
|
-
hexToBytes: actual.hexToBytes,
|
|
80
|
-
uint8ArrayToBase64: actual.uint8ArrayToBase64,
|
|
81
|
-
base64ToUint8Array: actual.base64ToUint8Array,
|
|
104
|
+
...actual,
|
|
105
|
+
normalizeHexWord: vi.fn((hex: string) => '0x' + hex.replace(/^0x/i, '').toLowerCase().padStart(64, '0')),
|
|
82
106
|
};
|
|
83
107
|
});
|
|
84
108
|
|
|
85
|
-
|
|
109
|
+
vi.mock('./inspector.js', () => ({
|
|
110
|
+
AccountInspector: {
|
|
111
|
+
fromAccount: mockDetectConfig,
|
|
112
|
+
},
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
// Mock fetch for GUARDIAN client
|
|
86
116
|
const mockFetch = vi.fn();
|
|
87
117
|
vi.stubGlobal('fetch', mockFetch);
|
|
88
118
|
|
|
119
|
+
function mockedAccount(commitmentHex: string, nonce = 0): any {
|
|
120
|
+
return {
|
|
121
|
+
commitment: () => ({
|
|
122
|
+
toHex: () => commitmentHex,
|
|
123
|
+
}),
|
|
124
|
+
nonce: () => ({
|
|
125
|
+
asInt: () => BigInt(nonce),
|
|
126
|
+
}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
89
130
|
describe('Multisig', () => {
|
|
90
|
-
let
|
|
131
|
+
let guardian: GuardianHttpClient;
|
|
91
132
|
let mockSigner: Signer;
|
|
92
133
|
let mockAccount: any;
|
|
93
134
|
let mockWebClient: any;
|
|
94
135
|
|
|
95
136
|
beforeEach(() => {
|
|
96
137
|
mockFetch.mockReset();
|
|
138
|
+
vi.mocked(executeForSummary).mockResolvedValue({
|
|
139
|
+
toCommitment: () => ({
|
|
140
|
+
toHex: () => '0x' + 'c'.repeat(64),
|
|
141
|
+
}),
|
|
142
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
143
|
+
} as any);
|
|
144
|
+
mockRpcGetAccountDetails.mockReset();
|
|
145
|
+
mockAccountDeserialize.mockReset();
|
|
146
|
+
mockRpcGetAccountDetails.mockResolvedValue({
|
|
147
|
+
commitment: () => ({
|
|
148
|
+
toHex: () => '0x' + 'b'.repeat(64),
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
mockAccountDeserialize.mockReturnValue(mockedAccount('0x' + 'b'.repeat(64), 1));
|
|
152
|
+
mockDetectConfig.mockReset();
|
|
153
|
+
mockDetectConfig.mockReturnValue({
|
|
154
|
+
threshold: 1,
|
|
155
|
+
numSigners: 1,
|
|
156
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
157
|
+
guardianEnabled: true,
|
|
158
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
159
|
+
vaultBalances: [],
|
|
160
|
+
procedureThresholds: new Map(),
|
|
161
|
+
});
|
|
97
162
|
|
|
98
|
-
|
|
163
|
+
guardian = new GuardianHttpClient('http://localhost:3000');
|
|
99
164
|
|
|
100
165
|
mockSigner = {
|
|
101
|
-
scheme: 'falcon',
|
|
102
166
|
commitment: '0x' + '1'.repeat(64),
|
|
103
167
|
publicKey: '0x' + '2'.repeat(64),
|
|
168
|
+
scheme: 'falcon',
|
|
104
169
|
signAccountIdWithTimestamp: vi.fn().mockResolvedValue('0x' + 'a'.repeat(128)),
|
|
105
|
-
|
|
170
|
+
signRequest: vi.fn().mockReturnValue('0x' + 'a'.repeat(128)),
|
|
171
|
+
signCommitment: vi.fn().mockReturnValue('0x' + 'b'.repeat(128)),
|
|
106
172
|
};
|
|
107
173
|
|
|
108
|
-
|
|
174
|
+
guardian.setSigner(mockSigner);
|
|
109
175
|
|
|
110
176
|
mockAccount = {
|
|
111
177
|
id: () => ({
|
|
@@ -123,6 +189,8 @@ describe('Multisig', () => {
|
|
|
123
189
|
applyTransaction: vi.fn(),
|
|
124
190
|
getConsumableNotes: vi.fn().mockResolvedValue([]),
|
|
125
191
|
syncState: vi.fn(),
|
|
192
|
+
getAccount: vi.fn().mockResolvedValue(null),
|
|
193
|
+
newAccount: vi.fn(),
|
|
126
194
|
};
|
|
127
195
|
});
|
|
128
196
|
|
|
@@ -131,28 +199,28 @@ describe('Multisig', () => {
|
|
|
131
199
|
const config = {
|
|
132
200
|
threshold: 2,
|
|
133
201
|
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
134
|
-
|
|
202
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
135
203
|
};
|
|
136
204
|
|
|
137
|
-
const multisig = new Multisig(mockAccount, config,
|
|
205
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
138
206
|
|
|
139
207
|
expect(multisig.threshold).toBe(2);
|
|
140
208
|
expect(multisig.signerCommitments).toEqual(config.signerCommitments);
|
|
141
|
-
expect(multisig.
|
|
209
|
+
expect(multisig.guardianCommitment).toBe(config.guardianCommitment);
|
|
142
210
|
expect(multisig.account).toBe(mockAccount);
|
|
143
211
|
});
|
|
144
212
|
|
|
145
|
-
it('should create Multisig
|
|
213
|
+
it('should create Multisig with explicit accountId override', () => {
|
|
146
214
|
const config = {
|
|
147
215
|
threshold: 2,
|
|
148
216
|
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
149
|
-
|
|
217
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
150
218
|
};
|
|
151
219
|
|
|
152
220
|
const accountId = '0x' + 'd'.repeat(30);
|
|
153
|
-
const multisig = new Multisig(
|
|
221
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, accountId);
|
|
154
222
|
|
|
155
|
-
expect(multisig.account).
|
|
223
|
+
expect(multisig.account).toBe(mockAccount);
|
|
156
224
|
expect(multisig.accountId).toBe(accountId);
|
|
157
225
|
});
|
|
158
226
|
});
|
|
@@ -162,22 +230,22 @@ describe('Multisig', () => {
|
|
|
162
230
|
const config = {
|
|
163
231
|
threshold: 1,
|
|
164
232
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
165
|
-
|
|
233
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
166
234
|
};
|
|
167
235
|
|
|
168
|
-
const multisig = new Multisig(mockAccount, config,
|
|
236
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
169
237
|
expect(multisig.accountId).toBe('0x' + 'a'.repeat(30));
|
|
170
238
|
});
|
|
171
239
|
|
|
172
|
-
it('should return provided account ID
|
|
240
|
+
it('should return provided account ID when constructor override is set', () => {
|
|
173
241
|
const config = {
|
|
174
242
|
threshold: 1,
|
|
175
243
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
176
|
-
|
|
244
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
177
245
|
};
|
|
178
246
|
|
|
179
247
|
const accountId = '0x' + 'e'.repeat(30);
|
|
180
|
-
const multisig = new Multisig(
|
|
248
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, accountId);
|
|
181
249
|
expect(multisig.accountId).toBe(accountId);
|
|
182
250
|
});
|
|
183
251
|
});
|
|
@@ -187,23 +255,23 @@ describe('Multisig', () => {
|
|
|
187
255
|
const config = {
|
|
188
256
|
threshold: 1,
|
|
189
257
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
190
|
-
|
|
258
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
191
259
|
};
|
|
192
260
|
|
|
193
|
-
const multisig = new Multisig(mockAccount, config,
|
|
261
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
194
262
|
expect(multisig.signerCommitment).toBe(mockSigner.commitment);
|
|
195
263
|
});
|
|
196
264
|
});
|
|
197
265
|
|
|
198
266
|
describe('fetchState', () => {
|
|
199
|
-
it('should fetch account state from
|
|
267
|
+
it('should fetch account state from GUARDIAN', async () => {
|
|
200
268
|
const config = {
|
|
201
269
|
threshold: 1,
|
|
202
270
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
203
|
-
|
|
271
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
204
272
|
};
|
|
205
273
|
|
|
206
|
-
const multisig = new Multisig(mockAccount, config,
|
|
274
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
207
275
|
|
|
208
276
|
mockFetch.mockResolvedValueOnce({
|
|
209
277
|
ok: true,
|
|
@@ -224,15 +292,378 @@ describe('Multisig', () => {
|
|
|
224
292
|
});
|
|
225
293
|
});
|
|
226
294
|
|
|
227
|
-
describe('
|
|
228
|
-
it('should
|
|
295
|
+
describe('syncState', () => {
|
|
296
|
+
it('should overwrite local state when account is missing locally', async () => {
|
|
297
|
+
const config = {
|
|
298
|
+
threshold: 1,
|
|
299
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
300
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const multisig = new Multisig(
|
|
304
|
+
mockAccount,
|
|
305
|
+
config,
|
|
306
|
+
guardian,
|
|
307
|
+
mockSigner,
|
|
308
|
+
mockWebClient,
|
|
309
|
+
undefined,
|
|
310
|
+
'https://rpc.devnet.miden.io'
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
mockWebClient.getAccount.mockResolvedValueOnce(null);
|
|
314
|
+
mockFetch.mockResolvedValueOnce({
|
|
315
|
+
ok: true,
|
|
316
|
+
json: async () => ({
|
|
317
|
+
account_id: multisig.accountId,
|
|
318
|
+
commitment: '0x' + 'b'.repeat(64),
|
|
319
|
+
state_json: { data: 'AQID' },
|
|
320
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
321
|
+
updated_at: '2024-01-02T00:00:00Z',
|
|
322
|
+
}),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
await multisig.syncState();
|
|
326
|
+
|
|
327
|
+
expect(mockWebClient.newAccount).toHaveBeenCalledTimes(1);
|
|
328
|
+
expect(mockRpcGetAccountDetails).toHaveBeenCalledTimes(1);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should overwrite local state when incoming commitment matches on-chain commitment', async () => {
|
|
332
|
+
const config = {
|
|
333
|
+
threshold: 1,
|
|
334
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
335
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const multisig = new Multisig(
|
|
339
|
+
mockAccount,
|
|
340
|
+
config,
|
|
341
|
+
guardian,
|
|
342
|
+
mockSigner,
|
|
343
|
+
mockWebClient,
|
|
344
|
+
undefined,
|
|
345
|
+
'https://rpc.devnet.miden.io'
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'a'.repeat(64), 0));
|
|
349
|
+
mockRpcGetAccountDetails.mockResolvedValueOnce({
|
|
350
|
+
commitment: () => ({
|
|
351
|
+
toHex: () => '0x' + 'b'.repeat(64),
|
|
352
|
+
}),
|
|
353
|
+
});
|
|
354
|
+
mockFetch.mockResolvedValueOnce({
|
|
355
|
+
ok: true,
|
|
356
|
+
json: async () => ({
|
|
357
|
+
account_id: multisig.accountId,
|
|
358
|
+
commitment: '0x' + 'b'.repeat(64),
|
|
359
|
+
state_json: { data: 'AQID' },
|
|
360
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
361
|
+
updated_at: '2024-01-02T00:00:00Z',
|
|
362
|
+
}),
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
await multisig.syncState();
|
|
366
|
+
|
|
367
|
+
expect(mockWebClient.newAccount).toHaveBeenCalledTimes(1);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('refreshes multisig config from synced account state', async () => {
|
|
371
|
+
const config = {
|
|
372
|
+
threshold: 1,
|
|
373
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
374
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const multisig = new Multisig(
|
|
378
|
+
mockAccount,
|
|
379
|
+
config,
|
|
380
|
+
guardian,
|
|
381
|
+
mockSigner,
|
|
382
|
+
mockWebClient,
|
|
383
|
+
undefined,
|
|
384
|
+
'https://rpc.devnet.miden.io'
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
mockWebClient.getAccount.mockResolvedValueOnce({
|
|
388
|
+
commitment: () => ({
|
|
389
|
+
toHex: () => '0x' + 'b'.repeat(64),
|
|
390
|
+
}),
|
|
391
|
+
});
|
|
392
|
+
mockFetch.mockResolvedValueOnce({
|
|
393
|
+
ok: true,
|
|
394
|
+
json: async () => ({
|
|
395
|
+
account_id: multisig.accountId,
|
|
396
|
+
commitment: '0x' + 'b'.repeat(64),
|
|
397
|
+
state_json: { data: 'AQID' },
|
|
398
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
399
|
+
updated_at: '2024-01-02T00:00:00Z',
|
|
400
|
+
}),
|
|
401
|
+
});
|
|
402
|
+
mockDetectConfig.mockReturnValueOnce({
|
|
403
|
+
threshold: 2,
|
|
404
|
+
numSigners: 2,
|
|
405
|
+
signerCommitments: ['0x' + '1'.repeat(64), '0x' + '2'.repeat(64)],
|
|
406
|
+
guardianEnabled: true,
|
|
407
|
+
guardianCommitment: '0x' + 'd'.repeat(64),
|
|
408
|
+
vaultBalances: [],
|
|
409
|
+
procedureThresholds: new Map(),
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
await multisig.syncState();
|
|
413
|
+
|
|
414
|
+
expect(multisig.threshold).toBe(2);
|
|
415
|
+
expect(multisig.signerCommitments).toEqual([
|
|
416
|
+
'0x' + '1'.repeat(64),
|
|
417
|
+
'0x' + '2'.repeat(64),
|
|
418
|
+
]);
|
|
419
|
+
expect(multisig.guardianCommitment).toBe('0x' + 'd'.repeat(64));
|
|
420
|
+
expect(mockWebClient.newAccount).not.toHaveBeenCalled();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should overwrite local state when account is not found on-chain', async () => {
|
|
424
|
+
const config = {
|
|
425
|
+
threshold: 1,
|
|
426
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
427
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const multisig = new Multisig(
|
|
431
|
+
mockAccount,
|
|
432
|
+
config,
|
|
433
|
+
guardian,
|
|
434
|
+
mockSigner,
|
|
435
|
+
mockWebClient,
|
|
436
|
+
undefined,
|
|
437
|
+
'https://rpc.devnet.miden.io'
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'a'.repeat(64), 0));
|
|
441
|
+
mockRpcGetAccountDetails.mockRejectedValueOnce(
|
|
442
|
+
new Error('No account header record found for given ID')
|
|
443
|
+
);
|
|
444
|
+
mockFetch.mockResolvedValueOnce({
|
|
445
|
+
ok: true,
|
|
446
|
+
json: async () => ({
|
|
447
|
+
account_id: multisig.accountId,
|
|
448
|
+
commitment: '0x' + 'b'.repeat(64),
|
|
449
|
+
state_json: { data: 'AQID' },
|
|
450
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
451
|
+
updated_at: '2024-01-02T00:00:00Z',
|
|
452
|
+
}),
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
await multisig.syncState();
|
|
456
|
+
|
|
457
|
+
expect(mockWebClient.newAccount).toHaveBeenCalledTimes(1);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should throw when incoming commitment does not match on-chain commitment', async () => {
|
|
461
|
+
const config = {
|
|
462
|
+
threshold: 1,
|
|
463
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
464
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const multisig = new Multisig(
|
|
468
|
+
mockAccount,
|
|
469
|
+
config,
|
|
470
|
+
guardian,
|
|
471
|
+
mockSigner,
|
|
472
|
+
mockWebClient,
|
|
473
|
+
undefined,
|
|
474
|
+
'https://rpc.devnet.miden.io'
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'a'.repeat(64), 0));
|
|
478
|
+
mockAccountDeserialize.mockReturnValueOnce(mockedAccount('0x' + 'b'.repeat(64), 1));
|
|
479
|
+
mockRpcGetAccountDetails.mockResolvedValueOnce({
|
|
480
|
+
commitment: () => ({
|
|
481
|
+
toHex: () => '0x' + 'c'.repeat(64),
|
|
482
|
+
}),
|
|
483
|
+
});
|
|
484
|
+
mockFetch.mockResolvedValueOnce({
|
|
485
|
+
ok: true,
|
|
486
|
+
json: async () => ({
|
|
487
|
+
account_id: multisig.accountId,
|
|
488
|
+
commitment: '0x' + 'b'.repeat(64),
|
|
489
|
+
state_json: { data: 'AQID' },
|
|
490
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
491
|
+
updated_at: '2024-01-02T00:00:00Z',
|
|
492
|
+
}),
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
await expect(multisig.syncState()).rejects.toThrow('Refusing to overwrite local state');
|
|
496
|
+
expect(mockWebClient.newAccount).not.toHaveBeenCalled();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('should throw when incoming state nonce is lower than local nonce', async () => {
|
|
500
|
+
const config = {
|
|
501
|
+
threshold: 1,
|
|
502
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
503
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const multisig = new Multisig(
|
|
507
|
+
mockAccount,
|
|
508
|
+
config,
|
|
509
|
+
guardian,
|
|
510
|
+
mockSigner,
|
|
511
|
+
mockWebClient,
|
|
512
|
+
undefined,
|
|
513
|
+
'https://rpc.devnet.miden.io'
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'a'.repeat(64), 3));
|
|
517
|
+
mockAccountDeserialize.mockReturnValueOnce(mockedAccount('0x' + 'b'.repeat(64), 2));
|
|
518
|
+
mockFetch.mockResolvedValueOnce({
|
|
519
|
+
ok: true,
|
|
520
|
+
json: async () => ({
|
|
521
|
+
account_id: multisig.accountId,
|
|
522
|
+
commitment: '0x' + 'b'.repeat(64),
|
|
523
|
+
state_json: { data: 'AQID' },
|
|
524
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
525
|
+
updated_at: '2024-01-02T00:00:00Z',
|
|
526
|
+
}),
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
await expect(multisig.syncState()).rejects.toThrow(
|
|
530
|
+
'incoming nonce 2 is not greater than local nonce 3'
|
|
531
|
+
);
|
|
532
|
+
expect(mockWebClient.newAccount).not.toHaveBeenCalled();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('should throw when incoming state nonce equals local nonce but commitment differs', async () => {
|
|
536
|
+
const config = {
|
|
537
|
+
threshold: 1,
|
|
538
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
539
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const multisig = new Multisig(
|
|
543
|
+
mockAccount,
|
|
544
|
+
config,
|
|
545
|
+
guardian,
|
|
546
|
+
mockSigner,
|
|
547
|
+
mockWebClient,
|
|
548
|
+
undefined,
|
|
549
|
+
'https://rpc.devnet.miden.io'
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'a'.repeat(64), 2));
|
|
553
|
+
mockAccountDeserialize.mockReturnValueOnce(mockedAccount('0x' + 'b'.repeat(64), 2));
|
|
554
|
+
mockFetch.mockResolvedValueOnce({
|
|
555
|
+
ok: true,
|
|
556
|
+
json: async () => ({
|
|
557
|
+
account_id: multisig.accountId,
|
|
558
|
+
commitment: '0x' + 'b'.repeat(64),
|
|
559
|
+
state_json: { data: 'AQID' },
|
|
560
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
561
|
+
updated_at: '2024-01-02T00:00:00Z',
|
|
562
|
+
}),
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
await expect(multisig.syncState()).rejects.toThrow(
|
|
566
|
+
'incoming nonce 2 is not greater than local nonce 2'
|
|
567
|
+
);
|
|
568
|
+
expect(mockWebClient.newAccount).not.toHaveBeenCalled();
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
describe('verifyStateCommitment', () => {
|
|
573
|
+
it('should pass when local and on-chain commitments match', async () => {
|
|
574
|
+
const config = {
|
|
575
|
+
threshold: 1,
|
|
576
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
577
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
578
|
+
};
|
|
579
|
+
mockWebClient.getAccount.mockResolvedValueOnce({
|
|
580
|
+
commitment: () => ({
|
|
581
|
+
toHex: () => '0x' + 'b'.repeat(64),
|
|
582
|
+
}),
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
const multisigWithRpc = new Multisig(
|
|
586
|
+
mockAccount,
|
|
587
|
+
config,
|
|
588
|
+
guardian,
|
|
589
|
+
mockSigner,
|
|
590
|
+
mockWebClient,
|
|
591
|
+
undefined,
|
|
592
|
+
'https://rpc.devnet.miden.io'
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
await expect(
|
|
596
|
+
multisigWithRpc.verifyStateCommitment()
|
|
597
|
+
).resolves.toMatchObject({
|
|
598
|
+
accountId: multisigWithRpc.accountId,
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
it('should throw when local account state is missing', async () => {
|
|
603
|
+
const config = {
|
|
604
|
+
threshold: 1,
|
|
605
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
606
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
607
|
+
};
|
|
608
|
+
mockWebClient.getAccount.mockResolvedValueOnce(null);
|
|
609
|
+
|
|
610
|
+
const multisigWithRpc = new Multisig(
|
|
611
|
+
mockAccount,
|
|
612
|
+
config,
|
|
613
|
+
guardian,
|
|
614
|
+
mockSigner,
|
|
615
|
+
mockWebClient,
|
|
616
|
+
undefined,
|
|
617
|
+
'https://rpc.devnet.miden.io'
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
await expect(
|
|
621
|
+
multisigWithRpc.verifyStateCommitment()
|
|
622
|
+
).rejects.toThrow('Local account state not found');
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('should throw when local and on-chain commitments differ', async () => {
|
|
626
|
+
const config = {
|
|
627
|
+
threshold: 1,
|
|
628
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
629
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
630
|
+
};
|
|
631
|
+
mockWebClient.getAccount.mockResolvedValueOnce({
|
|
632
|
+
commitment: () => ({
|
|
633
|
+
toHex: () => '0x' + 'f'.repeat(64),
|
|
634
|
+
}),
|
|
635
|
+
});
|
|
636
|
+
mockRpcGetAccountDetails.mockResolvedValueOnce({
|
|
637
|
+
commitment: () => ({
|
|
638
|
+
toHex: () => '0x' + 'b'.repeat(64),
|
|
639
|
+
}),
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const multisigWithRpc = new Multisig(
|
|
643
|
+
mockAccount,
|
|
644
|
+
config,
|
|
645
|
+
guardian,
|
|
646
|
+
mockSigner,
|
|
647
|
+
mockWebClient,
|
|
648
|
+
undefined,
|
|
649
|
+
'https://rpc.devnet.miden.io'
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
await expect(
|
|
653
|
+
multisigWithRpc.verifyStateCommitment()
|
|
654
|
+
).rejects.toThrow('Local account commitment does not match on-chain commitment');
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
describe('registerOnGuardian', () => {
|
|
659
|
+
it('should register account on GUARDIAN', async () => {
|
|
229
660
|
const config = {
|
|
230
661
|
threshold: 1,
|
|
231
662
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
232
|
-
|
|
663
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
233
664
|
};
|
|
234
665
|
|
|
235
|
-
const multisig = new Multisig(mockAccount, config,
|
|
666
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
236
667
|
|
|
237
668
|
mockFetch.mockResolvedValueOnce({
|
|
238
669
|
ok: true,
|
|
@@ -243,29 +674,60 @@ describe('Multisig', () => {
|
|
|
243
674
|
}),
|
|
244
675
|
});
|
|
245
676
|
|
|
246
|
-
await expect(multisig.
|
|
677
|
+
await expect(multisig.registerOnGuardian()).resolves.toBeUndefined();
|
|
247
678
|
});
|
|
248
679
|
|
|
249
|
-
it('should
|
|
680
|
+
it('should register ECDSA accounts with MidenEcdsa auth', async () => {
|
|
250
681
|
const config = {
|
|
251
682
|
threshold: 1,
|
|
252
683
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
253
|
-
|
|
684
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
const ecdsaSigner: Signer = {
|
|
688
|
+
...mockSigner,
|
|
689
|
+
publicKey: '0x' + '2'.repeat(66),
|
|
690
|
+
scheme: 'ecdsa',
|
|
254
691
|
};
|
|
255
692
|
|
|
256
|
-
|
|
693
|
+
guardian.setSigner(ecdsaSigner);
|
|
694
|
+
const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
|
|
695
|
+
|
|
696
|
+
mockFetch.mockResolvedValueOnce({
|
|
697
|
+
ok: true,
|
|
698
|
+
json: async () => ({
|
|
699
|
+
success: true,
|
|
700
|
+
message: 'Account configured',
|
|
701
|
+
ack_pubkey: '0x' + 'd'.repeat(66),
|
|
702
|
+
}),
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
await expect(multisig.registerOnGuardian()).resolves.toBeUndefined();
|
|
257
706
|
|
|
258
|
-
|
|
707
|
+
const [, requestInit] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
708
|
+
const body = JSON.parse(String(requestInit.body));
|
|
709
|
+
expect(body.auth).toEqual({
|
|
710
|
+
MidenEcdsa: {
|
|
711
|
+
cosigner_commitments: config.signerCommitments,
|
|
712
|
+
},
|
|
713
|
+
});
|
|
259
714
|
});
|
|
260
715
|
|
|
261
|
-
it('should accept initial state base64', async () => {
|
|
716
|
+
it('should accept explicit initial state base64', async () => {
|
|
262
717
|
const config = {
|
|
263
718
|
threshold: 1,
|
|
264
719
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
265
|
-
|
|
720
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
266
721
|
};
|
|
267
722
|
|
|
268
|
-
const multisig = new Multisig(
|
|
723
|
+
const multisig = new Multisig(
|
|
724
|
+
mockAccount,
|
|
725
|
+
config,
|
|
726
|
+
guardian,
|
|
727
|
+
mockSigner,
|
|
728
|
+
mockWebClient,
|
|
729
|
+
'0x' + 'e'.repeat(30),
|
|
730
|
+
);
|
|
269
731
|
|
|
270
732
|
mockFetch.mockResolvedValueOnce({
|
|
271
733
|
ok: true,
|
|
@@ -275,17 +737,17 @@ describe('Multisig', () => {
|
|
|
275
737
|
}),
|
|
276
738
|
});
|
|
277
739
|
|
|
278
|
-
await expect(multisig.
|
|
740
|
+
await expect(multisig.registerOnGuardian('base64initialstate')).resolves.toBeUndefined();
|
|
279
741
|
});
|
|
280
742
|
|
|
281
|
-
it('should throw on
|
|
743
|
+
it('should throw on GUARDIAN registration failure', async () => {
|
|
282
744
|
const config = {
|
|
283
745
|
threshold: 1,
|
|
284
746
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
285
|
-
|
|
747
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
286
748
|
};
|
|
287
749
|
|
|
288
|
-
const multisig = new Multisig(mockAccount, config,
|
|
750
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
289
751
|
|
|
290
752
|
mockFetch.mockResolvedValueOnce({
|
|
291
753
|
ok: true,
|
|
@@ -295,19 +757,19 @@ describe('Multisig', () => {
|
|
|
295
757
|
}),
|
|
296
758
|
});
|
|
297
759
|
|
|
298
|
-
await expect(multisig.
|
|
760
|
+
await expect(multisig.registerOnGuardian()).rejects.toThrow('Failed to register on GUARDIAN');
|
|
299
761
|
});
|
|
300
762
|
});
|
|
301
763
|
|
|
302
|
-
describe('
|
|
303
|
-
it('should sync proposals from
|
|
764
|
+
describe('syncProposals', () => {
|
|
765
|
+
it('should sync proposals from GUARDIAN', async () => {
|
|
304
766
|
const config = {
|
|
305
767
|
threshold: 2,
|
|
306
768
|
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
307
|
-
|
|
769
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
308
770
|
};
|
|
309
771
|
|
|
310
|
-
const multisig = new Multisig(mockAccount, config,
|
|
772
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
311
773
|
|
|
312
774
|
const mockProposals = [
|
|
313
775
|
{
|
|
@@ -330,7 +792,7 @@ describe('Multisig', () => {
|
|
|
330
792
|
proposer_id: '0x' + 'c'.repeat(64),
|
|
331
793
|
cosigner_sigs: [
|
|
332
794
|
{
|
|
333
|
-
signer_id: '0x' + '
|
|
795
|
+
signer_id: '0x' + 'a'.repeat(64),
|
|
334
796
|
signature: { scheme: 'falcon', signature: '0x' + 'e'.repeat(128) },
|
|
335
797
|
timestamp: '2024-01-01T00:00:00Z',
|
|
336
798
|
},
|
|
@@ -344,22 +806,21 @@ describe('Multisig', () => {
|
|
|
344
806
|
json: async () => ({ proposals: mockProposals }),
|
|
345
807
|
});
|
|
346
808
|
|
|
347
|
-
const proposals = await multisig.
|
|
809
|
+
const proposals = await multisig.syncProposals();
|
|
348
810
|
|
|
349
811
|
expect(proposals.length).toBe(1);
|
|
350
812
|
expect(proposals[0].nonce).toBe(1);
|
|
351
|
-
expect(proposals[0].status
|
|
352
|
-
expect(proposals[0].commitment).toBe(proposals[0].id);
|
|
813
|
+
expect(proposals[0].status).toBe('pending');
|
|
353
814
|
});
|
|
354
815
|
|
|
355
816
|
it('should return ready status when enough signatures', async () => {
|
|
356
817
|
const config = {
|
|
357
818
|
threshold: 1, // Only 1 signature needed
|
|
358
819
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
359
|
-
|
|
820
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
360
821
|
};
|
|
361
822
|
|
|
362
|
-
const multisig = new Multisig(mockAccount, config,
|
|
823
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
363
824
|
|
|
364
825
|
const mockProposals = [
|
|
365
826
|
{
|
|
@@ -382,7 +843,7 @@ describe('Multisig', () => {
|
|
|
382
843
|
proposer_id: '0x' + 'c'.repeat(64),
|
|
383
844
|
cosigner_sigs: [
|
|
384
845
|
{
|
|
385
|
-
signer_id: '0x' + '
|
|
846
|
+
signer_id: '0x' + 'a'.repeat(64),
|
|
386
847
|
signature: { scheme: 'falcon', signature: '0x' + 'e'.repeat(128) },
|
|
387
848
|
timestamp: '2024-01-01T00:00:00Z',
|
|
388
849
|
},
|
|
@@ -396,44 +857,626 @@ describe('Multisig', () => {
|
|
|
396
857
|
json: async () => ({ proposals: mockProposals }),
|
|
397
858
|
});
|
|
398
859
|
|
|
399
|
-
const proposals = await multisig.
|
|
860
|
+
const proposals = await multisig.syncProposals();
|
|
400
861
|
|
|
401
|
-
expect(proposals[0].status
|
|
862
|
+
expect(proposals[0].status).toBe('ready');
|
|
402
863
|
});
|
|
403
|
-
});
|
|
404
864
|
|
|
405
|
-
|
|
406
|
-
it('should return empty list initially', () => {
|
|
865
|
+
it('should reject proposals whose metadata does not match tx_summary', async () => {
|
|
407
866
|
const config = {
|
|
408
867
|
threshold: 1,
|
|
409
868
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
410
|
-
|
|
869
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
411
870
|
};
|
|
412
871
|
|
|
413
|
-
const multisig = new Multisig(mockAccount, config,
|
|
414
|
-
|
|
872
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
873
|
+
|
|
874
|
+
mockFetch.mockResolvedValueOnce({
|
|
875
|
+
ok: true,
|
|
876
|
+
json: async () => ({
|
|
877
|
+
proposals: [
|
|
878
|
+
{
|
|
879
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
880
|
+
nonce: 1,
|
|
881
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
882
|
+
delta_payload: {
|
|
883
|
+
tx_summary: { data: 'AQID' },
|
|
884
|
+
signatures: [],
|
|
885
|
+
metadata: {
|
|
886
|
+
proposal_type: 'add_signer',
|
|
887
|
+
target_threshold: 1,
|
|
888
|
+
signer_commitments: ['0x' + 'a'.repeat(64)],
|
|
889
|
+
description: '',
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
status: {
|
|
893
|
+
status: 'pending',
|
|
894
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
895
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
896
|
+
cosigner_sigs: [],
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
}),
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
vi.mocked(executeForSummary).mockResolvedValueOnce({
|
|
904
|
+
toCommitment: () => ({
|
|
905
|
+
toHex: () => '0x' + 'f'.repeat(64),
|
|
906
|
+
}),
|
|
907
|
+
} as any);
|
|
908
|
+
|
|
909
|
+
await expect(multisig.syncProposals()).rejects.toThrow(
|
|
910
|
+
'Invalid proposal: metadata does not match tx_summary'
|
|
911
|
+
);
|
|
415
912
|
});
|
|
416
|
-
});
|
|
417
913
|
|
|
418
|
-
|
|
419
|
-
it('should create a new proposal', async () => {
|
|
914
|
+
it('should reject non-32-byte signer IDs from GUARDIAN proposals', async () => {
|
|
420
915
|
const config = {
|
|
421
916
|
threshold: 1,
|
|
422
917
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
423
|
-
|
|
918
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
424
919
|
};
|
|
425
920
|
|
|
426
|
-
const multisig = new Multisig(mockAccount, config,
|
|
921
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
427
922
|
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
923
|
+
const mockProposals = [
|
|
924
|
+
{
|
|
925
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
926
|
+
nonce: 1,
|
|
927
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
928
|
+
delta_payload: {
|
|
929
|
+
tx_summary: { data: 'AQID' },
|
|
930
|
+
signatures: [],
|
|
931
|
+
metadata: {
|
|
932
|
+
proposal_type: 'add_signer',
|
|
933
|
+
target_threshold: 1,
|
|
934
|
+
signer_commitments: ['0x' + 'a'.repeat(64)],
|
|
935
|
+
description: '',
|
|
936
|
+
},
|
|
937
|
+
},
|
|
938
|
+
status: {
|
|
939
|
+
status: 'pending',
|
|
940
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
941
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
942
|
+
cosigner_sigs: [
|
|
943
|
+
{
|
|
944
|
+
signer_id: '0x1',
|
|
945
|
+
signature: { scheme: 'falcon', signature: '0x' + 'e'.repeat(128) },
|
|
946
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
947
|
+
},
|
|
948
|
+
],
|
|
949
|
+
},
|
|
950
|
+
},
|
|
951
|
+
];
|
|
952
|
+
|
|
953
|
+
mockFetch.mockResolvedValueOnce({
|
|
954
|
+
ok: true,
|
|
955
|
+
json: async () => ({ proposals: mockProposals }),
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
await expect(multisig.syncProposals()).rejects.toThrow('expected signerId as 32-byte hex');
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
it('should reject duplicate normalized signer IDs from GUARDIAN proposals', async () => {
|
|
962
|
+
const config = {
|
|
963
|
+
threshold: 2,
|
|
964
|
+
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
965
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
969
|
+
|
|
970
|
+
const mockProposals = [
|
|
971
|
+
{
|
|
972
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
973
|
+
nonce: 1,
|
|
974
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
975
|
+
delta_payload: {
|
|
976
|
+
tx_summary: { data: 'AQID' },
|
|
977
|
+
signatures: [],
|
|
978
|
+
metadata: {
|
|
979
|
+
proposal_type: 'add_signer',
|
|
980
|
+
target_threshold: 2,
|
|
981
|
+
signer_commitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
982
|
+
description: '',
|
|
983
|
+
},
|
|
984
|
+
},
|
|
985
|
+
status: {
|
|
986
|
+
status: 'pending',
|
|
987
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
988
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
989
|
+
cosigner_sigs: [
|
|
990
|
+
{
|
|
991
|
+
signer_id: '0x' + 'A'.repeat(64),
|
|
992
|
+
signature: { scheme: 'falcon', signature: '0x' + 'e'.repeat(128) },
|
|
993
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
signer_id: '0x' + 'a'.repeat(64),
|
|
997
|
+
signature: { scheme: 'falcon', signature: '0x' + 'f'.repeat(128) },
|
|
998
|
+
timestamp: '2024-01-01T00:00:01Z',
|
|
999
|
+
},
|
|
1000
|
+
],
|
|
1001
|
+
},
|
|
1002
|
+
},
|
|
1003
|
+
];
|
|
1004
|
+
|
|
1005
|
+
mockFetch.mockResolvedValueOnce({
|
|
1006
|
+
ok: true,
|
|
1007
|
+
json: async () => ({ proposals: mockProposals }),
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
await expect(multisig.syncProposals()).rejects.toThrow('duplicate signatures for signer');
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
describe('listProposals', () => {
|
|
1015
|
+
it('should return empty list initially', () => {
|
|
1016
|
+
const config = {
|
|
1017
|
+
threshold: 1,
|
|
1018
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1019
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1023
|
+
expect(multisig.listProposals()).toEqual([]);
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
describe('createProposal', () => {
|
|
1028
|
+
it('should create a new proposal', async () => {
|
|
1029
|
+
const config = {
|
|
1030
|
+
threshold: 1,
|
|
1031
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1032
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1036
|
+
|
|
1037
|
+
const mockDelta = {
|
|
1038
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1039
|
+
nonce: 1,
|
|
1040
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1041
|
+
delta_payload: {
|
|
1042
|
+
tx_summary: { data: 'AQID' },
|
|
1043
|
+
signatures: [],
|
|
1044
|
+
},
|
|
1045
|
+
status: {
|
|
1046
|
+
status: 'pending',
|
|
1047
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1048
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1049
|
+
cosigner_sigs: [],
|
|
1050
|
+
},
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
mockFetch.mockResolvedValueOnce({
|
|
1054
|
+
ok: true,
|
|
1055
|
+
json: async () => ({
|
|
1056
|
+
delta: mockDelta,
|
|
1057
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1058
|
+
}),
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
const proposal = await multisig.createProposal(1, 'AQID', {
|
|
1062
|
+
proposalType: 'add_signer',
|
|
1063
|
+
targetThreshold: 1,
|
|
1064
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1065
|
+
description: '',
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
expect(proposal.nonce).toBe(1);
|
|
1069
|
+
expect(proposal.id).toBe('0x' + 'c'.repeat(64));
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it('should reject a mismatched returned commitment', async () => {
|
|
1073
|
+
const config = {
|
|
1074
|
+
threshold: 1,
|
|
1075
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1076
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1080
|
+
|
|
1081
|
+
const mockDelta = {
|
|
1082
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1083
|
+
nonce: 1,
|
|
1084
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1085
|
+
delta_payload: {
|
|
1086
|
+
tx_summary: { data: 'AQID' },
|
|
1087
|
+
signatures: [],
|
|
1088
|
+
},
|
|
1089
|
+
status: {
|
|
1090
|
+
status: 'pending',
|
|
1091
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1092
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1093
|
+
cosigner_sigs: [],
|
|
1094
|
+
},
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
mockFetch.mockResolvedValueOnce({
|
|
1098
|
+
ok: true,
|
|
1099
|
+
json: async () => ({
|
|
1100
|
+
delta: mockDelta,
|
|
1101
|
+
commitment: '0x' + 'd'.repeat(64),
|
|
1102
|
+
}),
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
await expect(
|
|
1106
|
+
multisig.createProposal(1, 'AQID', {
|
|
1107
|
+
proposalType: 'add_signer',
|
|
1108
|
+
targetThreshold: 1,
|
|
1109
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1110
|
+
description: '',
|
|
1111
|
+
}),
|
|
1112
|
+
).rejects.toThrow(
|
|
1113
|
+
'Invalid proposal: commitment 0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd does not match tx_summary 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
|
|
1114
|
+
);
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
it('should reject a response whose tx_summary does not match the provided metadata', async () => {
|
|
1118
|
+
const config = {
|
|
1119
|
+
threshold: 1,
|
|
1120
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1121
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1125
|
+
|
|
1126
|
+
const mockDelta = {
|
|
1127
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1128
|
+
nonce: 1,
|
|
1129
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1130
|
+
delta_payload: {
|
|
1131
|
+
tx_summary: { data: 'AQID' },
|
|
1132
|
+
signatures: [],
|
|
1133
|
+
},
|
|
1134
|
+
status: {
|
|
1135
|
+
status: 'pending',
|
|
1136
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1137
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1138
|
+
cosigner_sigs: [],
|
|
1139
|
+
},
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
mockFetch.mockResolvedValueOnce({
|
|
1143
|
+
ok: true,
|
|
1144
|
+
json: async () => ({
|
|
1145
|
+
delta: mockDelta,
|
|
1146
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1147
|
+
}),
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
vi.mocked(executeForSummary).mockResolvedValueOnce({
|
|
1151
|
+
toCommitment: () => ({
|
|
1152
|
+
toHex: () => '0x' + 'f'.repeat(64),
|
|
1153
|
+
}),
|
|
1154
|
+
} as any);
|
|
1155
|
+
|
|
1156
|
+
await expect(
|
|
1157
|
+
multisig.createProposal(1, 'AQID', {
|
|
1158
|
+
proposalType: 'add_signer',
|
|
1159
|
+
targetThreshold: 1,
|
|
1160
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1161
|
+
description: '',
|
|
1162
|
+
})
|
|
1163
|
+
).rejects.toThrow('Invalid proposal: metadata does not match tx_summary');
|
|
1164
|
+
});
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
describe('createP2idProposal', () => {
|
|
1168
|
+
it('should include the faucet asset in the proposal description', async () => {
|
|
1169
|
+
const { executeForSummary } = await import('./transaction.js');
|
|
1170
|
+
vi.mocked(executeForSummary).mockResolvedValue({
|
|
1171
|
+
toCommitment: () => ({
|
|
1172
|
+
toHex: () => '0x' + 'c'.repeat(64),
|
|
1173
|
+
}),
|
|
1174
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
1175
|
+
} as any);
|
|
1176
|
+
|
|
1177
|
+
const config = {
|
|
1178
|
+
threshold: 1,
|
|
1179
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1180
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1184
|
+
|
|
1185
|
+
const mockDelta = {
|
|
1186
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1187
|
+
nonce: 1,
|
|
1188
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1189
|
+
delta_payload: {
|
|
1190
|
+
tx_summary: { data: 'AQID' },
|
|
1191
|
+
signatures: [],
|
|
1192
|
+
metadata: {
|
|
1193
|
+
proposal_type: 'p2id',
|
|
1194
|
+
recipient_id: '0xrecipient',
|
|
1195
|
+
faucet_id: '0xfaucet',
|
|
1196
|
+
amount: '100',
|
|
1197
|
+
description: '',
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
status: {
|
|
1201
|
+
status: 'pending',
|
|
1202
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1203
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1204
|
+
cosigner_sigs: [],
|
|
1205
|
+
},
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
mockFetch.mockResolvedValueOnce({
|
|
1209
|
+
ok: true,
|
|
1210
|
+
json: async () => ({
|
|
1211
|
+
delta: mockDelta,
|
|
1212
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1213
|
+
}),
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
const proposal = await multisig.createP2idProposal('0xrecipient', '0xfaucet', 100n, 1);
|
|
1217
|
+
|
|
1218
|
+
expect(proposal.metadata.description).toBe('Send 100 of asset 0xfaucet... to 0xrecipien...');
|
|
1219
|
+
});
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
describe('createChangeThresholdProposal', () => {
|
|
1223
|
+
it('passes the signer scheme to update-signers requests', async () => {
|
|
1224
|
+
vi.mocked(executeForSummary).mockResolvedValue({
|
|
1225
|
+
toCommitment: () => ({
|
|
1226
|
+
toHex: () => '0x' + 'c'.repeat(64),
|
|
1227
|
+
}),
|
|
1228
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
1229
|
+
} as any);
|
|
1230
|
+
|
|
1231
|
+
const ecdsaSigner: Signer = {
|
|
1232
|
+
...mockSigner,
|
|
1233
|
+
publicKey: '0x' + '2'.repeat(66),
|
|
1234
|
+
scheme: 'ecdsa',
|
|
1235
|
+
};
|
|
1236
|
+
guardian.setSigner(ecdsaSigner);
|
|
1237
|
+
|
|
1238
|
+
const config = {
|
|
1239
|
+
threshold: 1,
|
|
1240
|
+
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
1241
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
const mockDelta = {
|
|
1245
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1246
|
+
nonce: 1,
|
|
1247
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1248
|
+
delta_payload: {
|
|
1249
|
+
tx_summary: { data: 'AQID' },
|
|
1250
|
+
signatures: [],
|
|
1251
|
+
metadata: {
|
|
1252
|
+
proposal_type: 'change_threshold',
|
|
1253
|
+
target_threshold: 2,
|
|
1254
|
+
description: '',
|
|
1255
|
+
},
|
|
1256
|
+
},
|
|
1257
|
+
status: {
|
|
1258
|
+
status: 'pending',
|
|
1259
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1260
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1261
|
+
cosigner_sigs: [],
|
|
1262
|
+
},
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
mockFetch.mockResolvedValueOnce({
|
|
1266
|
+
ok: true,
|
|
1267
|
+
json: async () => ({
|
|
1268
|
+
delta: mockDelta,
|
|
1269
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1270
|
+
}),
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
|
|
1274
|
+
await multisig.createChangeThresholdProposal(2, 1);
|
|
1275
|
+
|
|
1276
|
+
expect(buildUpdateSignersTransactionRequest).toHaveBeenCalledWith(
|
|
1277
|
+
mockWebClient,
|
|
1278
|
+
2,
|
|
1279
|
+
config.signerCommitments,
|
|
1280
|
+
{ signatureScheme: 'ecdsa' },
|
|
1281
|
+
);
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
describe('createSwitchGuardianProposal', () => {
|
|
1286
|
+
it('should verify new endpoint commitment before creating proposal', async () => {
|
|
1287
|
+
vi.mocked(executeForSummary).mockResolvedValue({
|
|
1288
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
1289
|
+
} as any);
|
|
1290
|
+
|
|
1291
|
+
const config = {
|
|
1292
|
+
threshold: 1,
|
|
1293
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1294
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1298
|
+
const newGuardianPubkey = '0x' + '1'.repeat(64);
|
|
1299
|
+
|
|
1300
|
+
mockFetch.mockResolvedValueOnce({
|
|
1301
|
+
ok: true,
|
|
1302
|
+
json: async () => ({ commitment: newGuardianPubkey }),
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
const proposal = await multisig.createSwitchGuardianProposal('http://new-guardian.com', newGuardianPubkey);
|
|
1306
|
+
|
|
1307
|
+
expect(proposal.metadata?.proposalType).toBe('switch_guardian');
|
|
1308
|
+
if (proposal.metadata?.proposalType === 'switch_guardian') {
|
|
1309
|
+
expect(proposal.metadata.newGuardianEndpoint).toBe('http://new-guardian.com');
|
|
1310
|
+
}
|
|
1311
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
1312
|
+
'http://new-guardian.com/pubkey?scheme=falcon',
|
|
1313
|
+
expect.objectContaining({ method: 'GET' })
|
|
1314
|
+
);
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
it('should reject switch proposal when endpoint commitment does not match', async () => {
|
|
1318
|
+
vi.mocked(executeForSummary).mockResolvedValue({
|
|
1319
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
1320
|
+
} as any);
|
|
1321
|
+
|
|
1322
|
+
const config = {
|
|
1323
|
+
threshold: 1,
|
|
1324
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1325
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1329
|
+
|
|
1330
|
+
mockFetch.mockResolvedValueOnce({
|
|
1331
|
+
ok: true,
|
|
1332
|
+
json: async () => ({ commitment: '0x' + '2'.repeat(64) }),
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
await expect(
|
|
1336
|
+
multisig.createSwitchGuardianProposal('http://new-guardian.com', '0x' + '1'.repeat(64))
|
|
1337
|
+
).rejects.toThrow('Refusing to use GUARDIAN endpoint');
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
it('should use the signer scheme when resolving new GUARDIAN commitments', async () => {
|
|
1341
|
+
vi.mocked(executeForSummary).mockResolvedValue({
|
|
1342
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
1343
|
+
} as any);
|
|
1344
|
+
|
|
1345
|
+
const ecdsaSigner: Signer = {
|
|
1346
|
+
...mockSigner,
|
|
1347
|
+
publicKey: '0x' + '2'.repeat(66),
|
|
1348
|
+
scheme: 'ecdsa',
|
|
1349
|
+
};
|
|
1350
|
+
guardian.setSigner(ecdsaSigner);
|
|
1351
|
+
|
|
1352
|
+
const config = {
|
|
1353
|
+
threshold: 1,
|
|
1354
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1355
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
|
|
1359
|
+
const newGuardianCommitment = '0x' + '1'.repeat(64);
|
|
1360
|
+
|
|
1361
|
+
mockFetch.mockResolvedValueOnce({
|
|
1362
|
+
ok: true,
|
|
1363
|
+
json: async () => ({ commitment: newGuardianCommitment }),
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
await multisig.createSwitchGuardianProposal('http://new-guardian.com', newGuardianCommitment);
|
|
1367
|
+
|
|
1368
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
1369
|
+
'http://new-guardian.com/pubkey?scheme=ecdsa',
|
|
1370
|
+
expect.objectContaining({ method: 'GET' }),
|
|
1371
|
+
);
|
|
1372
|
+
expect(buildUpdateGuardianTransactionRequest).toHaveBeenCalledWith(
|
|
1373
|
+
mockWebClient,
|
|
1374
|
+
newGuardianCommitment,
|
|
1375
|
+
{ signatureScheme: 'ecdsa' },
|
|
1376
|
+
);
|
|
1377
|
+
});
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
describe('createUpdateProcedureThresholdProposal', () => {
|
|
1381
|
+
it('should create procedure-threshold update proposals', async () => {
|
|
1382
|
+
vi.mocked(executeForSummary).mockResolvedValue({
|
|
1383
|
+
toCommitment: () => ({
|
|
1384
|
+
toHex: () => '0x' + 'c'.repeat(64),
|
|
1385
|
+
}),
|
|
1386
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
1387
|
+
} as any);
|
|
1388
|
+
|
|
1389
|
+
const config = {
|
|
1390
|
+
threshold: 2,
|
|
1391
|
+
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
1392
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1396
|
+
|
|
1397
|
+
const mockDelta = {
|
|
1398
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1399
|
+
nonce: 1,
|
|
1400
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1401
|
+
delta_payload: {
|
|
1402
|
+
tx_summary: { data: 'AQID' },
|
|
1403
|
+
signatures: [],
|
|
1404
|
+
metadata: {
|
|
1405
|
+
proposal_type: 'update_procedure_threshold',
|
|
1406
|
+
target_threshold: 1,
|
|
1407
|
+
target_procedure: 'send_asset',
|
|
1408
|
+
description: '',
|
|
1409
|
+
},
|
|
1410
|
+
},
|
|
1411
|
+
status: {
|
|
1412
|
+
status: 'pending',
|
|
1413
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1414
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1415
|
+
cosigner_sigs: [],
|
|
1416
|
+
},
|
|
1417
|
+
};
|
|
1418
|
+
|
|
1419
|
+
mockFetch.mockResolvedValueOnce({
|
|
1420
|
+
ok: true,
|
|
1421
|
+
json: async () => ({
|
|
1422
|
+
delta: mockDelta,
|
|
1423
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1424
|
+
}),
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
const proposal = await multisig.createUpdateProcedureThresholdProposal('send_asset', 1, 1);
|
|
1428
|
+
|
|
1429
|
+
expect(buildUpdateProcedureThresholdTransactionRequest).toHaveBeenCalledWith(
|
|
1430
|
+
mockWebClient,
|
|
1431
|
+
'send_asset',
|
|
1432
|
+
1,
|
|
1433
|
+
{ signatureScheme: 'falcon' },
|
|
1434
|
+
);
|
|
1435
|
+
expect(proposal.metadata.proposalType).toBe('update_procedure_threshold');
|
|
1436
|
+
if (proposal.metadata.proposalType === 'update_procedure_threshold') {
|
|
1437
|
+
expect(proposal.metadata.targetProcedure).toBe('send_asset');
|
|
1438
|
+
expect(proposal.metadata.targetThreshold).toBe(1);
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
it('passes the signer scheme to ECDSA procedure-threshold updates', async () => {
|
|
1443
|
+
vi.mocked(executeForSummary).mockResolvedValue({
|
|
1444
|
+
toCommitment: () => ({
|
|
1445
|
+
toHex: () => '0x' + 'c'.repeat(64),
|
|
1446
|
+
}),
|
|
1447
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
1448
|
+
} as any);
|
|
1449
|
+
|
|
1450
|
+
const ecdsaSigner: Signer = {
|
|
1451
|
+
...mockSigner,
|
|
1452
|
+
publicKey: '0x' + '2'.repeat(66),
|
|
1453
|
+
scheme: 'ecdsa',
|
|
1454
|
+
};
|
|
1455
|
+
guardian.setSigner(ecdsaSigner);
|
|
1456
|
+
|
|
1457
|
+
const config = {
|
|
1458
|
+
threshold: 2,
|
|
1459
|
+
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
1460
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
|
|
1464
|
+
|
|
1465
|
+
const mockDelta = {
|
|
1466
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1467
|
+
nonce: 1,
|
|
1468
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1469
|
+
delta_payload: {
|
|
1470
|
+
tx_summary: { data: 'AQID' },
|
|
1471
|
+
signatures: [],
|
|
1472
|
+
metadata: {
|
|
1473
|
+
proposal_type: 'update_procedure_threshold',
|
|
1474
|
+
target_threshold: 1,
|
|
1475
|
+
target_procedure: 'send_asset',
|
|
1476
|
+
description: '',
|
|
1477
|
+
},
|
|
1478
|
+
},
|
|
1479
|
+
status: {
|
|
437
1480
|
status: 'pending',
|
|
438
1481
|
timestamp: '2024-01-01T00:00:00Z',
|
|
439
1482
|
proposer_id: '0x' + 'c'.repeat(64),
|
|
@@ -445,31 +1488,30 @@ describe('Multisig', () => {
|
|
|
445
1488
|
ok: true,
|
|
446
1489
|
json: async () => ({
|
|
447
1490
|
delta: mockDelta,
|
|
448
|
-
commitment: '0x' + '
|
|
1491
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
449
1492
|
}),
|
|
450
1493
|
});
|
|
451
1494
|
|
|
452
|
-
|
|
453
|
-
proposalType: 'add_signer',
|
|
454
|
-
targetThreshold: 1,
|
|
455
|
-
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
456
|
-
description: '',
|
|
457
|
-
});
|
|
1495
|
+
await multisig.createUpdateProcedureThresholdProposal('send_asset', 1, 1);
|
|
458
1496
|
|
|
459
|
-
expect(
|
|
460
|
-
|
|
1497
|
+
expect(buildUpdateProcedureThresholdTransactionRequest).toHaveBeenCalledWith(
|
|
1498
|
+
mockWebClient,
|
|
1499
|
+
'send_asset',
|
|
1500
|
+
1,
|
|
1501
|
+
{ signatureScheme: 'ecdsa' },
|
|
1502
|
+
);
|
|
461
1503
|
});
|
|
462
1504
|
});
|
|
463
1505
|
|
|
464
|
-
describe('
|
|
1506
|
+
describe('signProposal', () => {
|
|
465
1507
|
it('should sign a proposal', async () => {
|
|
466
1508
|
const config = {
|
|
467
1509
|
threshold: 1,
|
|
468
|
-
signerCommitments: [
|
|
469
|
-
|
|
1510
|
+
signerCommitments: [mockSigner.commitment],
|
|
1511
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
470
1512
|
};
|
|
471
1513
|
|
|
472
|
-
const multisig = new Multisig(mockAccount, config,
|
|
1514
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
473
1515
|
|
|
474
1516
|
// First create a proposal
|
|
475
1517
|
const mockDelta = {
|
|
@@ -492,7 +1534,7 @@ describe('Multisig', () => {
|
|
|
492
1534
|
ok: true,
|
|
493
1535
|
json: async () => ({
|
|
494
1536
|
delta: mockDelta,
|
|
495
|
-
commitment: '0x' + '
|
|
1537
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
496
1538
|
}),
|
|
497
1539
|
});
|
|
498
1540
|
|
|
@@ -503,7 +1545,6 @@ describe('Multisig', () => {
|
|
|
503
1545
|
description: '',
|
|
504
1546
|
});
|
|
505
1547
|
|
|
506
|
-
// Now sign it
|
|
507
1548
|
const signedDelta = {
|
|
508
1549
|
...mockDelta,
|
|
509
1550
|
status: {
|
|
@@ -529,114 +1570,496 @@ describe('Multisig', () => {
|
|
|
529
1570
|
},
|
|
530
1571
|
};
|
|
531
1572
|
|
|
532
|
-
// Mock for signDeltaProposal
|
|
533
1573
|
mockFetch.mockResolvedValueOnce({
|
|
534
1574
|
ok: true,
|
|
535
1575
|
json: async () => signedDelta,
|
|
536
1576
|
});
|
|
537
1577
|
|
|
538
|
-
|
|
1578
|
+
const proposalId = '0x' + 'c'.repeat(64);
|
|
1579
|
+
const signedProposal = await multisig.signProposal(proposalId);
|
|
1580
|
+
|
|
1581
|
+
expect(mockSigner.signCommitment).toHaveBeenCalledWith(proposalId);
|
|
1582
|
+
expect(signedProposal.signatures.length).toBe(1);
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
it('should reject signing when metadata does not match tx_summary', async () => {
|
|
1586
|
+
const config = {
|
|
1587
|
+
threshold: 1,
|
|
1588
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1589
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1593
|
+
|
|
1594
|
+
const mockDelta = {
|
|
1595
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1596
|
+
nonce: 1,
|
|
1597
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1598
|
+
delta_payload: {
|
|
1599
|
+
tx_summary: { data: 'AQID' },
|
|
1600
|
+
signatures: [],
|
|
1601
|
+
},
|
|
1602
|
+
status: {
|
|
1603
|
+
status: 'pending',
|
|
1604
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1605
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1606
|
+
cosigner_sigs: [],
|
|
1607
|
+
},
|
|
1608
|
+
};
|
|
1609
|
+
|
|
539
1610
|
mockFetch.mockResolvedValueOnce({
|
|
540
1611
|
ok: true,
|
|
541
|
-
json: async () => ({
|
|
1612
|
+
json: async () => ({
|
|
1613
|
+
delta: mockDelta,
|
|
1614
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1615
|
+
}),
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
await multisig.createProposal(1, 'AQID', {
|
|
1619
|
+
proposalType: 'add_signer',
|
|
1620
|
+
targetThreshold: 1,
|
|
1621
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1622
|
+
description: '',
|
|
542
1623
|
});
|
|
543
1624
|
|
|
1625
|
+
vi.mocked(executeForSummary).mockResolvedValueOnce({
|
|
1626
|
+
toCommitment: () => ({
|
|
1627
|
+
toHex: () => '0x' + 'f'.repeat(64),
|
|
1628
|
+
}),
|
|
1629
|
+
} as any);
|
|
1630
|
+
|
|
1631
|
+
await expect(multisig.signProposal('0x' + 'c'.repeat(64))).rejects.toThrow(
|
|
1632
|
+
'Invalid proposal: metadata does not match tx_summary'
|
|
1633
|
+
);
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
it('should reject proposals for a different account before signing', async () => {
|
|
1637
|
+
const config = {
|
|
1638
|
+
threshold: 1,
|
|
1639
|
+
signerCommitments: [mockSigner.commitment],
|
|
1640
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1641
|
+
};
|
|
1642
|
+
|
|
1643
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
544
1644
|
const proposalId = '0x' + 'd'.repeat(64);
|
|
545
|
-
const proposals = await multisig.signTransactionProposal(proposalId);
|
|
546
1645
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
1646
|
+
mockFetch.mockResolvedValueOnce({
|
|
1647
|
+
ok: true,
|
|
1648
|
+
json: async () => ({
|
|
1649
|
+
proposals: [
|
|
1650
|
+
{
|
|
1651
|
+
account_id: '0x' + 'f'.repeat(30),
|
|
1652
|
+
nonce: 1,
|
|
1653
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1654
|
+
delta_payload: {
|
|
1655
|
+
tx_summary: { data: 'AQID' },
|
|
1656
|
+
signatures: [],
|
|
1657
|
+
metadata: {
|
|
1658
|
+
proposal_type: 'add_signer',
|
|
1659
|
+
description: '',
|
|
1660
|
+
target_threshold: 1,
|
|
1661
|
+
signer_commitments: [mockSigner.commitment],
|
|
1662
|
+
},
|
|
1663
|
+
},
|
|
1664
|
+
status: {
|
|
1665
|
+
status: 'pending',
|
|
1666
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1667
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1668
|
+
cosigner_sigs: [],
|
|
1669
|
+
},
|
|
1670
|
+
},
|
|
1671
|
+
],
|
|
1672
|
+
}),
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1675
|
+
await expect(multisig.signProposal(proposalId)).rejects.toThrow(
|
|
1676
|
+
'Proposal is for a different account: 0x' + 'f'.repeat(30),
|
|
1677
|
+
);
|
|
1678
|
+
expect(mockSigner.signCommitment).not.toHaveBeenCalled();
|
|
1679
|
+
});
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1682
|
+
describe('importProposal', () => {
|
|
1683
|
+
it('should reject imported proposals whose metadata does not match tx_summary', async () => {
|
|
1684
|
+
const config = {
|
|
1685
|
+
threshold: 1,
|
|
1686
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1687
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1688
|
+
};
|
|
1689
|
+
|
|
1690
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1691
|
+
|
|
1692
|
+
vi.mocked(executeForSummary).mockResolvedValueOnce({
|
|
1693
|
+
toCommitment: () => ({
|
|
1694
|
+
toHex: () => '0x' + 'f'.repeat(64),
|
|
1695
|
+
}),
|
|
1696
|
+
} as any);
|
|
1697
|
+
|
|
1698
|
+
await expect(
|
|
1699
|
+
multisig.importProposal(
|
|
1700
|
+
JSON.stringify({
|
|
1701
|
+
accountId: '0x' + 'a'.repeat(30),
|
|
1702
|
+
nonce: 1,
|
|
1703
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1704
|
+
txSummaryBase64: 'AQID',
|
|
1705
|
+
signatures: [],
|
|
1706
|
+
metadata: {
|
|
1707
|
+
proposalType: 'add_signer',
|
|
1708
|
+
targetThreshold: 1,
|
|
1709
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1710
|
+
description: '',
|
|
1711
|
+
},
|
|
1712
|
+
})
|
|
1713
|
+
)
|
|
1714
|
+
).rejects.toThrow('Invalid proposal: metadata does not match tx_summary');
|
|
1715
|
+
});
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
describe('signProposalOffline', () => {
|
|
1719
|
+
it('should reject signing imported proposals whose metadata does not match tx_summary', async () => {
|
|
1720
|
+
const config = {
|
|
1721
|
+
threshold: 1,
|
|
1722
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1723
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1724
|
+
};
|
|
1725
|
+
|
|
1726
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1727
|
+
|
|
1728
|
+
vi.mocked(executeForSummary).mockResolvedValueOnce({
|
|
1729
|
+
toCommitment: () => ({
|
|
1730
|
+
toHex: () => '0x' + 'c'.repeat(64),
|
|
1731
|
+
}),
|
|
1732
|
+
} as any);
|
|
1733
|
+
|
|
1734
|
+
const proposal = await multisig.importProposal(
|
|
1735
|
+
JSON.stringify({
|
|
1736
|
+
accountId: '0x' + 'a'.repeat(30),
|
|
1737
|
+
nonce: 1,
|
|
1738
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1739
|
+
txSummaryBase64: 'AQID',
|
|
1740
|
+
signatures: [],
|
|
1741
|
+
metadata: {
|
|
1742
|
+
proposalType: 'add_signer',
|
|
1743
|
+
targetThreshold: 1,
|
|
1744
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1745
|
+
description: '',
|
|
1746
|
+
},
|
|
1747
|
+
})
|
|
1748
|
+
);
|
|
1749
|
+
|
|
1750
|
+
proposal.metadata = {
|
|
1751
|
+
proposalType: 'add_signer',
|
|
1752
|
+
targetThreshold: 2,
|
|
1753
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1754
|
+
description: '',
|
|
1755
|
+
};
|
|
1756
|
+
|
|
1757
|
+
vi.mocked(executeForSummary).mockResolvedValueOnce({
|
|
1758
|
+
toCommitment: () => ({
|
|
1759
|
+
toHex: () => '0x' + 'f'.repeat(64),
|
|
1760
|
+
}),
|
|
1761
|
+
} as any);
|
|
1762
|
+
|
|
1763
|
+
await expect(multisig.signProposalOffline(proposal.id)).rejects.toThrow(
|
|
1764
|
+
'Invalid proposal: metadata does not match tx_summary'
|
|
1765
|
+
);
|
|
1766
|
+
});
|
|
1767
|
+
});
|
|
1768
|
+
|
|
1769
|
+
describe('exportProposal', () => {
|
|
1770
|
+
it('should export proposal for offline signing', async () => {
|
|
1771
|
+
const config = {
|
|
1772
|
+
threshold: 2,
|
|
1773
|
+
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
1774
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1775
|
+
};
|
|
1776
|
+
|
|
1777
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1778
|
+
|
|
1779
|
+
const mockProposals = [
|
|
1780
|
+
{
|
|
1781
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1782
|
+
nonce: 1,
|
|
1783
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1784
|
+
delta_payload: {
|
|
1785
|
+
tx_summary: { data: 'AQID' },
|
|
1786
|
+
signatures: [],
|
|
1787
|
+
metadata: {
|
|
1788
|
+
proposal_type: 'add_signer',
|
|
1789
|
+
description: '',
|
|
1790
|
+
target_threshold: 1,
|
|
1791
|
+
signer_commitments: ['0x' + 'a'.repeat(64)],
|
|
1792
|
+
},
|
|
1793
|
+
},
|
|
1794
|
+
status: {
|
|
1795
|
+
status: 'pending',
|
|
1796
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1797
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1798
|
+
cosigner_sigs: [
|
|
1799
|
+
{
|
|
1800
|
+
signer_id: '0x' + 'a'.repeat(64),
|
|
1801
|
+
signature: { scheme: 'falcon', signature: '0x' + 'e'.repeat(128) },
|
|
1802
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1803
|
+
},
|
|
1804
|
+
],
|
|
1805
|
+
},
|
|
1806
|
+
},
|
|
1807
|
+
];
|
|
1808
|
+
|
|
1809
|
+
mockFetch.mockResolvedValueOnce({
|
|
1810
|
+
ok: true,
|
|
1811
|
+
json: async () => mockProposals[0],
|
|
1812
|
+
});
|
|
1813
|
+
|
|
1814
|
+
// The proposal ID is computed from tx_summary, which is mocked to return 'c'.repeat(64)
|
|
1815
|
+
const exported = await multisig.exportProposal('0x' + 'c'.repeat(64));
|
|
1816
|
+
|
|
1817
|
+
expect(exported.accountId).toBe('0x' + 'a'.repeat(30));
|
|
1818
|
+
expect(exported.nonce).toBe(1);
|
|
1819
|
+
expect(exported.txSummaryBase64).toBe('AQID');
|
|
1820
|
+
expect(exported.signatures.length).toBe(1);
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
it('should preserve ECDSA signature metadata in exported proposals', async () => {
|
|
1824
|
+
const config = {
|
|
1825
|
+
threshold: 2,
|
|
1826
|
+
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
1827
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1828
|
+
};
|
|
1829
|
+
|
|
1830
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1831
|
+
const publicKey = '0x' + 'd'.repeat(66);
|
|
1832
|
+
|
|
1833
|
+
mockFetch.mockResolvedValueOnce({
|
|
1834
|
+
ok: true,
|
|
1835
|
+
json: async () => ({
|
|
1836
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1837
|
+
nonce: 1,
|
|
1838
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1839
|
+
delta_payload: {
|
|
1840
|
+
tx_summary: { data: 'AQID' },
|
|
1841
|
+
signatures: [],
|
|
1842
|
+
metadata: {
|
|
1843
|
+
proposal_type: 'change_threshold',
|
|
1844
|
+
description: '',
|
|
1845
|
+
target_threshold: 2,
|
|
1846
|
+
signer_commitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
1847
|
+
},
|
|
1848
|
+
},
|
|
1849
|
+
status: {
|
|
1850
|
+
status: 'pending',
|
|
1851
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1852
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1853
|
+
cosigner_sigs: [
|
|
1854
|
+
{
|
|
1855
|
+
signer_id: '0x' + 'a'.repeat(64),
|
|
1856
|
+
signature: {
|
|
1857
|
+
scheme: 'ecdsa',
|
|
1858
|
+
signature: '0x' + 'e'.repeat(130),
|
|
1859
|
+
public_key: publicKey,
|
|
1860
|
+
},
|
|
1861
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1862
|
+
},
|
|
1863
|
+
],
|
|
1864
|
+
},
|
|
1865
|
+
}),
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
const exported = await multisig.exportProposal('0x' + 'c'.repeat(64));
|
|
1869
|
+
|
|
1870
|
+
expect(exported.signatures).toEqual([
|
|
1871
|
+
{
|
|
1872
|
+
commitment: '0x' + 'a'.repeat(64),
|
|
1873
|
+
signatureHex: '0x' + 'e'.repeat(130),
|
|
1874
|
+
scheme: 'ecdsa',
|
|
1875
|
+
publicKey,
|
|
1876
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1877
|
+
},
|
|
1878
|
+
]);
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1881
|
+
it('should throw if proposal not found', async () => {
|
|
1882
|
+
const config = {
|
|
1883
|
+
threshold: 1,
|
|
1884
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1885
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1888
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1889
|
+
|
|
1890
|
+
mockFetch.mockResolvedValueOnce({
|
|
1891
|
+
ok: false,
|
|
1892
|
+
status: 404,
|
|
1893
|
+
statusText: 'Not Found',
|
|
1894
|
+
text: async () => 'Proposal not found',
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1897
|
+
await expect(
|
|
1898
|
+
multisig.exportProposal('0x' + 'nonexistent'.repeat(5))
|
|
1899
|
+
).rejects.toThrow('Proposal not found');
|
|
1900
|
+
});
|
|
1901
|
+
});
|
|
1902
|
+
|
|
1903
|
+
describe('importProposal', () => {
|
|
1904
|
+
it('should reject imported signatures with non-32-byte signer IDs', async () => {
|
|
1905
|
+
const config = {
|
|
1906
|
+
threshold: 1,
|
|
1907
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1908
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1909
|
+
};
|
|
1910
|
+
|
|
1911
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1912
|
+
|
|
1913
|
+
const exported = {
|
|
1914
|
+
accountId: multisig.accountId,
|
|
1915
|
+
nonce: 1,
|
|
1916
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1917
|
+
txSummaryBase64: 'AQID',
|
|
1918
|
+
signatures: [
|
|
1919
|
+
{
|
|
1920
|
+
commitment: '0x1',
|
|
1921
|
+
signatureHex: '0x' + 'b'.repeat(128),
|
|
1922
|
+
},
|
|
1923
|
+
],
|
|
1924
|
+
metadata: {
|
|
1925
|
+
proposalType: 'add_signer' as const,
|
|
1926
|
+
targetThreshold: 1,
|
|
1927
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1928
|
+
description: '',
|
|
1929
|
+
},
|
|
1930
|
+
};
|
|
1931
|
+
|
|
1932
|
+
await expect(multisig.importProposal(JSON.stringify(exported))).rejects.toThrow(
|
|
1933
|
+
'expected signerId as 32-byte hex',
|
|
1934
|
+
);
|
|
1935
|
+
});
|
|
1936
|
+
|
|
1937
|
+
it('should preserve ECDSA imported signature metadata', async () => {
|
|
1938
|
+
const config = {
|
|
1939
|
+
threshold: 1,
|
|
1940
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1941
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1942
|
+
};
|
|
1943
|
+
|
|
1944
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1945
|
+
const publicKey = '0x' + 'd'.repeat(66);
|
|
1946
|
+
|
|
1947
|
+
const proposal = await multisig.importProposal(
|
|
1948
|
+
JSON.stringify({
|
|
1949
|
+
accountId: multisig.accountId,
|
|
1950
|
+
nonce: 1,
|
|
1951
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1952
|
+
txSummaryBase64: 'AQID',
|
|
1953
|
+
signatures: [
|
|
1954
|
+
{
|
|
1955
|
+
commitment: '0x' + 'a'.repeat(64),
|
|
1956
|
+
signatureHex: '0x' + 'b'.repeat(130),
|
|
1957
|
+
scheme: 'ecdsa',
|
|
1958
|
+
publicKey,
|
|
1959
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1960
|
+
},
|
|
1961
|
+
],
|
|
1962
|
+
metadata: {
|
|
1963
|
+
proposalType: 'change_threshold',
|
|
1964
|
+
targetThreshold: 1,
|
|
1965
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1966
|
+
description: '',
|
|
1967
|
+
},
|
|
1968
|
+
})
|
|
1969
|
+
);
|
|
1970
|
+
|
|
1971
|
+
expect(proposal.signatures).toEqual([
|
|
1972
|
+
{
|
|
1973
|
+
signerId: '0x' + 'a'.repeat(64),
|
|
1974
|
+
signature: {
|
|
1975
|
+
scheme: 'ecdsa',
|
|
1976
|
+
signature: '0x' + 'b'.repeat(130),
|
|
1977
|
+
publicKey,
|
|
1978
|
+
},
|
|
1979
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1980
|
+
},
|
|
1981
|
+
]);
|
|
551
1982
|
});
|
|
552
|
-
});
|
|
553
1983
|
|
|
554
|
-
|
|
555
|
-
it('should export proposal to JSON from local cache', async () => {
|
|
1984
|
+
it('should reject imported ECDSA signatures without a public key', async () => {
|
|
556
1985
|
const config = {
|
|
557
|
-
threshold:
|
|
558
|
-
signerCommitments: ['0x' + 'a'.repeat(64)
|
|
559
|
-
|
|
1986
|
+
threshold: 1,
|
|
1987
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1988
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
560
1989
|
};
|
|
561
1990
|
|
|
562
|
-
const multisig = new Multisig(mockAccount, config,
|
|
1991
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
563
1992
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
signatures: [],
|
|
573
|
-
metadata: {
|
|
574
|
-
proposal_type: 'add_signer',
|
|
575
|
-
description: '',
|
|
576
|
-
target_threshold: 1,
|
|
577
|
-
signer_commitments: ['0x' + 'a'.repeat(64)],
|
|
578
|
-
},
|
|
579
|
-
},
|
|
580
|
-
status: {
|
|
581
|
-
status: 'pending',
|
|
582
|
-
timestamp: '2024-01-01T00:00:00Z',
|
|
583
|
-
proposer_id: '0x' + 'c'.repeat(64),
|
|
584
|
-
cosigner_sigs: [
|
|
1993
|
+
await expect(
|
|
1994
|
+
multisig.importProposal(
|
|
1995
|
+
JSON.stringify({
|
|
1996
|
+
accountId: multisig.accountId,
|
|
1997
|
+
nonce: 1,
|
|
1998
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1999
|
+
txSummaryBase64: 'AQID',
|
|
2000
|
+
signatures: [
|
|
585
2001
|
{
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
2002
|
+
commitment: '0x' + 'a'.repeat(64),
|
|
2003
|
+
signatureHex: '0x' + 'b'.repeat(130),
|
|
2004
|
+
scheme: 'ecdsa',
|
|
589
2005
|
},
|
|
590
2006
|
],
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
await multisig.syncTransactionProposals();
|
|
601
|
-
|
|
602
|
-
// The proposal ID is computed from tx_summary, which is mocked to return 'c'.repeat(64)
|
|
603
|
-
const json = multisig.exportTransactionProposalToJson('0x' + 'c'.repeat(64));
|
|
604
|
-
const exported = JSON.parse(json);
|
|
605
|
-
|
|
606
|
-
expect(exported.accountId).toBe('0x' + 'a'.repeat(30));
|
|
607
|
-
expect(exported.nonce).toBe(1);
|
|
608
|
-
expect(exported.txSummaryBase64).toBe('AQID');
|
|
609
|
-
expect(exported.signatures.length).toBe(1);
|
|
610
|
-
expect(exported.commitment).toBe('0x' + 'c'.repeat(64));
|
|
2007
|
+
metadata: {
|
|
2008
|
+
proposalType: 'change_threshold',
|
|
2009
|
+
targetThreshold: 1,
|
|
2010
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
2011
|
+
description: '',
|
|
2012
|
+
},
|
|
2013
|
+
})
|
|
2014
|
+
)
|
|
2015
|
+
).rejects.toThrow('ECDSA signature for 0x' + 'a'.repeat(64) + ' is missing publicKey');
|
|
611
2016
|
});
|
|
612
2017
|
|
|
613
|
-
it('should
|
|
2018
|
+
it('should reject offline signing if an imported proposal account is changed', async () => {
|
|
614
2019
|
const config = {
|
|
615
|
-
threshold:
|
|
616
|
-
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
617
|
-
|
|
2020
|
+
threshold: 2,
|
|
2021
|
+
signerCommitments: ['0x' + 'a'.repeat(64), mockSigner.commitment],
|
|
2022
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
618
2023
|
};
|
|
619
2024
|
|
|
620
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2025
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
621
2026
|
|
|
622
|
-
|
|
623
|
-
multisig.
|
|
624
|
-
|
|
2027
|
+
const exported = {
|
|
2028
|
+
accountId: multisig.accountId,
|
|
2029
|
+
nonce: 1,
|
|
2030
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
2031
|
+
txSummaryBase64: 'AQID',
|
|
2032
|
+
signatures: [],
|
|
2033
|
+
metadata: {
|
|
2034
|
+
proposalType: 'add_signer' as const,
|
|
2035
|
+
targetThreshold: 2,
|
|
2036
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
2037
|
+
description: '',
|
|
2038
|
+
},
|
|
2039
|
+
};
|
|
2040
|
+
|
|
2041
|
+
const proposal = await multisig.importProposal(JSON.stringify(exported));
|
|
2042
|
+
proposal.accountId = '0x' + 'f'.repeat(30);
|
|
2043
|
+
|
|
2044
|
+
await expect(multisig.signProposalOffline(proposal.id)).rejects.toThrow(
|
|
2045
|
+
'Proposal is for a different account: 0x' + 'f'.repeat(30),
|
|
2046
|
+
);
|
|
2047
|
+
expect(mockSigner.signCommitment).not.toHaveBeenCalled();
|
|
625
2048
|
});
|
|
626
2049
|
});
|
|
627
2050
|
|
|
628
|
-
describe('
|
|
2051
|
+
describe('executeProposal', () => {
|
|
629
2052
|
it('should throw if proposal not found locally', async () => {
|
|
630
2053
|
const config = {
|
|
631
2054
|
threshold: 1,
|
|
632
2055
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
633
|
-
|
|
2056
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
634
2057
|
};
|
|
635
2058
|
|
|
636
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2059
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
637
2060
|
|
|
638
2061
|
await expect(
|
|
639
|
-
multisig.
|
|
2062
|
+
multisig.executeProposal('0x' + 'nonexistent'.repeat(5))
|
|
640
2063
|
).rejects.toThrow('Proposal not found');
|
|
641
2064
|
});
|
|
642
2065
|
|
|
@@ -644,10 +2067,10 @@ describe('Multisig', () => {
|
|
|
644
2067
|
const config = {
|
|
645
2068
|
threshold: 2, // Need 2 signatures
|
|
646
2069
|
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
647
|
-
|
|
2070
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
648
2071
|
};
|
|
649
2072
|
|
|
650
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2073
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
651
2074
|
|
|
652
2075
|
// Sync with pending proposal (only 1 signature)
|
|
653
2076
|
const mockProposals = [
|
|
@@ -671,7 +2094,7 @@ describe('Multisig', () => {
|
|
|
671
2094
|
proposer_id: '0x' + 'c'.repeat(64),
|
|
672
2095
|
cosigner_sigs: [
|
|
673
2096
|
{
|
|
674
|
-
signer_id: '0x' + '
|
|
2097
|
+
signer_id: '0x' + 'a'.repeat(64),
|
|
675
2098
|
signature: { scheme: 'falcon', signature: '0x' + 'e'.repeat(128) },
|
|
676
2099
|
timestamp: '2024-01-01T00:00:00Z',
|
|
677
2100
|
},
|
|
@@ -685,22 +2108,22 @@ describe('Multisig', () => {
|
|
|
685
2108
|
json: async () => ({ proposals: mockProposals }),
|
|
686
2109
|
});
|
|
687
2110
|
|
|
688
|
-
await multisig.
|
|
2111
|
+
await multisig.syncProposals();
|
|
689
2112
|
|
|
690
2113
|
// Proposal ID is mocked to return 'c'.repeat(64)
|
|
691
2114
|
await expect(
|
|
692
|
-
multisig.
|
|
2115
|
+
multisig.executeProposal('0x' + 'c'.repeat(64))
|
|
693
2116
|
).rejects.toThrow('not ready for execution');
|
|
694
2117
|
});
|
|
695
2118
|
|
|
696
|
-
it('should fail when
|
|
2119
|
+
it('should fail when GUARDIAN ack signature is missing (selector ON)', async () => {
|
|
697
2120
|
const config = {
|
|
698
2121
|
threshold: 1,
|
|
699
2122
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
700
|
-
|
|
2123
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
701
2124
|
};
|
|
702
2125
|
|
|
703
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2126
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
704
2127
|
|
|
705
2128
|
const readyDelta = {
|
|
706
2129
|
account_id: '0x' + 'a'.repeat(30),
|
|
@@ -722,7 +2145,7 @@ describe('Multisig', () => {
|
|
|
722
2145
|
proposer_id: '0x' + 'c'.repeat(64),
|
|
723
2146
|
cosigner_sigs: [
|
|
724
2147
|
{
|
|
725
|
-
signer_id: '0x' + '
|
|
2148
|
+
signer_id: '0x' + 'a'.repeat(64),
|
|
726
2149
|
signature: { scheme: 'falcon', signature: '0x' + 'e'.repeat(128) },
|
|
727
2150
|
timestamp: '2024-01-01T00:00:00Z',
|
|
728
2151
|
},
|
|
@@ -737,12 +2160,12 @@ describe('Multisig', () => {
|
|
|
737
2160
|
ok: true,
|
|
738
2161
|
json: async () => ({ proposals: [readyDelta] }),
|
|
739
2162
|
});
|
|
740
|
-
await multisig.
|
|
2163
|
+
await multisig.syncProposals();
|
|
741
2164
|
|
|
742
|
-
// executeProposal:
|
|
2165
|
+
// executeProposal: getDeltaProposal
|
|
743
2166
|
mockFetch.mockResolvedValueOnce({
|
|
744
2167
|
ok: true,
|
|
745
|
-
json: async () =>
|
|
2168
|
+
json: async () => readyDelta,
|
|
746
2169
|
});
|
|
747
2170
|
// executeProposal: pushDelta without ack_sig
|
|
748
2171
|
mockFetch.mockResolvedValueOnce({
|
|
@@ -750,8 +2173,430 @@ describe('Multisig', () => {
|
|
|
750
2173
|
json: async () => ({ ...readyDelta, ack_sig: null }),
|
|
751
2174
|
});
|
|
752
2175
|
|
|
753
|
-
await expect(multisig.
|
|
754
|
-
'
|
|
2176
|
+
await expect(multisig.executeProposal(proposalId)).rejects.toThrow(
|
|
2177
|
+
'GUARDIAN did not return acknowledgment signature'
|
|
2178
|
+
);
|
|
2179
|
+
});
|
|
2180
|
+
|
|
2181
|
+
it('should encode ECDSA proposal and ack signatures with scheme-aware advice', async () => {
|
|
2182
|
+
const { buildSignatureAdviceEntry, signatureHexToBytes } = await import('./utils/signature.js');
|
|
2183
|
+
vi.mocked(signatureHexToBytes).mockClear();
|
|
2184
|
+
vi.mocked(buildSignatureAdviceEntry).mockClear();
|
|
2185
|
+
|
|
2186
|
+
const config = {
|
|
2187
|
+
threshold: 1,
|
|
2188
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
2189
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
2190
|
+
guardianPublicKey: '0x' + '1'.repeat(66),
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
const ecdsaSigner: Signer = {
|
|
2194
|
+
...mockSigner,
|
|
2195
|
+
scheme: 'ecdsa',
|
|
2196
|
+
publicKey: '0x' + '2'.repeat(66),
|
|
2197
|
+
};
|
|
2198
|
+
|
|
2199
|
+
const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
|
|
2200
|
+
const proposalId = '0x' + 'c'.repeat(64);
|
|
2201
|
+
const cosignerPubkey = '0x' + '3'.repeat(66);
|
|
2202
|
+
const ackPubkey = '0x' + '4'.repeat(66);
|
|
2203
|
+
const cosignerSignature = '0x' + '5'.repeat(130);
|
|
2204
|
+
const ackSignature = '0x' + '6'.repeat(130);
|
|
2205
|
+
|
|
2206
|
+
(multisig as any).proposals.set(proposalId, {
|
|
2207
|
+
id: proposalId,
|
|
2208
|
+
accountId: multisig.accountId,
|
|
2209
|
+
nonce: 1,
|
|
2210
|
+
status: 'ready',
|
|
2211
|
+
txSummary: 'AQID',
|
|
2212
|
+
signatures: [
|
|
2213
|
+
{
|
|
2214
|
+
signerId: '0x' + 'a'.repeat(64),
|
|
2215
|
+
signature: {
|
|
2216
|
+
scheme: 'ecdsa',
|
|
2217
|
+
signature: cosignerSignature,
|
|
2218
|
+
publicKey: cosignerPubkey,
|
|
2219
|
+
},
|
|
2220
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
2221
|
+
},
|
|
2222
|
+
],
|
|
2223
|
+
metadata: {
|
|
2224
|
+
proposalType: 'change_threshold',
|
|
2225
|
+
targetThreshold: 1,
|
|
2226
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
2227
|
+
description: '',
|
|
2228
|
+
},
|
|
2229
|
+
});
|
|
2230
|
+
|
|
2231
|
+
mockFetch.mockResolvedValueOnce({
|
|
2232
|
+
ok: true,
|
|
2233
|
+
json: async () => ({
|
|
2234
|
+
account_id: multisig.accountId,
|
|
2235
|
+
nonce: 1,
|
|
2236
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
2237
|
+
delta_payload: {
|
|
2238
|
+
tx_summary: { data: 'AQID' },
|
|
2239
|
+
signatures: [],
|
|
2240
|
+
metadata: {
|
|
2241
|
+
proposal_type: 'change_threshold',
|
|
2242
|
+
target_threshold: 1,
|
|
2243
|
+
signer_commitments: ['0x' + 'a'.repeat(64)],
|
|
2244
|
+
},
|
|
2245
|
+
},
|
|
2246
|
+
status: {
|
|
2247
|
+
status: 'pending',
|
|
2248
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
2249
|
+
proposer_id: '0x' + 'a'.repeat(64),
|
|
2250
|
+
cosigner_sigs: [],
|
|
2251
|
+
},
|
|
2252
|
+
}),
|
|
2253
|
+
});
|
|
2254
|
+
mockFetch.mockResolvedValueOnce({
|
|
2255
|
+
ok: true,
|
|
2256
|
+
json: async () => ({
|
|
2257
|
+
account_id: multisig.accountId,
|
|
2258
|
+
nonce: 1,
|
|
2259
|
+
ack_sig: ackSignature,
|
|
2260
|
+
ack_pubkey: ackPubkey,
|
|
2261
|
+
ack_scheme: 'ecdsa',
|
|
2262
|
+
}),
|
|
2263
|
+
});
|
|
2264
|
+
mockWebClient.executeTransaction.mockResolvedValueOnce({});
|
|
2265
|
+
mockWebClient.proveTransaction.mockResolvedValueOnce({});
|
|
2266
|
+
mockWebClient.submitProvenTransaction.mockResolvedValueOnce(1n);
|
|
2267
|
+
mockWebClient.applyTransaction.mockResolvedValueOnce(undefined);
|
|
2268
|
+
|
|
2269
|
+
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
2270
|
+
|
|
2271
|
+
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(
|
|
2272
|
+
1,
|
|
2273
|
+
cosignerSignature,
|
|
2274
|
+
'ecdsa',
|
|
2275
|
+
);
|
|
2276
|
+
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(
|
|
2277
|
+
2,
|
|
2278
|
+
ackSignature,
|
|
2279
|
+
'ecdsa',
|
|
2280
|
+
);
|
|
2281
|
+
expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(
|
|
2282
|
+
1,
|
|
2283
|
+
expect.anything(),
|
|
2284
|
+
expect.anything(),
|
|
2285
|
+
expect.anything(),
|
|
2286
|
+
cosignerPubkey,
|
|
2287
|
+
cosignerSignature,
|
|
2288
|
+
);
|
|
2289
|
+
expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(
|
|
2290
|
+
2,
|
|
2291
|
+
expect.anything(),
|
|
2292
|
+
expect.anything(),
|
|
2293
|
+
expect.anything(),
|
|
2294
|
+
ackPubkey,
|
|
2295
|
+
ackSignature,
|
|
2296
|
+
);
|
|
2297
|
+
});
|
|
2298
|
+
|
|
2299
|
+
it('should execute imported ECDSA proposals with scheme-aware advice', async () => {
|
|
2300
|
+
const { buildSignatureAdviceEntry, signatureHexToBytes } = await import('./utils/signature.js');
|
|
2301
|
+
vi.mocked(signatureHexToBytes).mockClear();
|
|
2302
|
+
vi.mocked(buildSignatureAdviceEntry).mockClear();
|
|
2303
|
+
|
|
2304
|
+
const config = {
|
|
2305
|
+
threshold: 1,
|
|
2306
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
2307
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
2308
|
+
guardianPublicKey: '0x' + '1'.repeat(66),
|
|
2309
|
+
};
|
|
2310
|
+
|
|
2311
|
+
const ecdsaSigner: Signer = {
|
|
2312
|
+
...mockSigner,
|
|
2313
|
+
scheme: 'ecdsa',
|
|
2314
|
+
publicKey: '0x' + '2'.repeat(66),
|
|
2315
|
+
};
|
|
2316
|
+
|
|
2317
|
+
const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
|
|
2318
|
+
const proposalId = '0x' + 'c'.repeat(64);
|
|
2319
|
+
const cosignerPubkey = '0x' + '3'.repeat(66);
|
|
2320
|
+
const ackPubkey = '0x' + '4'.repeat(66);
|
|
2321
|
+
const cosignerSignature = '0x' + '5'.repeat(130);
|
|
2322
|
+
const ackSignature = '0x' + '6'.repeat(130);
|
|
2323
|
+
|
|
2324
|
+
await multisig.importProposal(
|
|
2325
|
+
JSON.stringify({
|
|
2326
|
+
accountId: multisig.accountId,
|
|
2327
|
+
nonce: 1,
|
|
2328
|
+
commitment: proposalId,
|
|
2329
|
+
txSummaryBase64: 'AQID',
|
|
2330
|
+
signatures: [
|
|
2331
|
+
{
|
|
2332
|
+
commitment: '0x' + 'a'.repeat(64),
|
|
2333
|
+
signatureHex: cosignerSignature,
|
|
2334
|
+
scheme: 'ecdsa',
|
|
2335
|
+
publicKey: cosignerPubkey,
|
|
2336
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
2337
|
+
},
|
|
2338
|
+
],
|
|
2339
|
+
metadata: {
|
|
2340
|
+
proposalType: 'change_threshold',
|
|
2341
|
+
targetThreshold: 1,
|
|
2342
|
+
targetSignerCommitments: ['0x' + 'a'.repeat(64)],
|
|
2343
|
+
description: '',
|
|
2344
|
+
},
|
|
2345
|
+
})
|
|
2346
|
+
);
|
|
2347
|
+
|
|
2348
|
+
mockFetch.mockResolvedValueOnce({
|
|
2349
|
+
ok: true,
|
|
2350
|
+
json: async () => ({
|
|
2351
|
+
account_id: multisig.accountId,
|
|
2352
|
+
nonce: 1,
|
|
2353
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
2354
|
+
delta_payload: {
|
|
2355
|
+
tx_summary: { data: 'AQID' },
|
|
2356
|
+
signatures: [],
|
|
2357
|
+
metadata: {
|
|
2358
|
+
proposal_type: 'change_threshold',
|
|
2359
|
+
target_threshold: 1,
|
|
2360
|
+
signer_commitments: ['0x' + 'a'.repeat(64)],
|
|
2361
|
+
},
|
|
2362
|
+
},
|
|
2363
|
+
status: {
|
|
2364
|
+
status: 'pending',
|
|
2365
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
2366
|
+
proposer_id: '0x' + 'a'.repeat(64),
|
|
2367
|
+
cosigner_sigs: [],
|
|
2368
|
+
},
|
|
2369
|
+
}),
|
|
2370
|
+
});
|
|
2371
|
+
mockFetch.mockResolvedValueOnce({
|
|
2372
|
+
ok: true,
|
|
2373
|
+
json: async () => ({
|
|
2374
|
+
account_id: multisig.accountId,
|
|
2375
|
+
nonce: 1,
|
|
2376
|
+
ack_sig: ackSignature,
|
|
2377
|
+
ack_pubkey: ackPubkey,
|
|
2378
|
+
ack_scheme: 'ecdsa',
|
|
2379
|
+
}),
|
|
2380
|
+
});
|
|
2381
|
+
mockWebClient.executeTransaction.mockResolvedValueOnce({});
|
|
2382
|
+
mockWebClient.proveTransaction.mockResolvedValueOnce({});
|
|
2383
|
+
mockWebClient.submitProvenTransaction.mockResolvedValueOnce(1n);
|
|
2384
|
+
mockWebClient.applyTransaction.mockResolvedValueOnce(undefined);
|
|
2385
|
+
|
|
2386
|
+
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
2387
|
+
|
|
2388
|
+
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(
|
|
2389
|
+
1,
|
|
2390
|
+
cosignerSignature,
|
|
2391
|
+
'ecdsa',
|
|
2392
|
+
);
|
|
2393
|
+
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(
|
|
2394
|
+
2,
|
|
2395
|
+
ackSignature,
|
|
2396
|
+
'ecdsa',
|
|
2397
|
+
);
|
|
2398
|
+
expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(
|
|
2399
|
+
1,
|
|
2400
|
+
expect.anything(),
|
|
2401
|
+
expect.anything(),
|
|
2402
|
+
expect.anything(),
|
|
2403
|
+
cosignerPubkey,
|
|
2404
|
+
cosignerSignature,
|
|
2405
|
+
);
|
|
2406
|
+
expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(
|
|
2407
|
+
2,
|
|
2408
|
+
expect.anything(),
|
|
2409
|
+
expect.anything(),
|
|
2410
|
+
expect.anything(),
|
|
2411
|
+
ackPubkey,
|
|
2412
|
+
ackSignature,
|
|
2413
|
+
);
|
|
2414
|
+
});
|
|
2415
|
+
|
|
2416
|
+
it('should verify switch_guardian endpoint commitment before execution', async () => {
|
|
2417
|
+
const config = {
|
|
2418
|
+
threshold: 1,
|
|
2419
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
2420
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
2421
|
+
};
|
|
2422
|
+
|
|
2423
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
2424
|
+
const proposalId = '0x' + 'c'.repeat(64);
|
|
2425
|
+
const newGuardianPubkey = '0x' + '1'.repeat(64);
|
|
2426
|
+
|
|
2427
|
+
(multisig as any).proposals.set(proposalId, {
|
|
2428
|
+
id: proposalId,
|
|
2429
|
+
accountId: multisig.accountId,
|
|
2430
|
+
nonce: 1,
|
|
2431
|
+
status: 'ready',
|
|
2432
|
+
txSummary: 'AQID',
|
|
2433
|
+
signatures: [
|
|
2434
|
+
{
|
|
2435
|
+
signerId: '0x' + 'a'.repeat(64),
|
|
2436
|
+
signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
|
|
2437
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
2438
|
+
},
|
|
2439
|
+
],
|
|
2440
|
+
metadata: {
|
|
2441
|
+
proposalType: 'switch_guardian',
|
|
2442
|
+
newGuardianPubkey,
|
|
2443
|
+
newGuardianEndpoint: 'http://new-guardian.com',
|
|
2444
|
+
description: '',
|
|
2445
|
+
},
|
|
2446
|
+
});
|
|
2447
|
+
|
|
2448
|
+
mockFetch.mockResolvedValueOnce({
|
|
2449
|
+
ok: true,
|
|
2450
|
+
json: async () => ({ commitment: newGuardianPubkey }),
|
|
2451
|
+
});
|
|
2452
|
+
mockWebClient.getAccount.mockResolvedValueOnce({
|
|
2453
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
2454
|
+
});
|
|
2455
|
+
mockFetch.mockResolvedValueOnce({
|
|
2456
|
+
ok: true,
|
|
2457
|
+
json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
|
|
2458
|
+
});
|
|
2459
|
+
mockWebClient.executeTransaction.mockResolvedValueOnce({});
|
|
2460
|
+
mockWebClient.proveTransaction.mockResolvedValueOnce({});
|
|
2461
|
+
mockWebClient.submitProvenTransaction.mockResolvedValueOnce(1n);
|
|
2462
|
+
mockWebClient.applyTransaction.mockResolvedValueOnce(undefined);
|
|
2463
|
+
|
|
2464
|
+
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
2465
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
2466
|
+
});
|
|
2467
|
+
|
|
2468
|
+
it('should reject switch_guardian execution when endpoint commitment mismatches', async () => {
|
|
2469
|
+
const config = {
|
|
2470
|
+
threshold: 1,
|
|
2471
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
2472
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
2473
|
+
};
|
|
2474
|
+
|
|
2475
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
2476
|
+
const proposalId = '0x' + 'c'.repeat(64);
|
|
2477
|
+
|
|
2478
|
+
(multisig as any).proposals.set(proposalId, {
|
|
2479
|
+
id: proposalId,
|
|
2480
|
+
accountId: multisig.accountId,
|
|
2481
|
+
nonce: 1,
|
|
2482
|
+
status: 'ready',
|
|
2483
|
+
txSummary: 'AQID',
|
|
2484
|
+
signatures: [
|
|
2485
|
+
{
|
|
2486
|
+
signerId: '0x' + 'a'.repeat(64),
|
|
2487
|
+
signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
|
|
2488
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
2489
|
+
},
|
|
2490
|
+
],
|
|
2491
|
+
metadata: {
|
|
2492
|
+
proposalType: 'switch_guardian',
|
|
2493
|
+
newGuardianPubkey: '0x' + '1'.repeat(64),
|
|
2494
|
+
newGuardianEndpoint: 'http://new-guardian.com',
|
|
2495
|
+
description: '',
|
|
2496
|
+
},
|
|
2497
|
+
});
|
|
2498
|
+
|
|
2499
|
+
mockFetch.mockResolvedValueOnce({
|
|
2500
|
+
ok: true,
|
|
2501
|
+
json: async () => ({ commitment: '0x' + '2'.repeat(64) }),
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
await expect(multisig.executeProposal(proposalId)).rejects.toThrow(
|
|
2505
|
+
'Refusing to use GUARDIAN endpoint'
|
|
2506
|
+
);
|
|
2507
|
+
expect(mockWebClient.executeTransaction).not.toHaveBeenCalled();
|
|
2508
|
+
});
|
|
2509
|
+
|
|
2510
|
+
it('should reject duplicate normalized signer IDs during execution', async () => {
|
|
2511
|
+
const config = {
|
|
2512
|
+
threshold: 2,
|
|
2513
|
+
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
2514
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
2515
|
+
};
|
|
2516
|
+
|
|
2517
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
2518
|
+
const proposalId = '0x' + 'c'.repeat(64);
|
|
2519
|
+
|
|
2520
|
+
(multisig as any).proposals.set(proposalId, {
|
|
2521
|
+
id: proposalId,
|
|
2522
|
+
accountId: multisig.accountId,
|
|
2523
|
+
nonce: 1,
|
|
2524
|
+
status: 'ready',
|
|
2525
|
+
txSummary: 'AQID',
|
|
2526
|
+
signatures: [
|
|
2527
|
+
{
|
|
2528
|
+
signerId: '0x' + 'a'.repeat(64),
|
|
2529
|
+
signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
|
|
2530
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
2531
|
+
},
|
|
2532
|
+
{
|
|
2533
|
+
signerId: '0x' + 'A'.repeat(64),
|
|
2534
|
+
signature: { scheme: 'falcon', signature: '0x' + 'c'.repeat(128) },
|
|
2535
|
+
timestamp: '2024-01-01T00:00:01Z',
|
|
2536
|
+
},
|
|
2537
|
+
],
|
|
2538
|
+
metadata: {
|
|
2539
|
+
proposalType: 'switch_guardian',
|
|
2540
|
+
newGuardianPubkey: '0x' + '1'.repeat(64),
|
|
2541
|
+
newGuardianEndpoint: 'http://new-guardian.com',
|
|
2542
|
+
description: '',
|
|
2543
|
+
},
|
|
2544
|
+
});
|
|
2545
|
+
|
|
2546
|
+
await expect(multisig.executeProposal(proposalId)).rejects.toThrow(
|
|
2547
|
+
'duplicate signatures for signer',
|
|
2548
|
+
);
|
|
2549
|
+
});
|
|
2550
|
+
|
|
2551
|
+
it('should reject advice-map key collisions during execution', async () => {
|
|
2552
|
+
const { buildSignatureAdviceEntry } = await import('./utils/signature.js');
|
|
2553
|
+
vi.mocked(buildSignatureAdviceEntry)
|
|
2554
|
+
.mockImplementationOnce(() => ({
|
|
2555
|
+
key: { toHex: () => '0x' + 'f'.repeat(64) },
|
|
2556
|
+
values: [1, 2, 3],
|
|
2557
|
+
}) as any)
|
|
2558
|
+
.mockImplementationOnce(() => ({
|
|
2559
|
+
key: { toHex: () => '0x' + 'f'.repeat(64) },
|
|
2560
|
+
values: [1, 2, 3],
|
|
2561
|
+
}) as any);
|
|
2562
|
+
|
|
2563
|
+
const config = {
|
|
2564
|
+
threshold: 2,
|
|
2565
|
+
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
2566
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
2567
|
+
};
|
|
2568
|
+
|
|
2569
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
2570
|
+
const proposalId = '0x' + 'c'.repeat(64);
|
|
2571
|
+
|
|
2572
|
+
(multisig as any).proposals.set(proposalId, {
|
|
2573
|
+
id: proposalId,
|
|
2574
|
+
accountId: multisig.accountId,
|
|
2575
|
+
nonce: 1,
|
|
2576
|
+
status: 'ready',
|
|
2577
|
+
txSummary: 'AQID',
|
|
2578
|
+
signatures: [
|
|
2579
|
+
{
|
|
2580
|
+
signerId: '0x' + 'a'.repeat(64),
|
|
2581
|
+
signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
|
|
2582
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
2583
|
+
},
|
|
2584
|
+
{
|
|
2585
|
+
signerId: '0x' + 'b'.repeat(64),
|
|
2586
|
+
signature: { scheme: 'falcon', signature: '0x' + 'c'.repeat(128) },
|
|
2587
|
+
timestamp: '2024-01-01T00:00:01Z',
|
|
2588
|
+
},
|
|
2589
|
+
],
|
|
2590
|
+
metadata: {
|
|
2591
|
+
proposalType: 'switch_guardian',
|
|
2592
|
+
newGuardianPubkey: '0x' + '1'.repeat(64),
|
|
2593
|
+
newGuardianEndpoint: 'http://new-guardian.com',
|
|
2594
|
+
description: '',
|
|
2595
|
+
},
|
|
2596
|
+
});
|
|
2597
|
+
|
|
2598
|
+
await expect(multisig.executeProposal(proposalId)).rejects.toThrow(
|
|
2599
|
+
'Duplicate advice-map key detected',
|
|
755
2600
|
);
|
|
756
2601
|
});
|
|
757
2602
|
});
|
|
@@ -761,10 +2606,10 @@ describe('Multisig', () => {
|
|
|
761
2606
|
const config = {
|
|
762
2607
|
threshold: 1,
|
|
763
2608
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
764
|
-
|
|
2609
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
765
2610
|
};
|
|
766
2611
|
|
|
767
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2612
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
768
2613
|
|
|
769
2614
|
// Create a proposal with metadata
|
|
770
2615
|
const mockDelta = {
|
|
@@ -774,7 +2619,11 @@ describe('Multisig', () => {
|
|
|
774
2619
|
delta_payload: {
|
|
775
2620
|
tx_summary: { data: 'AQID' },
|
|
776
2621
|
signatures: [],
|
|
777
|
-
metadata: {
|
|
2622
|
+
metadata: {
|
|
2623
|
+
proposal_type: 'add_signer',
|
|
2624
|
+
target_threshold: 2,
|
|
2625
|
+
signer_commitments: ['0x1', '0x2'],
|
|
2626
|
+
},
|
|
778
2627
|
},
|
|
779
2628
|
status: {
|
|
780
2629
|
status: 'pending',
|
|
@@ -788,7 +2637,7 @@ describe('Multisig', () => {
|
|
|
788
2637
|
ok: true,
|
|
789
2638
|
json: async () => ({
|
|
790
2639
|
delta: mockDelta,
|
|
791
|
-
commitment: '0x' + '
|
|
2640
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
792
2641
|
}),
|
|
793
2642
|
});
|
|
794
2643
|
|
|
@@ -809,20 +2658,20 @@ describe('Multisig', () => {
|
|
|
809
2658
|
}),
|
|
810
2659
|
});
|
|
811
2660
|
|
|
812
|
-
const syncedProposals = await multisig.
|
|
2661
|
+
const syncedProposals = await multisig.syncProposals();
|
|
813
2662
|
const syncedProposal = syncedProposals.find(p => p.nonce === 1);
|
|
814
2663
|
|
|
815
2664
|
expect(syncedProposal?.metadata?.proposalType).toBe('add_signer');
|
|
816
2665
|
});
|
|
817
2666
|
|
|
818
|
-
it('should use
|
|
2667
|
+
it('should use GUARDIAN metadata for new proposals from other signers', async () => {
|
|
819
2668
|
const config = {
|
|
820
2669
|
threshold: 2,
|
|
821
2670
|
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
822
|
-
|
|
2671
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
823
2672
|
};
|
|
824
2673
|
|
|
825
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2674
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
826
2675
|
|
|
827
2676
|
// Sync proposals - no local proposals exist
|
|
828
2677
|
const mockProposals = [
|
|
@@ -854,7 +2703,7 @@ describe('Multisig', () => {
|
|
|
854
2703
|
json: async () => ({ proposals: mockProposals }),
|
|
855
2704
|
});
|
|
856
2705
|
|
|
857
|
-
const proposals = await multisig.
|
|
2706
|
+
const proposals = await multisig.syncProposals();
|
|
858
2707
|
|
|
859
2708
|
expect(proposals.length).toBe(1);
|
|
860
2709
|
expect(proposals[0].metadata?.proposalType).toBe('p2id');
|
|
@@ -866,10 +2715,10 @@ describe('Multisig', () => {
|
|
|
866
2715
|
const config = {
|
|
867
2716
|
threshold: 1,
|
|
868
2717
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
869
|
-
|
|
2718
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
870
2719
|
};
|
|
871
2720
|
|
|
872
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2721
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
873
2722
|
|
|
874
2723
|
const mockDelta = {
|
|
875
2724
|
account_id: '0x' + 'a'.repeat(30),
|
|
@@ -897,7 +2746,7 @@ describe('Multisig', () => {
|
|
|
897
2746
|
ok: true,
|
|
898
2747
|
json: async () => ({
|
|
899
2748
|
delta: mockDelta,
|
|
900
|
-
commitment: '0x' + '
|
|
2749
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
901
2750
|
}),
|
|
902
2751
|
});
|
|
903
2752
|
|
|
@@ -914,10 +2763,10 @@ describe('Multisig', () => {
|
|
|
914
2763
|
const config = {
|
|
915
2764
|
threshold: 1,
|
|
916
2765
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
917
|
-
|
|
2766
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
918
2767
|
};
|
|
919
2768
|
|
|
920
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2769
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
921
2770
|
|
|
922
2771
|
const mockDelta = {
|
|
923
2772
|
account_id: '0x' + 'a'.repeat(30),
|
|
@@ -945,7 +2794,7 @@ describe('Multisig', () => {
|
|
|
945
2794
|
ok: true,
|
|
946
2795
|
json: async () => ({
|
|
947
2796
|
delta: mockDelta,
|
|
948
|
-
commitment: '0x' + '
|
|
2797
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
949
2798
|
}),
|
|
950
2799
|
});
|
|
951
2800
|
|
|
@@ -960,14 +2809,14 @@ describe('Multisig', () => {
|
|
|
960
2809
|
expect(proposal.metadata?.proposalType).toBe('p2id');
|
|
961
2810
|
});
|
|
962
2811
|
|
|
963
|
-
it('should create
|
|
2812
|
+
it('should create switch_guardian proposal', async () => {
|
|
964
2813
|
const config = {
|
|
965
2814
|
threshold: 1,
|
|
966
2815
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
967
|
-
|
|
2816
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
968
2817
|
};
|
|
969
2818
|
|
|
970
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2819
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
971
2820
|
|
|
972
2821
|
const mockDelta = {
|
|
973
2822
|
account_id: '0x' + 'a'.repeat(30),
|
|
@@ -995,18 +2844,18 @@ describe('Multisig', () => {
|
|
|
995
2844
|
ok: true,
|
|
996
2845
|
json: async () => ({
|
|
997
2846
|
delta: mockDelta,
|
|
998
|
-
commitment: '0x' + '
|
|
2847
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
999
2848
|
}),
|
|
1000
2849
|
});
|
|
1001
2850
|
|
|
1002
2851
|
const proposal = await multisig.createProposal(1, 'AQID', {
|
|
1003
|
-
proposalType: '
|
|
1004
|
-
|
|
1005
|
-
|
|
2852
|
+
proposalType: 'switch_guardian',
|
|
2853
|
+
newGuardianPubkey: '0xnewpubkey',
|
|
2854
|
+
newGuardianEndpoint: 'http://new-guardian.com',
|
|
1006
2855
|
description: '',
|
|
1007
2856
|
});
|
|
1008
2857
|
|
|
1009
|
-
expect(proposal.metadata?.proposalType).toBe('
|
|
2858
|
+
expect(proposal.metadata?.proposalType).toBe('switch_guardian');
|
|
1010
2859
|
});
|
|
1011
2860
|
});
|
|
1012
2861
|
|
|
@@ -1015,10 +2864,10 @@ describe('Multisig', () => {
|
|
|
1015
2864
|
const config = {
|
|
1016
2865
|
threshold: 2,
|
|
1017
2866
|
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
1018
|
-
|
|
2867
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1019
2868
|
};
|
|
1020
2869
|
|
|
1021
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2870
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1022
2871
|
|
|
1023
2872
|
// First sync with 1 signature (pending)
|
|
1024
2873
|
const mockProposalsPending = [
|
|
@@ -1056,8 +2905,8 @@ describe('Multisig', () => {
|
|
|
1056
2905
|
json: async () => ({ proposals: mockProposalsPending }),
|
|
1057
2906
|
});
|
|
1058
2907
|
|
|
1059
|
-
let proposals = await multisig.
|
|
1060
|
-
expect(proposals[0].status
|
|
2908
|
+
let proposals = await multisig.syncProposals();
|
|
2909
|
+
expect(proposals[0].status).toBe('pending');
|
|
1061
2910
|
|
|
1062
2911
|
// Second sync with 2 signatures (ready)
|
|
1063
2912
|
const mockProposalsReady = [
|
|
@@ -1097,8 +2946,8 @@ describe('Multisig', () => {
|
|
|
1097
2946
|
json: async () => ({ proposals: mockProposalsReady }),
|
|
1098
2947
|
});
|
|
1099
2948
|
|
|
1100
|
-
proposals = await multisig.
|
|
1101
|
-
expect(proposals[0].status
|
|
2949
|
+
proposals = await multisig.syncProposals();
|
|
2950
|
+
expect(proposals[0].status).toBe('ready');
|
|
1102
2951
|
});
|
|
1103
2952
|
});
|
|
1104
2953
|
|
|
@@ -1107,10 +2956,10 @@ describe('Multisig', () => {
|
|
|
1107
2956
|
const config = {
|
|
1108
2957
|
threshold: 3,
|
|
1109
2958
|
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64), '0x' + 'c'.repeat(64)],
|
|
1110
|
-
|
|
2959
|
+
guardianCommitment: '0x' + 'd'.repeat(64),
|
|
1111
2960
|
};
|
|
1112
2961
|
|
|
1113
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2962
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1114
2963
|
expect(multisig.threshold).toBe(3);
|
|
1115
2964
|
});
|
|
1116
2965
|
|
|
@@ -1119,33 +2968,33 @@ describe('Multisig', () => {
|
|
|
1119
2968
|
const config = {
|
|
1120
2969
|
threshold: 2,
|
|
1121
2970
|
signerCommitments,
|
|
1122
|
-
|
|
2971
|
+
guardianCommitment: '0x' + 'd'.repeat(64),
|
|
1123
2972
|
};
|
|
1124
2973
|
|
|
1125
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2974
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1126
2975
|
expect(multisig.signerCommitments).toEqual(signerCommitments);
|
|
1127
2976
|
});
|
|
1128
2977
|
|
|
1129
|
-
it('should expose
|
|
1130
|
-
const
|
|
2978
|
+
it('should expose guardianCommitment', () => {
|
|
2979
|
+
const guardianCommitment = '0x' + 'guardian'.repeat(20);
|
|
1131
2980
|
const config = {
|
|
1132
2981
|
threshold: 1,
|
|
1133
2982
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1134
|
-
|
|
2983
|
+
guardianCommitment,
|
|
1135
2984
|
};
|
|
1136
2985
|
|
|
1137
|
-
const multisig = new Multisig(mockAccount, config,
|
|
1138
|
-
expect(multisig.
|
|
2986
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
2987
|
+
expect(multisig.guardianCommitment).toBe(guardianCommitment);
|
|
1139
2988
|
});
|
|
1140
2989
|
|
|
1141
2990
|
it('should expose account when provided', () => {
|
|
1142
2991
|
const config = {
|
|
1143
2992
|
threshold: 1,
|
|
1144
2993
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1145
|
-
|
|
2994
|
+
guardianCommitment: '0x' + 'd'.repeat(64),
|
|
1146
2995
|
};
|
|
1147
2996
|
|
|
1148
|
-
const multisig = new Multisig(mockAccount, config,
|
|
2997
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1149
2998
|
expect(multisig.account).toBe(mockAccount);
|
|
1150
2999
|
});
|
|
1151
3000
|
});
|
|
@@ -1155,12 +3004,12 @@ describe('Multisig', () => {
|
|
|
1155
3004
|
const config = {
|
|
1156
3005
|
threshold: 2,
|
|
1157
3006
|
signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
|
|
1158
|
-
|
|
3007
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1159
3008
|
};
|
|
1160
3009
|
|
|
1161
|
-
const multisig = new Multisig(mockAccount, config,
|
|
3010
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1162
3011
|
|
|
1163
|
-
// Simulates a
|
|
3012
|
+
// Simulates a GUARDIAN response with canonical snake_case metadata
|
|
1164
3013
|
const rustProposals = [
|
|
1165
3014
|
{
|
|
1166
3015
|
account_id: '0x' + 'a'.repeat(30),
|
|
@@ -1190,7 +3039,7 @@ describe('Multisig', () => {
|
|
|
1190
3039
|
json: async () => ({ proposals: rustProposals }),
|
|
1191
3040
|
});
|
|
1192
3041
|
|
|
1193
|
-
const proposals = await multisig.
|
|
3042
|
+
const proposals = await multisig.syncProposals();
|
|
1194
3043
|
|
|
1195
3044
|
expect(proposals.length).toBe(1);
|
|
1196
3045
|
// The TS client should normalize snake_case to camelCase
|
|
@@ -1205,10 +3054,10 @@ describe('Multisig', () => {
|
|
|
1205
3054
|
const config = {
|
|
1206
3055
|
threshold: 1,
|
|
1207
3056
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1208
|
-
|
|
3057
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1209
3058
|
};
|
|
1210
3059
|
|
|
1211
|
-
const multisig = new Multisig(mockAccount, config,
|
|
3060
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1212
3061
|
|
|
1213
3062
|
// P2ID proposal with canonical snake_case fields
|
|
1214
3063
|
const p2idProposals = [
|
|
@@ -1247,7 +3096,7 @@ describe('Multisig', () => {
|
|
|
1247
3096
|
json: async () => ({ proposals: p2idProposals }),
|
|
1248
3097
|
});
|
|
1249
3098
|
|
|
1250
|
-
const proposals = await multisig.
|
|
3099
|
+
const proposals = await multisig.syncProposals();
|
|
1251
3100
|
|
|
1252
3101
|
expect(proposals.length).toBe(1);
|
|
1253
3102
|
expect(proposals[0].metadata?.proposalType).toBe('p2id');
|
|
@@ -1258,16 +3107,16 @@ describe('Multisig', () => {
|
|
|
1258
3107
|
}
|
|
1259
3108
|
});
|
|
1260
3109
|
|
|
1261
|
-
it('should parse
|
|
3110
|
+
it('should parse switch_guardian proposal with snake_case fields', async () => {
|
|
1262
3111
|
const config = {
|
|
1263
3112
|
threshold: 1,
|
|
1264
3113
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
1265
|
-
|
|
3114
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
1266
3115
|
};
|
|
1267
3116
|
|
|
1268
|
-
const multisig = new Multisig(mockAccount, config,
|
|
3117
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
1269
3118
|
|
|
1270
|
-
const
|
|
3119
|
+
const switchGuardianProposals = [
|
|
1271
3120
|
{
|
|
1272
3121
|
account_id: '0x' + 'a'.repeat(30),
|
|
1273
3122
|
nonce: 1,
|
|
@@ -1276,9 +3125,9 @@ describe('Multisig', () => {
|
|
|
1276
3125
|
tx_summary: { data: 'AQID' },
|
|
1277
3126
|
signatures: [],
|
|
1278
3127
|
metadata: {
|
|
1279
|
-
proposal_type: '
|
|
1280
|
-
|
|
1281
|
-
|
|
3128
|
+
proposal_type: 'switch_guardian',
|
|
3129
|
+
new_guardian_pubkey: '0xnewpubkey',
|
|
3130
|
+
new_guardian_endpoint: 'http://new-guardian.com',
|
|
1282
3131
|
salt: '0xsalt',
|
|
1283
3132
|
},
|
|
1284
3133
|
},
|
|
@@ -1293,16 +3142,16 @@ describe('Multisig', () => {
|
|
|
1293
3142
|
|
|
1294
3143
|
mockFetch.mockResolvedValueOnce({
|
|
1295
3144
|
ok: true,
|
|
1296
|
-
json: async () => ({ proposals:
|
|
3145
|
+
json: async () => ({ proposals: switchGuardianProposals }),
|
|
1297
3146
|
});
|
|
1298
3147
|
|
|
1299
|
-
const proposals = await multisig.
|
|
3148
|
+
const proposals = await multisig.syncProposals();
|
|
1300
3149
|
|
|
1301
3150
|
expect(proposals.length).toBe(1);
|
|
1302
|
-
expect(proposals[0].metadata?.proposalType).toBe('
|
|
1303
|
-
if (proposals[0].metadata?.proposalType === '
|
|
1304
|
-
expect(proposals[0].metadata.
|
|
1305
|
-
expect(proposals[0].metadata.
|
|
3151
|
+
expect(proposals[0].metadata?.proposalType).toBe('switch_guardian');
|
|
3152
|
+
if (proposals[0].metadata?.proposalType === 'switch_guardian') {
|
|
3153
|
+
expect(proposals[0].metadata.newGuardianPubkey).toBe('0xnewpubkey');
|
|
3154
|
+
expect(proposals[0].metadata.newGuardianEndpoint).toBe('http://new-guardian.com');
|
|
1306
3155
|
}
|
|
1307
3156
|
});
|
|
1308
3157
|
});
|