@openzeppelin/miden-multisig-client 0.17.0 → 0.18.0-rc.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 +159 -46
- package/dist/account/builder.d.ts +4 -4
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +17 -7
- package/dist/account/builder.js.map +1 -1
- package/dist/account/layout.d.ts +5 -5
- package/dist/account/layout.d.ts.map +1 -1
- package/dist/account/layout.js +5 -5
- package/dist/account/layout.js.map +1 -1
- package/dist/client.d.ts +18 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +79 -6
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/multisig/authArgErrors.d.ts +14 -31
- package/dist/multisig/authArgErrors.d.ts.map +1 -1
- package/dist/multisig/authArgErrors.js +22 -46
- package/dist/multisig/authArgErrors.js.map +1 -1
- package/dist/multisig/consumeNotesErrors.d.ts +13 -1
- package/dist/multisig/consumeNotesErrors.d.ts.map +1 -1
- package/dist/multisig/consumeNotesErrors.js +17 -0
- package/dist/multisig/consumeNotesErrors.js.map +1 -1
- package/dist/multisig/signing.d.ts +1 -1
- package/dist/multisig/signing.d.ts.map +1 -1
- package/dist/multisig/signing.js +8 -3
- package/dist/multisig/signing.js.map +1 -1
- package/dist/multisig.d.ts +118 -11
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +421 -141
- package/dist/multisig.js.map +1 -1
- package/dist/procedures.d.ts +7 -7
- package/dist/procedures.js +7 -7
- package/dist/proposal/factory.d.ts.map +1 -1
- package/dist/proposal/factory.js +7 -0
- package/dist/proposal/factory.js.map +1 -1
- package/dist/raw-client.d.ts +1 -0
- package/dist/raw-client.d.ts.map +1 -1
- package/dist/raw-client.js +10 -2
- package/dist/raw-client.js.map +1 -1
- package/dist/recovery/publicNoteBackfill.js +1 -1
- package/dist/recovery/publicNoteBackfill.js.map +1 -1
- package/dist/retry/classify.d.ts +3 -0
- package/dist/retry/classify.d.ts.map +1 -1
- package/dist/retry/classify.js +2 -2
- package/dist/retry/classify.js.map +1 -1
- package/dist/signer.d.ts +1 -0
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +1 -0
- package/dist/signer.js.map +1 -1
- package/dist/signers/index.d.ts +1 -0
- package/dist/signers/index.d.ts.map +1 -1
- package/dist/signers/index.js +1 -0
- package/dist/signers/index.js.map +1 -1
- package/dist/signers/ledger.d.ts +25 -0
- package/dist/signers/ledger.d.ts.map +1 -0
- package/dist/signers/ledger.js +96 -0
- package/dist/signers/ledger.js.map +1 -0
- package/dist/state/adopt.d.ts +45 -0
- package/dist/state/adopt.d.ts.map +1 -0
- package/dist/state/adopt.js +101 -0
- package/dist/state/adopt.js.map +1 -0
- package/dist/transaction/authArgs.d.ts +57 -0
- package/dist/transaction/authArgs.d.ts.map +1 -0
- package/dist/transaction/authArgs.js +108 -0
- package/dist/transaction/authArgs.js.map +1 -0
- package/dist/transaction/consumeNotes.d.ts +9 -5
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +8 -23
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/noteAuthentication.d.ts +39 -0
- package/dist/transaction/noteAuthentication.d.ts.map +1 -0
- package/dist/transaction/noteAuthentication.js +94 -0
- package/dist/transaction/noteAuthentication.js.map +1 -0
- package/dist/transaction/options.d.ts +25 -0
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/p2id.d.ts +3 -2
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +36 -27
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/summary.d.ts +41 -15
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +71 -19
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/updateGuardian.d.ts +3 -3
- package/dist/transaction/updateGuardian.d.ts.map +1 -1
- package/dist/transaction/updateGuardian.js +5 -16
- package/dist/transaction/updateGuardian.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.d.ts +3 -3
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +6 -16
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +3 -3
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +9 -16
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.d.ts +3 -2
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +3 -2
- package/dist/transaction.js.map +1 -1
- package/dist/types/proposal.d.ts +31 -0
- package/dist/types/proposal.d.ts.map +1 -1
- package/dist/types/proposal.js +8 -0
- package/dist/types/proposal.js.map +1 -1
- package/dist/utils/eip712.d.ts +80 -0
- package/dist/utils/eip712.d.ts.map +1 -0
- package/dist/utils/eip712.js +49 -0
- package/dist/utils/eip712.js.map +1 -0
- package/dist/utils/signature.d.ts +4 -0
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +49 -1
- package/dist/utils/signature.js.map +1 -1
- package/package.json +11 -6
- package/src/account/builder.ts +18 -7
- package/src/account/layout.ts +5 -5
- package/src/client.ts +94 -6
- package/src/index.ts +21 -3
- package/src/multisig/authArgErrors.ts +23 -56
- package/src/multisig/consumeNotesErrors.ts +20 -1
- package/src/multisig/signing.ts +8 -2
- package/src/multisig.ts +530 -183
- package/src/procedures.ts +7 -7
- package/src/proposal/factory.ts +7 -0
- package/src/raw-client.ts +11 -7
- package/src/recovery/publicNoteBackfill.ts +1 -1
- package/src/retry/classify.ts +3 -3
- package/src/signer.ts +1 -0
- package/src/signers/index.ts +1 -0
- package/src/signers/ledger.ts +122 -0
- package/src/state/adopt.ts +132 -0
- package/src/transaction/authArgs.ts +142 -0
- package/src/transaction/consumeNotes.ts +23 -30
- package/src/transaction/noteAuthentication.ts +136 -0
- package/src/transaction/options.ts +27 -0
- package/src/transaction/p2id.ts +45 -34
- package/src/transaction/summary.ts +86 -22
- package/src/transaction/updateGuardian.ts +8 -22
- package/src/transaction/updateProcedureThreshold.ts +8 -20
- package/src/transaction/updateSigners.ts +11 -22
- package/src/transaction.ts +12 -1
- package/src/types/proposal.ts +30 -0
- package/src/utils/eip712.ts +57 -0
- package/src/utils/signature.ts +57 -0
- package/src/prover/test-node.d.ts +0 -6
package/src/transaction.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
export {
|
|
2
|
+
MAX_APPROVAL_EXPIRATION_DELTA,
|
|
3
|
+
buildMultisigRequest,
|
|
4
|
+
multisigRequestBuilder,
|
|
5
|
+
requestBoundBlockNum,
|
|
6
|
+
requestSaltHex,
|
|
7
|
+
} from './transaction/authArgs.js';
|
|
1
8
|
export {
|
|
2
9
|
buildConsumeNotesTransactionRequest,
|
|
10
|
+
buildConsumeNotesTransactionRequestFromNotes,
|
|
3
11
|
} from './transaction/consumeNotes.js';
|
|
4
12
|
export {
|
|
13
|
+
chainAnchorBlockNum,
|
|
5
14
|
chainAnchorFromBase64,
|
|
6
15
|
chainAnchorToBase64,
|
|
7
16
|
executeForSummary,
|
|
8
17
|
executeForSummaryAt,
|
|
9
|
-
|
|
18
|
+
summaryApprovalExpirationBlockNum,
|
|
19
|
+
summarySalt,
|
|
20
|
+
SummaryAnchorMismatchError,
|
|
10
21
|
} from './transaction/summary.js';
|
|
11
22
|
export {
|
|
12
23
|
buildP2idNoteFromMetadata,
|
package/src/types/proposal.ts
CHANGED
|
@@ -162,6 +162,35 @@ export interface Proposal {
|
|
|
162
162
|
txSummary: string;
|
|
163
163
|
signatures: ProposalSignatureEntry[];
|
|
164
164
|
metadata: ProposalMetadata;
|
|
165
|
+
/**
|
|
166
|
+
* Result of the last summary-binding check on this value. Only the check
|
|
167
|
+
* itself writes `verified`; a freshly parsed or imported proposal is
|
|
168
|
+
* `unchecked`. `syncProposals` surfaces failed proposals instead of
|
|
169
|
+
* failing wholesale (issue #462); `signProposal` and `executeProposal`
|
|
170
|
+
* re-verify and refuse them.
|
|
171
|
+
*/
|
|
172
|
+
verification: ProposalVerification;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Outcome of checking a proposal's metadata against its signed summary.
|
|
177
|
+
* `failed.retryable` is true when the failure came from a transient node or
|
|
178
|
+
* RPC error, so the same proposal may verify on a later sync; false when the
|
|
179
|
+
* proposal itself cannot be reproduced (tampered metadata, an anchor block the
|
|
180
|
+
* node has pruned) and it has to be re-proposed.
|
|
181
|
+
*/
|
|
182
|
+
export type ProposalVerification =
|
|
183
|
+
| { status: 'unchecked' }
|
|
184
|
+
| { status: 'verified' }
|
|
185
|
+
| { status: 'failed'; retryable: boolean; message: string };
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* True when the proposal is both verified and has met its signature
|
|
189
|
+
* threshold, i.e. it can be executed. `status` alone keeps meaning
|
|
190
|
+
* "threshold met": a fully signed proposal can still be dead.
|
|
191
|
+
*/
|
|
192
|
+
export function isProposalActionable(proposal: Proposal): boolean {
|
|
193
|
+
return proposal.verification.status === 'verified' && proposal.status === 'ready';
|
|
165
194
|
}
|
|
166
195
|
|
|
167
196
|
export interface TransactionProposal {
|
|
@@ -185,6 +214,7 @@ export interface ExportedProposal {
|
|
|
185
214
|
signatureHex: string;
|
|
186
215
|
scheme?: SignatureScheme;
|
|
187
216
|
publicKey?: string;
|
|
217
|
+
messageFormat?: 'eip712';
|
|
188
218
|
timestamp?: string;
|
|
189
219
|
}>;
|
|
190
220
|
metadata: ProposalMetadata;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
2
|
+
import { bytesToHex, hexToBytes } from './encoding.js';
|
|
3
|
+
|
|
4
|
+
type TypedField = { name: string; type: string };
|
|
5
|
+
const EIP191_PREFIX = 0x19;
|
|
6
|
+
const EIP712_VERSION = 0x01;
|
|
7
|
+
const DOMAIN_VERSION = '1';
|
|
8
|
+
|
|
9
|
+
const domainFields: TypedField[] = [
|
|
10
|
+
{ name: 'name', type: 'string' },
|
|
11
|
+
{ name: 'version', type: 'string' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function typedData(name: string, primaryType: string, fieldName: string, value: string) {
|
|
15
|
+
return {
|
|
16
|
+
types: {
|
|
17
|
+
EIP712Domain: domainFields,
|
|
18
|
+
[primaryType]: [{ name: fieldName, type: 'bytes32' }],
|
|
19
|
+
},
|
|
20
|
+
primaryType,
|
|
21
|
+
domain: { name, version: DOMAIN_VERSION },
|
|
22
|
+
message: { [fieldName]: value },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function guardianRequestTypedData(requestHash: Uint8Array) {
|
|
27
|
+
return typedData('Guardian Request', 'GuardianRequest', 'requestHash', bytesToHex(requestHash));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function guardianLookupTypedData(lookupHash: Uint8Array) {
|
|
31
|
+
return typedData('Guardian Lookup', 'GuardianLookup', 'lookupHash', bytesToHex(lookupHash));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function midenTransactionTypedData(txSummaryHash: Uint8Array) {
|
|
35
|
+
return typedData('Miden Transaction', 'MidenTransaction', 'txSummaryHash', bytesToHex(txSummaryHash));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function guardianKeyDiscoveryTypedData(challenge: Uint8Array) {
|
|
39
|
+
return typedData('Guardian Key Discovery', 'GuardianKeyDiscovery', 'challenge', bytesToHex(challenge));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function typedDataDigest(data: ReturnType<typeof typedData>): Uint8Array {
|
|
43
|
+
const domainType = new TextEncoder().encode('EIP712Domain(string name,string version)');
|
|
44
|
+
const primaryType = new TextEncoder().encode(`${data.primaryType}(bytes32 ${Object.keys(data.message)[0]})`);
|
|
45
|
+
const domain = new Uint8Array(96);
|
|
46
|
+
domain.set(keccak_256(domainType));
|
|
47
|
+
domain.set(keccak_256(new TextEncoder().encode(data.domain.name)), 32);
|
|
48
|
+
domain.set(keccak_256(new TextEncoder().encode(data.domain.version)), 64);
|
|
49
|
+
const struct = new Uint8Array(64);
|
|
50
|
+
struct.set(keccak_256(primaryType));
|
|
51
|
+
struct.set(hexToBytes(Object.values(data.message)[0]), 32);
|
|
52
|
+
const preimage = new Uint8Array(66);
|
|
53
|
+
preimage.set([EIP191_PREFIX, EIP712_VERSION]);
|
|
54
|
+
preimage.set(keccak_256(domain), 2);
|
|
55
|
+
preimage.set(keccak_256(struct), 34);
|
|
56
|
+
return keccak_256(preimage);
|
|
57
|
+
}
|
package/src/utils/signature.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { AdviceMap, Felt, FeltArray, Poseidon2, Signature, Word } from '@miden-sdk/miden-sdk';
|
|
2
2
|
import * as midenSdk from '@miden-sdk/miden-sdk';
|
|
3
3
|
import { EcdsaFormat } from './ecdsa.js';
|
|
4
|
+
import { midenTransactionTypedData, typedDataDigest } from './eip712.js';
|
|
4
5
|
import { hexToBytes, normalizeHexWord } from './encoding.js';
|
|
6
|
+
import { wordToBytes } from './word.js';
|
|
7
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
5
8
|
import type { ProposalSignatureEntry, SignatureScheme } from '../types.js';
|
|
6
9
|
|
|
7
10
|
export const ECDSA_AUTH_SCHEME_ID = 1;
|
|
@@ -43,6 +46,60 @@ export function buildSignatureAdviceEntry(
|
|
|
43
46
|
return { key, values: signature.toPreparedSignature(message) };
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
// Little-endian ASCII "EIP712", matching the protocol advice-key domain.
|
|
50
|
+
const EIP712_SIGNATURE_KEY_DOMAIN = 0x323137504945n;
|
|
51
|
+
|
|
52
|
+
function littleEndianU32Limbs(bytes: Uint8Array): Felt[] {
|
|
53
|
+
const limbs: Felt[] = [];
|
|
54
|
+
for (let i = 28; i >= 0; i -= 4) {
|
|
55
|
+
const limb = (bytes[i] << 24) | (bytes[i + 1] << 16) | (bytes[i + 2] << 8) | bytes[i + 3];
|
|
56
|
+
limbs.push(new Felt(BigInt(limb >>> 0)));
|
|
57
|
+
}
|
|
58
|
+
return limbs;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildEip712SignatureAdviceEntry(
|
|
62
|
+
pubkeyCommitment: Word,
|
|
63
|
+
txSummaryCommitment: Word,
|
|
64
|
+
signatureHex: string,
|
|
65
|
+
publicKeyHex: string,
|
|
66
|
+
): { key: Word; values: Felt[] } {
|
|
67
|
+
const expectedCommitment = tryComputeEcdsaCommitmentHex(publicKeyHex);
|
|
68
|
+
if (expectedCommitment !== normalizeHexWord(pubkeyCommitment.toHex())) {
|
|
69
|
+
throw new Error('EIP-712 public key commitment mismatch');
|
|
70
|
+
}
|
|
71
|
+
const publicKey = secp256k1.ProjectivePoint.fromHex(publicKeyHex.replace(/^0x/i, ''));
|
|
72
|
+
const uncompressed = publicKey.toRawBytes(false);
|
|
73
|
+
const normalizedSignature = EcdsaFormat.normalizeRecoveryByte(signatureHex);
|
|
74
|
+
if (!/^0x[0-9a-fA-F]{130}$/.test(normalizedSignature)) {
|
|
75
|
+
throw new Error('EIP-712 signature must be 65 hex-encoded bytes');
|
|
76
|
+
}
|
|
77
|
+
const signature = hexToBytes(normalizedSignature);
|
|
78
|
+
if (signature.length !== 65 || (signature[64] !== 0 && signature[64] !== 1)) {
|
|
79
|
+
throw new Error('EIP-712 signature must contain a valid recovery ID');
|
|
80
|
+
}
|
|
81
|
+
const typedData = midenTransactionTypedData(wordToBytes(txSummaryCommitment));
|
|
82
|
+
if (!secp256k1.verify(signature.slice(0, 64), typedDataDigest(typedData), publicKey.toRawBytes(true))) {
|
|
83
|
+
throw new Error('EIP-712 signature does not match the transaction summary');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const rawKey = Poseidon2.hashElements(new FeltArray([
|
|
87
|
+
...pubkeyCommitment.toFelts(),
|
|
88
|
+
...txSummaryCommitment.toFelts(),
|
|
89
|
+
]));
|
|
90
|
+
const domain = new Word(new BigUint64Array([EIP712_SIGNATURE_KEY_DOMAIN, 0n, 0n, 0n]));
|
|
91
|
+
const key = Poseidon2.hashElements(new FeltArray([...rawKey.toFelts(), ...domain.toFelts()]));
|
|
92
|
+
return {
|
|
93
|
+
key,
|
|
94
|
+
values: [
|
|
95
|
+
...littleEndianU32Limbs(uncompressed.slice(1, 33)),
|
|
96
|
+
...littleEndianU32Limbs(uncompressed.slice(33, 65)),
|
|
97
|
+
...littleEndianU32Limbs(signature.slice(0, 32)),
|
|
98
|
+
...littleEndianU32Limbs(signature.slice(32, 64)),
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
46
103
|
/** Rejects unrecoverable ECDSA signatures before entering WASM. */
|
|
47
104
|
export function assertEcdsaSignatureRecoverable(
|
|
48
105
|
signatureHex: string,
|