@openzeppelin/miden-multisig-client 0.12.7 → 0.13.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.
Files changed (201) hide show
  1. package/README.md +42 -4
  2. package/dist/account/builder.d.ts +1 -1
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +7 -5
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/account/masm.d.ts +3 -3
  7. package/dist/account/masm.d.ts.map +1 -1
  8. package/dist/account/masm.js +101 -98
  9. package/dist/account/masm.js.map +1 -1
  10. package/dist/account/storage.d.ts +1 -1
  11. package/dist/account/storage.d.ts.map +1 -1
  12. package/dist/account/storage.js +17 -7
  13. package/dist/account/storage.js.map +1 -1
  14. package/dist/client.d.ts +1 -1
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +2 -2
  17. package/dist/client.js.map +1 -1
  18. package/dist/client.test.js +4 -4
  19. package/dist/client.test.js.map +1 -1
  20. package/dist/index.d.ts +8 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +6 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/inspector.d.ts +1 -1
  25. package/dist/inspector.d.ts.map +1 -1
  26. package/dist/inspector.js +16 -10
  27. package/dist/inspector.js.map +1 -1
  28. package/dist/inspector.test.js +33 -22
  29. package/dist/inspector.test.js.map +1 -1
  30. package/dist/multisig/helpers.d.ts +1 -1
  31. package/dist/multisig/helpers.d.ts.map +1 -1
  32. package/dist/multisig/helpers.js +1 -1
  33. package/dist/multisig/helpers.js.map +1 -1
  34. package/dist/multisig/helpers.test.js +2 -2
  35. package/dist/multisig/helpers.test.js.map +1 -1
  36. package/dist/multisig/proposal/execution.d.ts +18 -0
  37. package/dist/multisig/proposal/execution.d.ts.map +1 -0
  38. package/dist/multisig/proposal/execution.js +147 -0
  39. package/dist/multisig/proposal/execution.js.map +1 -0
  40. package/dist/multisig/proposal/parser.d.ts +17 -0
  41. package/dist/multisig/proposal/parser.d.ts.map +1 -0
  42. package/dist/multisig/proposal/parser.js +147 -0
  43. package/dist/multisig/proposal/parser.js.map +1 -0
  44. package/dist/multisig/signing.d.ts +5 -0
  45. package/dist/multisig/signing.d.ts.map +1 -0
  46. package/dist/multisig/signing.js +14 -0
  47. package/dist/multisig/signing.js.map +1 -0
  48. package/dist/multisig.d.ts +9 -13
  49. package/dist/multisig.d.ts.map +1 -1
  50. package/dist/multisig.js +109 -337
  51. package/dist/multisig.js.map +1 -1
  52. package/dist/multisig.test.js +8 -4
  53. package/dist/multisig.test.js.map +1 -1
  54. package/dist/signer.d.ts +2 -20
  55. package/dist/signer.d.ts.map +1 -1
  56. package/dist/signer.js +2 -82
  57. package/dist/signer.js.map +1 -1
  58. package/dist/signer.test.js +24 -24
  59. package/dist/signer.test.js.map +1 -1
  60. package/dist/signers/ecdsa.d.ts +12 -0
  61. package/dist/signers/ecdsa.d.ts.map +1 -0
  62. package/dist/signers/ecdsa.js +30 -0
  63. package/dist/signers/ecdsa.js.map +1 -0
  64. package/dist/signers/falcon.d.ts +12 -0
  65. package/dist/signers/falcon.d.ts.map +1 -0
  66. package/dist/signers/falcon.js +31 -0
  67. package/dist/signers/falcon.js.map +1 -0
  68. package/dist/signers/falcon.test.d.ts +2 -0
  69. package/dist/signers/falcon.test.d.ts.map +1 -0
  70. package/dist/signers/falcon.test.js +113 -0
  71. package/dist/signers/falcon.test.js.map +1 -0
  72. package/dist/signers/index.d.ts +5 -0
  73. package/dist/signers/index.d.ts.map +1 -0
  74. package/dist/signers/index.js +5 -0
  75. package/dist/signers/index.js.map +1 -0
  76. package/dist/signers/miden-wallet.d.ts +16 -0
  77. package/dist/signers/miden-wallet.d.ts.map +1 -0
  78. package/dist/signers/miden-wallet.js +35 -0
  79. package/dist/signers/miden-wallet.js.map +1 -0
  80. package/dist/signers/miden-wallet.test.d.ts +2 -0
  81. package/dist/signers/miden-wallet.test.d.ts.map +1 -0
  82. package/dist/signers/miden-wallet.test.js +107 -0
  83. package/dist/signers/miden-wallet.test.js.map +1 -0
  84. package/dist/signers/para.d.ts +21 -0
  85. package/dist/signers/para.d.ts.map +1 -0
  86. package/dist/signers/para.js +43 -0
  87. package/dist/signers/para.js.map +1 -0
  88. package/dist/signers/para.test.d.ts +2 -0
  89. package/dist/signers/para.test.d.ts.map +1 -0
  90. package/dist/signers/para.test.js +88 -0
  91. package/dist/signers/para.test.js.map +1 -0
  92. package/dist/transaction/consumeNotes.d.ts +3 -3
  93. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  94. package/dist/transaction/consumeNotes.js +11 -7
  95. package/dist/transaction/consumeNotes.js.map +1 -1
  96. package/dist/transaction/options.d.ts +2 -2
  97. package/dist/transaction/options.d.ts.map +1 -1
  98. package/dist/transaction/p2id.d.ts +1 -1
  99. package/dist/transaction/p2id.d.ts.map +1 -1
  100. package/dist/transaction/p2id.js +5 -5
  101. package/dist/transaction/p2id.js.map +1 -1
  102. package/dist/transaction/summary.d.ts +1 -1
  103. package/dist/transaction/summary.d.ts.map +1 -1
  104. package/dist/transaction/summary.js +1 -1
  105. package/dist/transaction/summary.js.map +1 -1
  106. package/dist/transaction/updatePsm.d.ts +1 -1
  107. package/dist/transaction/updatePsm.d.ts.map +1 -1
  108. package/dist/transaction/updatePsm.js +5 -5
  109. package/dist/transaction/updatePsm.js.map +1 -1
  110. package/dist/transaction/updateSigners.d.ts +1 -1
  111. package/dist/transaction/updateSigners.d.ts.map +1 -1
  112. package/dist/transaction/updateSigners.js +5 -5
  113. package/dist/transaction/updateSigners.js.map +1 -1
  114. package/dist/transaction.test.js +1 -1
  115. package/dist/transaction.test.js.map +1 -1
  116. package/dist/types.d.ts +1 -1
  117. package/dist/types.d.ts.map +1 -1
  118. package/dist/utils/digest.d.ts +6 -0
  119. package/dist/utils/digest.d.ts.map +1 -0
  120. package/dist/utils/digest.js +22 -0
  121. package/dist/utils/digest.js.map +1 -0
  122. package/dist/utils/ecdsa.d.ts +8 -0
  123. package/dist/utils/ecdsa.d.ts.map +1 -0
  124. package/dist/utils/ecdsa.js +60 -0
  125. package/dist/utils/ecdsa.js.map +1 -0
  126. package/dist/utils/ecdsa.test.d.ts +2 -0
  127. package/dist/utils/ecdsa.test.d.ts.map +1 -0
  128. package/dist/utils/ecdsa.test.js +85 -0
  129. package/dist/utils/ecdsa.test.js.map +1 -0
  130. package/dist/utils/index.d.ts +1 -0
  131. package/dist/utils/index.d.ts.map +1 -1
  132. package/dist/utils/index.js +1 -0
  133. package/dist/utils/index.js.map +1 -1
  134. package/dist/utils/key.d.ts +10 -0
  135. package/dist/utils/key.d.ts.map +1 -0
  136. package/dist/utils/key.js +38 -0
  137. package/dist/utils/key.js.map +1 -0
  138. package/dist/utils/key.test.d.ts +2 -0
  139. package/dist/utils/key.test.d.ts.map +1 -0
  140. package/dist/utils/key.test.js +80 -0
  141. package/dist/utils/key.test.js.map +1 -0
  142. package/dist/utils/random.d.ts +1 -1
  143. package/dist/utils/random.d.ts.map +1 -1
  144. package/dist/utils/random.js +1 -1
  145. package/dist/utils/random.js.map +1 -1
  146. package/dist/utils/signature.d.ts +2 -1
  147. package/dist/utils/signature.d.ts.map +1 -1
  148. package/dist/utils/signature.js +6 -3
  149. package/dist/utils/signature.js.map +1 -1
  150. package/dist/utils/signature.test.js +11 -11
  151. package/dist/utils/signature.test.js.map +1 -1
  152. package/dist/utils/word.d.ts +6 -1
  153. package/dist/utils/word.d.ts.map +1 -1
  154. package/dist/utils/word.js +11 -0
  155. package/dist/utils/word.js.map +1 -1
  156. package/dist/utils/word.test.js +54 -3
  157. package/dist/utils/word.test.js.map +1 -1
  158. package/package.json +4 -5
  159. package/src/account/builder.ts +7 -5
  160. package/src/account/masm.ts +103 -99
  161. package/src/account/storage.ts +19 -7
  162. package/src/client.test.ts +4 -4
  163. package/src/client.ts +3 -3
  164. package/src/index.ts +8 -1
  165. package/src/inspector.test.ts +38 -25
  166. package/src/inspector.ts +18 -10
  167. package/src/multisig/helpers.test.ts +2 -2
  168. package/src/multisig/helpers.ts +2 -1
  169. package/src/multisig/proposal/execution.ts +320 -0
  170. package/src/multisig/proposal/parser.ts +191 -0
  171. package/src/multisig/signing.ts +24 -0
  172. package/src/multisig.test.ts +8 -4
  173. package/src/multisig.ts +153 -434
  174. package/src/signer.ts +2 -95
  175. package/src/signers/ecdsa.ts +35 -0
  176. package/src/{signer.test.ts → signers/falcon.test.ts} +29 -33
  177. package/src/signers/falcon.ts +36 -0
  178. package/src/signers/index.ts +4 -0
  179. package/src/signers/miden-wallet.test.ts +119 -0
  180. package/src/signers/miden-wallet.ts +49 -0
  181. package/src/signers/para.test.ts +103 -0
  182. package/src/signers/para.ts +60 -0
  183. package/src/transaction/consumeNotes.ts +14 -10
  184. package/src/transaction/options.ts +2 -2
  185. package/src/transaction/p2id.ts +3 -8
  186. package/src/transaction/summary.ts +2 -2
  187. package/src/transaction/updatePsm.ts +5 -6
  188. package/src/transaction/updateSigners.ts +5 -6
  189. package/src/transaction.test.ts +1 -1
  190. package/src/types.ts +1 -1
  191. package/src/utils/digest.ts +25 -0
  192. package/src/utils/ecdsa.test.ts +104 -0
  193. package/src/utils/ecdsa.ts +77 -0
  194. package/src/utils/index.ts +1 -0
  195. package/src/utils/key.test.ts +89 -0
  196. package/src/utils/key.ts +49 -0
  197. package/src/utils/random.ts +1 -1
  198. package/src/utils/signature.test.ts +11 -11
  199. package/src/utils/signature.ts +10 -3
  200. package/src/utils/word.test.ts +57 -3
  201. package/src/utils/word.ts +13 -1
