@openzeppelin/miden-multisig-client 0.12.6 → 0.12.8

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 (89) hide show
  1. package/README.md +70 -75
  2. package/dist/account/builder.d.ts +0 -18
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +16 -24
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/account/masm.d.ts +2 -0
  7. package/dist/account/masm.d.ts.map +1 -1
  8. package/dist/account/masm.js +428 -1
  9. package/dist/account/masm.js.map +1 -1
  10. package/dist/account/storage.d.ts.map +1 -1
  11. package/dist/account/storage.js +5 -6
  12. package/dist/account/storage.js.map +1 -1
  13. package/dist/client.d.ts +5 -57
  14. package/dist/client.d.ts.map +1 -1
  15. package/dist/client.js +7 -53
  16. package/dist/client.js.map +1 -1
  17. package/dist/client.test.js +28 -0
  18. package/dist/client.test.js.map +1 -1
  19. package/dist/index.d.ts +3 -46
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -45
  22. package/dist/index.js.map +1 -1
  23. package/dist/inspector.d.ts +4 -30
  24. package/dist/inspector.d.ts.map +1 -1
  25. package/dist/inspector.js +13 -43
  26. package/dist/inspector.js.map +1 -1
  27. package/dist/multisig/helpers.d.ts.map +1 -1
  28. package/dist/multisig/helpers.js.map +1 -1
  29. package/dist/multisig.d.ts +39 -161
  30. package/dist/multisig.d.ts.map +1 -1
  31. package/dist/multisig.js +190 -241
  32. package/dist/multisig.js.map +1 -1
  33. package/dist/multisig.test.js +41 -31
  34. package/dist/multisig.test.js.map +1 -1
  35. package/dist/procedures.d.ts +20 -47
  36. package/dist/procedures.d.ts.map +1 -1
  37. package/dist/procedures.js +17 -50
  38. package/dist/procedures.js.map +1 -1
  39. package/dist/signer.d.ts +11 -26
  40. package/dist/signer.d.ts.map +1 -1
  41. package/dist/signer.js +41 -26
  42. package/dist/signer.js.map +1 -1
  43. package/dist/transaction/options.d.ts +2 -0
  44. package/dist/transaction/options.d.ts.map +1 -1
  45. package/dist/transaction/updatePsm.d.ts.map +1 -1
  46. package/dist/transaction/updatePsm.js +21 -3
  47. package/dist/transaction/updatePsm.js.map +1 -1
  48. package/dist/transaction/updateSigners.d.ts.map +1 -1
  49. package/dist/transaction/updateSigners.js +21 -3
  50. package/dist/transaction/updateSigners.js.map +1 -1
  51. package/dist/transaction.d.ts.map +1 -1
  52. package/dist/transaction.js +0 -2
  53. package/dist/transaction.js.map +1 -1
  54. package/dist/transaction.test.js +5 -0
  55. package/dist/transaction.test.js.map +1 -1
  56. package/dist/types/proposal.d.ts +16 -8
  57. package/dist/types/proposal.d.ts.map +1 -1
  58. package/dist/types.d.ts +18 -14
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/utils/signature.d.ts +10 -2
  61. package/dist/utils/signature.d.ts.map +1 -1
  62. package/dist/utils/signature.js +77 -5
  63. package/dist/utils/signature.js.map +1 -1
  64. package/dist/utils/signature.test.js +16 -1
  65. package/dist/utils/signature.test.js.map +1 -1
  66. package/masm/multisig_ecdsa.masm +424 -0
  67. package/masm/psm_ecdsa.masm +179 -0
  68. package/package.json +2 -2
  69. package/src/account/builder.ts +18 -25
  70. package/src/account/masm.ts +433 -2
  71. package/src/account/storage.ts +5 -6
  72. package/src/client.test.ts +36 -0
  73. package/src/client.ts +10 -59
  74. package/src/index.ts +13 -62
  75. package/src/inspector.ts +15 -44
  76. package/src/multisig/helpers.ts +1 -2
  77. package/src/multisig.test.ts +45 -34
  78. package/src/multisig.ts +246 -273
  79. package/src/procedures.ts +21 -56
  80. package/src/signer.ts +48 -28
  81. package/src/transaction/options.ts +2 -0
  82. package/src/transaction/updatePsm.ts +24 -3
  83. package/src/transaction/updateSigners.ts +25 -3
  84. package/src/transaction.test.ts +6 -0
  85. package/src/transaction.ts +0 -2
  86. package/src/types/proposal.ts +15 -8
  87. package/src/types.ts +26 -16
  88. package/src/utils/signature.test.ts +24 -1
  89. package/src/utils/signature.ts +86 -4
