@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.
Files changed (146) hide show
  1. package/README.md +159 -46
  2. package/dist/account/builder.d.ts +4 -4
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +17 -7
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/account/layout.d.ts +5 -5
  7. package/dist/account/layout.d.ts.map +1 -1
  8. package/dist/account/layout.js +5 -5
  9. package/dist/account/layout.js.map +1 -1
  10. package/dist/client.d.ts +18 -1
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +79 -6
  13. package/dist/client.js.map +1 -1
  14. package/dist/index.d.ts +6 -6
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +5 -5
  17. package/dist/index.js.map +1 -1
  18. package/dist/multisig/authArgErrors.d.ts +14 -31
  19. package/dist/multisig/authArgErrors.d.ts.map +1 -1
  20. package/dist/multisig/authArgErrors.js +22 -46
  21. package/dist/multisig/authArgErrors.js.map +1 -1
  22. package/dist/multisig/consumeNotesErrors.d.ts +13 -1
  23. package/dist/multisig/consumeNotesErrors.d.ts.map +1 -1
  24. package/dist/multisig/consumeNotesErrors.js +17 -0
  25. package/dist/multisig/consumeNotesErrors.js.map +1 -1
  26. package/dist/multisig/signing.d.ts +1 -1
  27. package/dist/multisig/signing.d.ts.map +1 -1
  28. package/dist/multisig/signing.js +8 -3
  29. package/dist/multisig/signing.js.map +1 -1
  30. package/dist/multisig.d.ts +118 -11
  31. package/dist/multisig.d.ts.map +1 -1
  32. package/dist/multisig.js +421 -141
  33. package/dist/multisig.js.map +1 -1
  34. package/dist/procedures.d.ts +7 -7
  35. package/dist/procedures.js +7 -7
  36. package/dist/proposal/factory.d.ts.map +1 -1
  37. package/dist/proposal/factory.js +7 -0
  38. package/dist/proposal/factory.js.map +1 -1
  39. package/dist/raw-client.d.ts +1 -0
  40. package/dist/raw-client.d.ts.map +1 -1
  41. package/dist/raw-client.js +10 -2
  42. package/dist/raw-client.js.map +1 -1
  43. package/dist/recovery/publicNoteBackfill.js +1 -1
  44. package/dist/recovery/publicNoteBackfill.js.map +1 -1
  45. package/dist/retry/classify.d.ts +3 -0
  46. package/dist/retry/classify.d.ts.map +1 -1
  47. package/dist/retry/classify.js +2 -2
  48. package/dist/retry/classify.js.map +1 -1
  49. package/dist/signer.d.ts +1 -0
  50. package/dist/signer.d.ts.map +1 -1
  51. package/dist/signer.js +1 -0
  52. package/dist/signer.js.map +1 -1
  53. package/dist/signers/index.d.ts +1 -0
  54. package/dist/signers/index.d.ts.map +1 -1
  55. package/dist/signers/index.js +1 -0
  56. package/dist/signers/index.js.map +1 -1
  57. package/dist/signers/ledger.d.ts +25 -0
  58. package/dist/signers/ledger.d.ts.map +1 -0
  59. package/dist/signers/ledger.js +96 -0
  60. package/dist/signers/ledger.js.map +1 -0
  61. package/dist/state/adopt.d.ts +45 -0
  62. package/dist/state/adopt.d.ts.map +1 -0
  63. package/dist/state/adopt.js +101 -0
  64. package/dist/state/adopt.js.map +1 -0
  65. package/dist/transaction/authArgs.d.ts +57 -0
  66. package/dist/transaction/authArgs.d.ts.map +1 -0
  67. package/dist/transaction/authArgs.js +108 -0
  68. package/dist/transaction/authArgs.js.map +1 -0
  69. package/dist/transaction/consumeNotes.d.ts +9 -5
  70. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  71. package/dist/transaction/consumeNotes.js +8 -23
  72. package/dist/transaction/consumeNotes.js.map +1 -1
  73. package/dist/transaction/noteAuthentication.d.ts +39 -0
  74. package/dist/transaction/noteAuthentication.d.ts.map +1 -0
  75. package/dist/transaction/noteAuthentication.js +94 -0
  76. package/dist/transaction/noteAuthentication.js.map +1 -0
  77. package/dist/transaction/options.d.ts +25 -0
  78. package/dist/transaction/options.d.ts.map +1 -1
  79. package/dist/transaction/p2id.d.ts +3 -2
  80. package/dist/transaction/p2id.d.ts.map +1 -1
  81. package/dist/transaction/p2id.js +36 -27
  82. package/dist/transaction/p2id.js.map +1 -1
  83. package/dist/transaction/summary.d.ts +41 -15
  84. package/dist/transaction/summary.d.ts.map +1 -1
  85. package/dist/transaction/summary.js +71 -19
  86. package/dist/transaction/summary.js.map +1 -1
  87. package/dist/transaction/updateGuardian.d.ts +3 -3
  88. package/dist/transaction/updateGuardian.d.ts.map +1 -1
  89. package/dist/transaction/updateGuardian.js +5 -16
  90. package/dist/transaction/updateGuardian.js.map +1 -1
  91. package/dist/transaction/updateProcedureThreshold.d.ts +3 -3
  92. package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
  93. package/dist/transaction/updateProcedureThreshold.js +6 -16
  94. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  95. package/dist/transaction/updateSigners.d.ts +3 -3
  96. package/dist/transaction/updateSigners.d.ts.map +1 -1
  97. package/dist/transaction/updateSigners.js +9 -16
  98. package/dist/transaction/updateSigners.js.map +1 -1
  99. package/dist/transaction.d.ts +3 -2
  100. package/dist/transaction.d.ts.map +1 -1
  101. package/dist/transaction.js +3 -2
  102. package/dist/transaction.js.map +1 -1
  103. package/dist/types/proposal.d.ts +31 -0
  104. package/dist/types/proposal.d.ts.map +1 -1
  105. package/dist/types/proposal.js +8 -0
  106. package/dist/types/proposal.js.map +1 -1
  107. package/dist/utils/eip712.d.ts +80 -0
  108. package/dist/utils/eip712.d.ts.map +1 -0
  109. package/dist/utils/eip712.js +49 -0
  110. package/dist/utils/eip712.js.map +1 -0
  111. package/dist/utils/signature.d.ts +4 -0
  112. package/dist/utils/signature.d.ts.map +1 -1
  113. package/dist/utils/signature.js +49 -1
  114. package/dist/utils/signature.js.map +1 -1
  115. package/package.json +11 -6
  116. package/src/account/builder.ts +18 -7
  117. package/src/account/layout.ts +5 -5
  118. package/src/client.ts +94 -6
  119. package/src/index.ts +21 -3
  120. package/src/multisig/authArgErrors.ts +23 -56
  121. package/src/multisig/consumeNotesErrors.ts +20 -1
  122. package/src/multisig/signing.ts +8 -2
  123. package/src/multisig.ts +530 -183
  124. package/src/procedures.ts +7 -7
  125. package/src/proposal/factory.ts +7 -0
  126. package/src/raw-client.ts +11 -7
  127. package/src/recovery/publicNoteBackfill.ts +1 -1
  128. package/src/retry/classify.ts +3 -3
  129. package/src/signer.ts +1 -0
  130. package/src/signers/index.ts +1 -0
  131. package/src/signers/ledger.ts +122 -0
  132. package/src/state/adopt.ts +132 -0
  133. package/src/transaction/authArgs.ts +142 -0
  134. package/src/transaction/consumeNotes.ts +23 -30
  135. package/src/transaction/noteAuthentication.ts +136 -0
  136. package/src/transaction/options.ts +27 -0
  137. package/src/transaction/p2id.ts +45 -34
  138. package/src/transaction/summary.ts +86 -22
  139. package/src/transaction/updateGuardian.ts +8 -22
  140. package/src/transaction/updateProcedureThreshold.ts +8 -20
  141. package/src/transaction/updateSigners.ts +11 -22
  142. package/src/transaction.ts +12 -1
  143. package/src/types/proposal.ts +30 -0
  144. package/src/utils/eip712.ts +57 -0
  145. package/src/utils/signature.ts +57 -0
  146. package/src/prover/test-node.d.ts +0 -6
@@ -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
- summaryAuthArg,
18
+ summaryApprovalExpirationBlockNum,
19
+ summarySalt,
20
+ SummaryAnchorMismatchError,
10
21
  } from './transaction/summary.js';
11
22
  export {
12
23
  buildP2idNoteFromMetadata,
@@ -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
+ }
@@ -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,
@@ -1,6 +0,0 @@
1
- declare module 'node:fs' {
2
- export function readFileSync(
3
- path: URL,
4
- encoding: 'utf8',
5
- ): string;
6
- }