@@ -1,22 +1,27 @@
1
- import type { TransactionRequest, Word } from '@demox-labs/miden-sdk';
2
- import { NoteId, NoteIdAndArgs, NoteIdAndArgsArray, TransactionRequestBuilder, Word as WordType } from '@demox-labs/miden-sdk';
1
+ import type { TransactionRequest, Word, WebClient } from '@miden-sdk/miden-sdk';
2
+ import { NoteAndArgs, NoteAndArgsArray, TransactionRequestBuilder, Word as WordType } from '@miden-sdk/miden-sdk';
3
3
  import { randomWord } from '../utils/random.js';
4
4
  import { normalizeHexWord } from '../utils/encoding.js';
5
5
  import type { SignatureOptions } from './options.js';
6
6
 
7
- export function buildConsumeNotesTransactionRequest(
7
+ export async function buildConsumeNotesTransactionRequest(
8
+ webClient: WebClient,
8
9
  noteIds: string[],
9
10
  options: SignatureOptions = {},
10
- ): { request: TransactionRequest; salt: Word } {
11
+ ): Promise<{ request: TransactionRequest; salt: Word }> {
11
12
  if (noteIds.length === 0) {
12
13
  throw new Error('At least one note ID is required');
13
14
  }
14
15
 
15
- const noteIdAndArgsArray = new NoteIdAndArgsArray();
16
+ const noteAndArgsArray = new NoteAndArgsArray();
16
17
  for (const noteIdHex of noteIds) {
17
- const noteId = NoteId.fromHex(noteIdHex);
18
- const noteIdAndArgs = new NoteIdAndArgs(noteId, null);
19
- noteIdAndArgsArray.push(noteIdAndArgs);
18
+ const inputNoteRecord = await webClient.getInputNote(noteIdHex);
19
+ if (!inputNoteRecord) {
20
+ throw new Error(`Note not found in local store: ${noteIdHex}`);
21
+ }
22
+ const note = inputNoteRecord.toNote();
23
+ const noteAndArgs = new NoteAndArgs(note, null);
24
+ noteAndArgsArray.push(noteAndArgs);
20
25
  }
21
26
 
22
27
  const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
@@ -24,7 +29,7 @@ export function buildConsumeNotesTransactionRequest(
24
29
  const authSaltForBuilder = WordType.fromHex(normalizeHexWord(authSaltHex));
25
30
 
26
31
  let txBuilder = new TransactionRequestBuilder();
27
- txBuilder = txBuilder.withAuthenticatedInputNotes(noteIdAndArgsArray);
32
+ txBuilder = txBuilder.withInputNotes(noteAndArgsArray);
28
33
  txBuilder = txBuilder.withAuthArg(authSaltForBuilder);
29
34
 
30
35
  if (options.signatureAdviceMap) {
@@ -38,4 +43,3 @@ export function buildConsumeNotesTransactionRequest(
38
43
  salt: authSaltForReturn,
39
44
  };
40
45
  }
41
-
@@ -1,5 +1,5 @@
1
- import type { AdviceMap, Word } from '@demox-labs/miden-sdk';
2
- import type { SignatureScheme } from '../types.js';
1
+ import type { AdviceMap, Word } from '@miden-sdk/miden-sdk';
2
+ import type { SignatureScheme } from '@openzeppelin/psm-client';
3
3
 
4
4
  export interface SignatureOptions {
5
5
  salt?: Word;
@@ -1,4 +1,4 @@
1
- import type { TransactionRequest, Word } from '@demox-labs/miden-sdk';
1
+ import type { TransactionRequest, Word } from '@miden-sdk/miden-sdk';
2
2
  import {
3
3
  AccountId,
4
4
  Felt,
@@ -7,7 +7,6 @@ import {
7
7
  MidenArrays,
8
8
  Note,
9
9
  NoteAssets,
10
- NoteExecutionHint,
11
10
  NoteInputs,
12
11
  NoteMetadata,
13
12
  NoteRecipient,
@@ -18,7 +17,7 @@ import {
18
17
  Rpo256,
19
18
  TransactionRequestBuilder,
20
19
  Word as WordType,
21
- } from '@demox-labs/miden-sdk';
20
+ } from '@miden-sdk/miden-sdk';
22
21
  import { randomWord } from '../utils/random.js';
23
22
  import { normalizeHexWord } from '../utils/encoding.js';
24
23
  import type { SignatureOptions } from './options.js';
@@ -28,7 +27,6 @@ function buildP2idNote(
28
27
  recipient: AccountId,
29
28
  noteAssets: NoteAssets,
30
29
  noteType: NoteType,
31
- aux: Felt,
32
30
  saltHex: string,
33
31
  ): Note {
34
32
  const salt = WordType.fromHex(normalizeHexWord(saltHex));
@@ -44,14 +42,12 @@ function buildP2idNote(
44
42
  ]));
45
43
 
46
44
  const noteRecipient = new NoteRecipient(serialNum, noteScript, noteInputs);
47
- const noteTag = NoteTag.fromAccountId(recipient);
45
+ const noteTag = NoteTag.withAccountTarget(recipient);
48
46
 
49
47
  const noteMetadata = new NoteMetadata(
50
48
  sender,
51
49
  noteType,
52
50
  noteTag,
53
- NoteExecutionHint.always(),
54
- aux,
55
51
  );
56
52
 
57
53
  return new Note(noteAssets, noteMetadata, noteRecipient);
@@ -78,7 +74,6 @@ export function buildP2idTransactionRequest(
78
74
  recipient,
79
75
  noteAssets,
80
76
  NoteType.Public,
81
- new Felt(0n),
82
77
  authSaltHex,
83
78
  );
84
79
 
@@ -1,5 +1,5 @@
1
- import type { TransactionRequest, TransactionSummary, WebClient } from '@demox-labs/miden-sdk';
2
- import { AccountId } from '@demox-labs/miden-sdk';
1
+ import type { TransactionRequest, TransactionSummary, WebClient } from '@miden-sdk/miden-sdk';
2
+ import { AccountId } from '@miden-sdk/miden-sdk';
3
3
 
4
4
  export async function executeForSummary(
5
5
  client: WebClient,
@@ -7,19 +7,19 @@ import {
7
7
  WebClient,
8
8
  Word,
9
9
  Word as WordType,
10
- } from '@demox-labs/miden-sdk';
10
+ } from '@miden-sdk/miden-sdk';
11
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
16
  function buildUpdatePsmFalconScript(webClient: WebClient): TransactionScript {
17
- const libBuilder = webClient.createScriptBuilder();
17
+ const libBuilder = webClient.createCodeBuilder();
18
18
  const psmLib = libBuilder.buildLibrary('openzeppelin::psm', PSM_MASM);
19
19
  libBuilder.linkDynamicLibrary(psmLib);
20
20
 
21
21
  const scriptSource = `
22
- use.openzeppelin::psm
22
+ use openzeppelin::psm
23
23
 
24
24
  begin
25
25
  adv.push_mapval
@@ -32,12 +32,12 @@ end
32
32
  }
33
33
 
34
34
  function buildUpdatePsmEcdsaScript(webClient: WebClient): TransactionScript {
35
- const libBuilder = webClient.createScriptBuilder();
35
+ const libBuilder = webClient.createCodeBuilder();
36
36
  const psmLib = libBuilder.buildLibrary('openzeppelin::psm_ecdsa', PSM_ECDSA_MASM);
37
37
  libBuilder.linkDynamicLibrary(psmLib);
38
38
 
39
39
  const scriptSource = `
40
- use.openzeppelin::psm_ecdsa
40
+ use openzeppelin::psm_ecdsa
41
41
 
42
42
  begin
43
43
  adv.push_mapval
@@ -87,4 +87,3 @@ export async function buildUpdatePsmTransactionRequest(
87
87
  salt: authSaltForReturn,
88
88
  };
89
89
  }
90
-
@@ -9,7 +9,7 @@ import {
9
9
  WebClient,
10
10
  Word,
11
11
  Word as WordType,
12
- } from '@demox-labs/miden-sdk';
12
+ } from '@miden-sdk/miden-sdk';
13
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';
@@ -36,7 +36,7 @@ function buildMultisigConfigAdvice(
36
36
  }
37
37
 
38
38
  function buildUpdateSignersFalconScript(webClient: WebClient): TransactionScript {
39
- const libBuilder = webClient.createScriptBuilder();
39
+ const libBuilder = webClient.createCodeBuilder();
40
40
  const psmLib = libBuilder.buildLibrary('openzeppelin::psm', PSM_MASM);
41
41
  libBuilder.linkStaticLibrary(psmLib);
42
42
 
@@ -44,7 +44,7 @@ function buildUpdateSignersFalconScript(webClient: WebClient): TransactionScript
44
44
  libBuilder.linkDynamicLibrary(multisigLib);
45
45
 
46
46
  const scriptSource = `
47
- use.auth::multisig
47
+ use auth::multisig
48
48
 
49
49
  begin
50
50
  call.multisig::update_signers_and_threshold
@@ -55,7 +55,7 @@ end
55
55
  }
56
56
 
57
57
  function buildUpdateSignersEcdsaScript(webClient: WebClient): TransactionScript {
58
- const libBuilder = webClient.createScriptBuilder();
58
+ const libBuilder = webClient.createCodeBuilder();
59
59
  const psmLib = libBuilder.buildLibrary('openzeppelin::psm_ecdsa', PSM_ECDSA_MASM);
60
60
  libBuilder.linkStaticLibrary(psmLib);
61
61
 
@@ -63,7 +63,7 @@ function buildUpdateSignersEcdsaScript(webClient: WebClient): TransactionScript
63
63
  libBuilder.linkDynamicLibrary(multisigLib);
64
64
 
65
65
  const scriptSource = `
66
- use.auth::multisig
66
+ use auth::multisig
67
67
 
68
68
  begin
69
69
  call.multisig::update_signers_and_threshold
@@ -115,4 +115,3 @@ export async function buildUpdateSignersTransactionRequest(
115
115
  configHash: configHashForReturn,
116
116
  };
117
117
  }
118
-
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeAll } from 'vitest';
2
2
 
3
3
  // Mock Miden SDK before importing transaction module
4
- vi.mock('@demox-labs/miden-sdk', () => ({
4
+ vi.mock('@miden-sdk/miden-sdk', () => ({
5
5
  AccountId: { fromHex: vi.fn() },
6
6
  AdviceMap: vi.fn(),
7
7
  Felt: vi.fn().mockImplementation((v) => ({ value: v })),
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Account } from '@demox-labs/miden-sdk';
1
+ import type { Account } from '@miden-sdk/miden-sdk';
2
2
  import type { SignatureScheme } from '@openzeppelin/psm-client';
3
3
  import type { ProcedureName } from './procedures.js';
4
4
  import type { TransactionProposal } from './types/proposal.js';
@@ -0,0 +1,25 @@
1
+ import { AccountId, Felt, FeltArray, Rpo256, Word } from '@miden-sdk/miden-sdk';
2
+
3
+ export class AuthDigest {
4
+ static fromAccountIdWithTimestamp(accountId: string, timestamp: number): Word {
5
+ const paddedHex = accountId.startsWith('0x') ? accountId : `0x${accountId}`;
6
+ const parsedAccountId = AccountId.fromHex(paddedHex);
7
+ const prefix = parsedAccountId.prefix();
8
+ const suffix = parsedAccountId.suffix();
9
+
10
+ const feltArray = new FeltArray([
11
+ prefix,
12
+ suffix,
13
+ new Felt(BigInt(timestamp)),
14
+ new Felt(BigInt(0)),
15
+ ]);
16
+
17
+ return Rpo256.hashElements(feltArray);
18
+ }
19
+
20
+ static fromCommitmentHex(commitmentHex: string): Word {
21
+ const paddedHex = commitmentHex.startsWith('0x') ? commitmentHex : `0x${commitmentHex}`;
22
+ const cleanHex = paddedHex.slice(2).padStart(64, '0');
23
+ return Word.fromHex(`0x${cleanHex}`);
24
+ }
25
+ }
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { EcdsaFormat } from './ecdsa.js';
3
+
4
+ describe('EcdsaFormat', () => {
5
+ describe('normalizeRecoveryByte', () => {
6
+ it('should normalize v=27 to recovery_id=0', () => {
7
+ const rHex = 'aa'.repeat(32);
8
+ const sHex = 'bb'.repeat(32);
9
+ const sig = `0x${rHex}${sHex}1b`;
10
+ const result = EcdsaFormat.normalizeRecoveryByte(sig);
11
+ expect(result).toBe(`0x${rHex}${sHex}00`);
12
+ });
13
+
14
+ it('should normalize v=28 to recovery_id=1', () => {
15
+ const rHex = 'aa'.repeat(32);
16
+ const sHex = 'bb'.repeat(32);
17
+ const sig = `0x${rHex}${sHex}1c`;
18
+ const result = EcdsaFormat.normalizeRecoveryByte(sig);
19
+ expect(result).toBe(`0x${rHex}${sHex}01`);
20
+ });
21
+
22
+ it('should pass through v=0 unchanged', () => {
23
+ const rHex = 'aa'.repeat(32);
24
+ const sHex = 'bb'.repeat(32);
25
+ const sig = `0x${rHex}${sHex}00`;
26
+ const result = EcdsaFormat.normalizeRecoveryByte(sig);
27
+ expect(result).toBe(sig);
28
+ });
29
+
30
+ it('should pass through 64-byte signature unchanged', () => {
31
+ const sig = `0x${'ab'.repeat(64)}`;
32
+ const result = EcdsaFormat.normalizeRecoveryByte(sig);
33
+ expect(result).toBe(sig);
34
+ });
35
+
36
+ it('should throw on invalid length', () => {
37
+ expect(() => EcdsaFormat.normalizeRecoveryByte('0x' + 'ab'.repeat(10))).toThrow(
38
+ 'Invalid ECDSA signature length',
39
+ );
40
+ });
41
+
42
+ it('should handle input without 0x prefix', () => {
43
+ const rHex = 'aa'.repeat(32);
44
+ const sHex = 'bb'.repeat(32);
45
+ const sig = `${rHex}${sHex}1b`;
46
+ const result = EcdsaFormat.normalizeRecoveryByte(sig);
47
+ expect(result).toBe(`0x${rHex}${sHex}00`);
48
+ });
49
+ });
50
+
51
+ describe('compressPublicKey', () => {
52
+ it('should compress a 65-byte uncompressed key with even y', () => {
53
+ const x = 'aa'.repeat(32);
54
+ const yEven = 'bb'.repeat(31) + 'b0';
55
+ const uncompressed = `0x04${x}${yEven}`;
56
+ const result = EcdsaFormat.compressPublicKey(uncompressed);
57
+ expect(result).toBe(`0x02${x}`);
58
+ });
59
+
60
+ it('should compress a 65-byte uncompressed key with odd y', () => {
61
+ const x = 'aa'.repeat(32);
62
+ const yOdd = 'bb'.repeat(31) + 'b1';
63
+ const uncompressed = `0x04${x}${yOdd}`;
64
+ const result = EcdsaFormat.compressPublicKey(uncompressed);
65
+ expect(result).toBe(`0x03${x}`);
66
+ });
67
+
68
+ it('should pass through a 33-byte compressed key unchanged', () => {
69
+ const compressed = `0x02${'aa'.repeat(32)}`;
70
+ const result = EcdsaFormat.compressPublicKey(compressed);
71
+ expect(result).toBe(compressed);
72
+ });
73
+
74
+ it('should throw on invalid length', () => {
75
+ expect(() => EcdsaFormat.compressPublicKey('0x' + 'ab'.repeat(10))).toThrow(
76
+ 'Expected 65-byte uncompressed public key',
77
+ );
78
+ });
79
+
80
+ it('should throw on 65 bytes without 04 prefix', () => {
81
+ expect(() => EcdsaFormat.compressPublicKey('0x05' + 'ab'.repeat(64))).toThrow(
82
+ 'Expected 65-byte uncompressed public key',
83
+ );
84
+ });
85
+ });
86
+
87
+ describe('keccakDigestHex', () => {
88
+ it('should return 0x-prefixed hex of keccak-256 hash', () => {
89
+ const result = EcdsaFormat.keccakDigestHex(new Uint8Array(0));
90
+ expect(result).toMatch(/^0x[a-f0-9]{64}$/);
91
+ });
92
+
93
+ it('should produce correct hash for empty input', () => {
94
+ const result = EcdsaFormat.keccakDigestHex(new Uint8Array(0));
95
+ expect(result).toBe('0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470');
96
+ });
97
+
98
+ it('should produce different hashes for different inputs', () => {
99
+ const a = EcdsaFormat.keccakDigestHex(new Uint8Array([1]));
100
+ const b = EcdsaFormat.keccakDigestHex(new Uint8Array([2]));
101
+ expect(a).not.toBe(b);
102
+ });
103
+ });
104
+ });
@@ -0,0 +1,77 @@
1
+ import { keccak_256 } from '@noble/hashes/sha3.js';
2
+ import { ensureHexPrefix } from './encoding.js';
3
+
4
+ const ECDSA_SIGNATURE_BYTE_LENGTH = 64;
5
+ const ECDSA_SIGNATURE_WITH_RECOVERY_BYTE_LENGTH = 65;
6
+
7
+ export class EcdsaFormat {
8
+ static normalizeSignatureHex(signatureHex: string): string {
9
+ const clean = ensureHexPrefix(signatureHex).slice(2);
10
+ const byteLength = clean.length / 2;
11
+
12
+ if (byteLength === ECDSA_SIGNATURE_WITH_RECOVERY_BYTE_LENGTH) {
13
+ return `0x${clean.slice(0, ECDSA_SIGNATURE_BYTE_LENGTH * 2)}`;
14
+ }
15
+
16
+ if (byteLength === ECDSA_SIGNATURE_BYTE_LENGTH) {
17
+ return `0x${clean}`;
18
+ }
19
+
20
+ throw new Error(
21
+ `Invalid ECDSA signature length: expected ${ECDSA_SIGNATURE_BYTE_LENGTH} or ${ECDSA_SIGNATURE_WITH_RECOVERY_BYTE_LENGTH} bytes, got ${byteLength}`,
22
+ );
23
+ }
24
+
25
+ static normalizeRecoveryByte(signatureHex: string): string {
26
+ const clean = ensureHexPrefix(signatureHex).slice(2);
27
+ const byteLength = clean.length / 2;
28
+
29
+ if (byteLength === ECDSA_SIGNATURE_WITH_RECOVERY_BYTE_LENGTH) {
30
+ const vByte = parseInt(clean.slice(-2), 16);
31
+ if (vByte === 27 || vByte === 28) {
32
+ const normalized = clean.slice(0, -2) + (vByte - 27).toString(16).padStart(2, '0');
33
+ return `0x${normalized}`;
34
+ }
35
+ return `0x${clean}`;
36
+ }
37
+
38
+ if (byteLength === ECDSA_SIGNATURE_BYTE_LENGTH) {
39
+ return `0x${clean}`;
40
+ }
41
+
42
+ throw new Error(
43
+ `Invalid ECDSA signature length: expected ${ECDSA_SIGNATURE_BYTE_LENGTH} or ${ECDSA_SIGNATURE_WITH_RECOVERY_BYTE_LENGTH} bytes, got ${byteLength}`,
44
+ );
45
+ }
46
+
47
+ static validatePublicKeyHex(publicKeyHex: string): boolean {
48
+ const clean = ensureHexPrefix(publicKeyHex).slice(2);
49
+ const byteLength = clean.length / 2;
50
+ return byteLength === 33 || byteLength === 65;
51
+ }
52
+
53
+ static compressPublicKey(uncompressedHex: string): string {
54
+ const clean = ensureHexPrefix(uncompressedHex).slice(2);
55
+ const byteLength = clean.length / 2;
56
+
57
+ if (byteLength === 33) return `0x${clean}`;
58
+
59
+ if (byteLength !== 65 || !clean.startsWith('04')) {
60
+ throw new Error(`Expected 65-byte uncompressed public key, got ${byteLength} bytes`);
61
+ }
62
+
63
+ const x = clean.slice(2, 66);
64
+ const yLastNibble = parseInt(clean.slice(-1), 16);
65
+ const tag = yLastNibble % 2 === 0 ? '02' : '03';
66
+ return `0x${tag}${x}`;
67
+ }
68
+
69
+ static keccakDigestHex(data: Uint8Array): string {
70
+ const hash = keccak_256(data);
71
+ let hex = '0x';
72
+ for (let i = 0; i < hash.length; i++) {
73
+ hex += hash[i].toString(16).padStart(2, '0');
74
+ }
75
+ return hex;
76
+ }
77
+ }
@@ -2,4 +2,5 @@ export * from './encoding.js';
2
2
  export * from './random.js';
3
3
  export * from './signature.js';
4
4
  export * from './word.js';
5
+ export * from './key.js';
5
6
 
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { PublicKeyFormat } from './key.js';
3
+
4
+ vi.mock('@miden-sdk/miden-sdk', () => ({
5
+ Word: class {
6
+ private u64s: BigUint64Array;
7
+ constructor(u64s: BigUint64Array) {
8
+ this.u64s = u64s;
9
+ }
10
+ toHex() {
11
+ const h3 = this.u64s[3].toString(16).padStart(16, '0');
12
+ const h2 = this.u64s[2].toString(16).padStart(16, '0');
13
+ const h1 = this.u64s[1].toString(16).padStart(16, '0');
14
+ const h0 = this.u64s[0].toString(16).padStart(16, '0');
15
+ return '0x' + h3 + h2 + h1 + h0;
16
+ }
17
+ },
18
+ }));
19
+
20
+ vi.mock('./signature.js', () => ({
21
+ tryComputeCommitmentHex: vi.fn((_hex: string, _scheme: string) => '0x' + 'cc'.repeat(32)),
22
+ }));
23
+
24
+ describe('PublicKeyFormat', () => {
25
+ describe('parse', () => {
26
+ it('should treat 32-byte input as Word commitment with falcon scheme', () => {
27
+ const bytes = new Uint8Array(32);
28
+ bytes[0] = 0x42;
29
+ const result = PublicKeyFormat.parse(bytes);
30
+ expect(result.scheme).toBe('falcon');
31
+ expect(result.commitment).toBe(result.publicKeyHex);
32
+ expect(result.publicKeyHex).toMatch(/^0x[a-f0-9]{64}$/);
33
+ });
34
+
35
+ it('should detect 0x00-prefixed key as falcon', () => {
36
+ const bytes = new Uint8Array(200);
37
+ bytes[0] = 0x00;
38
+ bytes[1] = 0xab;
39
+ const result = PublicKeyFormat.parse(bytes);
40
+ expect(result.scheme).toBe('falcon');
41
+ expect(result.commitment).toBe('0x' + 'cc'.repeat(32));
42
+ });
43
+
44
+ it('should detect 0x01-prefixed 34-byte key as ecdsa', () => {
45
+ const bytes = new Uint8Array(34);
46
+ bytes[0] = 0x01;
47
+ bytes[1] = 0x02;
48
+ const result = PublicKeyFormat.parse(bytes);
49
+ expect(result.scheme).toBe('ecdsa');
50
+ expect(result.commitment).toBe('0x' + 'cc'.repeat(32));
51
+ });
52
+
53
+ it('should detect 33-byte input as ecdsa', () => {
54
+ const bytes = new Uint8Array(33);
55
+ bytes[0] = 0x02;
56
+ const result = PublicKeyFormat.parse(bytes);
57
+ expect(result.scheme).toBe('ecdsa');
58
+ });
59
+
60
+ it('should detect 65-byte input as ecdsa', () => {
61
+ const bytes = new Uint8Array(65);
62
+ bytes[0] = 0x04;
63
+ const result = PublicKeyFormat.parse(bytes);
64
+ expect(result.scheme).toBe('ecdsa');
65
+ });
66
+
67
+ it('should default to falcon for unknown lengths', () => {
68
+ const bytes = new Uint8Array(500);
69
+ const result = PublicKeyFormat.parse(bytes);
70
+ expect(result.scheme).toBe('falcon');
71
+ });
72
+ });
73
+
74
+ describe('wordBytesToHex', () => {
75
+ it('should convert 32 zero bytes to a valid hex string', () => {
76
+ const bytes = new Uint8Array(32);
77
+ const result = PublicKeyFormat.wordBytesToHex(bytes);
78
+ expect(result).toMatch(/^0x[a-f0-9]{64}$/);
79
+ });
80
+
81
+ it('should produce consistent output for same input', () => {
82
+ const bytes = new Uint8Array(32);
83
+ bytes[0] = 0xff;
84
+ const a = PublicKeyFormat.wordBytesToHex(bytes);
85
+ const b = PublicKeyFormat.wordBytesToHex(bytes);
86
+ expect(a).toBe(b);
87
+ });
88
+ });
89
+ });
@@ -0,0 +1,49 @@
1
+ import { Word } from '@miden-sdk/miden-sdk';
2
+ import type { SignatureScheme } from '../types.js';
3
+ import { bytesToHex } from './encoding.js';
4
+ import { tryComputeCommitmentHex } from './signature.js';
5
+
6
+ export class PublicKeyFormat {
7
+ static parse(publicKey: Uint8Array): {
8
+ scheme: SignatureScheme;
9
+ publicKeyHex: string;
10
+ commitment: string | null;
11
+ } {
12
+ if (publicKey.length === 32) {
13
+ const commitmentHex = PublicKeyFormat.wordBytesToHex(publicKey);
14
+ return { scheme: 'falcon', publicKeyHex: commitmentHex, commitment: commitmentHex };
15
+ }
16
+
17
+ const firstByte = publicKey[0];
18
+
19
+ if (firstByte === 0x00 && publicKey.length > 100) {
20
+ const hex = bytesToHex(publicKey.slice(1));
21
+ return { scheme: 'falcon', publicKeyHex: hex, commitment: tryComputeCommitmentHex(hex, 'falcon') };
22
+ }
23
+
24
+ if (firstByte === 0x01 && publicKey.length === 34) {
25
+ const hex = bytesToHex(publicKey.slice(1));
26
+ return { scheme: 'ecdsa', publicKeyHex: hex, commitment: tryComputeCommitmentHex(hex, 'ecdsa') };
27
+ }
28
+
29
+ if (publicKey.length === 33 || publicKey.length === 65) {
30
+ const hex = bytesToHex(publicKey);
31
+ return { scheme: 'ecdsa', publicKeyHex: hex, commitment: tryComputeCommitmentHex(hex, 'ecdsa') };
32
+ }
33
+
34
+ const hex = bytesToHex(publicKey);
35
+ return { scheme: 'falcon', publicKeyHex: hex, commitment: tryComputeCommitmentHex(hex, 'falcon') };
36
+ }
37
+
38
+ static wordBytesToHex(bytes: Uint8Array): string {
39
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
40
+ const u64s = new BigUint64Array(4);
41
+ for (let i = 0; i < 4; i++) {
42
+ u64s[i] = view.getBigUint64(i * 8, true);
43
+ }
44
+ const word = new Word(u64s);
45
+ const hex = word.toHex();
46
+ const clean = hex.replace(/^0x/i, '').toLowerCase().padStart(64, '0');
47
+ return `0x${clean}`;
48
+ }
49
+ }
@@ -1,4 +1,4 @@
1
- import { Word } from '@demox-labs/miden-sdk';
1
+ import { Word } from '@miden-sdk/miden-sdk';
2
2
 
3
3
  export function randomWord(): Word {
4
4
  const bytes = new Uint8Array(32);