@openzeppelin/miden-multisig-client 0.12.8 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -4
- package/dist/account/builder.d.ts +1 -1
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +7 -5
- package/dist/account/builder.js.map +1 -1
- package/dist/account/builder.test.d.ts +2 -0
- package/dist/account/builder.test.d.ts.map +1 -0
- package/dist/account/builder.test.js +39 -0
- package/dist/account/builder.test.js.map +1 -0
- package/dist/account/masm.d.ts +3 -3
- package/dist/account/masm.d.ts.map +1 -1
- package/dist/account/masm.js +101 -98
- package/dist/account/masm.js.map +1 -1
- package/dist/account/storage.d.ts +1 -1
- package/dist/account/storage.d.ts.map +1 -1
- package/dist/account/storage.js +17 -7
- package/dist/account/storage.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +4 -4
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.d.ts.map +1 -1
- package/dist/inspector.js +16 -10
- package/dist/inspector.js.map +1 -1
- package/dist/inspector.test.js +33 -22
- package/dist/inspector.test.js.map +1 -1
- package/dist/multisig/helpers.d.ts +1 -1
- package/dist/multisig/helpers.d.ts.map +1 -1
- package/dist/multisig/helpers.js +1 -1
- package/dist/multisig/helpers.js.map +1 -1
- package/dist/multisig/helpers.test.js +2 -2
- package/dist/multisig/helpers.test.js.map +1 -1
- package/dist/multisig/proposal/execution.d.ts +19 -0
- package/dist/multisig/proposal/execution.d.ts.map +1 -0
- package/dist/multisig/proposal/execution.js +158 -0
- package/dist/multisig/proposal/execution.js.map +1 -0
- package/dist/multisig/proposal/parser.d.ts +17 -0
- package/dist/multisig/proposal/parser.d.ts.map +1 -0
- package/dist/multisig/proposal/parser.js +147 -0
- package/dist/multisig/proposal/parser.js.map +1 -0
- package/dist/multisig/signing.d.ts +5 -0
- package/dist/multisig/signing.d.ts.map +1 -0
- package/dist/multisig/signing.js +14 -0
- package/dist/multisig/signing.js.map +1 -0
- package/dist/multisig.d.ts +10 -13
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +128 -337
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +8 -4
- package/dist/multisig.test.js.map +1 -1
- package/dist/proposal/exported.d.ts +12 -0
- package/dist/proposal/exported.d.ts.map +1 -0
- package/dist/proposal/exported.js +88 -0
- package/dist/proposal/exported.js.map +1 -0
- package/dist/proposal/metadata.d.ts +15 -0
- package/dist/proposal/metadata.d.ts.map +1 -0
- package/dist/proposal/metadata.js +212 -0
- package/dist/proposal/metadata.js.map +1 -0
- package/dist/signer.d.ts +2 -20
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +2 -82
- package/dist/signer.js.map +1 -1
- package/dist/signer.test.js +22 -16
- package/dist/signer.test.js.map +1 -1
- package/dist/signers/ecdsa.d.ts +12 -0
- package/dist/signers/ecdsa.d.ts.map +1 -0
- package/dist/signers/ecdsa.js +30 -0
- package/dist/signers/ecdsa.js.map +1 -0
- package/dist/signers/falcon.d.ts +12 -0
- package/dist/signers/falcon.d.ts.map +1 -0
- package/dist/signers/falcon.js +31 -0
- package/dist/signers/falcon.js.map +1 -0
- package/dist/signers/falcon.test.d.ts +2 -0
- package/dist/signers/falcon.test.d.ts.map +1 -0
- package/dist/signers/falcon.test.js +113 -0
- package/dist/signers/falcon.test.js.map +1 -0
- package/dist/signers/index.d.ts +5 -0
- package/dist/signers/index.d.ts.map +1 -0
- package/dist/signers/index.js +5 -0
- package/dist/signers/index.js.map +1 -0
- package/dist/signers/miden-wallet.d.ts +16 -0
- package/dist/signers/miden-wallet.d.ts.map +1 -0
- package/dist/signers/miden-wallet.js +35 -0
- package/dist/signers/miden-wallet.js.map +1 -0
- package/dist/signers/miden-wallet.test.d.ts +2 -0
- package/dist/signers/miden-wallet.test.d.ts.map +1 -0
- package/dist/signers/miden-wallet.test.js +107 -0
- package/dist/signers/miden-wallet.test.js.map +1 -0
- package/dist/signers/para.d.ts +21 -0
- package/dist/signers/para.d.ts.map +1 -0
- package/dist/signers/para.js +43 -0
- package/dist/signers/para.js.map +1 -0
- package/dist/signers/para.test.d.ts +2 -0
- package/dist/signers/para.test.d.ts.map +1 -0
- package/dist/signers/para.test.js +88 -0
- package/dist/signers/para.test.js.map +1 -0
- package/dist/transaction/consumeNotes.d.ts +3 -3
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +11 -7
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/options.d.ts +2 -2
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/p2id.d.ts +1 -1
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.interop.test.d.ts +2 -0
- package/dist/transaction/p2id.interop.test.d.ts.map +1 -0
- package/dist/transaction/p2id.interop.test.js +36 -0
- package/dist/transaction/p2id.interop.test.js.map +1 -0
- package/dist/transaction/p2id.js +5 -5
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/p2id.test.d.ts +2 -0
- package/dist/transaction/p2id.test.d.ts.map +1 -0
- package/dist/transaction/p2id.test.js +138 -0
- package/dist/transaction/p2id.test.js.map +1 -0
- package/dist/transaction/rpoRandomCoin.d.ts +7 -0
- package/dist/transaction/rpoRandomCoin.d.ts.map +1 -0
- package/dist/transaction/rpoRandomCoin.js +17 -0
- package/dist/transaction/rpoRandomCoin.js.map +1 -0
- package/dist/transaction/rpoRandomCoin.test.d.ts +2 -0
- package/dist/transaction/rpoRandomCoin.test.d.ts.map +1 -0
- package/dist/transaction/rpoRandomCoin.test.js +52 -0
- package/dist/transaction/rpoRandomCoin.test.js.map +1 -0
- package/dist/transaction/summary.d.ts +1 -1
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +1 -1
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/updatePsm.d.ts +1 -1
- package/dist/transaction/updatePsm.d.ts.map +1 -1
- package/dist/transaction/updatePsm.js +5 -5
- package/dist/transaction/updatePsm.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +1 -1
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +5 -5
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.test.js +1 -1
- package/dist/transaction.test.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/digest.d.ts +6 -0
- package/dist/utils/digest.d.ts.map +1 -0
- package/dist/utils/digest.js +22 -0
- package/dist/utils/digest.js.map +1 -0
- package/dist/utils/ecdsa.d.ts +8 -0
- package/dist/utils/ecdsa.d.ts.map +1 -0
- package/dist/utils/ecdsa.js +60 -0
- package/dist/utils/ecdsa.js.map +1 -0
- package/dist/utils/ecdsa.test.d.ts +2 -0
- package/dist/utils/ecdsa.test.d.ts.map +1 -0
- package/dist/utils/ecdsa.test.js +85 -0
- package/dist/utils/ecdsa.test.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/key.d.ts +10 -0
- package/dist/utils/key.d.ts.map +1 -0
- package/dist/utils/key.js +38 -0
- package/dist/utils/key.js.map +1 -0
- package/dist/utils/key.test.d.ts +2 -0
- package/dist/utils/key.test.d.ts.map +1 -0
- package/dist/utils/key.test.js +80 -0
- package/dist/utils/key.test.js.map +1 -0
- package/dist/utils/random.d.ts +1 -1
- package/dist/utils/random.d.ts.map +1 -1
- package/dist/utils/random.js +1 -1
- package/dist/utils/random.js.map +1 -1
- package/dist/utils/signature.d.ts +2 -1
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +6 -3
- package/dist/utils/signature.js.map +1 -1
- package/dist/utils/signature.test.js +11 -11
- package/dist/utils/signature.test.js.map +1 -1
- package/dist/utils/state-validation.d.ts +5 -0
- package/dist/utils/state-validation.d.ts.map +1 -0
- package/dist/utils/state-validation.js +30 -0
- package/dist/utils/state-validation.js.map +1 -0
- package/dist/utils/validation.d.ts +6 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +52 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/utils/word.d.ts +6 -1
- package/dist/utils/word.d.ts.map +1 -1
- package/dist/utils/word.js +11 -0
- package/dist/utils/word.js.map +1 -1
- package/dist/utils/word.test.js +54 -3
- package/dist/utils/word.test.js.map +1 -1
- package/package.json +4 -5
- package/src/account/builder.ts +7 -5
- package/src/account/masm.ts +103 -99
- package/src/account/storage.ts +19 -7
- package/src/client.test.ts +4 -4
- package/src/client.ts +3 -3
- package/src/index.ts +8 -1
- package/src/inspector.test.ts +38 -25
- package/src/inspector.ts +18 -10
- package/src/multisig/helpers.test.ts +2 -2
- package/src/multisig/helpers.ts +2 -1
- package/src/multisig/proposal/execution.ts +364 -0
- package/src/multisig/proposal/parser.ts +191 -0
- package/src/multisig/signing.ts +24 -0
- package/src/multisig.test.ts +8 -4
- package/src/multisig.ts +176 -434
- package/src/signer.ts +2 -95
- package/src/signers/ecdsa.ts +35 -0
- package/src/{signer.test.ts → signers/falcon.test.ts} +29 -33
- package/src/signers/falcon.ts +36 -0
- package/src/signers/index.ts +4 -0
- package/src/signers/miden-wallet.test.ts +119 -0
- package/src/signers/miden-wallet.ts +49 -0
- package/src/signers/para.test.ts +103 -0
- package/src/signers/para.ts +60 -0
- package/src/transaction/consumeNotes.ts +14 -10
- package/src/transaction/options.ts +2 -2
- package/src/transaction/p2id.ts +3 -8
- package/src/transaction/summary.ts +2 -2
- package/src/transaction/updatePsm.ts +5 -6
- package/src/transaction/updateSigners.ts +5 -6
- package/src/transaction.test.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/digest.ts +25 -0
- package/src/utils/ecdsa.test.ts +104 -0
- package/src/utils/ecdsa.ts +77 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/key.test.ts +89 -0
- package/src/utils/key.ts +49 -0
- package/src/utils/random.ts +1 -1
- package/src/utils/signature.test.ts +11 -11
- package/src/utils/signature.ts +10 -3
- package/src/utils/word.test.ts +57 -3
- package/src/utils/word.ts +13 -1
package/src/inspector.test.ts
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { AccountInspector } from './inspector.js';
|
|
3
3
|
|
|
4
|
+
// Storage slot names matching the MASM definitions
|
|
5
|
+
const MULTISIG_SLOT_NAMES = {
|
|
6
|
+
THRESHOLD_CONFIG: 'openzeppelin::multisig::threshold_config',
|
|
7
|
+
SIGNER_PUBLIC_KEYS: 'openzeppelin::multisig::signer_public_keys',
|
|
8
|
+
EXECUTED_TRANSACTIONS: 'openzeppelin::multisig::executed_transactions',
|
|
9
|
+
PROCEDURE_THRESHOLDS: 'openzeppelin::multisig::procedure_thresholds',
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
const PSM_SLOT_NAMES = {
|
|
13
|
+
SELECTOR: 'openzeppelin::psm::selector',
|
|
14
|
+
PUBLIC_KEY: 'openzeppelin::psm::public_key',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
4
17
|
// Mock the Miden SDK
|
|
5
|
-
vi.mock('@
|
|
18
|
+
vi.mock('@miden-sdk/miden-sdk', () => {
|
|
6
19
|
const createMockWord = (values: bigint[]) => ({
|
|
7
20
|
toU64s: () => values,
|
|
8
21
|
toHex: () => '0x' + values.map(v => v.toString(16).padStart(16, '0')).join(''),
|
|
9
22
|
});
|
|
10
23
|
|
|
11
|
-
const createMockStorage = (slots: Map<
|
|
12
|
-
getItem: (
|
|
13
|
-
getMapItem: (
|
|
14
|
-
const map = maps.get(
|
|
24
|
+
const createMockStorage = (slots: Map<string, any>, maps: Map<string, Map<string, any>>) => ({
|
|
25
|
+
getItem: (slotName: string) => slots.get(slotName) ?? createMockWord([0n, 0n, 0n, 0n]),
|
|
26
|
+
getMapItem: (slotName: string, key: any) => {
|
|
27
|
+
const map = maps.get(slotName);
|
|
15
28
|
if (!map) throw new Error('Map not found');
|
|
16
29
|
const keyStr = key.toU64s?.()[0]?.toString() ?? '0';
|
|
17
30
|
const value = map.get(keyStr);
|
|
@@ -43,13 +56,13 @@ vi.mock('@demox-labs/miden-sdk', () => {
|
|
|
43
56
|
const psmMap = new Map<string, any>();
|
|
44
57
|
psmMap.set('0', createMockWord([BigInt('0xeeeeeeeeeeeeeeee'), BigInt('0xffffffffffffffff'), BigInt('0x0000000000000001'), BigInt('0x0000000000000002')]));
|
|
45
58
|
|
|
46
|
-
const slots = new Map<
|
|
47
|
-
slots.set(
|
|
48
|
-
slots.set(
|
|
59
|
+
const slots = new Map<string, any>();
|
|
60
|
+
slots.set('openzeppelin::multisig::threshold_config', slot0);
|
|
61
|
+
slots.set('openzeppelin::psm::selector', slot4);
|
|
49
62
|
|
|
50
|
-
const maps = new Map<
|
|
51
|
-
maps.set(
|
|
52
|
-
maps.set(
|
|
63
|
+
const maps = new Map<string, Map<string, any>>();
|
|
64
|
+
maps.set('openzeppelin::multisig::signer_public_keys', signerMap);
|
|
65
|
+
maps.set('openzeppelin::psm::public_key', psmMap);
|
|
53
66
|
|
|
54
67
|
return {
|
|
55
68
|
storage: () => createMockStorage(slots, maps),
|
|
@@ -111,7 +124,7 @@ describe('AccountInspector', () => {
|
|
|
111
124
|
|
|
112
125
|
describe('fromAccount', () => {
|
|
113
126
|
it('extracts threshold from slot 0', async () => {
|
|
114
|
-
const { Account } = await import('@
|
|
127
|
+
const { Account } = await import('@miden-sdk/miden-sdk');
|
|
115
128
|
const account = Account.deserialize(new Uint8Array([1, 2, 3]));
|
|
116
129
|
const config = AccountInspector.fromAccount(account);
|
|
117
130
|
|
|
@@ -119,7 +132,7 @@ describe('AccountInspector', () => {
|
|
|
119
132
|
});
|
|
120
133
|
|
|
121
134
|
it('extracts numSigners from slot 0', async () => {
|
|
122
|
-
const { Account } = await import('@
|
|
135
|
+
const { Account } = await import('@miden-sdk/miden-sdk');
|
|
123
136
|
const account = Account.deserialize(new Uint8Array([1, 2, 3]));
|
|
124
137
|
const config = AccountInspector.fromAccount(account);
|
|
125
138
|
|
|
@@ -134,18 +147,18 @@ describe('AccountInspector edge cases', () => {
|
|
|
134
147
|
});
|
|
135
148
|
|
|
136
149
|
it('handles account with PSM disabled', async () => {
|
|
137
|
-
const { Account } = await import('@
|
|
150
|
+
const { Account } = await import('@miden-sdk/miden-sdk');
|
|
138
151
|
|
|
139
152
|
// Override mock for this test
|
|
140
153
|
vi.mocked(Account.deserialize).mockReturnValueOnce({
|
|
141
154
|
storage: () => ({
|
|
142
|
-
getItem: (
|
|
143
|
-
if (
|
|
144
|
-
if (
|
|
155
|
+
getItem: (slotName: string) => {
|
|
156
|
+
if (slotName === 'openzeppelin::multisig::threshold_config') return { toU64s: () => [1n, 1n, 0n, 0n] };
|
|
157
|
+
if (slotName === 'openzeppelin::psm::selector') return { toU64s: () => [0n, 0n, 0n, 0n] }; // PSM disabled
|
|
145
158
|
return { toU64s: () => [0n, 0n, 0n, 0n] };
|
|
146
159
|
},
|
|
147
|
-
getMapItem: (
|
|
148
|
-
if (
|
|
160
|
+
getMapItem: (slotName: string, key: any) => {
|
|
161
|
+
if (slotName === 'openzeppelin::multisig::signer_public_keys' && key.toU64s?.()[0] === 0n) {
|
|
149
162
|
return {
|
|
150
163
|
toHex: () => '0x' + 'a'.repeat(64),
|
|
151
164
|
toU64s: () => [1n, 2n, 3n, 4n],
|
|
@@ -167,7 +180,7 @@ describe('AccountInspector edge cases', () => {
|
|
|
167
180
|
});
|
|
168
181
|
|
|
169
182
|
it('handles account with empty vault', async () => {
|
|
170
|
-
const { Account } = await import('@
|
|
183
|
+
const { Account } = await import('@miden-sdk/miden-sdk');
|
|
171
184
|
|
|
172
185
|
vi.mocked(Account.deserialize).mockReturnValueOnce({
|
|
173
186
|
storage: () => ({
|
|
@@ -188,13 +201,13 @@ describe('AccountInspector edge cases', () => {
|
|
|
188
201
|
});
|
|
189
202
|
|
|
190
203
|
it('handles missing signer map entries gracefully', async () => {
|
|
191
|
-
const { Account } = await import('@
|
|
204
|
+
const { Account } = await import('@miden-sdk/miden-sdk');
|
|
192
205
|
|
|
193
206
|
vi.mocked(Account.deserialize).mockReturnValueOnce({
|
|
194
207
|
storage: () => ({
|
|
195
|
-
getItem: (
|
|
196
|
-
if (
|
|
197
|
-
if (
|
|
208
|
+
getItem: (slotName: string) => {
|
|
209
|
+
if (slotName === 'openzeppelin::multisig::threshold_config') return { toU64s: () => [2n, 5n, 0n, 0n] }; // threshold=2, numSigners=5
|
|
210
|
+
if (slotName === 'openzeppelin::psm::selector') return { toU64s: () => [0n, 0n, 0n, 0n] };
|
|
198
211
|
return { toU64s: () => [0n, 0n, 0n, 0n] };
|
|
199
212
|
},
|
|
200
213
|
getMapItem: () => {
|
|
@@ -215,7 +228,7 @@ describe('AccountInspector edge cases', () => {
|
|
|
215
228
|
});
|
|
216
229
|
|
|
217
230
|
it('handles vault access error gracefully', async () => {
|
|
218
|
-
const { Account } = await import('@
|
|
231
|
+
const { Account } = await import('@miden-sdk/miden-sdk');
|
|
219
232
|
|
|
220
233
|
vi.mocked(Account.deserialize).mockReturnValueOnce({
|
|
221
234
|
storage: () => ({
|
package/src/inspector.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { Account, Word } from '@
|
|
1
|
+
import { Account, Word } from '@miden-sdk/miden-sdk';
|
|
2
2
|
import { base64ToUint8Array } from './utils/encoding.js';
|
|
3
3
|
import { wordElementToBigInt, wordToHex } from './utils/word.js';
|
|
4
4
|
import { getProcedureRoot, getProcedureNames, type ProcedureName } from './procedures.js';
|
|
5
5
|
import type { SignatureScheme } from './types.js';
|
|
6
6
|
|
|
7
|
+
const MULTISIG_SLOT_NAMES = {
|
|
8
|
+
THRESHOLD_CONFIG: 'openzeppelin::multisig::threshold_config',
|
|
9
|
+
SIGNER_PUBLIC_KEYS: 'openzeppelin::multisig::signer_public_keys',
|
|
10
|
+
EXECUTED_TRANSACTIONS: 'openzeppelin::multisig::executed_transactions',
|
|
11
|
+
PROCEDURE_THRESHOLDS: 'openzeppelin::multisig::procedure_thresholds',
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
const PSM_SLOT_NAMES = {
|
|
15
|
+
SELECTOR: 'openzeppelin::psm::selector',
|
|
16
|
+
PUBLIC_KEY: 'openzeppelin::psm::public_key',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
7
19
|
export interface VaultBalance {
|
|
8
20
|
faucetId: string;
|
|
9
21
|
amount: bigint;
|
|
@@ -32,7 +44,7 @@ export class AccountInspector {
|
|
|
32
44
|
static fromAccount(account: Account, signatureScheme: SignatureScheme = 'falcon'): DetectedMultisigConfig {
|
|
33
45
|
const storage = account.storage();
|
|
34
46
|
|
|
35
|
-
const slot0 = storage.getItem(
|
|
47
|
+
const slot0 = storage.getItem(MULTISIG_SLOT_NAMES.THRESHOLD_CONFIG) as Word;
|
|
36
48
|
const threshold = Number(wordElementToBigInt(slot0, 0));
|
|
37
49
|
const numSigners = Number(wordElementToBigInt(slot0, 1));
|
|
38
50
|
|
|
@@ -40,12 +52,11 @@ export class AccountInspector {
|
|
|
40
52
|
for (let i = 0; i < numSigners; i++) {
|
|
41
53
|
try {
|
|
42
54
|
const key = new Word(new BigUint64Array([BigInt(i), 0n, 0n, 0n]));
|
|
43
|
-
const commitment = storage.getMapItem(
|
|
55
|
+
const commitment = storage.getMapItem(MULTISIG_SLOT_NAMES.SIGNER_PUBLIC_KEYS, key) as Word;
|
|
44
56
|
if (commitment) {
|
|
45
57
|
signerCommitments.push(wordToHex(commitment));
|
|
46
58
|
}
|
|
47
59
|
} catch {
|
|
48
|
-
// Signer not found in storage map
|
|
49
60
|
}
|
|
50
61
|
}
|
|
51
62
|
|
|
@@ -53,19 +64,18 @@ export class AccountInspector {
|
|
|
53
64
|
let psmCommitment: string | null = null;
|
|
54
65
|
|
|
55
66
|
try {
|
|
56
|
-
const psmSlot0 = storage.getItem(
|
|
67
|
+
const psmSlot0 = storage.getItem(PSM_SLOT_NAMES.SELECTOR) as Word;
|
|
57
68
|
const selector = Number(wordElementToBigInt(psmSlot0, 0));
|
|
58
69
|
psmEnabled = selector === 1;
|
|
59
70
|
|
|
60
71
|
if (psmEnabled) {
|
|
61
72
|
const zeroKey = new Word(new BigUint64Array([0n, 0n, 0n, 0n]));
|
|
62
|
-
const psmKey = storage.getMapItem(
|
|
73
|
+
const psmKey = storage.getMapItem(PSM_SLOT_NAMES.PUBLIC_KEY, zeroKey) as Word;
|
|
63
74
|
if (psmKey) {
|
|
64
75
|
psmCommitment = wordToHex(psmKey);
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
} catch {
|
|
68
|
-
// PSM not configured
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
const vaultBalances: VaultBalance[] = [];
|
|
@@ -79,7 +89,6 @@ export class AccountInspector {
|
|
|
79
89
|
});
|
|
80
90
|
}
|
|
81
91
|
} catch {
|
|
82
|
-
// Vault access failed
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
const procedureThresholds = new Map<ProcedureName, number>();
|
|
@@ -87,7 +96,7 @@ export class AccountInspector {
|
|
|
87
96
|
try {
|
|
88
97
|
const rootHex = getProcedureRoot(procName, signatureScheme);
|
|
89
98
|
const rootWord = Word.fromHex(rootHex);
|
|
90
|
-
const value = storage.getMapItem(
|
|
99
|
+
const value = storage.getMapItem(MULTISIG_SLOT_NAMES.PROCEDURE_THRESHOLDS, rootWord) as Word;
|
|
91
100
|
if (value) {
|
|
92
101
|
const procThreshold = Number(wordElementToBigInt(value, 0));
|
|
93
102
|
if (procThreshold > 0) {
|
|
@@ -95,7 +104,6 @@ export class AccountInspector {
|
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
106
|
} catch {
|
|
98
|
-
// Not set
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { computeCommitmentFromTxSummary, accountIdToHex } from './helpers.js';
|
|
3
3
|
|
|
4
|
-
vi.mock('@
|
|
4
|
+
vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
5
5
|
Account: {},
|
|
6
6
|
TransactionSummary: {
|
|
7
7
|
deserialize: vi.fn((bytes: Uint8Array) => ({
|
|
@@ -27,7 +27,7 @@ describe('multisig helpers', () => {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
it('deserializes the tx summary bytes', async () => {
|
|
30
|
-
const { TransactionSummary } = await import('@
|
|
30
|
+
const { TransactionSummary } = await import('@miden-sdk/miden-sdk');
|
|
31
31
|
const base64 = btoa(String.fromCharCode(...new Uint8Array([4, 5, 6])));
|
|
32
32
|
|
|
33
33
|
computeCommitmentFromTxSummary(base64);
|
package/src/multisig/helpers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Account, TransactionSummary } from '@
|
|
1
|
+
import { Account, TransactionSummary, Word } from '@miden-sdk/miden-sdk';
|
|
2
2
|
import { base64ToUint8Array } from '../utils/encoding.js';
|
|
3
3
|
|
|
4
4
|
export function computeCommitmentFromTxSummary(txSummaryBase64: string): string {
|
|
@@ -7,6 +7,7 @@ export function computeCommitmentFromTxSummary(txSummaryBase64: string): string
|
|
|
7
7
|
const commitment = summary.toCommitment();
|
|
8
8
|
return commitment.toHex();
|
|
9
9
|
}
|
|
10
|
+
|
|
10
11
|
export function accountIdToHex(account: Account): string {
|
|
11
12
|
const accountId = account.id();
|
|
12
13
|
const str = accountId.toString();
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { type DeltaObject, type PsmHttpClient } from '@openzeppelin/psm-client';
|
|
2
|
+
import type { WebClient, TransactionRequest } from '@miden-sdk/miden-sdk';
|
|
3
|
+
import {
|
|
4
|
+
AccountId,
|
|
5
|
+
AdviceMap,
|
|
6
|
+
FeltArray,
|
|
7
|
+
Signature,
|
|
8
|
+
TransactionSummary,
|
|
9
|
+
Word,
|
|
10
|
+
} from '@miden-sdk/miden-sdk';
|
|
11
|
+
import type { ProposalType, TransactionProposal } from '../../types.js';
|
|
12
|
+
import {
|
|
13
|
+
buildConsumeNotesTransactionRequest,
|
|
14
|
+
buildP2idTransactionRequest,
|
|
15
|
+
buildUpdatePsmTransactionRequest,
|
|
16
|
+
buildUpdateSignersTransactionRequest,
|
|
17
|
+
} from '../../transaction.js';
|
|
18
|
+
import { base64ToUint8Array, normalizeHexWord } from '../../utils/encoding.js';
|
|
19
|
+
import {
|
|
20
|
+
buildSignatureAdviceEntry,
|
|
21
|
+
signatureHexToBytes,
|
|
22
|
+
tryComputeEcdsaCommitmentHex,
|
|
23
|
+
} from '../../utils/signature.js';
|
|
24
|
+
import { computeCommitmentFromTxSummary } from '../helpers.js';
|
|
25
|
+
|
|
26
|
+
interface ResolveExecutionSourceResult {
|
|
27
|
+
delta?: DeltaObject;
|
|
28
|
+
txSummaryBase64: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface PrepareExecutionResult {
|
|
32
|
+
txSummary: TransactionSummary;
|
|
33
|
+
saltHex: string;
|
|
34
|
+
txCommitmentHex: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ExecuteProposalWorkflowParams {
|
|
38
|
+
proposal: TransactionProposal;
|
|
39
|
+
accountId: string;
|
|
40
|
+
threshold: number;
|
|
41
|
+
signerCommitments: string[];
|
|
42
|
+
psmCommitment: string;
|
|
43
|
+
psmPublicKey?: string;
|
|
44
|
+
signatureScheme: 'falcon' | 'ecdsa';
|
|
45
|
+
getEffectiveThreshold: (proposalType: ProposalType) => number;
|
|
46
|
+
psm: PsmHttpClient;
|
|
47
|
+
webClient: WebClient;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function executeProposalWorkflow(
|
|
51
|
+
params: ExecuteProposalWorkflowParams,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
ensureProposalReady(
|
|
54
|
+
params.proposal,
|
|
55
|
+
params.threshold,
|
|
56
|
+
params.getEffectiveThreshold,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const isSwitchPsm = params.proposal.metadata.proposalType === 'switch_psm';
|
|
60
|
+
const executionSource = await resolveExecutionSource(
|
|
61
|
+
params.psm,
|
|
62
|
+
params.accountId,
|
|
63
|
+
params.proposal,
|
|
64
|
+
isSwitchPsm,
|
|
65
|
+
);
|
|
66
|
+
const executionData = prepareExecutionData(executionSource.txSummaryBase64);
|
|
67
|
+
|
|
68
|
+
const adviceMap = buildCosignerAdviceMap(
|
|
69
|
+
params.proposal,
|
|
70
|
+
params.signerCommitments,
|
|
71
|
+
executionData.txCommitmentHex,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!isSwitchPsm && executionSource.delta) {
|
|
75
|
+
await appendPsmAckAdvice(
|
|
76
|
+
params.psm,
|
|
77
|
+
executionSource.delta,
|
|
78
|
+
params.psmCommitment,
|
|
79
|
+
params.psmPublicKey,
|
|
80
|
+
params.signatureScheme,
|
|
81
|
+
executionData.txCommitmentHex,
|
|
82
|
+
adviceMap,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const finalRequest = await buildFinalRequest(
|
|
87
|
+
params,
|
|
88
|
+
executionData.saltHex,
|
|
89
|
+
adviceMap,
|
|
90
|
+
);
|
|
91
|
+
await submitTransaction(params.webClient, params.accountId, finalRequest);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
export async function createTransactionProposalRequest(
|
|
96
|
+
params: ExecuteProposalWorkflowParams,
|
|
97
|
+
): Promise<TransactionRequest> {
|
|
98
|
+
ensureProposalReady(
|
|
99
|
+
params.proposal,
|
|
100
|
+
params.threshold,
|
|
101
|
+
params.getEffectiveThreshold,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const isSwitchPsm = params.proposal.metadata.proposalType === 'switch_psm';
|
|
105
|
+
const executionSource = await resolveExecutionSource(
|
|
106
|
+
params.psm,
|
|
107
|
+
params.accountId,
|
|
108
|
+
params.proposal,
|
|
109
|
+
isSwitchPsm,
|
|
110
|
+
);
|
|
111
|
+
const executionData = prepareExecutionData(executionSource.txSummaryBase64);
|
|
112
|
+
|
|
113
|
+
const adviceMap = buildCosignerAdviceMap(
|
|
114
|
+
params.proposal,
|
|
115
|
+
params.signerCommitments,
|
|
116
|
+
executionData.txCommitmentHex,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (!isSwitchPsm && executionSource.delta) {
|
|
120
|
+
await appendPsmAckAdvice(
|
|
121
|
+
params.psm,
|
|
122
|
+
executionSource.delta,
|
|
123
|
+
params.psmCommitment,
|
|
124
|
+
params.psmPublicKey,
|
|
125
|
+
params.signatureScheme,
|
|
126
|
+
executionData.txCommitmentHex,
|
|
127
|
+
adviceMap,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return await buildFinalRequest(
|
|
132
|
+
params,
|
|
133
|
+
executionData.saltHex,
|
|
134
|
+
adviceMap,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function ensureProposalReady(
|
|
139
|
+
proposal: TransactionProposal,
|
|
140
|
+
defaultThreshold: number,
|
|
141
|
+
getEffectiveThreshold: (proposalType: ProposalType) => number,
|
|
142
|
+
): void {
|
|
143
|
+
const proposalType = proposal.metadata?.proposalType;
|
|
144
|
+
const effectiveThreshold = proposalType
|
|
145
|
+
? getEffectiveThreshold(proposalType)
|
|
146
|
+
: defaultThreshold;
|
|
147
|
+
|
|
148
|
+
if (proposal.signatures.length < effectiveThreshold) {
|
|
149
|
+
throw new Error('Proposal is not ready for execution. Still pending signatures.');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function resolveExecutionSource(
|
|
154
|
+
psm: PsmHttpClient,
|
|
155
|
+
accountId: string,
|
|
156
|
+
proposal: TransactionProposal,
|
|
157
|
+
isSwitchPsm: boolean,
|
|
158
|
+
): Promise<ResolveExecutionSourceResult> {
|
|
159
|
+
if (isSwitchPsm) {
|
|
160
|
+
return { txSummaryBase64: proposal.txSummary };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const deltas = await psm.getDeltaProposals(accountId);
|
|
164
|
+
const delta = deltas.find(
|
|
165
|
+
(d) => computeCommitmentFromTxSummary(d.deltaPayload.txSummary.data) === proposal.commitment,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (!delta) {
|
|
169
|
+
throw new Error(`Proposal not found on server: ${proposal.commitment}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
delta,
|
|
174
|
+
txSummaryBase64: delta.deltaPayload.txSummary.data,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function prepareExecutionData(txSummaryBase64: string): PrepareExecutionResult {
|
|
179
|
+
const txSummaryBytes = base64ToUint8Array(txSummaryBase64);
|
|
180
|
+
const txSummary = TransactionSummary.deserialize(txSummaryBytes);
|
|
181
|
+
const saltHex = txSummary.salt().toHex();
|
|
182
|
+
const txCommitmentHex = txSummary.toCommitment().toHex();
|
|
183
|
+
return { txSummary, saltHex, txCommitmentHex };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildCosignerAdviceMap(
|
|
187
|
+
proposal: TransactionProposal,
|
|
188
|
+
signerCommitments: string[],
|
|
189
|
+
txCommitmentHex: string,
|
|
190
|
+
): AdviceMap {
|
|
191
|
+
const adviceMap = new AdviceMap();
|
|
192
|
+
const normalizedSignerCommitments = new Set(
|
|
193
|
+
signerCommitments.map((c) => normalizeHexWord(c)),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
for (const cosignerSig of proposal.signatures) {
|
|
197
|
+
let signerCommitmentHex = normalizeHexWord(cosignerSig.signerId);
|
|
198
|
+
if (cosignerSig.signature.scheme === 'ecdsa' && cosignerSig.signature.publicKey) {
|
|
199
|
+
const derived = tryComputeEcdsaCommitmentHex(cosignerSig.signature.publicKey);
|
|
200
|
+
if (derived && derived !== signerCommitmentHex) {
|
|
201
|
+
if (!normalizedSignerCommitments.has(derived)) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`ECDSA public key commitment mismatch: derived commitment ${derived} is not in signerCommitments.`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
signerCommitmentHex = derived;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const signerCommitment = Word.fromHex(signerCommitmentHex);
|
|
211
|
+
const sigBytes = signatureHexToBytes(
|
|
212
|
+
cosignerSig.signature.signature,
|
|
213
|
+
cosignerSig.signature.scheme,
|
|
214
|
+
);
|
|
215
|
+
const signature = Signature.deserialize(sigBytes);
|
|
216
|
+
const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
|
|
217
|
+
const isEcdsa =
|
|
218
|
+
cosignerSig.signature.scheme === 'ecdsa' && cosignerSig.signature.publicKey;
|
|
219
|
+
|
|
220
|
+
const { key, values } = buildSignatureAdviceEntry(
|
|
221
|
+
signerCommitment,
|
|
222
|
+
txCommitment,
|
|
223
|
+
signature,
|
|
224
|
+
isEcdsa ? cosignerSig.signature.publicKey : undefined,
|
|
225
|
+
isEcdsa ? cosignerSig.signature.signature : undefined,
|
|
226
|
+
);
|
|
227
|
+
adviceMap.insert(key, new FeltArray(values));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return adviceMap;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function appendPsmAckAdvice(
|
|
234
|
+
psm: PsmHttpClient,
|
|
235
|
+
delta: DeltaObject,
|
|
236
|
+
psmCommitmentHex: string,
|
|
237
|
+
psmPublicKey: string | undefined,
|
|
238
|
+
defaultAckScheme: 'falcon' | 'ecdsa',
|
|
239
|
+
txCommitmentHex: string,
|
|
240
|
+
adviceMap: AdviceMap,
|
|
241
|
+
): Promise<void> {
|
|
242
|
+
const executionDelta = {
|
|
243
|
+
...delta,
|
|
244
|
+
deltaPayload: delta.deltaPayload.txSummary,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const pushResult = await psm.pushDelta(executionDelta);
|
|
248
|
+
const ackSigHex = pushResult.ackSig;
|
|
249
|
+
if (!ackSigHex) {
|
|
250
|
+
throw new Error('PSM did not return acknowledgment signature');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const psmAckScheme: 'ecdsa' | 'falcon' =
|
|
254
|
+
(pushResult.ackScheme as 'ecdsa' | 'falcon') || defaultAckScheme;
|
|
255
|
+
const ackPubkey = pushResult.ackPubkey || psmPublicKey;
|
|
256
|
+
const normalizedPsmCommitment = normalizeHexWord(psmCommitmentHex);
|
|
257
|
+
|
|
258
|
+
if (psmAckScheme === 'ecdsa' && ackPubkey) {
|
|
259
|
+
const derived = tryComputeEcdsaCommitmentHex(ackPubkey);
|
|
260
|
+
if (derived && derived !== normalizedPsmCommitment) {
|
|
261
|
+
throw new Error('PSM public key commitment mismatch');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const psmCommitment = Word.fromHex(normalizedPsmCommitment);
|
|
266
|
+
const ackSigBytes = signatureHexToBytes(ackSigHex, psmAckScheme);
|
|
267
|
+
const ackSignature = Signature.deserialize(ackSigBytes);
|
|
268
|
+
const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
|
|
269
|
+
const isAckEcdsa = psmAckScheme === 'ecdsa' && ackPubkey;
|
|
270
|
+
const { key, values } = buildSignatureAdviceEntry(
|
|
271
|
+
psmCommitment,
|
|
272
|
+
txCommitment,
|
|
273
|
+
ackSignature,
|
|
274
|
+
isAckEcdsa ? ackPubkey : undefined,
|
|
275
|
+
isAckEcdsa ? ackSigHex : undefined,
|
|
276
|
+
);
|
|
277
|
+
adviceMap.insert(key, new FeltArray(values));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function buildFinalRequest(
|
|
281
|
+
params: ExecuteProposalWorkflowParams,
|
|
282
|
+
saltHex: string,
|
|
283
|
+
adviceMap: AdviceMap,
|
|
284
|
+
): Promise<TransactionRequest> {
|
|
285
|
+
const metadata = params.proposal.metadata;
|
|
286
|
+
const normalizedSalt = Word.fromHex(normalizeHexWord(saltHex));
|
|
287
|
+
|
|
288
|
+
switch (metadata.proposalType) {
|
|
289
|
+
case 'consume_notes': {
|
|
290
|
+
if (!metadata.noteIds || metadata.noteIds.length === 0) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
'Proposal missing noteIds. Was it created with createConsumeNotesProposal?',
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
const { request } = await buildConsumeNotesTransactionRequest(
|
|
296
|
+
params.webClient,
|
|
297
|
+
metadata.noteIds,
|
|
298
|
+
{ salt: normalizedSalt, signatureAdviceMap: adviceMap },
|
|
299
|
+
);
|
|
300
|
+
return request;
|
|
301
|
+
}
|
|
302
|
+
case 'switch_psm': {
|
|
303
|
+
if (!metadata.newPsmPubkey) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
'Proposal missing newPsmPubkey. Was it created with createSwitchPsmProposal?',
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
const { request } = await buildUpdatePsmTransactionRequest(
|
|
309
|
+
params.webClient,
|
|
310
|
+
metadata.newPsmPubkey,
|
|
311
|
+
{
|
|
312
|
+
salt: normalizedSalt,
|
|
313
|
+
signatureAdviceMap: adviceMap,
|
|
314
|
+
signatureScheme: params.signatureScheme,
|
|
315
|
+
},
|
|
316
|
+
);
|
|
317
|
+
return request;
|
|
318
|
+
}
|
|
319
|
+
case 'p2id': {
|
|
320
|
+
if (!metadata.recipientId || !metadata.faucetId || !metadata.amount) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
'Proposal missing P2ID metadata (recipientId, faucetId, amount). Was it created with createP2idProposal?',
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
const { request } = buildP2idTransactionRequest(
|
|
326
|
+
params.accountId,
|
|
327
|
+
metadata.recipientId,
|
|
328
|
+
metadata.faucetId,
|
|
329
|
+
BigInt(metadata.amount),
|
|
330
|
+
{ salt: normalizedSalt, signatureAdviceMap: adviceMap },
|
|
331
|
+
);
|
|
332
|
+
return request;
|
|
333
|
+
}
|
|
334
|
+
case 'unknown':
|
|
335
|
+
throw new Error(
|
|
336
|
+
'Cannot execute proposal with unknown type. The proposal must have been imported without proper metadata.',
|
|
337
|
+
);
|
|
338
|
+
default: {
|
|
339
|
+
const { request } = await buildUpdateSignersTransactionRequest(
|
|
340
|
+
params.webClient,
|
|
341
|
+
metadata.targetThreshold,
|
|
342
|
+
metadata.targetSignerCommitments,
|
|
343
|
+
{
|
|
344
|
+
salt: normalizedSalt,
|
|
345
|
+
signatureAdviceMap: adviceMap,
|
|
346
|
+
signatureScheme: params.signatureScheme,
|
|
347
|
+
},
|
|
348
|
+
);
|
|
349
|
+
return request;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function submitTransaction(
|
|
355
|
+
webClient: WebClient,
|
|
356
|
+
accountIdHex: string,
|
|
357
|
+
request: TransactionRequest,
|
|
358
|
+
): Promise<void> {
|
|
359
|
+
const accountId = AccountId.fromHex(accountIdHex);
|
|
360
|
+
const result = await webClient.executeTransaction(accountId, request);
|
|
361
|
+
const proven = await webClient.proveTransaction(result, null);
|
|
362
|
+
const submissionHeight = await webClient.submitProvenTransaction(proven, result);
|
|
363
|
+
await webClient.applyTransaction(result, submissionHeight);
|
|
364
|
+
}
|