@openzeppelin/miden-multisig-client 0.13.4 → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -13
- package/dist/account/builder.d.ts +3 -3
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +10 -10
- package/dist/account/builder.js.map +1 -1
- package/dist/account/builder.test.js +16 -14
- package/dist/account/builder.test.js.map +1 -1
- package/dist/account/masm/account-components/auth.d.ts +4 -4
- package/dist/account/masm/account-components/auth.d.ts.map +1 -1
- package/dist/account/masm/account-components/auth.js +8 -12
- package/dist/account/masm/account-components/auth.js.map +1 -1
- package/dist/account/masm/auth.d.ts +4 -4
- package/dist/account/masm/auth.d.ts.map +1 -1
- package/dist/account/masm/auth.js +226 -124
- package/dist/account/masm/auth.js.map +1 -1
- package/dist/account/storage.d.ts.map +1 -1
- package/dist/account/storage.js +24 -6
- package/dist/account/storage.js.map +1 -1
- package/dist/account/storage.test.d.ts +2 -0
- package/dist/account/storage.test.d.ts.map +1 -0
- package/dist/account/storage.test.js +73 -0
- package/dist/account/storage.test.js.map +1 -0
- package/dist/client.d.ts +9 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +24 -14
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +45 -8
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +6 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -12
- package/dist/index.js.map +1 -1
- package/dist/multisig/proposal/execution.d.ts +3 -2
- package/dist/multisig/proposal/execution.d.ts.map +1 -1
- package/dist/multisig/proposal/execution.js +8 -12
- package/dist/multisig/proposal/execution.js.map +1 -1
- package/dist/multisig.d.ts +7 -4
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +53 -35
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +17 -29
- package/dist/multisig.test.js.map +1 -1
- package/dist/procedures.d.ts +8 -8
- package/dist/procedures.js +8 -8
- package/dist/raw-client.d.ts +13 -0
- package/dist/raw-client.d.ts.map +1 -0
- package/dist/raw-client.js +42 -0
- package/dist/raw-client.js.map +1 -0
- package/dist/raw-client.test.d.ts +2 -0
- package/dist/raw-client.test.d.ts.map +1 -0
- package/dist/raw-client.test.js +89 -0
- package/dist/raw-client.test.js.map +1 -0
- package/dist/signers/ecdsa.d.ts +3 -1
- package/dist/signers/ecdsa.d.ts.map +1 -1
- package/dist/signers/ecdsa.js +15 -1
- package/dist/signers/ecdsa.js.map +1 -1
- package/dist/signers/ecdsa.test.js +27 -4
- package/dist/signers/ecdsa.test.js.map +1 -1
- package/dist/signers/falcon.d.ts +3 -1
- package/dist/signers/falcon.d.ts.map +1 -1
- package/dist/signers/falcon.js +15 -1
- package/dist/signers/falcon.js.map +1 -1
- package/dist/signers/falcon.test.js +39 -3
- package/dist/signers/falcon.test.js.map +1 -1
- package/dist/transaction/consumeNotes.d.ts +2 -2
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +5 -4
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/options.d.ts +1 -0
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +7 -10
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/p2id.test.js +19 -9
- package/dist/transaction/p2id.test.js.map +1 -1
- package/dist/transaction/summary.d.ts +2 -2
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +4 -2
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/updateGuardian.d.ts +2 -2
- package/dist/transaction/updateGuardian.d.ts.map +1 -1
- package/dist/transaction/updateGuardian.js +8 -11
- package/dist/transaction/updateGuardian.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.d.ts +2 -2
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +8 -10
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +2 -2
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +8 -10
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.test.js +6 -6
- package/dist/transaction.test.js.map +1 -1
- package/dist/utils/key.d.ts.map +1 -1
- package/dist/utils/key.js +3 -3
- package/dist/utils/key.js.map +1 -1
- package/dist/utils/key.test.js +4 -2
- package/dist/utils/key.test.js.map +1 -1
- package/dist/utils/signature.d.ts +2 -0
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +10 -5
- package/dist/utils/signature.js.map +1 -1
- package/dist/utils/signature.test.js +9 -9
- package/dist/utils/signature.test.js.map +1 -1
- package/masm/account_components/auth/multisig.masm +2 -3
- package/masm/account_components/auth/multisig_ecdsa.masm +2 -3
- package/masm/account_components/auth/multisig_guardian.masm +2 -3
- package/masm/account_components/auth/multisig_guardian_ecdsa.masm +2 -3
- package/masm/auth/guardian.masm +30 -8
- package/masm/auth/guardian_ecdsa.masm +24 -7
- package/masm/auth/multisig.masm +86 -55
- package/masm/auth/multisig_ecdsa.masm +86 -54
- package/package.json +3 -3
- package/src/account/builder.test.ts +16 -14
- package/src/account/builder.ts +13 -12
- package/src/account/masm/account-components/auth.ts +8 -12
- package/src/account/masm/auth.ts +226 -124
- package/src/account/storage.test.ts +90 -0
- package/src/account/storage.ts +27 -6
- package/src/client.test.ts +52 -8
- package/src/client.ts +39 -16
- package/src/index.ts +6 -12
- package/src/multisig/proposal/execution.ts +18 -14
- package/src/multisig.test.ts +17 -29
- package/src/multisig.ts +61 -36
- package/src/procedures.ts +8 -8
- package/src/raw-client.test.ts +133 -0
- package/src/raw-client.ts +75 -0
- package/src/signers/ecdsa.test.ts +35 -4
- package/src/signers/ecdsa.ts +18 -2
- package/src/signers/falcon.test.ts +50 -3
- package/src/signers/falcon.ts +18 -2
- package/src/transaction/consumeNotes.ts +16 -5
- package/src/transaction/options.ts +1 -0
- package/src/transaction/p2id.test.ts +19 -9
- package/src/transaction/p2id.ts +8 -13
- package/src/transaction/summary.ts +11 -4
- package/src/transaction/updateGuardian.ts +22 -14
- package/src/transaction/updateProcedureThreshold.ts +20 -15
- package/src/transaction/updateSigners.ts +22 -14
- package/src/transaction.test.ts +6 -6
- package/src/utils/key.test.ts +4 -2
- package/src/utils/key.ts +7 -3
- package/src/utils/signature.test.ts +10 -10
- package/src/utils/signature.ts +12 -5
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const { storageMaps, storageSlotMap } = vi.hoisted(() => {
|
|
4
|
+
const storageMaps: Array<{ entries: Array<{ key: unknown; value: unknown }> }> = [];
|
|
5
|
+
const storageSlotMap = vi.fn((name, map) => ({ kind: 'map', name, map }));
|
|
6
|
+
|
|
7
|
+
return { storageMaps, storageSlotMap };
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
vi.mock('@miden-sdk/miden-sdk', () => {
|
|
11
|
+
class MockWord {
|
|
12
|
+
data: BigUint64Array | string;
|
|
13
|
+
|
|
14
|
+
constructor(data: BigUint64Array | string) {
|
|
15
|
+
this.data = data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static fromHex(hex: string) {
|
|
19
|
+
return new MockWord(hex);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class MockStorageMap {
|
|
24
|
+
entries: Array<{ key: unknown; value: unknown }> = [];
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
storageMaps.push(this);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
insert(key: unknown, value: unknown) {
|
|
31
|
+
this.entries.push({ key, value });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
Word: MockWord,
|
|
37
|
+
StorageMap: MockStorageMap,
|
|
38
|
+
StorageSlot: {
|
|
39
|
+
fromValue: vi.fn((name, value) => ({ kind: 'value', name, value })),
|
|
40
|
+
map: storageSlotMap,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('buildMultisigStorageSlots', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
storageMaps.length = 0;
|
|
48
|
+
storageSlotMap.mockClear();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('keeps single-signer public-key and scheme maps to the configured signer count', async () => {
|
|
52
|
+
const { buildMultisigStorageSlots } = await import('./storage.js');
|
|
53
|
+
|
|
54
|
+
buildMultisigStorageSlots({
|
|
55
|
+
threshold: 1,
|
|
56
|
+
signerCommitments: ['0x' + '1'.repeat(64)],
|
|
57
|
+
guardianCommitment: '0x' + '2'.repeat(64),
|
|
58
|
+
signatureScheme: 'falcon',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const signersMap = storageMaps[0];
|
|
62
|
+
const signerSchemesMap = storageMaps[1];
|
|
63
|
+
|
|
64
|
+
expect(signersMap.entries).toHaveLength(1);
|
|
65
|
+
expect(signerSchemesMap.entries).toHaveLength(1);
|
|
66
|
+
expect(signersMap.entries[0].key).toMatchObject({
|
|
67
|
+
data: new BigUint64Array([0n, 0n, 0n, 0n]),
|
|
68
|
+
});
|
|
69
|
+
expect(signerSchemesMap.entries[0].key).toMatchObject({
|
|
70
|
+
data: new BigUint64Array([0n, 0n, 0n, 0n]),
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('does not pad signer maps when there are already multiple signers', async () => {
|
|
75
|
+
const { buildMultisigStorageSlots } = await import('./storage.js');
|
|
76
|
+
|
|
77
|
+
buildMultisigStorageSlots({
|
|
78
|
+
threshold: 1,
|
|
79
|
+
signerCommitments: ['0x' + '1'.repeat(64), '0x' + '3'.repeat(64)],
|
|
80
|
+
guardianCommitment: '0x' + '2'.repeat(64),
|
|
81
|
+
signatureScheme: 'falcon',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const signersMap = storageMaps[0];
|
|
85
|
+
const signerSchemesMap = storageMaps[1];
|
|
86
|
+
|
|
87
|
+
expect(signersMap.entries).toHaveLength(2);
|
|
88
|
+
expect(signerSchemesMap.entries).toHaveLength(2);
|
|
89
|
+
});
|
|
90
|
+
});
|
package/src/account/storage.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { getProcedureRoot } from '../procedures.js';
|
|
|
7
7
|
const MULTISIG_SLOT_NAMES = {
|
|
8
8
|
THRESHOLD_CONFIG: 'openzeppelin::multisig::threshold_config',
|
|
9
9
|
SIGNER_PUBLIC_KEYS: 'openzeppelin::multisig::signer_public_keys',
|
|
10
|
+
SIGNER_SCHEME_IDS: 'openzeppelin::multisig::signer_scheme_ids',
|
|
10
11
|
EXECUTED_TRANSACTIONS: 'openzeppelin::multisig::executed_transactions',
|
|
11
12
|
PROCEDURE_THRESHOLDS: 'openzeppelin::multisig::procedure_thresholds',
|
|
12
13
|
} as const;
|
|
@@ -14,11 +15,17 @@ const MULTISIG_SLOT_NAMES = {
|
|
|
14
15
|
const GUARDIAN_SLOT_NAMES = {
|
|
15
16
|
SELECTOR: 'openzeppelin::guardian::selector',
|
|
16
17
|
PUBLIC_KEY: 'openzeppelin::guardian::public_key',
|
|
18
|
+
SCHEME_ID: 'openzeppelin::guardian::scheme_id',
|
|
17
19
|
} as const;
|
|
18
20
|
|
|
21
|
+
function signerMapKey(index: bigint): Word {
|
|
22
|
+
return new Word(new BigUint64Array([index, 0n, 0n, 0n]));
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
export class StorageLayoutBuilder {
|
|
20
26
|
buildMultisigSlots(config: MultisigConfig): StorageSlot[] {
|
|
21
27
|
const numSigners = config.signerCommitments.length;
|
|
28
|
+
const schemeId = config.signatureScheme === 'ecdsa' ? 1n : 2n;
|
|
22
29
|
const slot0Word = new Word(
|
|
23
30
|
new BigUint64Array([
|
|
24
31
|
BigInt(config.threshold),
|
|
@@ -31,13 +38,21 @@ export class StorageLayoutBuilder {
|
|
|
31
38
|
|
|
32
39
|
const signersMap = new StorageMap();
|
|
33
40
|
config.signerCommitments.forEach((commitment, index) => {
|
|
34
|
-
const key =
|
|
41
|
+
const key = signerMapKey(BigInt(index));
|
|
35
42
|
const value = Word.fromHex(ensureHexPrefix(commitment));
|
|
36
43
|
signersMap.insert(key, value);
|
|
37
44
|
});
|
|
38
45
|
const slot1 = StorageSlot.map(MULTISIG_SLOT_NAMES.SIGNER_PUBLIC_KEYS, signersMap);
|
|
39
46
|
|
|
40
|
-
const
|
|
47
|
+
const signerSchemesMap = new StorageMap();
|
|
48
|
+
config.signerCommitments.forEach((_, index) => {
|
|
49
|
+
const key = signerMapKey(BigInt(index));
|
|
50
|
+
const value = new Word(new BigUint64Array([schemeId, 0n, 0n, 0n]));
|
|
51
|
+
signerSchemesMap.insert(key, value);
|
|
52
|
+
});
|
|
53
|
+
const slot2 = StorageSlot.map(MULTISIG_SLOT_NAMES.SIGNER_SCHEME_IDS, signerSchemesMap);
|
|
54
|
+
|
|
55
|
+
const slot3 = StorageSlot.map(MULTISIG_SLOT_NAMES.EXECUTED_TRANSACTIONS, new StorageMap());
|
|
41
56
|
|
|
42
57
|
// Map entries: PROC_ROOT => [proc_threshold, 0, 0, 0]
|
|
43
58
|
// Use SDK's Word.fromHex to match how account code procedure roots are represented
|
|
@@ -50,23 +65,29 @@ export class StorageLayoutBuilder {
|
|
|
50
65
|
procThresholdMap.insert(key, value);
|
|
51
66
|
}
|
|
52
67
|
}
|
|
53
|
-
const
|
|
68
|
+
const slot4 = StorageSlot.map(MULTISIG_SLOT_NAMES.PROCEDURE_THRESHOLDS, procThresholdMap);
|
|
54
69
|
|
|
55
|
-
return [slot0, slot1, slot2, slot3];
|
|
70
|
+
return [slot0, slot1, slot2, slot3, slot4];
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
buildGuardianSlots(config: MultisigConfig): StorageSlot[] {
|
|
59
74
|
const selector = config.guardianEnabled !== false ? 1n : 0n;
|
|
75
|
+
const schemeId = config.signatureScheme === 'ecdsa' ? 1n : 2n;
|
|
60
76
|
const selectorWord = new Word(new BigUint64Array([selector, 0n, 0n, 0n]));
|
|
61
77
|
const slot0 = StorageSlot.fromValue(GUARDIAN_SLOT_NAMES.SELECTOR, selectorWord);
|
|
62
78
|
|
|
63
79
|
const guardianKeyMap = new StorageMap();
|
|
64
|
-
const zeroKey =
|
|
80
|
+
const zeroKey = signerMapKey(0n);
|
|
65
81
|
const guardianKey = Word.fromHex(ensureHexPrefix(config.guardianCommitment));
|
|
66
82
|
guardianKeyMap.insert(zeroKey, guardianKey);
|
|
67
83
|
const slot1 = StorageSlot.map(GUARDIAN_SLOT_NAMES.PUBLIC_KEY, guardianKeyMap);
|
|
68
84
|
|
|
69
|
-
|
|
85
|
+
const guardianSchemeMap = new StorageMap();
|
|
86
|
+
const guardianScheme = new Word(new BigUint64Array([schemeId, 0n, 0n, 0n]));
|
|
87
|
+
guardianSchemeMap.insert(zeroKey, guardianScheme);
|
|
88
|
+
const slot2 = StorageSlot.map(GUARDIAN_SLOT_NAMES.SCHEME_ID, guardianSchemeMap);
|
|
89
|
+
|
|
90
|
+
return [slot0, slot1, slot2];
|
|
70
91
|
}
|
|
71
92
|
}
|
|
72
93
|
|
package/src/client.test.ts
CHANGED
|
@@ -63,14 +63,13 @@ describe('MultisigClient', () => {
|
|
|
63
63
|
mockFetch.mockReset();
|
|
64
64
|
|
|
65
65
|
webClient = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
getAccount: vi.fn().mockResolvedValue(null),
|
|
66
|
+
accounts: {
|
|
67
|
+
get: vi.fn().mockResolvedValue(null),
|
|
68
|
+
insert: vi.fn().mockResolvedValue(undefined),
|
|
69
|
+
},
|
|
70
|
+
keystore: {
|
|
71
|
+
insert: vi.fn().mockResolvedValue(undefined),
|
|
72
|
+
},
|
|
74
73
|
};
|
|
75
74
|
|
|
76
75
|
mockSigner = {
|
|
@@ -132,6 +131,23 @@ describe('MultisigClient', () => {
|
|
|
132
131
|
const multisig = await client.create(config, mockSigner);
|
|
133
132
|
expect(multisig.signerCommitment).toBe(mockSigner.commitment);
|
|
134
133
|
});
|
|
134
|
+
|
|
135
|
+
it('binds the signer auth key to the created account when supported', async () => {
|
|
136
|
+
const client = new MultisigClient(webClient);
|
|
137
|
+
const bindAccountKey = vi.fn().mockResolvedValue(undefined);
|
|
138
|
+
const bindingSigner = {
|
|
139
|
+
...mockSigner,
|
|
140
|
+
bindAccountKey,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
await client.create({
|
|
144
|
+
threshold: 1,
|
|
145
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
146
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
147
|
+
}, bindingSigner);
|
|
148
|
+
|
|
149
|
+
expect(bindAccountKey).toHaveBeenCalledWith(webClient, '0x' + 'a'.repeat(30));
|
|
150
|
+
});
|
|
135
151
|
});
|
|
136
152
|
|
|
137
153
|
describe('load', () => {
|
|
@@ -160,6 +176,8 @@ describe('MultisigClient', () => {
|
|
|
160
176
|
expect(multisig.signerCommitments).toEqual(['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)]);
|
|
161
177
|
expect(multisig.guardianCommitment).toBe('0x' + 'c'.repeat(64));
|
|
162
178
|
expect(multisig.account).not.toBeNull();
|
|
179
|
+
expect(webClient.accounts.get).toHaveBeenCalledTimes(1);
|
|
180
|
+
expect(webClient.accounts.insert).toHaveBeenCalledTimes(1);
|
|
163
181
|
});
|
|
164
182
|
|
|
165
183
|
it('should throw if account not found on GUARDIAN', async () => {
|
|
@@ -204,6 +222,32 @@ describe('MultisigClient', () => {
|
|
|
204
222
|
const multisig = await client.load(accountId, mockSigner);
|
|
205
223
|
|
|
206
224
|
await expect(multisig.registerOnGuardian()).resolves.toBeUndefined();
|
|
225
|
+
expect(webClient.accounts.get).toHaveBeenCalledTimes(1);
|
|
226
|
+
expect(webClient.accounts.insert).toHaveBeenCalledTimes(1);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('binds the signer auth key after loading an account when supported', async () => {
|
|
230
|
+
const client = new MultisigClient(webClient);
|
|
231
|
+
const bindAccountKey = vi.fn().mockResolvedValue(undefined);
|
|
232
|
+
const bindingSigner = {
|
|
233
|
+
...mockSigner,
|
|
234
|
+
bindAccountKey,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
mockFetch.mockResolvedValueOnce({
|
|
238
|
+
ok: true,
|
|
239
|
+
json: async () => ({
|
|
240
|
+
account_id: '0x' + 'd'.repeat(30),
|
|
241
|
+
commitment: '0x' + 'e'.repeat(64),
|
|
242
|
+
state_json: { data: 'base64state' },
|
|
243
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
244
|
+
updated_at: '2024-01-02T00:00:00Z',
|
|
245
|
+
}),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
await client.load('0x' + 'd'.repeat(30), bindingSigner);
|
|
249
|
+
|
|
250
|
+
expect(bindAccountKey).toHaveBeenCalledWith(webClient, '0x' + 'd'.repeat(30));
|
|
207
251
|
});
|
|
208
252
|
});
|
|
209
253
|
});
|
package/src/client.ts
CHANGED
|
@@ -5,13 +5,29 @@
|
|
|
5
5
|
* to create new multisig accounts and load existing ones.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { type
|
|
8
|
+
import { type MidenClient, Account, AccountId } from '@miden-sdk/miden-sdk';
|
|
9
9
|
import { GuardianHttpClient } from '@openzeppelin/guardian-client';
|
|
10
10
|
import { Multisig } from './multisig.js';
|
|
11
11
|
import { createMultisigAccount } from './account/index.js';
|
|
12
12
|
import { AccountInspector } from './inspector.js';
|
|
13
|
+
import { getRawMidenClient, resolveMidenRpcEndpoint } from './raw-client.js';
|
|
13
14
|
import type { MultisigConfig, Signer } from './types.js';
|
|
14
15
|
|
|
16
|
+
interface AccountKeyBindingSigner {
|
|
17
|
+
bindAccountKey?(midenClient: MidenClient, accountId: string): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function bindSignerAccountKey(
|
|
21
|
+
signer: Signer,
|
|
22
|
+
midenClient: MidenClient,
|
|
23
|
+
accountId: string,
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
const bindingSigner = signer as Signer & AccountKeyBindingSigner;
|
|
26
|
+
if (typeof bindingSigner.bindAccountKey === 'function') {
|
|
27
|
+
await bindingSigner.bindAccountKey(midenClient, accountId);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
/**
|
|
16
32
|
* Configuration for MultisigClient.
|
|
17
33
|
*/
|
|
@@ -28,17 +44,17 @@ export interface MultisigClientConfig {
|
|
|
28
44
|
* @example
|
|
29
45
|
* ```typescript
|
|
30
46
|
* import { MultisigClient, FalconSigner } from '@openzeppelin/miden-multisig-client';
|
|
31
|
-
* import {
|
|
47
|
+
* import { MidenClient, AuthSecretKey } from '@miden-sdk/miden-sdk';
|
|
32
48
|
*
|
|
33
49
|
* // Initialize
|
|
34
|
-
* const
|
|
35
|
-
* const secretKey =
|
|
50
|
+
* const midenClient = await MidenClient.createDevnet();
|
|
51
|
+
* const secretKey = AuthSecretKey.rpoFalconWithRNG(seed);
|
|
36
52
|
* const signer = new FalconSigner(secretKey);
|
|
37
53
|
*
|
|
38
54
|
* // Create client
|
|
39
|
-
* const client = new MultisigClient(
|
|
55
|
+
* const client = new MultisigClient(midenClient, {
|
|
40
56
|
* guardianEndpoint: 'http://localhost:3000',
|
|
41
|
-
* midenRpcEndpoint: 'https://rpc.
|
|
57
|
+
* midenRpcEndpoint: 'https://rpc.devnet.miden.io',
|
|
42
58
|
* });
|
|
43
59
|
*
|
|
44
60
|
* // Get GUARDIAN pubkey for config
|
|
@@ -50,13 +66,13 @@ export interface MultisigClientConfig {
|
|
|
50
66
|
* ```
|
|
51
67
|
*/
|
|
52
68
|
export class MultisigClient {
|
|
53
|
-
private readonly
|
|
54
|
-
private readonly midenRpcEndpoint
|
|
69
|
+
private readonly midenClient: MidenClient;
|
|
70
|
+
private readonly midenRpcEndpoint: string;
|
|
55
71
|
private _guardianClient: GuardianHttpClient;
|
|
56
72
|
|
|
57
|
-
constructor(
|
|
58
|
-
this.
|
|
59
|
-
this.midenRpcEndpoint = config.midenRpcEndpoint;
|
|
73
|
+
constructor(midenClient: MidenClient, config: MultisigClientConfig = {}) {
|
|
74
|
+
this.midenClient = midenClient;
|
|
75
|
+
this.midenRpcEndpoint = resolveMidenRpcEndpoint(config.midenRpcEndpoint);
|
|
60
76
|
this._guardianClient = new GuardianHttpClient(config.guardianEndpoint ?? 'http://localhost:3000');
|
|
61
77
|
}
|
|
62
78
|
|
|
@@ -86,14 +102,20 @@ export class MultisigClient {
|
|
|
86
102
|
async create(config: MultisigConfig, signer: Signer): Promise<Multisig> {
|
|
87
103
|
this._guardianClient.setSigner(signer);
|
|
88
104
|
|
|
89
|
-
const { account } = await createMultisigAccount(
|
|
105
|
+
const { account } = await createMultisigAccount(
|
|
106
|
+
this.midenClient,
|
|
107
|
+
config,
|
|
108
|
+
this.midenRpcEndpoint,
|
|
109
|
+
);
|
|
110
|
+
const accountId = account.id().toString();
|
|
111
|
+
await bindSignerAccountKey(signer, this.midenClient, accountId);
|
|
90
112
|
|
|
91
113
|
return new Multisig(
|
|
92
114
|
account,
|
|
93
115
|
config,
|
|
94
116
|
this._guardianClient,
|
|
95
117
|
signer,
|
|
96
|
-
this.
|
|
118
|
+
this.midenClient,
|
|
97
119
|
undefined,
|
|
98
120
|
this.midenRpcEndpoint
|
|
99
121
|
);
|
|
@@ -134,17 +156,18 @@ export class MultisigClient {
|
|
|
134
156
|
),
|
|
135
157
|
};
|
|
136
158
|
|
|
137
|
-
const existingAccount = await this.
|
|
159
|
+
const existingAccount = await this.midenClient.accounts.get(AccountId.fromHex(accountId));
|
|
138
160
|
if (!existingAccount) {
|
|
139
|
-
|
|
161
|
+
await this.midenClient.accounts.insert({ account, overwrite: true });
|
|
140
162
|
}
|
|
163
|
+
await bindSignerAccountKey(signer, this.midenClient, accountId);
|
|
141
164
|
|
|
142
165
|
return new Multisig(
|
|
143
166
|
account,
|
|
144
167
|
config,
|
|
145
168
|
this._guardianClient,
|
|
146
169
|
signer,
|
|
147
|
-
this.
|
|
170
|
+
this.midenClient,
|
|
148
171
|
accountId,
|
|
149
172
|
this.midenRpcEndpoint
|
|
150
173
|
);
|
package/src/index.ts
CHANGED
|
@@ -9,27 +9,21 @@
|
|
|
9
9
|
* MultisigClient,
|
|
10
10
|
* FalconSigner,
|
|
11
11
|
* } from '@openzeppelin/miden-multisig-client';
|
|
12
|
-
* import {
|
|
12
|
+
* import { MidenClient, AuthSecretKey } from '@miden-sdk/miden-sdk';
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* const
|
|
16
|
-
* await webClient.syncState();
|
|
17
|
-
*
|
|
18
|
-
* // Generate a key dynamically
|
|
19
|
-
* const seed = new Uint8Array(32);
|
|
20
|
-
* crypto.getRandomValues(seed);
|
|
21
|
-
* const secretKey = SecretKey.rpoFalconWithRNG(seed);
|
|
14
|
+
* const midenClient = await MidenClient.createDevnet();
|
|
15
|
+
* const secretKey = AuthSecretKey.rpoFalconWithRNG(undefined);
|
|
22
16
|
*
|
|
23
17
|
* // Store in miden-sdk's keystore
|
|
24
|
-
* await
|
|
18
|
+
* await midenClient.keystore.insert(secretKey.publicKey(), secretKey);
|
|
25
19
|
*
|
|
26
20
|
* // Create a signer
|
|
27
21
|
* const signer = new FalconSigner(secretKey);
|
|
28
22
|
*
|
|
29
23
|
* // Create multisig client
|
|
30
|
-
* const client = new MultisigClient(
|
|
24
|
+
* const client = new MultisigClient(midenClient, {
|
|
31
25
|
* guardianEndpoint: 'http://localhost:3000',
|
|
32
|
-
* midenRpcEndpoint: 'https://rpc.
|
|
26
|
+
* midenRpcEndpoint: 'https://rpc.devnet.miden.io',
|
|
33
27
|
* });
|
|
34
28
|
*
|
|
35
29
|
* // Get GUARDIAN pubkey for config
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type DeltaObject, type GuardianHttpClient } from '@openzeppelin/guardian-client';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
MidenClient,
|
|
4
|
+
TransactionProver,
|
|
5
|
+
TransactionRequest,
|
|
6
|
+
} from '@miden-sdk/miden-sdk';
|
|
3
7
|
import {
|
|
4
|
-
AccountId,
|
|
5
8
|
AdviceMap,
|
|
6
9
|
FeltArray,
|
|
7
10
|
Signature,
|
|
@@ -45,7 +48,8 @@ interface ExecuteProposalWorkflowParams {
|
|
|
45
48
|
signatureScheme: 'falcon' | 'ecdsa';
|
|
46
49
|
getEffectiveThreshold: (proposalType: ProposalType) => number;
|
|
47
50
|
guardian: GuardianHttpClient;
|
|
48
|
-
|
|
51
|
+
midenClient: MidenClient;
|
|
52
|
+
transactionProver?: TransactionProver | null;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
export async function executeProposalWorkflow(
|
|
@@ -89,7 +93,11 @@ export async function executeProposalWorkflow(
|
|
|
89
93
|
executionData.saltHex,
|
|
90
94
|
adviceMap,
|
|
91
95
|
);
|
|
92
|
-
await submitTransaction(
|
|
96
|
+
await submitTransaction(
|
|
97
|
+
params.midenClient,
|
|
98
|
+
params.accountId,
|
|
99
|
+
finalRequest,
|
|
100
|
+
);
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
|
|
@@ -297,7 +305,7 @@ async function buildFinalRequest(
|
|
|
297
305
|
);
|
|
298
306
|
}
|
|
299
307
|
const { request } = await buildConsumeNotesTransactionRequest(
|
|
300
|
-
params.
|
|
308
|
+
params.midenClient,
|
|
301
309
|
metadata.noteIds,
|
|
302
310
|
{ salt: normalizedSalt, signatureAdviceMap: adviceMap },
|
|
303
311
|
);
|
|
@@ -310,7 +318,7 @@ async function buildFinalRequest(
|
|
|
310
318
|
);
|
|
311
319
|
}
|
|
312
320
|
const { request } = await buildUpdateGuardianTransactionRequest(
|
|
313
|
-
params.
|
|
321
|
+
params.midenClient,
|
|
314
322
|
metadata.newGuardianPubkey,
|
|
315
323
|
{
|
|
316
324
|
salt: normalizedSalt,
|
|
@@ -322,7 +330,7 @@ async function buildFinalRequest(
|
|
|
322
330
|
}
|
|
323
331
|
case 'update_procedure_threshold': {
|
|
324
332
|
const { request } = await buildUpdateProcedureThresholdTransactionRequest(
|
|
325
|
-
params.
|
|
333
|
+
params.midenClient,
|
|
326
334
|
metadata.targetProcedure,
|
|
327
335
|
metadata.targetThreshold,
|
|
328
336
|
{
|
|
@@ -354,7 +362,7 @@ async function buildFinalRequest(
|
|
|
354
362
|
);
|
|
355
363
|
default: {
|
|
356
364
|
const { request } = await buildUpdateSignersTransactionRequest(
|
|
357
|
-
params.
|
|
365
|
+
params.midenClient,
|
|
358
366
|
metadata.targetThreshold,
|
|
359
367
|
metadata.targetSignerCommitments,
|
|
360
368
|
{
|
|
@@ -369,13 +377,9 @@ async function buildFinalRequest(
|
|
|
369
377
|
}
|
|
370
378
|
|
|
371
379
|
async function submitTransaction(
|
|
372
|
-
|
|
380
|
+
midenClient: MidenClient,
|
|
373
381
|
accountIdHex: string,
|
|
374
382
|
request: TransactionRequest,
|
|
375
383
|
): Promise<void> {
|
|
376
|
-
|
|
377
|
-
const result = await webClient.executeTransaction(accountId, request);
|
|
378
|
-
const proven = await webClient.proveTransaction(result, null);
|
|
379
|
-
const submissionHeight = await webClient.submitProvenTransaction(proven, result);
|
|
380
|
-
await webClient.applyTransaction(result, submissionHeight);
|
|
384
|
+
await midenClient.transactions.submit(accountIdHex, request);
|
|
381
385
|
}
|
package/src/multisig.test.ts
CHANGED
|
@@ -48,7 +48,7 @@ vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
|
48
48
|
insert: vi.fn(),
|
|
49
49
|
})),
|
|
50
50
|
FeltArray: vi.fn().mockImplementation((arr: any[]) => arr),
|
|
51
|
-
|
|
51
|
+
Poseidon2: {
|
|
52
52
|
hashElements: vi.fn().mockReturnValue({
|
|
53
53
|
toHex: () => '0x' + 'e'.repeat(64),
|
|
54
54
|
}),
|
|
@@ -121,6 +121,9 @@ function mockedAccount(commitmentHex: string, nonce = 0): any {
|
|
|
121
121
|
commitment: () => ({
|
|
122
122
|
toHex: () => commitmentHex,
|
|
123
123
|
}),
|
|
124
|
+
to_commitment: () => ({
|
|
125
|
+
toHex: () => commitmentHex,
|
|
126
|
+
}),
|
|
124
127
|
nonce: () => ({
|
|
125
128
|
asInt: () => BigInt(nonce),
|
|
126
129
|
}),
|
|
@@ -187,6 +190,11 @@ describe('Multisig', () => {
|
|
|
187
190
|
proveTransaction: vi.fn(),
|
|
188
191
|
submitProvenTransaction: vi.fn(),
|
|
189
192
|
applyTransaction: vi.fn(),
|
|
193
|
+
submitNewTransaction: vi.fn(),
|
|
194
|
+
submitNewTransactionWithProver: vi.fn(),
|
|
195
|
+
transactions: {
|
|
196
|
+
submit: vi.fn(),
|
|
197
|
+
},
|
|
190
198
|
getConsumableNotes: vi.fn().mockResolvedValue([]),
|
|
191
199
|
syncState: vi.fn(),
|
|
192
200
|
getAccount: vi.fn().mockResolvedValue(null),
|
|
@@ -384,11 +392,7 @@ describe('Multisig', () => {
|
|
|
384
392
|
'https://rpc.devnet.miden.io'
|
|
385
393
|
);
|
|
386
394
|
|
|
387
|
-
mockWebClient.getAccount.mockResolvedValueOnce(
|
|
388
|
-
commitment: () => ({
|
|
389
|
-
toHex: () => '0x' + 'b'.repeat(64),
|
|
390
|
-
}),
|
|
391
|
-
});
|
|
395
|
+
mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'b'.repeat(64), 0));
|
|
392
396
|
mockFetch.mockResolvedValueOnce({
|
|
393
397
|
ok: true,
|
|
394
398
|
json: async () => ({
|
|
@@ -576,11 +580,7 @@ describe('Multisig', () => {
|
|
|
576
580
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
577
581
|
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
578
582
|
};
|
|
579
|
-
mockWebClient.getAccount.mockResolvedValueOnce(
|
|
580
|
-
commitment: () => ({
|
|
581
|
-
toHex: () => '0x' + 'b'.repeat(64),
|
|
582
|
-
}),
|
|
583
|
-
});
|
|
583
|
+
mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'b'.repeat(64), 0));
|
|
584
584
|
|
|
585
585
|
const multisigWithRpc = new Multisig(
|
|
586
586
|
mockAccount,
|
|
@@ -628,11 +628,7 @@ describe('Multisig', () => {
|
|
|
628
628
|
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
629
629
|
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
630
630
|
};
|
|
631
|
-
mockWebClient.getAccount.mockResolvedValueOnce(
|
|
632
|
-
commitment: () => ({
|
|
633
|
-
toHex: () => '0x' + 'f'.repeat(64),
|
|
634
|
-
}),
|
|
635
|
-
});
|
|
631
|
+
mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'f'.repeat(64), 0));
|
|
636
632
|
mockRpcGetAccountDetails.mockResolvedValueOnce({
|
|
637
633
|
commitment: () => ({
|
|
638
634
|
toHex: () => '0x' + 'b'.repeat(64),
|
|
@@ -2816,12 +2812,10 @@ describe('Multisig', () => {
|
|
|
2816
2812
|
ack_scheme: 'ecdsa',
|
|
2817
2813
|
}),
|
|
2818
2814
|
});
|
|
2819
|
-
mockWebClient.
|
|
2820
|
-
mockWebClient.proveTransaction.mockResolvedValueOnce({});
|
|
2821
|
-
mockWebClient.submitProvenTransaction.mockResolvedValueOnce(1n);
|
|
2822
|
-
mockWebClient.applyTransaction.mockResolvedValueOnce(undefined);
|
|
2815
|
+
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
2823
2816
|
|
|
2824
2817
|
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
2818
|
+
expect(mockWebClient.transactions.submit).toHaveBeenCalledTimes(1);
|
|
2825
2819
|
|
|
2826
2820
|
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(
|
|
2827
2821
|
1,
|
|
@@ -2933,10 +2927,7 @@ describe('Multisig', () => {
|
|
|
2933
2927
|
ack_scheme: 'ecdsa',
|
|
2934
2928
|
}),
|
|
2935
2929
|
});
|
|
2936
|
-
mockWebClient.
|
|
2937
|
-
mockWebClient.proveTransaction.mockResolvedValueOnce({});
|
|
2938
|
-
mockWebClient.submitProvenTransaction.mockResolvedValueOnce(1n);
|
|
2939
|
-
mockWebClient.applyTransaction.mockResolvedValueOnce(undefined);
|
|
2930
|
+
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
2940
2931
|
|
|
2941
2932
|
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
2942
2933
|
|
|
@@ -3011,13 +3002,10 @@ describe('Multisig', () => {
|
|
|
3011
3002
|
ok: true,
|
|
3012
3003
|
json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
|
|
3013
3004
|
});
|
|
3014
|
-
mockWebClient.
|
|
3015
|
-
mockWebClient.proveTransaction.mockResolvedValueOnce({});
|
|
3016
|
-
mockWebClient.submitProvenTransaction.mockResolvedValueOnce(1n);
|
|
3017
|
-
mockWebClient.applyTransaction.mockResolvedValueOnce(undefined);
|
|
3005
|
+
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
3018
3006
|
|
|
3019
3007
|
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
3020
|
-
expect(mockWebClient.
|
|
3008
|
+
expect(mockWebClient.transactions.submit).toHaveBeenCalledTimes(1);
|
|
3021
3009
|
});
|
|
3022
3010
|
|
|
3023
3011
|
it('should reject switch_guardian execution when endpoint commitment mismatches', async () => {
|