package/src/procedures.ts CHANGED
@@ -1,72 +1,37 @@
1
- /**
2
- * known procedure roots for multisig accounts.
3
- *
4
- * IMPORTANT: These values are extracted from the TypeScript SDK's compiled MASM output,
5
- * NOT from the Rust crate. The SDK may compile MASM differently, resulting in different
6
- * procedure roots. Use the debug output from builder.ts to update these values.
7
- */
1
+ import type { SignatureScheme } from '@openzeppelin/psm-client';
8
2
 
9
- /**
10
- * Static mapping of procedure names to their deterministic roots.
11
- *
12
- * Component ordering: Multisig (auth) -> PSM -> BasicWallet
13
- */
14
- export const PROCEDURE_ROOTS = {
15
- // Multisig component procedures
16
- /** Update signer list and threshold configuration */
3
+ export const PROCEDURE_ROOTS_FALCON = {
17
4
  update_signers: '0x53d0ad381a193de0cf6af3730141e498274103dfc3b8c8e7367bd49d4a66c72b',
18
- /** Authenticate transaction with multisig (Falcon512) */
19
5
  auth_tx: '0x474c613b38001cc36d68557e9d881495d6a461a9027033445c7672c586509026',
20
-
21
- // PSM component procedures
22
- /** Update PSM public key */
23
6
  update_psm: '0xb103236807e5bf09c27efc2c5287ca8b03ab9efc1d852b62ebd31f5f1927ec26',
24
- /** Verify PSM signature */
25
7
  verify_psm: '0x30727fc23c6105a678fea8b4c1920f35fa85c03f16a0cf98372c8f56701f8a87',
26
-
27
- // BasicWallet procedures
28
- /** Send assets from account (move_asset_to_note) */
29
8
  send_asset: '0x0e406b067ed2bcd7de745ca6517f519fd1a9be245f913347ac673ca1db30c1d6',
30
- /** Receive assets into account */
31
9
  receive_asset: '0x6f4bdbdc4b13d7ed933d590d88ac9dfb98020c9e917697845b5e169395b76a01',
32
10
  } as const;
33
11
 
34
- /**
35
- * Valid procedure names that can be used for threshold overrides.
36
- */
37
- export type ProcedureName = keyof typeof PROCEDURE_ROOTS;
12
+ export const PROCEDURE_ROOTS_ECDSA = {
13
+ update_signers: '0x930f9ea86d33c7b3b2d23c4e9ac2492add461359344ed25b1acd38f3713dd79c',
14
+ auth_tx: '0x2bc7664a9dd47b36e7c8b8c3df03412798e4410173f36acfe03d191a38add053',
15
+ update_psm: '0x26ec27195f1fd3eb622b851dfc9eab038bca87522cfc7ec209bfe507682303b1',
16
+ verify_psm: '0xbeb9e08a83eca030968f2f137b5673136f21bc16c82fd408c2ce2495ccbdcd15',
17
+ send_asset: '0xd6c130dba13c67ac4733915f24bea9d19f517f51a65c74ded7bcd27e066b400e',
18
+ receive_asset: '0x016ab79593165e5b849776919e0c0298fb9dac880d593d93edd7134bdcdb4b6f',
19
+ } as const;
20
+
21
+ export const PROCEDURE_ROOTS = PROCEDURE_ROOTS_FALCON;
22
+
23
+ export type ProcedureName = keyof typeof PROCEDURE_ROOTS_FALCON;
38
24
 
39
- /**
40
- * Get the procedure root for a given procedure name.
41
- *
42
- * @param name - The procedure name
43
- * @returns The procedure root as a hex string
44
- *
45
- * @example
46
- * ```typescript
47
- * const root = getProcedureRoot('receive_asset');
48
- * // '0x6f4bdbdc4b13d7ed933d590d88ac9dfb98020c9e917697845b5e169395b76a01'
49
- * ```
50
- */
51
- export function getProcedureRoot(name: ProcedureName): string {
52
- return PROCEDURE_ROOTS[name];
25
+ export function getProcedureRoot(name: ProcedureName, scheme: SignatureScheme = 'falcon'): string {
26
+ const roots = scheme === 'ecdsa' ? PROCEDURE_ROOTS_ECDSA : PROCEDURE_ROOTS_FALCON;
27
+ return roots[name];
53
28
  }
54
29
 
55
- /**
56
- * Check if a string is a valid procedure name.
57
- *
58
- * @param name - The string to check
59
- * @returns true if the string is a valid procedure name
60
- */
61
30
  export function isProcedureName(name: string): name is ProcedureName {
62
- return name in PROCEDURE_ROOTS;
31
+ return name in PROCEDURE_ROOTS_FALCON;
63
32
  }
64
33
 
65
- /**
66
- * Get all available procedure names.
67
- *
68
- * @returns Array of all valid procedure names
69
- */
70
- export function getProcedureNames(): ProcedureName[] {
71
- return Object.keys(PROCEDURE_ROOTS) as ProcedureName[];
34
+ export function getProcedureNames(scheme: SignatureScheme = 'falcon'): ProcedureName[] {
35
+ const roots = scheme === 'ecdsa' ? PROCEDURE_ROOTS_ECDSA : PROCEDURE_ROOTS_FALCON;
36
+ return Object.keys(roots) as ProcedureName[];
72
37
  }
package/src/signer.ts CHANGED
@@ -1,27 +1,11 @@
1
- /**
2
- * Falcon Signer implementation for PSM client authentication.
3
- *
4
- * This implements the Signer interface using the Miden SDK's SecretKey
5
- * for Falcon signatures.
6
- */
7
-
8
1
  import { SecretKey, Word, AccountId, Felt, FeltArray, Rpo256 } from '@demox-labs/miden-sdk';
9
- import type { Signer } from './types.js';
2
+ import type { Signer, SignatureScheme } from './types.js';
10
3
  import { bytesToHex } from './utils/encoding.js';
11
4
 
12
- /**
13
- * A Falcon signer that implements the Signer interface.
14
- *
15
- * @example
16
- * ```typescript
17
- * const secretKey = SecretKey.rpoFalconWithRNG(seed);
18
- * const signer = new FalconSigner(secretKey);
19
- * console.log(signer.commitment);
20
- * ```
21
- */
22
5
  export class FalconSigner implements Signer {
23
6
  readonly commitment: string;
24
7
  readonly publicKey: string;
8
+ readonly scheme: SignatureScheme = 'falcon';
25
9
  private readonly secretKey: SecretKey;
26
10
 
27
11
  constructor(secretKey: SecretKey) {
@@ -33,14 +17,7 @@ export class FalconSigner implements Signer {
33
17
  this.publicKey = bytesToHex(falconPubKey);
34
18
  }
35
19
 
36
- /**
37
- * Signs an account ID with a timestamp.
38
- * @param accountId - The account ID to sign
39
- * @param timestamp - Unix timestamp in milliseconds (use Date.now())
40
- * @returns Hex-encoded Falcon signature
41
- */
42
20
  signAccountIdWithTimestamp(accountId: string, timestamp: number): string {
43
- // Parse the account ID from hex
44
21
  const paddedHex = accountId.startsWith('0x') ? accountId : `0x${accountId}`;
45
22
  const parsedAccountId = AccountId.fromHex(paddedHex);
46
23
  const prefix = parsedAccountId.prefix();
@@ -60,9 +37,6 @@ export class FalconSigner implements Signer {
60
37
  return bytesToHex(falconSignature);
61
38
  }
62
39
 
63
- /**
64
- * Signs a commitment/word for proposal signing.
65
- */
66
40
  signCommitment(commitmentHex: string): string {
67
41
  const paddedHex = commitmentHex.startsWith('0x') ? commitmentHex : `0x${commitmentHex}`;
68
42
  const cleanHex = paddedHex.slice(2).padStart(64, '0');
@@ -73,3 +47,49 @@ export class FalconSigner implements Signer {
73
47
  return bytesToHex(falconSignature);
74
48
  }
75
49
  }
50
+
51
+ export class EcdsaSigner implements Signer {
52
+ readonly commitment: string;
53
+ readonly publicKey: string;
54
+ readonly scheme: SignatureScheme = 'ecdsa';
55
+ private readonly secretKey: SecretKey;
56
+
57
+ constructor(secretKey: SecretKey) {
58
+ this.secretKey = secretKey;
59
+ const pubKey = secretKey.publicKey();
60
+ this.commitment = pubKey.toCommitment().toHex();
61
+ const serialized = pubKey.serialize();
62
+ const ecdsaPubKey = serialized.slice(1);
63
+ this.publicKey = bytesToHex(ecdsaPubKey);
64
+ }
65
+
66
+ signAccountIdWithTimestamp(accountId: string, timestamp: number): string {
67
+ const paddedHex = accountId.startsWith('0x') ? accountId : `0x${accountId}`;
68
+ const parsedAccountId = AccountId.fromHex(paddedHex);
69
+ const prefix = parsedAccountId.prefix();
70
+ const suffix = parsedAccountId.suffix();
71
+
72
+ const feltArray = new FeltArray([
73
+ prefix,
74
+ suffix,
75
+ new Felt(BigInt(timestamp)),
76
+ new Felt(BigInt(0)),
77
+ ]);
78
+
79
+ const digest = Rpo256.hashElements(feltArray);
80
+ const signature = this.secretKey.sign(digest);
81
+ const signatureBytes = signature.serialize();
82
+ const ecdsaSignature = signatureBytes.slice(1);
83
+ return bytesToHex(ecdsaSignature);
84
+ }
85
+
86
+ signCommitment(commitmentHex: string): string {
87
+ const paddedHex = commitmentHex.startsWith('0x') ? commitmentHex : `0x${commitmentHex}`;
88
+ const cleanHex = paddedHex.slice(2).padStart(64, '0');
89
+ const word = Word.fromHex(`0x${cleanHex}`);
90
+ const signature = this.secretKey.sign(word);
91
+ const signatureBytes = signature.serialize();
92
+ const ecdsaSignature = signatureBytes.slice(1);
93
+ return bytesToHex(ecdsaSignature);
94
+ }
95
+ }
@@ -1,7 +1,9 @@
1
1
  import type { AdviceMap, Word } from '@demox-labs/miden-sdk';
2
+ import type { SignatureScheme } from '../types.js';
2
3
 
3
4
  export interface SignatureOptions {
4
5
  salt?: Word;
5
6
  signatureAdviceMap?: AdviceMap;
7
+ signatureScheme?: SignatureScheme;
6
8
  }
7
9
 
@@ -8,12 +8,12 @@ import {
8
8
  Word,
9
9
  Word as WordType,
10
10
  } from '@demox-labs/miden-sdk';
11
- import { PSM_MASM } from '../account/masm.js';
11
+ import { PSM_ECDSA_MASM, PSM_MASM } from '../account/masm.js';
12
12
  import { normalizeHexWord } from '../utils/encoding.js';
13
13
  import { randomWord } from '../utils/random.js';
14
14
  import type { SignatureOptions } from './options.js';
15
15
 
16
- function buildUpdatePsmScript(webClient: WebClient): TransactionScript {
16
+ function buildUpdatePsmFalconScript(webClient: WebClient): TransactionScript {
17
17
  const libBuilder = webClient.createScriptBuilder();
18
18
  const psmLib = libBuilder.buildLibrary('openzeppelin::psm', PSM_MASM);
19
19
  libBuilder.linkDynamicLibrary(psmLib);
@@ -31,12 +31,33 @@ end
31
31
  return libBuilder.compileTxScript(scriptSource);
32
32
  }
33
33
 
34
+ function buildUpdatePsmEcdsaScript(webClient: WebClient): TransactionScript {
35
+ const libBuilder = webClient.createScriptBuilder();
36
+ const psmLib = libBuilder.buildLibrary('openzeppelin::psm_ecdsa', PSM_ECDSA_MASM);
37
+ libBuilder.linkDynamicLibrary(psmLib);
38
+
39
+ const scriptSource = `
40
+ use.openzeppelin::psm_ecdsa
41
+
42
+ begin
43
+ adv.push_mapval
44
+ dropw
45
+ call.psm_ecdsa::update_psm_public_key
46
+ end
47
+ `;
48
+
49
+ return libBuilder.compileTxScript(scriptSource);
50
+ }
51
+
34
52
  export async function buildUpdatePsmTransactionRequest(
35
53
  webClient: WebClient,
36
54
  newPsmPubkey: string,
37
55
  options: SignatureOptions = {},
38
56
  ): Promise<{ request: TransactionRequest; salt: Word }> {
39
- const script = buildUpdatePsmScript(webClient);
57
+ const signatureScheme = options.signatureScheme ?? 'falcon';
58
+ const script = signatureScheme === 'ecdsa'
59
+ ? buildUpdatePsmEcdsaScript(webClient)
60
+ : buildUpdatePsmFalconScript(webClient);
40
61
 
41
62
  const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
42
63
 
@@ -10,7 +10,7 @@ import {
10
10
  Word,
11
11
  Word as WordType,
12
12
  } from '@demox-labs/miden-sdk';
13
- import { MULTISIG_MASM, PSM_MASM } from '../account/masm.js';
13
+ import { MULTISIG_ECDSA_MASM, MULTISIG_MASM, PSM_ECDSA_MASM, PSM_MASM } from '../account/masm.js';
14
14
  import { normalizeHexWord } from '../utils/encoding.js';
15
15
  import { randomWord } from '../utils/random.js';
16
16
  import type { SignatureOptions } from './options.js';
@@ -35,7 +35,7 @@ function buildMultisigConfigAdvice(
35
35
  return { configHash, payload };
36
36
  }
37
37
 
38
- function buildUpdateSignersScript(webClient: WebClient): TransactionScript {
38
+ function buildUpdateSignersFalconScript(webClient: WebClient): TransactionScript {
39
39
  const libBuilder = webClient.createScriptBuilder();
40
40
  const psmLib = libBuilder.buildLibrary('openzeppelin::psm', PSM_MASM);
41
41
  libBuilder.linkStaticLibrary(psmLib);
@@ -54,12 +54,32 @@ end
54
54
  return libBuilder.compileTxScript(scriptSource);
55
55
  }
56
56
 
57
+ function buildUpdateSignersEcdsaScript(webClient: WebClient): TransactionScript {
58
+ const libBuilder = webClient.createScriptBuilder();
59
+ const psmLib = libBuilder.buildLibrary('openzeppelin::psm_ecdsa', PSM_ECDSA_MASM);
60
+ libBuilder.linkStaticLibrary(psmLib);
61
+
62
+ const multisigLib = libBuilder.buildLibrary('auth::multisig', MULTISIG_ECDSA_MASM);
63
+ libBuilder.linkDynamicLibrary(multisigLib);
64
+
65
+ const scriptSource = `
66
+ use.auth::multisig
67
+
68
+ begin
69
+ call.multisig::update_signers_and_threshold
70
+ end
71
+ `;
72
+
73
+ return libBuilder.compileTxScript(scriptSource);
74
+ }
75
+
57
76
  export async function buildUpdateSignersTransactionRequest(
58
77
  webClient: WebClient,
59
78
  threshold: number,
60
79
  signerCommitments: string[],
61
80
  options: SignatureOptions = {},
62
81
  ): Promise<{ request: TransactionRequest; salt: Word; configHash: Word }> {
82
+ const signatureScheme = options.signatureScheme ?? 'falcon';
63
83
  const { configHash: configHashForAdvice, payload } = buildMultisigConfigAdvice(threshold, signerCommitments);
64
84
 
65
85
  const { configHash: configHashForScript } = buildMultisigConfigAdvice(threshold, signerCommitments);
@@ -69,7 +89,9 @@ export async function buildUpdateSignersTransactionRequest(
69
89
  const advice = new AdviceMap();
70
90
  advice.insert(configHashForAdvice, payload);
71
91
 
72
- const script = buildUpdateSignersScript(webClient);
92
+ const script = signatureScheme === 'ecdsa'
93
+ ? buildUpdateSignersEcdsaScript(webClient)
94
+ : buildUpdateSignersFalconScript(webClient);
73
95
 
74
96
  const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
75
97
 
@@ -116,6 +116,12 @@ describe('transaction utilities', () => {
116
116
  expect(result.slice(1)).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
117
117
  });
118
118
 
119
+ it('should prepend auth scheme byte (1 = ECDSA)', () => {
120
+ const result = signatureHexToBytes('deadbeef', 'ecdsa');
121
+ expect(result[0]).toBe(1); // ECDSA scheme byte
122
+ expect(result.slice(1)).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
123
+ });
124
+
119
125
  it('should handle 0x prefix', () => {
120
126
  const result = signatureHexToBytes('0xaabbccdd');
121
127
  expect(result[0]).toBe(0);
@@ -1,5 +1,3 @@
1
- // Backward-compatible entry point for transaction helpers.
2
- // The implementations now live in ./transaction/.
3
1
  export {
4
2
  buildConsumeNotesTransactionRequest,
5
3
  } from './transaction/consumeNotes.js';
@@ -1,14 +1,14 @@
1
- import type { ProposalType as PsmProposalType } from '@openzeppelin/psm-client';
1
+ import type { ProposalType as PsmProposalType, SignatureScheme } from '@openzeppelin/psm-client';
2
2
  export type ProposalType = Exclude<PsmProposalType, 'custom'>;
3
3
 
4
- export type ProposalStatus =
4
+ export type TransactionProposalStatus =
5
5
  | { type: 'pending'; signaturesCollected: number; signaturesRequired: number; signers: string[] }
6
6
  | { type: 'ready' }
7
7
  | { type: 'finalized' };
8
8
 
9
- export interface ProposalSignatureEntry {
9
+ export interface TransactionProposalSignature {
10
10
  signerId: string;
11
- signature: { scheme: 'falcon'; signature: string };
11
+ signature: { scheme: 'falcon' | 'ecdsa'; signature: string; publicKey?: string };
12
12
  timestamp: string;
13
13
  }
14
14
 
@@ -55,17 +55,18 @@ export type ProposalMetadata =
55
55
  | P2IdProposalMetadata
56
56
  | UnknownProposalMetadata;
57
57
 
58
- export interface Proposal {
58
+ export interface TransactionProposal {
59
59
  id: string;
60
+ commitment: string;
60
61
  accountId: string;
61
62
  nonce: number;
62
- status: ProposalStatus;
63
+ status: TransactionProposalStatus;
63
64
  txSummary: string;
64
- signatures: ProposalSignatureEntry[];
65
+ signatures: TransactionProposalSignature[];
65
66
  metadata: ProposalMetadata;
66
67
  }
67
68
 
68
- export interface ExportedProposal {
69
+ export interface ExportedTransactionProposal {
69
70
  accountId: string;
70
71
  nonce: number;
71
72
  commitment: string;
@@ -78,3 +79,9 @@ export interface ExportedProposal {
78
79
  metadata?: ProposalMetadata;
79
80
  }
80
81
 
82
+ export interface SignTransactionProposalParams {
83
+ commitment: string;
84
+ signature: string;
85
+ publicKey?: string;
86
+ scheme?: SignatureScheme;
87
+ }
package/src/types.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  import type { Account } from '@demox-labs/miden-sdk';
2
+ import type { SignatureScheme } from '@openzeppelin/psm-client';
2
3
  import type { ProcedureName } from './procedures.js';
4
+ import type { TransactionProposal } from './types/proposal.js';
5
+ import type { AccountState } from './multisig.js';
6
+ import type { DetectedMultisigConfig } from './inspector.js';
3
7
 
4
8
  export type {
5
9
  Signer,
6
10
  FalconSignature,
11
+ EcdsaSignature,
12
+ ProposalSignature,
13
+ SignatureScheme,
7
14
  CosignerSignature,
8
15
  AuthConfig,
9
16
  DeltaStatus,
@@ -19,14 +26,27 @@ export type {
19
26
  } from '@openzeppelin/psm-client';
20
27
 
21
28
  export type {
22
- ExportedProposal,
23
- Proposal,
29
+ TransactionProposal,
30
+ TransactionProposalStatus,
31
+ TransactionProposalSignature,
32
+ ExportedTransactionProposal,
33
+ SignTransactionProposalParams,
24
34
  ProposalMetadata,
25
- ProposalSignatureEntry,
26
- ProposalStatus,
27
35
  ProposalType,
28
36
  } from './types/proposal.js';
29
37
 
38
+ export interface SyncResult {
39
+ proposals: TransactionProposal[];
40
+ state: AccountState;
41
+ notes: ConsumableNote[];
42
+ config: DetectedMultisigConfig;
43
+ }
44
+
45
+ export interface TransactionProposalResult {
46
+ proposal: TransactionProposal;
47
+ proposals: TransactionProposal[];
48
+ }
49
+
30
50
  export interface MultisigAccountState {
31
51
  id: string;
32
52
  nonce: number;
@@ -34,20 +54,8 @@ export interface MultisigAccountState {
34
54
  cosignerCommitments: string[];
35
55
  }
36
56
 
37
- /**
38
- * Per-procedure threshold override.
39
- *
40
- * @example
41
- * ```typescript
42
- * const thresholds: ProcedureThreshold[] = [
43
- * { procedure: 'receive_asset', threshold: 1 },
44
- * { procedure: 'update_signers', threshold: 3 },
45
- * ];
46
- * ```
47
- */
48
57
  export interface ProcedureThreshold {
49
58
  procedure: ProcedureName;
50
- /** Threshold for this procedure (1 to numSigners) */
51
59
  threshold: number;
52
60
  }
53
61
 
@@ -55,9 +63,11 @@ export interface MultisigConfig {
55
63
  threshold: number;
56
64
  signerCommitments: string[];
57
65
  psmCommitment: string;
66
+ psmPublicKey?: string;
58
67
  psmEnabled?: boolean;
59
68
  storageMode?: 'private' | 'public';
60
69
  procedureThresholds?: ProcedureThreshold[];
70
+ signatureScheme?: SignatureScheme;
61
71
  }
62
72
 
63
73
  export interface CreateAccountResult {
@@ -1,5 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { signatureHexToBytes, buildSignatureAdviceEntry, mergeSignatureAdviceMaps, toWord } from './signature.js';
2
+ import {
3
+ signatureHexToBytes,
4
+ buildSignatureAdviceEntry,
5
+ mergeSignatureAdviceMaps,
6
+ toWord,
7
+ tryComputeEcdsaCommitmentHex,
8
+ } from './signature.js';
3
9
 
4
10
  // Mock the Miden SDK
5
11
  vi.mock('@demox-labs/miden-sdk', () => ({
@@ -29,6 +35,11 @@ vi.mock('@demox-labs/miden-sdk', () => ({
29
35
  ],
30
36
  })),
31
37
  },
38
+ PublicKey: {
39
+ deserialize: vi.fn().mockReturnValue({
40
+ toCommitment: () => ({ toHex: () => '0x' + 'c'.repeat(64) }),
41
+ }),
42
+ },
32
43
  }));
33
44
 
34
45
  describe('signature utilities', () => {
@@ -42,6 +53,11 @@ describe('signature utilities', () => {
42
53
  expect(result[0]).toBe(0); // RpoFalcon512 scheme byte
43
54
  });
44
55
 
56
+ it('prepends auth scheme byte (1 = ECDSA)', () => {
57
+ const result = signatureHexToBytes('deadbeef', 'ecdsa');
58
+ expect(result[0]).toBe(1); // ECDSA scheme byte
59
+ });
60
+
45
61
  it('converts hex to bytes after prefix', () => {
46
62
  const result = signatureHexToBytes('deadbeef');
47
63
  expect(result.slice(1)).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
@@ -184,4 +200,11 @@ describe('signature utilities', () => {
184
200
  expect(Word.fromHex).toHaveBeenCalledWith('0x' + fullHex);
185
201
  });
186
202
  });
203
+
204
+ describe('tryComputeEcdsaCommitmentHex', () => {
205
+ it('returns normalized commitment when SDK supports ECDSA public keys', () => {
206
+ const commitment = tryComputeEcdsaCommitmentHex('0x' + 'aa'.repeat(33));
207
+ expect(commitment).toBe('0x' + 'c'.repeat(64));
208
+ });
209
+ });
187
210
  });
@@ -1,28 +1,111 @@
1
1
  import { AdviceMap, Felt, FeltArray, Rpo256, Signature, Word } from '@demox-labs/miden-sdk';
2
+ import * as midenSdk from '@demox-labs/miden-sdk';
2
3
  import { hexToBytes, normalizeHexWord } from './encoding.js';
4
+ import type { SignatureScheme } from '../types.js';
3
5
 
4
- export function signatureHexToBytes(hex: string): Uint8Array {
6
+ export function signatureHexToBytes(hex: string, scheme: SignatureScheme = 'falcon'): Uint8Array {
5
7
  const sigBytes = hexToBytes(hex);
6
8
  const withPrefix = new Uint8Array(sigBytes.length + 1);
7
- withPrefix[0] = 0;
9
+ withPrefix[0] = scheme === 'ecdsa' ? 1 : 0;
8
10
  withPrefix.set(sigBytes, 1);
9
11
  return withPrefix;
10
12
  }
11
13
 
14
+ function bytesToPackedU32Felts(bytes: Uint8Array): Felt[] {
15
+ const felts: Felt[] = [];
16
+ for (let i = 0; i < bytes.length; i += 4) {
17
+ let packed = 0;
18
+ for (let j = 0; j < 4 && i + j < bytes.length; j++) {
19
+ packed |= bytes[i + j] << (j * 8);
20
+ }
21
+ felts.push(new Felt(BigInt(packed >>> 0)));
22
+ }
23
+ return felts;
24
+ }
25
+
26
+ function encodeEcdsaSignatureFelts(pubkeyBytes: Uint8Array, sigBytes: Uint8Array): Felt[] {
27
+ const pkFelts = bytesToPackedU32Felts(pubkeyBytes);
28
+ const sigFelts = bytesToPackedU32Felts(sigBytes);
29
+ const encoded = [...pkFelts, ...sigFelts];
30
+ encoded.reverse();
31
+ return encoded;
32
+ }
33
+
12
34
  export function buildSignatureAdviceEntry(
13
35
  pubkeyCommitment: Word,
14
36
  message: Word,
15
37
  signature: Signature,
38
+ ecdsaPubkeyHex?: string,
39
+ ecdsaSigHex?: string,
16
40
  ): { key: Word; values: Felt[] } {
17
41
  const elements = new FeltArray([
18
42
  ...pubkeyCommitment.toFelts(),
19
43
  ...message.toFelts(),
20
44
  ]);
21
45
  const key = Rpo256.hashElements(elements);
22
- const values = signature.toPreparedSignature(message);
46
+
47
+ let values: Felt[];
48
+ if (ecdsaPubkeyHex && ecdsaSigHex) {
49
+ const pkBytes = hexToBytes(ecdsaPubkeyHex);
50
+ const sigBytes = hexToBytes(ecdsaSigHex);
51
+ values = encodeEcdsaSignatureFelts(pkBytes, sigBytes);
52
+ } else {
53
+ values = signature.toPreparedSignature(message);
54
+ }
55
+
23
56
  return { key, values };
24
57
  }
25
58
 
59
+ export function tryComputeEcdsaCommitmentHex(pubkeyHex: string): string | null {
60
+ const bytes = hexToBytes(pubkeyHex);
61
+ const withPrefix = new Uint8Array(bytes.length + 1);
62
+ withPrefix[0] = 1;
63
+ withPrefix.set(bytes, 1);
64
+
65
+ try {
66
+ const { PublicKey } = midenSdk as any;
67
+ const instance = PublicKey.deserialize(withPrefix);
68
+ return normalizeHexWord(instance.toCommitment().toHex());
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ export function verifyEcdsaCommitment(
75
+ pubkeyHex: string,
76
+ expectedCommitmentHex: string,
77
+ ): { match: boolean; computedHex: string; packedFelts: string[]; error?: string } {
78
+ try {
79
+ const bytes = hexToBytes(pubkeyHex);
80
+ const packedU32Values: number[] = [];
81
+ for (let i = 0; i < bytes.length; i += 4) {
82
+ let packed = 0;
83
+ for (let j = 0; j < 4 && i + j < bytes.length; j++) {
84
+ packed |= bytes[i + j] << (j * 8);
85
+ }
86
+ packedU32Values.push(packed >>> 0);
87
+ }
88
+
89
+ const packedFelts = bytesToPackedU32Felts(bytes);
90
+ const feltArray = new FeltArray(packedFelts);
91
+ const computed = Rpo256.hashElements(feltArray);
92
+ const computedHex = normalizeHexWord(computed.toHex());
93
+ const expectedNorm = normalizeHexWord(expectedCommitmentHex);
94
+ return {
95
+ match: computedHex === expectedNorm,
96
+ computedHex,
97
+ packedFelts: packedU32Values.map(v => v.toString()),
98
+ };
99
+ } catch (e) {
100
+ return {
101
+ match: false,
102
+ computedHex: `ERROR: ${e}`,
103
+ packedFelts: [],
104
+ error: String(e),
105
+ };
106
+ }
107
+ }
108
+
26
109
  export function mergeSignatureAdviceMaps(advice: AdviceMap, entries: Array<{ key: Word; values: Felt[] }>): AdviceMap {
27
110
  for (const entry of entries) {
28
111
  advice.insert(entry.key, new FeltArray(entry.values));
@@ -33,4 +116,3 @@ export function mergeSignatureAdviceMaps(advice: AdviceMap, entries: Array<{ key
33
116
  export function toWord(hex: string): Word {
34
117
  return Word.fromHex(normalizeHexWord(hex));
35
118
  }
36
-