@openzeppelin/miden-multisig-client 0.14.9 → 0.15.0-rc.0
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/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +4 -3
- package/dist/account/builder.js.map +1 -1
- package/dist/account/builder.test.js +0 -3
- package/dist/account/builder.test.js.map +1 -1
- package/dist/account/masm/auth.d.ts +2 -2
- package/dist/account/masm/auth.d.ts.map +1 -1
- package/dist/account/masm/auth.js +22 -10
- package/dist/account/masm/auth.js.map +1 -1
- package/dist/account/masm/authLibraries.d.ts +6 -0
- package/dist/account/masm/authLibraries.d.ts.map +1 -0
- package/dist/account/masm/authLibraries.js +17 -0
- package/dist/account/masm/authLibraries.js.map +1 -0
- package/dist/lookupAuth.d.ts +4 -3
- package/dist/lookupAuth.d.ts.map +1 -1
- package/dist/lookupAuth.js +8 -7
- package/dist/lookupAuth.js.map +1 -1
- package/dist/lookupAuth.test.js +3 -2
- package/dist/lookupAuth.test.js.map +1 -1
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +34 -13
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +118 -4
- package/dist/multisig.test.js.map +1 -1
- package/dist/procedures.d.ts +7 -7
- package/dist/procedures.js +7 -7
- package/dist/raw-client.d.ts.map +1 -1
- package/dist/raw-client.js +4 -4
- package/dist/raw-client.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +10 -4
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +9 -3
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/utils/digest.d.ts.map +1 -1
- package/dist/utils/digest.js +4 -3
- package/dist/utils/digest.js.map +1 -1
- package/dist/utils/felt.d.ts +12 -0
- package/dist/utils/felt.d.ts.map +1 -0
- package/dist/utils/felt.js +19 -0
- package/dist/utils/felt.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/word.d.ts.map +1 -1
- package/dist/utils/word.js +7 -2
- package/dist/utils/word.js.map +1 -1
- package/masm/auth/multisig.masm +11 -5
- package/masm/auth/multisig_ecdsa.masm +11 -5
- package/package.json +3 -3
- package/src/account/builder.test.ts +0 -3
- package/src/account/builder.ts +3 -3
- package/src/account/masm/auth.ts +22 -10
- package/src/lookupAuth.test.ts +3 -2
- package/src/lookupAuth.ts +7 -6
- package/src/multisig.test.ts +123 -4
- package/src/multisig.ts +38 -14
- package/src/procedures.ts +7 -7
- package/src/raw-client.ts +4 -4
- package/src/transaction/updateProcedureThreshold.ts +16 -7
- package/src/transaction/updateSigners.ts +15 -6
- package/src/utils/digest.ts +4 -3
- package/src/utils/felt.ts +20 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/word.ts +8 -2
package/src/multisig.test.ts
CHANGED
|
@@ -1300,6 +1300,24 @@ describe('Multisig', () => {
|
|
|
1300
1300
|
ok: true,
|
|
1301
1301
|
json: async () => ({ commitment: newGuardianPubkey }),
|
|
1302
1302
|
});
|
|
1303
|
+
mockFetch.mockResolvedValueOnce({
|
|
1304
|
+
ok: true,
|
|
1305
|
+
json: async () => ({
|
|
1306
|
+
delta: {
|
|
1307
|
+
account_id: multisig.accountId,
|
|
1308
|
+
nonce: 1,
|
|
1309
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1310
|
+
delta_payload: { tx_summary: { data: 'AQID' }, signatures: [] },
|
|
1311
|
+
status: {
|
|
1312
|
+
status: 'pending',
|
|
1313
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1314
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1315
|
+
cosigner_sigs: [],
|
|
1316
|
+
},
|
|
1317
|
+
},
|
|
1318
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1319
|
+
}),
|
|
1320
|
+
});
|
|
1303
1321
|
|
|
1304
1322
|
const proposal = await multisig.createSwitchGuardianProposal('http://new-guardian.com', newGuardianPubkey);
|
|
1305
1323
|
|
|
@@ -1361,6 +1379,24 @@ describe('Multisig', () => {
|
|
|
1361
1379
|
ok: true,
|
|
1362
1380
|
json: async () => ({ commitment: newGuardianCommitment }),
|
|
1363
1381
|
});
|
|
1382
|
+
mockFetch.mockResolvedValueOnce({
|
|
1383
|
+
ok: true,
|
|
1384
|
+
json: async () => ({
|
|
1385
|
+
delta: {
|
|
1386
|
+
account_id: multisig.accountId,
|
|
1387
|
+
nonce: 1,
|
|
1388
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1389
|
+
delta_payload: { tx_summary: { data: 'AQID' }, signatures: [] },
|
|
1390
|
+
status: {
|
|
1391
|
+
status: 'pending',
|
|
1392
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1393
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1394
|
+
cosigner_sigs: [],
|
|
1395
|
+
},
|
|
1396
|
+
},
|
|
1397
|
+
commitment: '0x' + 'c'.repeat(64),
|
|
1398
|
+
}),
|
|
1399
|
+
});
|
|
1364
1400
|
|
|
1365
1401
|
await multisig.createSwitchGuardianProposal('http://new-guardian.com', newGuardianCommitment);
|
|
1366
1402
|
|
|
@@ -2194,11 +2230,9 @@ describe('Multisig', () => {
|
|
|
2194
2230
|
const newGuardianPubkey = '0x' + '1'.repeat(64);
|
|
2195
2231
|
const finalRequest = { kind: 'final-switch-guardian-request' };
|
|
2196
2232
|
|
|
2233
|
+
// switch_guardian is exempt from binding re-execution, so only the single
|
|
2234
|
+
// final-request build happens here.
|
|
2197
2235
|
vi.mocked(buildUpdateGuardianTransactionRequest)
|
|
2198
|
-
.mockResolvedValueOnce({
|
|
2199
|
-
request: { kind: 'verify-switch-guardian-request' },
|
|
2200
|
-
salt: { toHex: () => '0x' + 'd'.repeat(64) },
|
|
2201
|
-
} as any)
|
|
2202
2236
|
.mockResolvedValueOnce({
|
|
2203
2237
|
request: finalRequest,
|
|
2204
2238
|
salt: { toHex: () => '0x' + 'd'.repeat(64) },
|
|
@@ -2998,6 +3032,91 @@ describe('Multisig', () => {
|
|
|
2998
3032
|
ok: true,
|
|
2999
3033
|
json: async () => ({ commitment: newGuardianPubkey }),
|
|
3000
3034
|
});
|
|
3035
|
+
// Pre-switch canonicalization push: getDeltaProposal then pushDelta.
|
|
3036
|
+
mockFetch.mockResolvedValueOnce({
|
|
3037
|
+
ok: true,
|
|
3038
|
+
json: async () => ({
|
|
3039
|
+
account_id: multisig.accountId,
|
|
3040
|
+
nonce: 1,
|
|
3041
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
3042
|
+
delta_payload: {
|
|
3043
|
+
tx_summary: { data: 'AQID' },
|
|
3044
|
+
signatures: [],
|
|
3045
|
+
metadata: {
|
|
3046
|
+
proposal_type: 'switch_guardian',
|
|
3047
|
+
new_guardian_pubkey: newGuardianPubkey,
|
|
3048
|
+
new_guardian_endpoint: 'http://new-guardian.com',
|
|
3049
|
+
},
|
|
3050
|
+
},
|
|
3051
|
+
status: {
|
|
3052
|
+
status: 'pending',
|
|
3053
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
3054
|
+
proposer_id: '0x' + 'a'.repeat(64),
|
|
3055
|
+
cosigner_sigs: [],
|
|
3056
|
+
},
|
|
3057
|
+
}),
|
|
3058
|
+
});
|
|
3059
|
+
mockFetch.mockResolvedValueOnce({
|
|
3060
|
+
ok: true,
|
|
3061
|
+
json: async () => ({
|
|
3062
|
+
account_id: multisig.accountId,
|
|
3063
|
+
nonce: 1,
|
|
3064
|
+
ack_sig: '0x' + '6'.repeat(130),
|
|
3065
|
+
ack_pubkey: '0x' + 'f'.repeat(64),
|
|
3066
|
+
ack_scheme: 'falcon',
|
|
3067
|
+
}),
|
|
3068
|
+
});
|
|
3069
|
+
mockWebClient.getAccount.mockResolvedValueOnce({
|
|
3070
|
+
serialize: () => new Uint8Array([1, 2, 3]),
|
|
3071
|
+
});
|
|
3072
|
+
mockFetch.mockResolvedValueOnce({
|
|
3073
|
+
ok: true,
|
|
3074
|
+
json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
|
|
3075
|
+
});
|
|
3076
|
+
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
3077
|
+
|
|
3078
|
+
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
3079
|
+
expect(mockWebClient.transactions.submit).toHaveBeenCalledTimes(1);
|
|
3080
|
+
});
|
|
3081
|
+
|
|
3082
|
+
it('should still switch GUARDIAN when the pre-switch canonicalization push fails', async () => {
|
|
3083
|
+
const config = {
|
|
3084
|
+
threshold: 1,
|
|
3085
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
3086
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
3087
|
+
};
|
|
3088
|
+
|
|
3089
|
+
const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
|
|
3090
|
+
const proposalId = '0x' + 'c'.repeat(64);
|
|
3091
|
+
const newGuardianPubkey = '0x' + '1'.repeat(64);
|
|
3092
|
+
|
|
3093
|
+
(multisig as any).proposals.set(proposalId, {
|
|
3094
|
+
id: proposalId,
|
|
3095
|
+
accountId: multisig.accountId,
|
|
3096
|
+
nonce: 1,
|
|
3097
|
+
status: 'ready',
|
|
3098
|
+
txSummary: 'AQID',
|
|
3099
|
+
signatures: [
|
|
3100
|
+
{
|
|
3101
|
+
signerId: '0x' + 'a'.repeat(64),
|
|
3102
|
+
signature: { scheme: 'falcon', signature: '0x' + 'b'.repeat(128) },
|
|
3103
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
3104
|
+
},
|
|
3105
|
+
],
|
|
3106
|
+
metadata: {
|
|
3107
|
+
proposalType: 'switch_guardian',
|
|
3108
|
+
newGuardianPubkey,
|
|
3109
|
+
newGuardianEndpoint: 'http://new-guardian.com',
|
|
3110
|
+
description: '',
|
|
3111
|
+
},
|
|
3112
|
+
});
|
|
3113
|
+
|
|
3114
|
+
mockFetch.mockResolvedValueOnce({
|
|
3115
|
+
ok: true,
|
|
3116
|
+
json: async () => ({ commitment: newGuardianPubkey }),
|
|
3117
|
+
});
|
|
3118
|
+
// getDeltaProposal against the old GUARDIAN fails — must be swallowed.
|
|
3119
|
+
mockFetch.mockRejectedValueOnce(new Error('pre-switch GUARDIAN unreachable'));
|
|
3001
3120
|
mockWebClient.getAccount.mockResolvedValueOnce({
|
|
3002
3121
|
serialize: () => new Uint8Array([1, 2, 3]),
|
|
3003
3122
|
});
|
package/src/multisig.ts
CHANGED
|
@@ -739,19 +739,9 @@ export class Multisig {
|
|
|
739
739
|
description: `Switch GUARDIAN to ${newGuardianEndpoint}`,
|
|
740
740
|
};
|
|
741
741
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
accountId: this._accountId,
|
|
746
|
-
nonce: proposalNonce,
|
|
747
|
-
status: 'pending',
|
|
748
|
-
txSummary: summaryBase64,
|
|
749
|
-
signatures: [],
|
|
750
|
-
metadata,
|
|
751
|
-
};
|
|
752
|
-
|
|
753
|
-
this.proposals.set(proposal.id, proposal);
|
|
754
|
-
return proposal;
|
|
742
|
+
// SwitchGuardian is a regular delta proposal; push it to GUARDIAN so
|
|
743
|
+
// sign/execute (which fetch from GUARDIAN) can find it.
|
|
744
|
+
return this.createProposal(proposalNonce, summaryBase64, metadata);
|
|
755
745
|
}
|
|
756
746
|
|
|
757
747
|
/**
|
|
@@ -879,7 +869,12 @@ export class Multisig {
|
|
|
879
869
|
);
|
|
880
870
|
|
|
881
871
|
if (canConsumeNow) {
|
|
882
|
-
|
|
872
|
+
// Miden 0.15: InputNoteRecord.id() is `NoteId | undefined`; skip id-less records.
|
|
873
|
+
const id = inputNote.id();
|
|
874
|
+
if (id === undefined) {
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
const noteId = id.toString();
|
|
883
878
|
const details = inputNote.details();
|
|
884
879
|
const fungibleAssets = details.assets().fungibleAssets();
|
|
885
880
|
|
|
@@ -976,6 +971,24 @@ export class Multisig {
|
|
|
976
971
|
throw new Error('Switch GUARDIAN proposal metadata is incomplete after execution');
|
|
977
972
|
}
|
|
978
973
|
|
|
974
|
+
// Canonicalize the executed delta on the pre-switch GUARDIAN (clears the
|
|
975
|
+
// pending proposal). Must run before `this.guardian` is repointed below.
|
|
976
|
+
// Best-effort: an unreachable old GUARDIAN must not block the switch, so
|
|
977
|
+
// errors are swallowed (mirrors the Rust execute path).
|
|
978
|
+
try {
|
|
979
|
+
const normalizedProposalId = normalizeHexWord(proposal.id);
|
|
980
|
+
const switchDelta = await this.guardian.getDeltaProposal(
|
|
981
|
+
this._accountId,
|
|
982
|
+
normalizedProposalId,
|
|
983
|
+
);
|
|
984
|
+
await this.guardian.pushDelta({
|
|
985
|
+
...switchDelta,
|
|
986
|
+
deltaPayload: switchDelta.deltaPayload.txSummary,
|
|
987
|
+
});
|
|
988
|
+
} catch {
|
|
989
|
+
// best-effort; see above
|
|
990
|
+
}
|
|
991
|
+
|
|
979
992
|
try {
|
|
980
993
|
const webClient = await this.getRawClient();
|
|
981
994
|
await webClient.syncState();
|
|
@@ -1556,6 +1569,17 @@ export class Multisig {
|
|
|
1556
1569
|
return txSummaryCommitment;
|
|
1557
1570
|
}
|
|
1558
1571
|
|
|
1572
|
+
if (proposal.metadata.proposalType === 'switch_guardian') {
|
|
1573
|
+
// Exempt from binding re-execution (mirrors the `custom` exemption above).
|
|
1574
|
+
// The WASM `executeForSummary` leaves the guardian-disabling side effect
|
|
1575
|
+
// applied to the in-session account, so re-execution reconstructs a smaller
|
|
1576
|
+
// delta and falsely rejects with "metadata does not match tx_summary". The
|
|
1577
|
+
// native Rust client does not mutate, so this is an intentional divergence.
|
|
1578
|
+
// The id ↔ tx_summary match above plus `verifyGuardianEndpointCommitment`
|
|
1579
|
+
// at propose/execute time still bind the proposal.
|
|
1580
|
+
return txSummaryCommitment;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1559
1583
|
const summary = TransactionSummary.deserialize(base64ToUint8Array(proposal.txSummary));
|
|
1560
1584
|
const salt = proposal.metadata.saltHex
|
|
1561
1585
|
? Word.fromHex(normalizeHexWord(proposal.metadata.saltHex))
|
package/src/procedures.ts
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
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
|
-
verify_guardian: '
|
|
19
|
-
send_asset: '
|
|
20
|
-
receive_asset: '
|
|
14
|
+
update_signers: '0x34963b067dbba634e57b416bc2f2a9a8d4ac24147f40b2900148c9ba44774274',
|
|
15
|
+
update_procedure_threshold: '0xec74c4b96ce593c11017ae54dec9c0ae5e0d242e8b3074eb3908d961300aed67',
|
|
16
|
+
auth_tx: '0x0708020dce7b91b61116e3eb27e5d686e129a83df3c540e0a7693b4523814e72',
|
|
17
|
+
update_guardian: '0xeceb1f2c2d7d20312dbaf091e9a27a2b63f9fcba120948043069793a5715bc96',
|
|
18
|
+
verify_guardian: '0xe6a8a62d37117f55a79b5345aa3d263ab16e973d486bac9a1612663dfdecf82d',
|
|
19
|
+
send_asset: '0xfb1c73d10de1954e9e8948964e3e77cf4e33759d2e012cb00eb10c50f2974eb4',
|
|
20
|
+
receive_asset: '0x6170fd6d682d91777b551fd866258f43cc657f1291f8f071500f4e56e9c153da',
|
|
21
21
|
} as const;
|
|
22
22
|
|
|
23
23
|
/**
|
package/src/raw-client.ts
CHANGED
|
@@ -24,12 +24,12 @@ function isPublicMidenClient(client: RawClientSource): client is MidenClient {
|
|
|
24
24
|
return 'accounts' in client && 'sync' in client;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export function getRawMidenClient(
|
|
27
|
+
export async function getRawMidenClient(
|
|
28
28
|
client: RawClientSource,
|
|
29
29
|
rpcUrl?: string,
|
|
30
30
|
): Promise<WasmWebClient> {
|
|
31
31
|
if (!isPublicMidenClient(client)) {
|
|
32
|
-
return
|
|
32
|
+
return client;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const cached = rawClientCache.get(client);
|
|
@@ -41,7 +41,7 @@ export function getRawMidenClient(
|
|
|
41
41
|
resolveMidenRpcEndpoint(rpcUrl),
|
|
42
42
|
undefined,
|
|
43
43
|
undefined,
|
|
44
|
-
client.storeIdentifier(),
|
|
44
|
+
await client.storeIdentifier(),
|
|
45
45
|
);
|
|
46
46
|
rawClientCache.set(client, rawClient);
|
|
47
47
|
return rawClient;
|
|
@@ -62,7 +62,7 @@ export async function compileTxScript(
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const rawClient = await getRawMidenClient(client, rpcUrl);
|
|
65
|
-
const builder = rawClient.createCodeBuilder();
|
|
65
|
+
const builder = await rawClient.createCodeBuilder();
|
|
66
66
|
for (const library of libraries) {
|
|
67
67
|
const builtLibrary = builder.buildLibrary(library.namespace, library.code);
|
|
68
68
|
if (library.linking === 'static') {
|
|
@@ -21,19 +21,28 @@ import { randomWord } from '../utils/random.js';
|
|
|
21
21
|
import type { SignatureOptions } from './options.js';
|
|
22
22
|
import type { SignatureScheme } from '../types.js';
|
|
23
23
|
|
|
24
|
-
function
|
|
25
|
-
procedure: ProcedureName,
|
|
26
|
-
threshold: number,
|
|
27
|
-
): { configHash: Word; payload: FeltArray } {
|
|
24
|
+
function buildProcedureThresholdFelts(procedure: ProcedureName, threshold: number): Felt[] {
|
|
28
25
|
const procedureRoot = WordType.fromHex(normalizeHexWord(getProcedureRoot(procedure)));
|
|
29
|
-
|
|
26
|
+
return [
|
|
30
27
|
...procedureRoot.toFelts(),
|
|
31
28
|
new Felt(BigInt(threshold)),
|
|
32
29
|
new Felt(0n),
|
|
33
30
|
new Felt(0n),
|
|
34
31
|
new Felt(0n),
|
|
35
|
-
]
|
|
36
|
-
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildProcedureThresholdAdvice(
|
|
36
|
+
procedure: ProcedureName,
|
|
37
|
+
threshold: number,
|
|
38
|
+
): { configHash: Word; payload: FeltArray } {
|
|
39
|
+
// `Poseidon2.hashElements` consumes (frees) its `FeltArray` by value, so the
|
|
40
|
+
// advice payload must be a freshly built one — reusing the hashed array
|
|
41
|
+
// surfaces as "null pointer passed to rust" at the later `advice.insert`.
|
|
42
|
+
const configHash = Poseidon2.hashElements(
|
|
43
|
+
new FeltArray(buildProcedureThresholdFelts(procedure, threshold)),
|
|
44
|
+
);
|
|
45
|
+
const payload = new FeltArray(buildProcedureThresholdFelts(procedure, threshold));
|
|
37
46
|
return { configHash, payload };
|
|
38
47
|
}
|
|
39
48
|
|
|
@@ -21,10 +21,7 @@ import { randomWord } from '../utils/random.js';
|
|
|
21
21
|
import type { SignatureOptions } from './options.js';
|
|
22
22
|
import type { SignatureScheme } from '../types.js';
|
|
23
23
|
|
|
24
|
-
function
|
|
25
|
-
threshold: number,
|
|
26
|
-
signerCommitments: string[],
|
|
27
|
-
): { configHash: Word; payload: FeltArray } {
|
|
24
|
+
function buildMultisigConfigFelts(threshold: number, signerCommitments: string[]): Felt[] {
|
|
28
25
|
const numApprovers = signerCommitments.length;
|
|
29
26
|
const felts: Felt[] = [
|
|
30
27
|
new Felt(BigInt(threshold)),
|
|
@@ -36,8 +33,20 @@ function buildMultisigConfigAdvice(
|
|
|
36
33
|
const word = WordType.fromHex(normalizeHexWord(commitment));
|
|
37
34
|
felts.push(...word.toFelts());
|
|
38
35
|
}
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
return felts;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildMultisigConfigAdvice(
|
|
40
|
+
threshold: number,
|
|
41
|
+
signerCommitments: string[],
|
|
42
|
+
): { configHash: Word; payload: FeltArray } {
|
|
43
|
+
// `Poseidon2.hashElements` consumes (frees) its `FeltArray` by value, so the
|
|
44
|
+
// advice payload must be a freshly built one — reusing the hashed array
|
|
45
|
+
// surfaces as "null pointer passed to rust" at the later `advice.insert`.
|
|
46
|
+
const configHash = Poseidon2.hashElements(
|
|
47
|
+
new FeltArray(buildMultisigConfigFelts(threshold, signerCommitments)),
|
|
48
|
+
);
|
|
49
|
+
const payload = new FeltArray(buildMultisigConfigFelts(threshold, signerCommitments));
|
|
41
50
|
return { configHash, payload };
|
|
42
51
|
}
|
|
43
52
|
|
package/src/utils/digest.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RequestAuthPayload } from '@openzeppelin/guardian-client';
|
|
2
2
|
import { AccountId, Felt, FeltArray, Rpo256, Word } from '@miden-sdk/miden-sdk';
|
|
3
|
+
import { feltFromU64Reduced } from './felt.js';
|
|
3
4
|
|
|
4
5
|
export class AuthDigest {
|
|
5
6
|
static fromAccountIdWithTimestamp(accountId: string, timestamp: number): Word {
|
|
@@ -11,7 +12,7 @@ export class AuthDigest {
|
|
|
11
12
|
const feltArray = new FeltArray([
|
|
12
13
|
prefix,
|
|
13
14
|
suffix,
|
|
14
|
-
|
|
15
|
+
feltFromU64Reduced(BigInt(timestamp)),
|
|
15
16
|
new Felt(0n),
|
|
16
17
|
]);
|
|
17
18
|
|
|
@@ -39,7 +40,7 @@ export class AuthDigest {
|
|
|
39
40
|
const feltArray = new FeltArray([
|
|
40
41
|
prefix,
|
|
41
42
|
suffix,
|
|
42
|
-
|
|
43
|
+
feltFromU64Reduced(BigInt(timestamp)),
|
|
43
44
|
...payloadWord.toFelts(),
|
|
44
45
|
]);
|
|
45
46
|
|
|
@@ -67,7 +68,7 @@ export class AuthDigest {
|
|
|
67
68
|
for (let j = 0; j < 8 && i + j < bytes.length; j += 1) {
|
|
68
69
|
packed |= BigInt(bytes[i + j]) << (8n * BigInt(j));
|
|
69
70
|
}
|
|
70
|
-
payloadElements.push(
|
|
71
|
+
payloadElements.push(feltFromU64Reduced(packed));
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
return Rpo256.hashElements(new FeltArray(payloadElements));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Felt } from '@miden-sdk/miden-sdk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Goldilocks field order (`2^64 - 2^32 + 1`), matching `Felt::ORDER` in
|
|
5
|
+
* `miden-protocol`.
|
|
6
|
+
*/
|
|
7
|
+
const FELT_ORDER = 18446744069414584321n;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Maps an arbitrary `u64` onto a canonical field element by reducing modulo the
|
|
11
|
+
* field order, mirroring `guardian_shared::felt::felt_from_u64_reduced`.
|
|
12
|
+
*
|
|
13
|
+
* Miden 0.15's `Felt` constructor rejects non-canonical inputs (values
|
|
14
|
+
* `>= FELT_ORDER`), whereas 0.14 reduced silently. Byte-packed digest inputs are
|
|
15
|
+
* arbitrary `u64`s, so reducing here preserves the original digest layout, keeps
|
|
16
|
+
* construction infallible, and stays byte-identical to server-side signing.
|
|
17
|
+
*/
|
|
18
|
+
export function feltFromU64Reduced(value: bigint): Felt {
|
|
19
|
+
return new Felt(BigInt.asUintN(64, value) % FELT_ORDER);
|
|
20
|
+
}
|
package/src/utils/index.ts
CHANGED
package/src/utils/word.ts
CHANGED
|
@@ -5,8 +5,14 @@ export function wordToHex(word: Word): string {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function wordElementToBigInt(word: Word, index: number): bigint {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
if (index < 0 || index > 3) {
|
|
9
|
+
return 0n;
|
|
10
|
+
}
|
|
11
|
+
// The wallet-embedded 0.15 SDK lacks `toU64s()` on storage-read Words; fall
|
|
12
|
+
// back to `toFelts()`, which has the same element order.
|
|
13
|
+
const elements: BigUint64Array | bigint[] =
|
|
14
|
+
typeof word.toU64s === 'function' ? word.toU64s() : word.toFelts().map(f => f.asInt());
|
|
15
|
+
return index < elements.length ? elements[index] : 0n;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
export function wordToBytes(word: { toFelts: () => Array<{ asInt: () => bigint }> }): Uint8Array {
|