@openzeppelin/miden-multisig-client 0.16.2 → 0.17.0-rc.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 +144 -7
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +32 -25
- package/dist/account/builder.js.map +1 -1
- package/dist/account/builder.test.js +73 -21
- package/dist/account/builder.test.js.map +1 -1
- package/dist/account/layout.d.ts +31 -0
- package/dist/account/layout.d.ts.map +1 -0
- package/dist/account/layout.js +31 -0
- package/dist/account/layout.js.map +1 -0
- package/dist/account/masm/account-components/auth.d.ts +1 -4
- package/dist/account/masm/account-components/auth.d.ts.map +1 -1
- package/dist/account/masm/account-components/auth.js +36 -53
- package/dist/account/masm/account-components/auth.js.map +1 -1
- package/dist/account/masm/index.d.ts +0 -1
- package/dist/account/masm/index.d.ts.map +1 -1
- package/dist/account/masm/index.js +0 -1
- package/dist/account/masm/index.js.map +1 -1
- package/dist/account/storage.d.ts +4 -0
- package/dist/account/storage.d.ts.map +1 -1
- package/dist/account/storage.js +9 -20
- package/dist/account/storage.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -3
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +68 -15
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/inspector.d.ts +59 -1
- package/dist/inspector.d.ts.map +1 -1
- package/dist/inspector.js +157 -29
- package/dist/inspector.js.map +1 -1
- package/dist/inspector.test.js +246 -37
- package/dist/inspector.test.js.map +1 -1
- package/dist/multisig.d.ts +106 -32
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +289 -95
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +581 -58
- package/dist/multisig.test.js.map +1 -1
- package/dist/procedures.d.ts +6 -7
- package/dist/procedures.d.ts.map +1 -1
- package/dist/procedures.js +6 -7
- package/dist/procedures.js.map +1 -1
- package/dist/proposal/metadata.d.ts.map +1 -1
- package/dist/proposal/metadata.js +13 -2
- package/dist/proposal/metadata.js.map +1 -1
- package/dist/proposal/metadata.test.js +57 -0
- package/dist/proposal/metadata.test.js.map +1 -1
- package/dist/prover/workflow.d.ts +3 -2
- package/dist/prover/workflow.d.ts.map +1 -1
- package/dist/prover/workflow.js +5 -2
- package/dist/prover/workflow.js.map +1 -1
- package/dist/prover/workflow.test.js +6 -4
- package/dist/prover/workflow.test.js.map +1 -1
- package/dist/transaction/index.d.ts +1 -1
- package/dist/transaction/index.d.ts.map +1 -1
- package/dist/transaction/index.js +1 -1
- package/dist/transaction/index.js.map +1 -1
- package/dist/transaction/p2id.d.ts +17 -6
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +22 -31
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/p2id.test.js +62 -24
- package/dist/transaction/p2id.test.js.map +1 -1
- package/dist/transaction/summary.d.ts +45 -2
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +42 -2
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/summary.test.d.ts +2 -0
- package/dist/transaction/summary.test.d.ts.map +1 -0
- package/dist/transaction/summary.test.js +26 -0
- package/dist/transaction/summary.test.js.map +1 -0
- package/dist/transaction/updateGuardian.d.ts.map +1 -1
- package/dist/transaction/updateGuardian.js +16 -18
- package/dist/transaction/updateGuardian.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +15 -17
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +6 -1
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +20 -17
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.d.ts +2 -2
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +1 -1
- package/dist/transaction.js.map +1 -1
- package/dist/types/proposal.d.ts +28 -4
- package/dist/types/proposal.d.ts.map +1 -1
- package/dist/types/proposal.js +18 -0
- package/dist/types/proposal.js.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/signature.d.ts +11 -7
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +24 -58
- package/dist/utils/signature.js.map +1 -1
- package/dist/utils/word.d.ts +7 -0
- package/dist/utils/word.d.ts.map +1 -1
- package/dist/utils/word.js +15 -0
- package/dist/utils/word.js.map +1 -1
- package/masm/account_components/auth/guarded_multisig.masm +42 -0
- package/package.json +7 -4
- package/src/account/builder.test.ts +111 -45
- package/src/account/builder.ts +45 -33
- package/src/account/layout.ts +33 -0
- package/src/account/masm/account-components/auth.ts +36 -56
- package/src/account/masm/index.ts +0 -1
- package/src/account/storage.ts +9 -22
- package/src/client.test.ts +80 -15
- package/src/client.ts +5 -3
- package/src/index.ts +26 -1
- package/src/inspector.test.ts +330 -38
- package/src/inspector.ts +196 -33
- package/src/multisig.test.ts +679 -63
- package/src/multisig.ts +361 -107
- package/src/procedures.ts +6 -7
- package/src/proposal/metadata.test.ts +76 -0
- package/src/proposal/metadata.ts +13 -2
- package/src/prover/workflow.test.ts +9 -4
- package/src/prover/workflow.ts +15 -3
- package/src/transaction/index.ts +7 -1
- package/src/transaction/p2id.test.ts +112 -31
- package/src/transaction/p2id.ts +39 -38
- package/src/transaction/summary.test.ts +32 -0
- package/src/transaction/summary.ts +83 -4
- package/src/transaction/updateGuardian.ts +15 -25
- package/src/transaction/updateProcedureThreshold.ts +13 -29
- package/src/transaction/updateSigners.ts +38 -30
- package/src/transaction.ts +8 -1
- package/src/types/proposal.ts +43 -4
- package/src/types.ts +0 -1
- package/src/utils/signature.ts +32 -65
- package/src/utils/word.ts +17 -0
- package/dist/account/masm/auth.d.ts +0 -5
- package/dist/account/masm/auth.d.ts.map +0 -1
- package/dist/account/masm/auth.js +0 -1509
- package/dist/account/masm/auth.js.map +0 -1
- package/masm/account_components/auth/multisig.masm +0 -12
- package/masm/account_components/auth/multisig_ecdsa.masm +0 -12
- package/masm/account_components/auth/multisig_guardian.masm +0 -16
- package/masm/account_components/auth/multisig_guardian_ecdsa.masm +0 -16
- package/masm/auth/guardian.masm +0 -199
- package/masm/auth/guardian_ecdsa.masm +0 -195
- package/masm/auth/multisig.masm +0 -554
- package/masm/auth/multisig_ecdsa.masm +0 -554
- package/src/account/masm/auth.ts +0 -1512
package/src/procedures.ts
CHANGED
|
@@ -11,13 +11,12 @@
|
|
|
11
11
|
* human-readable encoding and should not be copied into this table.
|
|
12
12
|
*/
|
|
13
13
|
export const PROCEDURE_ROOTS = {
|
|
14
|
-
update_signers: '
|
|
15
|
-
update_procedure_threshold: '
|
|
16
|
-
auth_tx: '
|
|
17
|
-
update_guardian: '
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
receive_asset: '0x6170fd6d682d91777b551fd866258f43cc657f1291f8f071500f4e56e9c153da',
|
|
14
|
+
update_signers: '0xa261cfd3c8791ac5abe1e78e14eade2f20789d73ab1c23c430418de59bc3380e',
|
|
15
|
+
update_procedure_threshold: '0x97587c61d49313b1d5a3c8b7437e0080e67ed9bd9d3e7206bcae562f934ccd03',
|
|
16
|
+
auth_tx: '0xa6aa6f69d9358535272ba433cd48d20628a5c69598e00c6dd01a22e83a5f15df',
|
|
17
|
+
update_guardian: '0x0a614ff7c81a561cbd2a4c2d9482031a7a841ca5de33349daed23a9d871b3675',
|
|
18
|
+
send_asset: '0x595bc83258726a66bd904912cfd5186c07cbd902dfbc115b7d6bc8105efc57e3',
|
|
19
|
+
receive_asset: '0x34a56dd18f6fe5aab63198b9dcfc6467e793ebabb37d56b994b902504635da13',
|
|
21
20
|
} as const;
|
|
22
21
|
|
|
23
22
|
/**
|
|
@@ -157,3 +157,79 @@ describe('ProposalMetadataCodec p2id noteType (issue #322)', () => {
|
|
|
157
157
|
expect(() => ProposalMetadataCodec.validate(md)).toThrow(/unsupported noteType/);
|
|
158
158
|
});
|
|
159
159
|
});
|
|
160
|
+
|
|
161
|
+
describe('ProposalMetadataCodec p2id P2IDE heights (issue #366)', () => {
|
|
162
|
+
const baseWire: GuardianProposalMetadata = {
|
|
163
|
+
proposalType: 'p2id',
|
|
164
|
+
recipientId: '0xrecipient',
|
|
165
|
+
faucetId: '0xfaucet',
|
|
166
|
+
amount: '1000',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
it('round-trips reclaim and timelock heights through the codec', () => {
|
|
170
|
+
const md = ProposalMetadataCodec.fromGuardian({
|
|
171
|
+
...baseWire,
|
|
172
|
+
reclaimHeight: 12345,
|
|
173
|
+
timelockHeight: 700,
|
|
174
|
+
}) as P2IdProposalMetadata;
|
|
175
|
+
expect(md.reclaimHeight).toBe(12345);
|
|
176
|
+
expect(md.timelockHeight).toBe(700);
|
|
177
|
+
|
|
178
|
+
const wire = ProposalMetadataCodec.toGuardian(md);
|
|
179
|
+
expect(wire.reclaimHeight).toBe(12345);
|
|
180
|
+
expect(wire.timelockHeight).toBe(700);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('leaves heights absent for plain P2ID proposals (pre-#366 wire shape)', () => {
|
|
184
|
+
const md = ProposalMetadataCodec.fromGuardian(baseWire) as P2IdProposalMetadata;
|
|
185
|
+
expect(md.reclaimHeight).toBeUndefined();
|
|
186
|
+
expect(md.timelockHeight).toBeUndefined();
|
|
187
|
+
|
|
188
|
+
const wire = ProposalMetadataCodec.toGuardian(md);
|
|
189
|
+
expect(wire.reclaimHeight).toBeUndefined();
|
|
190
|
+
expect(wire.timelockHeight).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('fromGuardian rejects a zero height (encodes "no constraint" on-chain)', () => {
|
|
194
|
+
expect(() =>
|
|
195
|
+
ProposalMetadataCodec.fromGuardian({ ...baseWire, reclaimHeight: 0 }),
|
|
196
|
+
).toThrow(/unsupported reclaimHeight/);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('fromGuardian rejects a non-integer or out-of-range height', () => {
|
|
200
|
+
expect(() =>
|
|
201
|
+
ProposalMetadataCodec.fromGuardian({ ...baseWire, timelockHeight: 1.5 }),
|
|
202
|
+
).toThrow(/unsupported timelockHeight/);
|
|
203
|
+
expect(() =>
|
|
204
|
+
ProposalMetadataCodec.fromGuardian({ ...baseWire, reclaimHeight: 0x1_0000_0000 }),
|
|
205
|
+
).toThrow(/unsupported reclaimHeight/);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('validate rejects an invalid height', () => {
|
|
209
|
+
const md = {
|
|
210
|
+
proposalType: 'p2id',
|
|
211
|
+
description: '',
|
|
212
|
+
recipientId: '0xrecipient',
|
|
213
|
+
faucetId: '0xfaucet',
|
|
214
|
+
amount: '1000',
|
|
215
|
+
reclaimHeight: -1,
|
|
216
|
+
} as unknown as P2IdProposalMetadata;
|
|
217
|
+
expect(() => ProposalMetadataCodec.validate(md)).toThrow(/unsupported reclaimHeight/);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('toGuardian rejects an invalid height instead of emitting it on the wire', () => {
|
|
221
|
+
const base = {
|
|
222
|
+
proposalType: 'p2id',
|
|
223
|
+
description: '',
|
|
224
|
+
recipientId: '0xrecipient',
|
|
225
|
+
faucetId: '0xfaucet',
|
|
226
|
+
amount: '1000',
|
|
227
|
+
};
|
|
228
|
+
expect(() =>
|
|
229
|
+
ProposalMetadataCodec.toGuardian({ ...base, reclaimHeight: 0 } as P2IdProposalMetadata),
|
|
230
|
+
).toThrow(/unsupported reclaimHeight/);
|
|
231
|
+
expect(() =>
|
|
232
|
+
ProposalMetadataCodec.toGuardian({ ...base, timelockHeight: 1.5 } as P2IdProposalMetadata),
|
|
233
|
+
).toThrow(/unsupported timelockHeight/);
|
|
234
|
+
});
|
|
235
|
+
});
|
package/src/proposal/metadata.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ProposalMetadata as GuardianProposalMetadata } from '@openzeppelin/guardian-client';
|
|
2
2
|
import type { ProposalMetadata } from '../types.js';
|
|
3
3
|
import { isProcedureName } from '../procedures.js';
|
|
4
|
-
import { isP2idNoteVisibility } from '../types/proposal.js';
|
|
4
|
+
import { isP2idNoteVisibility, parseP2ideHeight } from '../types/proposal.js';
|
|
5
5
|
|
|
6
6
|
export class ProposalMetadataCodec {
|
|
7
7
|
static toGuardian(metadata: ProposalMetadata): GuardianProposalMetadata {
|
|
@@ -10,6 +10,7 @@ export class ProposalMetadataCodec {
|
|
|
10
10
|
description: metadata.description,
|
|
11
11
|
salt: metadata.saltHex,
|
|
12
12
|
requiredSignatures: metadata.requiredSignatures,
|
|
13
|
+
chainAnchor: metadata.chainAnchor,
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
switch (metadata.proposalType) {
|
|
@@ -30,6 +31,11 @@ export class ProposalMetadataCodec {
|
|
|
30
31
|
// keeps the pre-#322 wire shape and matches the Rust encoder (which
|
|
31
32
|
// round-trips through the NoteType enum). Absent => public.
|
|
32
33
|
noteType: metadata.noteType === 'private' ? 'private' : undefined,
|
|
34
|
+
// Validated on encode too: the create path already rejects bad
|
|
35
|
+
// heights when building the note, but metadata handed directly to
|
|
36
|
+
// the codec must not reach the wire with a value cosigners reject.
|
|
37
|
+
reclaimHeight: parseP2ideHeight('reclaimHeight', metadata.reclaimHeight),
|
|
38
|
+
timelockHeight: parseP2ideHeight('timelockHeight', metadata.timelockHeight),
|
|
33
39
|
};
|
|
34
40
|
case 'switch_guardian':
|
|
35
41
|
return {
|
|
@@ -68,6 +74,7 @@ export class ProposalMetadataCodec {
|
|
|
68
74
|
description: guardian.description ?? '',
|
|
69
75
|
saltHex: guardian.salt,
|
|
70
76
|
requiredSignatures: guardian.requiredSignatures,
|
|
77
|
+
chainAnchor: guardian.chainAnchor,
|
|
71
78
|
};
|
|
72
79
|
|
|
73
80
|
switch (guardian.proposalType) {
|
|
@@ -87,6 +94,8 @@ export class ProposalMetadataCodec {
|
|
|
87
94
|
faucetId: guardian.faucetId,
|
|
88
95
|
amount: guardian.amount,
|
|
89
96
|
noteType: guardian.noteType,
|
|
97
|
+
reclaimHeight: parseP2ideHeight('reclaimHeight', guardian.reclaimHeight),
|
|
98
|
+
timelockHeight: parseP2ideHeight('timelockHeight', guardian.timelockHeight),
|
|
90
99
|
};
|
|
91
100
|
case 'consume_notes':
|
|
92
101
|
if (!guardian.noteIds || guardian.noteIds.length === 0) {
|
|
@@ -138,7 +147,7 @@ export class ProposalMetadataCodec {
|
|
|
138
147
|
};
|
|
139
148
|
default:
|
|
140
149
|
// Any proposal type the SDK does not model collapses to the 'custom'
|
|
141
|
-
// bucket while preserving the original label
|
|
150
|
+
// bucket while preserving the original label.
|
|
142
151
|
return {
|
|
143
152
|
...base,
|
|
144
153
|
proposalType: 'custom',
|
|
@@ -182,6 +191,8 @@ export class ProposalMetadataCodec {
|
|
|
182
191
|
if (metadata.noteType !== undefined && !isP2idNoteVisibility(metadata.noteType)) {
|
|
183
192
|
throw new Error(`p2id proposal has unsupported noteType '${metadata.noteType}'`);
|
|
184
193
|
}
|
|
194
|
+
parseP2ideHeight('reclaimHeight', metadata.reclaimHeight);
|
|
195
|
+
parseP2ideHeight('timelockHeight', metadata.timelockHeight);
|
|
185
196
|
return metadata;
|
|
186
197
|
case 'custom':
|
|
187
198
|
// Custom proposals are opaque to the SDK; nothing to validate beyond
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AccountId,
|
|
3
|
+
ChainAnchor,
|
|
3
4
|
MidenClient,
|
|
4
5
|
TransactionProver,
|
|
5
6
|
TransactionRequest,
|
|
@@ -13,6 +14,8 @@ function asType<T>(value: unknown): T {
|
|
|
13
14
|
return value as T;
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
const anchor = asType<ChainAnchor>({ marker: 'anchor' });
|
|
18
|
+
|
|
16
19
|
describe('ProverWorkflow', () => {
|
|
17
20
|
it('executes once, retries proof with fresh provers, submits once, and applies once', async () => {
|
|
18
21
|
const transient = Object.assign(new Error('temporarily unavailable'), {
|
|
@@ -47,12 +50,14 @@ describe('ProverWorkflow', () => {
|
|
|
47
50
|
runtime,
|
|
48
51
|
);
|
|
49
52
|
|
|
50
|
-
await workflow.
|
|
53
|
+
await workflow.submitAt(
|
|
51
54
|
asType<AccountId>({}),
|
|
52
55
|
asType<TransactionRequest>({}),
|
|
56
|
+
anchor,
|
|
53
57
|
);
|
|
54
58
|
|
|
55
59
|
expect(executeRequest).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect(executeRequest.mock.calls[0][2]).toEqual({ anchor });
|
|
56
61
|
expect(prove).toHaveBeenCalledTimes(2);
|
|
57
62
|
expect(provers).toHaveLength(2);
|
|
58
63
|
expect(provers[0]).not.toBe(provers[1]);
|
|
@@ -83,7 +88,7 @@ describe('ProverWorkflow', () => {
|
|
|
83
88
|
);
|
|
84
89
|
|
|
85
90
|
await expect(
|
|
86
|
-
workflow.
|
|
91
|
+
workflow.submitAt(asType<AccountId>({}), asType<TransactionRequest>({}), anchor),
|
|
87
92
|
).rejects.toBe(final);
|
|
88
93
|
expect(prove).toHaveBeenCalledTimes(2);
|
|
89
94
|
expect(runtime.sleep).toHaveBeenCalledTimes(1);
|
|
@@ -113,7 +118,7 @@ describe('ProverWorkflow', () => {
|
|
|
113
118
|
);
|
|
114
119
|
|
|
115
120
|
await expect(
|
|
116
|
-
workflow.
|
|
121
|
+
workflow.submitAt(asType<AccountId>({}), asType<TransactionRequest>({}), anchor),
|
|
117
122
|
).rejects.toBe(rateLimited);
|
|
118
123
|
expect(prove).toHaveBeenCalledTimes(1);
|
|
119
124
|
expect(submit).toHaveBeenCalledTimes(1);
|
|
@@ -133,7 +138,7 @@ describe('ProverWorkflow', () => {
|
|
|
133
138
|
createProver: () => undefined,
|
|
134
139
|
});
|
|
135
140
|
|
|
136
|
-
await workflow.
|
|
141
|
+
await workflow.submitAt(asType<AccountId>({}), asType<TransactionRequest>({}), anchor);
|
|
137
142
|
|
|
138
143
|
expect(prove).toHaveBeenCalledWith();
|
|
139
144
|
});
|
package/src/prover/workflow.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AccountId,
|
|
3
|
+
ChainAnchor,
|
|
4
|
+
MidenClient,
|
|
5
|
+
TransactionRequest,
|
|
6
|
+
} from '@miden-sdk/miden-sdk';
|
|
2
7
|
import type { ResolvedProverConfig } from './config.js';
|
|
3
8
|
import type { RetryRuntime } from '../retry/runtime.js';
|
|
4
9
|
import { proveWithRetry } from './retry.js';
|
|
@@ -10,8 +15,15 @@ export class ProverWorkflow {
|
|
|
10
15
|
private readonly runtime?: RetryRuntime,
|
|
11
16
|
) {}
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
/** Proves, submits, and applies a request at its signed chain anchor. */
|
|
19
|
+
async submitAt(
|
|
20
|
+
accountId: AccountId,
|
|
21
|
+
request: TransactionRequest,
|
|
22
|
+
anchor: ChainAnchor,
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
const execution = await this.client.transactions.executeRequest(accountId, request, {
|
|
25
|
+
anchor,
|
|
26
|
+
});
|
|
15
27
|
const proof = await proveWithRetry(execution, this.config, this.runtime);
|
|
16
28
|
const submission = await proof.submit();
|
|
17
29
|
await submission.apply();
|
package/src/transaction/index.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
chainAnchorFromBase64,
|
|
3
|
+
chainAnchorToBase64,
|
|
4
|
+
executeForSummary,
|
|
5
|
+
executeForSummaryAt,
|
|
6
|
+
summarySalt,
|
|
7
|
+
} from './summary.js';
|
|
2
8
|
export { buildUpdateSignersTransactionRequest } from './updateSigners.js';
|
|
3
9
|
export { buildUpdateProcedureThresholdTransactionRequest } from './updateProcedureThreshold.js';
|
|
4
10
|
export { buildUpdateGuardianTransactionRequest } from './updateGuardian.js';
|
|
@@ -2,13 +2,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
import type { Word } from '@miden-sdk/miden-sdk';
|
|
3
3
|
|
|
4
4
|
const {
|
|
5
|
-
mockFromVaultKey,
|
|
6
5
|
mockFungibleAssetConstructor,
|
|
7
6
|
mockHashElements,
|
|
8
7
|
mockNormalizeHexWord,
|
|
9
8
|
mockRandomWord,
|
|
10
9
|
mockWordFromHex,
|
|
11
10
|
noteMetadataCalls,
|
|
11
|
+
noteRecipientCalls,
|
|
12
|
+
noteStorageCalls,
|
|
12
13
|
saltFelts,
|
|
13
14
|
} = vi.hoisted(() => {
|
|
14
15
|
const saltFelts = [
|
|
@@ -19,13 +20,10 @@ const {
|
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
return {
|
|
22
|
-
mockFromVaultKey: vi.fn((vaultKey: unknown, amount: bigint) => ({
|
|
23
|
-
kind: 'asset-from-vault-key',
|
|
24
|
-
vaultKey,
|
|
25
|
-
amount,
|
|
26
|
-
})),
|
|
27
23
|
mockFungibleAssetConstructor: vi.fn(),
|
|
28
24
|
noteMetadataCalls: [] as unknown[][],
|
|
25
|
+
noteRecipientCalls: [] as unknown[][],
|
|
26
|
+
noteStorageCalls: [] as unknown[][],
|
|
29
27
|
mockHashElements: vi.fn().mockReturnValue({ toString: () => 'serial' }),
|
|
30
28
|
mockNormalizeHexWord: vi.fn((hex: string) => hex),
|
|
31
29
|
mockRandomWord: vi.fn().mockReturnValue({
|
|
@@ -71,7 +69,9 @@ vi.mock('@miden-sdk/miden-sdk', () => {
|
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
class NoteStorage {
|
|
74
|
-
constructor(
|
|
72
|
+
constructor(inputs: FeltArray) {
|
|
73
|
+
noteStorageCalls.push([inputs]);
|
|
74
|
+
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
class NoteMetadata {
|
|
@@ -86,10 +86,12 @@ vi.mock('@miden-sdk/miden-sdk', () => {
|
|
|
86
86
|
|
|
87
87
|
class NoteRecipient {
|
|
88
88
|
constructor(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
) {
|
|
89
|
+
serialNum: unknown,
|
|
90
|
+
noteScript: unknown,
|
|
91
|
+
noteInputs: unknown,
|
|
92
|
+
) {
|
|
93
|
+
noteRecipientCalls.push([serialNum, noteScript, noteInputs]);
|
|
94
|
+
}
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
class Note {
|
|
@@ -104,8 +106,6 @@ vi.mock('@miden-sdk/miden-sdk', () => {
|
|
|
104
106
|
constructor(faucet: unknown, amount: bigint) {
|
|
105
107
|
mockFungibleAssetConstructor(faucet, amount);
|
|
106
108
|
}
|
|
107
|
-
|
|
108
|
-
static fromVaultKey = mockFromVaultKey;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
class NoteArray {
|
|
@@ -152,6 +152,7 @@ vi.mock('@miden-sdk/miden-sdk', () => {
|
|
|
152
152
|
NoteStorage,
|
|
153
153
|
NoteScript: {
|
|
154
154
|
p2id: vi.fn(() => ({ kind: 'p2id-script' })),
|
|
155
|
+
p2ide: vi.fn(() => ({ kind: 'p2ide-script' })),
|
|
155
156
|
},
|
|
156
157
|
NoteTag: {
|
|
157
158
|
withAccountTarget: vi.fn(() => ({ kind: 'tag' })),
|
|
@@ -182,31 +183,20 @@ vi.mock('../utils/random.js', () => ({
|
|
|
182
183
|
}));
|
|
183
184
|
|
|
184
185
|
import { buildP2idTransactionRequest, parseP2idNoteType, p2idNoteTypeToMetadata } from './p2id.js';
|
|
185
|
-
import
|
|
186
|
+
import { NoteType } from '@miden-sdk/miden-sdk';
|
|
186
187
|
|
|
187
188
|
const FAUCET_ID = '0x7bfb0f38b0fafa103f86a805594171';
|
|
188
189
|
|
|
189
|
-
const mockAccount = {
|
|
190
|
-
vault: () => ({
|
|
191
|
-
fungibleAssets: () => [
|
|
192
|
-
{
|
|
193
|
-
faucetId: () => ({ toString: () => FAUCET_ID }),
|
|
194
|
-
vaultKey: () => ({ kind: 'vault-key' }),
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
}),
|
|
198
|
-
} as unknown as Account;
|
|
199
|
-
import { NoteType } from '@miden-sdk/miden-sdk';
|
|
200
|
-
|
|
201
190
|
describe('buildP2idTransactionRequest', () => {
|
|
202
191
|
beforeEach(() => {
|
|
203
|
-
mockFromVaultKey.mockClear();
|
|
204
192
|
mockFungibleAssetConstructor.mockClear();
|
|
205
193
|
mockHashElements.mockClear();
|
|
206
194
|
mockNormalizeHexWord.mockClear();
|
|
207
195
|
mockRandomWord.mockClear();
|
|
208
196
|
mockWordFromHex.mockClear();
|
|
209
197
|
noteMetadataCalls.length = 0;
|
|
198
|
+
noteRecipientCalls.length = 0;
|
|
199
|
+
noteStorageCalls.length = 0;
|
|
210
200
|
});
|
|
211
201
|
|
|
212
202
|
it('derives serial number from salt felts plus four zero felts', () => {
|
|
@@ -217,7 +207,6 @@ describe('buildP2idTransactionRequest', () => {
|
|
|
217
207
|
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
218
208
|
FAUCET_ID,
|
|
219
209
|
10n,
|
|
220
|
-
mockAccount,
|
|
221
210
|
{ salt },
|
|
222
211
|
);
|
|
223
212
|
|
|
@@ -239,28 +228,120 @@ describe('buildP2idTransactionRequest', () => {
|
|
|
239
228
|
buildP2idTransactionRequest(
|
|
240
229
|
'0x7bfb0f38b0fafa103f86a805594170',
|
|
241
230
|
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
242
|
-
|
|
231
|
+
FAUCET_ID,
|
|
243
232
|
10n,
|
|
244
|
-
mockAccount,
|
|
245
233
|
);
|
|
246
234
|
|
|
247
235
|
expect(noteMetadataCalls).toHaveLength(1);
|
|
248
236
|
expect(noteMetadataCalls[0][1]).toBe(NoteType.Public);
|
|
249
237
|
});
|
|
250
238
|
|
|
239
|
+
it('builds the asset from the faucet id, whose callback flag it carries since Miden 0.16', () => {
|
|
240
|
+
buildP2idTransactionRequest(
|
|
241
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
242
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
243
|
+
FAUCET_ID,
|
|
244
|
+
10n,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(mockFungibleAssetConstructor).toHaveBeenCalledTimes(1);
|
|
248
|
+
const [faucet, amount] = mockFungibleAssetConstructor.mock.calls[0] as [{ hex: string }, bigint];
|
|
249
|
+
expect(faucet.hex).toBe(FAUCET_ID);
|
|
250
|
+
expect(amount).toBe(10n);
|
|
251
|
+
});
|
|
252
|
+
|
|
251
253
|
it('threads the requested noteType into the note metadata (issue #322)', () => {
|
|
252
254
|
buildP2idTransactionRequest(
|
|
253
255
|
'0x7bfb0f38b0fafa103f86a805594170',
|
|
254
256
|
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
255
257
|
'0x7bfb0f38b0fafa103f86a805594171',
|
|
256
258
|
10n,
|
|
257
|
-
mockAccount,
|
|
258
259
|
{ noteType: NoteType.Private },
|
|
259
260
|
);
|
|
260
261
|
|
|
261
262
|
expect(noteMetadataCalls).toHaveLength(1);
|
|
262
263
|
expect(noteMetadataCalls[0][1]).toBe(NoteType.Private);
|
|
263
264
|
});
|
|
265
|
+
|
|
266
|
+
it('builds a plain P2ID note without heights: p2id script, 2 storage items', () => {
|
|
267
|
+
buildP2idTransactionRequest(
|
|
268
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
269
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
270
|
+
FAUCET_ID,
|
|
271
|
+
10n,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(noteRecipientCalls).toHaveLength(1);
|
|
275
|
+
expect(noteRecipientCalls[0][1]).toEqual({ kind: 'p2id-script' });
|
|
276
|
+
const [storageInputs] = noteStorageCalls[0] as [{ values: unknown[] }];
|
|
277
|
+
expect(storageInputs.values).toEqual([2, 1]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('builds a P2IDE note when reclaimHeight is set (issue #366)', () => {
|
|
281
|
+
buildP2idTransactionRequest(
|
|
282
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
283
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
284
|
+
FAUCET_ID,
|
|
285
|
+
10n,
|
|
286
|
+
{ reclaimHeight: 12345 },
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
expect(noteRecipientCalls).toHaveLength(1);
|
|
290
|
+
expect(noteRecipientCalls[0][1]).toEqual({ kind: 'p2ide-script' });
|
|
291
|
+
|
|
292
|
+
// P2IDE storage layout: [suffix, prefix, reclaim, timelock], 0 = unset.
|
|
293
|
+
const [storageInputs] = noteStorageCalls[0] as [{ values: unknown[] }];
|
|
294
|
+
expect(storageInputs.values).toHaveLength(4);
|
|
295
|
+
expect(storageInputs.values.slice(0, 2)).toEqual([2, 1]);
|
|
296
|
+
expect((storageInputs.values[2] as { value: bigint }).value).toBe(12345n);
|
|
297
|
+
expect((storageInputs.values[3] as { value: bigint }).value).toBe(0n);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('builds a P2IDE note when only timelockHeight is set (issue #366)', () => {
|
|
301
|
+
buildP2idTransactionRequest(
|
|
302
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
303
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
304
|
+
FAUCET_ID,
|
|
305
|
+
10n,
|
|
306
|
+
{ timelockHeight: 777 },
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
expect(noteRecipientCalls[0][1]).toEqual({ kind: 'p2ide-script' });
|
|
310
|
+
const [storageInputs] = noteStorageCalls[0] as [{ values: unknown[] }];
|
|
311
|
+
expect((storageInputs.values[2] as { value: bigint }).value).toBe(0n);
|
|
312
|
+
expect((storageInputs.values[3] as { value: bigint }).value).toBe(777n);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('carries both heights into the P2IDE storage (issue #366)', () => {
|
|
316
|
+
buildP2idTransactionRequest(
|
|
317
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
318
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
319
|
+
FAUCET_ID,
|
|
320
|
+
10n,
|
|
321
|
+
{ reclaimHeight: 500, timelockHeight: 400 },
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
const [storageInputs] = noteStorageCalls[0] as [{ values: unknown[] }];
|
|
325
|
+
expect((storageInputs.values[2] as { value: bigint }).value).toBe(500n);
|
|
326
|
+
expect((storageInputs.values[3] as { value: bigint }).value).toBe(400n);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it.each([
|
|
330
|
+
['zero', 0],
|
|
331
|
+
['negative', -5],
|
|
332
|
+
['fractional', 1.5],
|
|
333
|
+
['above u32::MAX', 0x1_0000_0000],
|
|
334
|
+
])('rejects a %s height instead of building a divergent note (issue #366)', (_label, height) => {
|
|
335
|
+
expect(() =>
|
|
336
|
+
buildP2idTransactionRequest(
|
|
337
|
+
'0x7bfb0f38b0fafa103f86a805594170',
|
|
338
|
+
'0x8a65fc5a39e4cd106d648e3eb4ab5f',
|
|
339
|
+
FAUCET_ID,
|
|
340
|
+
10n,
|
|
341
|
+
{ reclaimHeight: height },
|
|
342
|
+
),
|
|
343
|
+
).toThrow(/unsupported reclaimHeight/);
|
|
344
|
+
});
|
|
264
345
|
});
|
|
265
346
|
|
|
266
347
|
describe('parseP2idNoteType', () => {
|
package/src/transaction/p2id.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TransactionRequest, Word } from '@miden-sdk/miden-sdk';
|
|
2
2
|
import {
|
|
3
3
|
AccountId,
|
|
4
|
+
Felt,
|
|
4
5
|
FeltArray,
|
|
5
6
|
FungibleAsset,
|
|
6
7
|
MidenArrays,
|
|
@@ -20,8 +21,21 @@ import { randomWord } from '../utils/random.js';
|
|
|
20
21
|
import { normalizeHexWord } from '../utils/encoding.js';
|
|
21
22
|
import type { SignatureOptions } from './options.js';
|
|
22
23
|
import type { P2idNoteVisibility } from '../types/proposal.js';
|
|
24
|
+
import { parseP2ideHeight } from '../types/proposal.js';
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
/**
|
|
27
|
+
* P2IDE execution constraints (issue #366). Presence of either height builds
|
|
28
|
+
* a P2IDE note instead of a plain P2ID note, mirroring the Miden SDK's
|
|
29
|
+
* `reclaimAfter`/`timelockUntil` semantics on `SendOptions`.
|
|
30
|
+
*/
|
|
31
|
+
export interface P2ideHeightOptions {
|
|
32
|
+
/** Absolute block height at which the sender may reclaim the note. */
|
|
33
|
+
reclaimHeight?: number;
|
|
34
|
+
/** Absolute block height before which the note cannot be consumed. */
|
|
35
|
+
timelockHeight?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface P2idTransactionOptions extends SignatureOptions, P2ideHeightOptions {
|
|
25
39
|
/** Visibility of the created note. Defaults to `NoteType.Public`. */
|
|
26
40
|
noteType?: NoteType;
|
|
27
41
|
}
|
|
@@ -60,47 +74,35 @@ export function deriveP2idSerialNumber(salt: Word): Word {
|
|
|
60
74
|
]));
|
|
61
75
|
}
|
|
62
76
|
|
|
63
|
-
/**
|
|
64
|
-
* Builds the fungible asset to transfer, sourcing the callback flag from the held asset.
|
|
65
|
-
*
|
|
66
|
-
* In Miden 0.15 the callback flag is part of the vault key, so rebuilding the asset from
|
|
67
|
-
* `faucet`/`amount` with the default flag would not match the held asset and the transfer
|
|
68
|
-
* would abort. When the faucet is absent the default flag is used, surfacing the
|
|
69
|
-
* missing-asset error during execution.
|
|
70
|
-
*/
|
|
71
|
-
function resolveFungibleAssetFromVault(
|
|
72
|
-
account: Account,
|
|
73
|
-
faucet: AccountId,
|
|
74
|
-
amount: bigint,
|
|
75
|
-
): FungibleAsset {
|
|
76
|
-
const faucetHex = faucet.toString();
|
|
77
|
-
const assetFromVault = account
|
|
78
|
-
.vault()
|
|
79
|
-
.fungibleAssets()
|
|
80
|
-
.find(asset => asset.faucetId().toString() === faucetHex);
|
|
81
|
-
|
|
82
|
-
if (!assetFromVault) {
|
|
83
|
-
return new FungibleAsset(faucet, amount);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return FungibleAsset.fromVaultKey(assetFromVault.vaultKey(), amount);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
77
|
function buildP2idNote(
|
|
90
78
|
sender: AccountId,
|
|
91
79
|
recipient: AccountId,
|
|
92
80
|
noteAssets: NoteAssets,
|
|
93
81
|
noteType: NoteType,
|
|
94
82
|
saltHex: string,
|
|
83
|
+
heights: P2ideHeightOptions = {},
|
|
95
84
|
): Note {
|
|
96
85
|
const salt = WordType.fromHex(normalizeHexWord(saltHex));
|
|
97
86
|
const serialNum = deriveP2idSerialNumber(salt);
|
|
98
87
|
|
|
99
|
-
const
|
|
100
|
-
const
|
|
88
|
+
const reclaimHeight = parseP2ideHeight('reclaimHeight', heights.reclaimHeight);
|
|
89
|
+
const timelockHeight = parseP2ideHeight('timelockHeight', heights.timelockHeight);
|
|
90
|
+
const isP2ide = reclaimHeight !== undefined || timelockHeight !== undefined;
|
|
91
|
+
|
|
92
|
+
// P2IDE storage layout (miden-standards `P2ideNoteStorage`): the P2ID
|
|
93
|
+
// storage plus reclaim/timelock heights as felts, 0 encoding "unset".
|
|
94
|
+
const noteScript = isP2ide ? NoteScript.p2ide() : NoteScript.p2id();
|
|
95
|
+
const storageFelts = [
|
|
101
96
|
recipient.suffix(),
|
|
102
97
|
recipient.prefix(),
|
|
103
|
-
]
|
|
98
|
+
];
|
|
99
|
+
if (isP2ide) {
|
|
100
|
+
storageFelts.push(
|
|
101
|
+
new Felt(BigInt(reclaimHeight ?? 0)),
|
|
102
|
+
new Felt(BigInt(timelockHeight ?? 0)),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const noteStorage = new NoteStorage(new FeltArray(storageFelts));
|
|
104
106
|
|
|
105
107
|
const noteRecipient = new NoteRecipient(serialNum, noteScript, noteStorage);
|
|
106
108
|
const noteTag = NoteTag.withAccountTarget(recipient);
|
|
@@ -117,26 +119,26 @@ function buildP2idNote(
|
|
|
117
119
|
/**
|
|
118
120
|
* Rebuilds the P2ID note a proposal creates, from its metadata fields. The
|
|
119
121
|
* note is deterministic in the salt, so the resulting ID matches the note the
|
|
120
|
-
* proposal produces on execution
|
|
121
|
-
*
|
|
122
|
+
* proposal produces on execution. Since Miden 0.16, the asset
|
|
123
|
+
* callback flag is encoded in the faucet account ID.
|
|
122
124
|
*/
|
|
123
125
|
export function buildP2idNoteFromMetadata(
|
|
124
126
|
senderId: string,
|
|
125
127
|
recipientId: string,
|
|
126
128
|
faucetId: string,
|
|
127
129
|
amount: bigint,
|
|
128
|
-
account: Account,
|
|
129
130
|
noteType: NoteType,
|
|
130
131
|
saltHex: string,
|
|
132
|
+
heights: P2ideHeightOptions = {},
|
|
131
133
|
): Note {
|
|
132
134
|
const sender = AccountId.fromHex(senderId);
|
|
133
135
|
const recipient = AccountId.fromHex(recipientId);
|
|
134
136
|
const faucet = AccountId.fromHex(faucetId);
|
|
135
137
|
|
|
136
|
-
const asset =
|
|
138
|
+
const asset = new FungibleAsset(faucet, amount);
|
|
137
139
|
const noteAssets = new NoteAssets([asset]);
|
|
138
140
|
|
|
139
|
-
return buildP2idNote(sender, recipient, noteAssets, noteType, saltHex);
|
|
141
|
+
return buildP2idNote(sender, recipient, noteAssets, noteType, saltHex, heights);
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
export function buildP2idTransactionRequest(
|
|
@@ -144,7 +146,6 @@ export function buildP2idTransactionRequest(
|
|
|
144
146
|
recipientId: string,
|
|
145
147
|
faucetId: string,
|
|
146
148
|
amount: bigint,
|
|
147
|
-
account: Account,
|
|
148
149
|
options: P2idTransactionOptions = {},
|
|
149
150
|
): { request: TransactionRequest; salt: Word } {
|
|
150
151
|
const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
|
|
@@ -154,9 +155,9 @@ export function buildP2idTransactionRequest(
|
|
|
154
155
|
recipientId,
|
|
155
156
|
faucetId,
|
|
156
157
|
amount,
|
|
157
|
-
account,
|
|
158
158
|
options.noteType ?? NoteType.Public,
|
|
159
159
|
authSaltHex,
|
|
160
|
+
{ reclaimHeight: options.reclaimHeight, timelockHeight: options.timelockHeight },
|
|
160
161
|
);
|
|
161
162
|
|
|
162
163
|
const outputNotes = new MidenArrays.NoteArray([note]);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
4
|
+
AccountId: { fromHex: vi.fn() },
|
|
5
|
+
Word: {
|
|
6
|
+
newFromFelts: vi.fn((felts: unknown[]) => ({ felts })),
|
|
7
|
+
},
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock('../raw-client.js', () => ({
|
|
11
|
+
getRawMidenClient: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const { summarySalt } = await import('./summary.js');
|
|
15
|
+
|
|
16
|
+
describe('summarySalt', () => {
|
|
17
|
+
it('reads the salt from the trailing four user params', () => {
|
|
18
|
+
const summary = {
|
|
19
|
+
userParams: () => [0, 0, 0, 11, 22, 33, 44],
|
|
20
|
+
} as never;
|
|
21
|
+
|
|
22
|
+
expect(summarySalt(summary)).toEqual({ felts: [11, 22, 33, 44] });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('ignores the leading three user params the auth component zeroes', () => {
|
|
26
|
+
const summary = {
|
|
27
|
+
userParams: () => [7, 8, 9, 11, 22, 33, 44],
|
|
28
|
+
} as never;
|
|
29
|
+
|
|
30
|
+
expect(summarySalt(summary)).toEqual({ felts: [11, 22, 33, 44] });
|
|
31
|
+
});
|
|
32
|
+
});
|