@openzeppelin/miden-multisig-client 0.12.8 → 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.
- package/README.md +42 -4
- package/dist/account/builder.d.ts +1 -1
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +7 -5
- package/dist/account/builder.js.map +1 -1
- package/dist/account/masm.d.ts +3 -3
- package/dist/account/masm.d.ts.map +1 -1
- package/dist/account/masm.js +101 -98
- package/dist/account/masm.js.map +1 -1
- package/dist/account/storage.d.ts +1 -1
- package/dist/account/storage.d.ts.map +1 -1
- package/dist/account/storage.js +17 -7
- package/dist/account/storage.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +4 -4
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.d.ts.map +1 -1
- package/dist/inspector.js +16 -10
- package/dist/inspector.js.map +1 -1
- package/dist/inspector.test.js +33 -22
- package/dist/inspector.test.js.map +1 -1
- package/dist/multisig/helpers.d.ts +1 -1
- package/dist/multisig/helpers.d.ts.map +1 -1
- package/dist/multisig/helpers.js +1 -1
- package/dist/multisig/helpers.js.map +1 -1
- package/dist/multisig/helpers.test.js +2 -2
- package/dist/multisig/helpers.test.js.map +1 -1
- package/dist/multisig/proposal/execution.d.ts +18 -0
- package/dist/multisig/proposal/execution.d.ts.map +1 -0
- package/dist/multisig/proposal/execution.js +147 -0
- package/dist/multisig/proposal/execution.js.map +1 -0
- package/dist/multisig/proposal/parser.d.ts +17 -0
- package/dist/multisig/proposal/parser.d.ts.map +1 -0
- package/dist/multisig/proposal/parser.js +147 -0
- package/dist/multisig/proposal/parser.js.map +1 -0
- package/dist/multisig/signing.d.ts +5 -0
- package/dist/multisig/signing.d.ts.map +1 -0
- package/dist/multisig/signing.js +14 -0
- package/dist/multisig/signing.js.map +1 -0
- package/dist/multisig.d.ts +9 -13
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +109 -337
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +8 -4
- package/dist/multisig.test.js.map +1 -1
- package/dist/signer.d.ts +2 -20
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +2 -82
- package/dist/signer.js.map +1 -1
- package/dist/signer.test.js +24 -24
- package/dist/signer.test.js.map +1 -1
- package/dist/signers/ecdsa.d.ts +12 -0
- package/dist/signers/ecdsa.d.ts.map +1 -0
- package/dist/signers/ecdsa.js +30 -0
- package/dist/signers/ecdsa.js.map +1 -0
- package/dist/signers/falcon.d.ts +12 -0
- package/dist/signers/falcon.d.ts.map +1 -0
- package/dist/signers/falcon.js +31 -0
- package/dist/signers/falcon.js.map +1 -0
- package/dist/signers/falcon.test.d.ts +2 -0
- package/dist/signers/falcon.test.d.ts.map +1 -0
- package/dist/signers/falcon.test.js +113 -0
- package/dist/signers/falcon.test.js.map +1 -0
- package/dist/signers/index.d.ts +5 -0
- package/dist/signers/index.d.ts.map +1 -0
- package/dist/signers/index.js +5 -0
- package/dist/signers/index.js.map +1 -0
- package/dist/signers/miden-wallet.d.ts +16 -0
- package/dist/signers/miden-wallet.d.ts.map +1 -0
- package/dist/signers/miden-wallet.js +35 -0
- package/dist/signers/miden-wallet.js.map +1 -0
- package/dist/signers/miden-wallet.test.d.ts +2 -0
- package/dist/signers/miden-wallet.test.d.ts.map +1 -0
- package/dist/signers/miden-wallet.test.js +107 -0
- package/dist/signers/miden-wallet.test.js.map +1 -0
- package/dist/signers/para.d.ts +21 -0
- package/dist/signers/para.d.ts.map +1 -0
- package/dist/signers/para.js +43 -0
- package/dist/signers/para.js.map +1 -0
- package/dist/signers/para.test.d.ts +2 -0
- package/dist/signers/para.test.d.ts.map +1 -0
- package/dist/signers/para.test.js +88 -0
- package/dist/signers/para.test.js.map +1 -0
- package/dist/transaction/consumeNotes.d.ts +3 -3
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +11 -7
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/options.d.ts +2 -2
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/p2id.d.ts +1 -1
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +5 -5
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/summary.d.ts +1 -1
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +1 -1
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/updatePsm.d.ts +1 -1
- package/dist/transaction/updatePsm.d.ts.map +1 -1
- package/dist/transaction/updatePsm.js +5 -5
- package/dist/transaction/updatePsm.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +1 -1
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +5 -5
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.test.js +1 -1
- package/dist/transaction.test.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/digest.d.ts +6 -0
- package/dist/utils/digest.d.ts.map +1 -0
- package/dist/utils/digest.js +22 -0
- package/dist/utils/digest.js.map +1 -0
- package/dist/utils/ecdsa.d.ts +8 -0
- package/dist/utils/ecdsa.d.ts.map +1 -0
- package/dist/utils/ecdsa.js +60 -0
- package/dist/utils/ecdsa.js.map +1 -0
- package/dist/utils/ecdsa.test.d.ts +2 -0
- package/dist/utils/ecdsa.test.d.ts.map +1 -0
- package/dist/utils/ecdsa.test.js +85 -0
- package/dist/utils/ecdsa.test.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/key.d.ts +10 -0
- package/dist/utils/key.d.ts.map +1 -0
- package/dist/utils/key.js +38 -0
- package/dist/utils/key.js.map +1 -0
- package/dist/utils/key.test.d.ts +2 -0
- package/dist/utils/key.test.d.ts.map +1 -0
- package/dist/utils/key.test.js +80 -0
- package/dist/utils/key.test.js.map +1 -0
- package/dist/utils/random.d.ts +1 -1
- package/dist/utils/random.d.ts.map +1 -1
- package/dist/utils/random.js +1 -1
- package/dist/utils/random.js.map +1 -1
- package/dist/utils/signature.d.ts +2 -1
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +6 -3
- package/dist/utils/signature.js.map +1 -1
- package/dist/utils/signature.test.js +11 -11
- package/dist/utils/signature.test.js.map +1 -1
- package/dist/utils/word.d.ts +6 -1
- package/dist/utils/word.d.ts.map +1 -1
- package/dist/utils/word.js +11 -0
- package/dist/utils/word.js.map +1 -1
- package/dist/utils/word.test.js +54 -3
- package/dist/utils/word.test.js.map +1 -1
- package/package.json +4 -5
- package/src/account/builder.ts +7 -5
- package/src/account/masm.ts +103 -99
- package/src/account/storage.ts +19 -7
- package/src/client.test.ts +4 -4
- package/src/client.ts +3 -3
- package/src/index.ts +8 -1
- package/src/inspector.test.ts +38 -25
- package/src/inspector.ts +18 -10
- package/src/multisig/helpers.test.ts +2 -2
- package/src/multisig/helpers.ts +2 -1
- package/src/multisig/proposal/execution.ts +320 -0
- package/src/multisig/proposal/parser.ts +191 -0
- package/src/multisig/signing.ts +24 -0
- package/src/multisig.test.ts +8 -4
- package/src/multisig.ts +153 -434
- package/src/signer.ts +2 -95
- package/src/signers/ecdsa.ts +35 -0
- package/src/{signer.test.ts → signers/falcon.test.ts} +29 -33
- package/src/signers/falcon.ts +36 -0
- package/src/signers/index.ts +4 -0
- package/src/signers/miden-wallet.test.ts +119 -0
- package/src/signers/miden-wallet.ts +49 -0
- package/src/signers/para.test.ts +103 -0
- package/src/signers/para.ts +60 -0
- package/src/transaction/consumeNotes.ts +14 -10
- package/src/transaction/options.ts +2 -2
- package/src/transaction/p2id.ts +3 -8
- package/src/transaction/summary.ts +2 -2
- package/src/transaction/updatePsm.ts +5 -6
- package/src/transaction/updateSigners.ts +5 -6
- package/src/transaction.test.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/digest.ts +25 -0
- package/src/utils/ecdsa.test.ts +104 -0
- package/src/utils/ecdsa.ts +77 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/key.test.ts +89 -0
- package/src/utils/key.ts +49 -0
- package/src/utils/random.ts +1 -1
- package/src/utils/signature.test.ts +11 -11
- package/src/utils/signature.ts +10 -3
- package/src/utils/word.test.ts +57 -3
- package/src/utils/word.ts +13 -1
package/src/signer.ts
CHANGED
|
@@ -1,95 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { bytesToHex } from './utils/encoding.js';
|
|
4
|
-
|
|
5
|
-
export class FalconSigner implements Signer {
|
|
6
|
-
readonly commitment: string;
|
|
7
|
-
readonly publicKey: string;
|
|
8
|
-
readonly scheme: SignatureScheme = 'falcon';
|
|
9
|
-
private readonly secretKey: SecretKey;
|
|
10
|
-
|
|
11
|
-
constructor(secretKey: SecretKey) {
|
|
12
|
-
this.secretKey = secretKey;
|
|
13
|
-
const pubKey = secretKey.publicKey();
|
|
14
|
-
this.commitment = pubKey.toCommitment().toHex();
|
|
15
|
-
const serialized = pubKey.serialize();
|
|
16
|
-
const falconPubKey = serialized.slice(1);
|
|
17
|
-
this.publicKey = bytesToHex(falconPubKey);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
signAccountIdWithTimestamp(accountId: string, timestamp: number): string {
|
|
21
|
-
const paddedHex = accountId.startsWith('0x') ? accountId : `0x${accountId}`;
|
|
22
|
-
const parsedAccountId = AccountId.fromHex(paddedHex);
|
|
23
|
-
const prefix = parsedAccountId.prefix();
|
|
24
|
-
const suffix = parsedAccountId.suffix();
|
|
25
|
-
|
|
26
|
-
const feltArray = new FeltArray([
|
|
27
|
-
prefix,
|
|
28
|
-
suffix,
|
|
29
|
-
new Felt(BigInt(timestamp)),
|
|
30
|
-
new Felt(BigInt(0)),
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
const digest = Rpo256.hashElements(feltArray);
|
|
34
|
-
const signature = this.secretKey.sign(digest);
|
|
35
|
-
const signatureBytes = signature.serialize();
|
|
36
|
-
const falconSignature = signatureBytes.slice(1);
|
|
37
|
-
return bytesToHex(falconSignature);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
signCommitment(commitmentHex: string): string {
|
|
41
|
-
const paddedHex = commitmentHex.startsWith('0x') ? commitmentHex : `0x${commitmentHex}`;
|
|
42
|
-
const cleanHex = paddedHex.slice(2).padStart(64, '0');
|
|
43
|
-
const word = Word.fromHex(`0x${cleanHex}`);
|
|
44
|
-
const signature = this.secretKey.sign(word);
|
|
45
|
-
const signatureBytes = signature.serialize();
|
|
46
|
-
const falconSignature = signatureBytes.slice(1);
|
|
47
|
-
return bytesToHex(falconSignature);
|
|
48
|
-
}
|
|
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
|
+
export { FalconSigner } from './signers/falcon.js';
|
|
2
|
+
export { EcdsaSigner } from './signers/ecdsa.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AuthSecretKey } from '@miden-sdk/miden-sdk';
|
|
2
|
+
import type { Signer, SignatureScheme } from '../types.js';
|
|
3
|
+
import { bytesToHex, normalizeHexWord } from '../utils/encoding.js';
|
|
4
|
+
import { AuthDigest } from '../utils/digest.js';
|
|
5
|
+
|
|
6
|
+
export class EcdsaSigner implements Signer {
|
|
7
|
+
readonly commitment: string;
|
|
8
|
+
readonly publicKey: string;
|
|
9
|
+
readonly scheme: SignatureScheme = 'ecdsa';
|
|
10
|
+
private readonly secretKey: AuthSecretKey;
|
|
11
|
+
|
|
12
|
+
constructor(secretKey: AuthSecretKey) {
|
|
13
|
+
this.secretKey = secretKey;
|
|
14
|
+
const pubKey = secretKey.publicKey();
|
|
15
|
+
const serialized = pubKey.serialize();
|
|
16
|
+
this.publicKey = bytesToHex(serialized.slice(1));
|
|
17
|
+
this.commitment = normalizeHexWord(pubKey.toCommitment().toHex());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async signAccountIdWithTimestamp(accountId: string, timestamp: number): Promise<string> {
|
|
21
|
+
const digest = AuthDigest.fromAccountIdWithTimestamp(accountId, timestamp);
|
|
22
|
+
const signature = this.secretKey.sign(digest);
|
|
23
|
+
const signatureBytes = signature.serialize();
|
|
24
|
+
const ecdsaSignature = signatureBytes.slice(1);
|
|
25
|
+
return bytesToHex(ecdsaSignature);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async signCommitment(commitmentHex: string): Promise<string> {
|
|
29
|
+
const word = AuthDigest.fromCommitmentHex(commitmentHex);
|
|
30
|
+
const signature = this.secretKey.sign(word);
|
|
31
|
+
const signatureBytes = signature.serialize();
|
|
32
|
+
const ecdsaSignature = signatureBytes.slice(1);
|
|
33
|
+
return bytesToHex(ecdsaSignature);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { FalconSigner } from './
|
|
2
|
+
import { FalconSigner } from './falcon.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
vi.mock('@demox-labs/miden-sdk', () => {
|
|
4
|
+
vi.mock('@miden-sdk/miden-sdk', () => {
|
|
6
5
|
const mockSignature = {
|
|
7
6
|
serialize: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
|
|
8
7
|
};
|
|
@@ -20,7 +19,7 @@ vi.mock('@demox-labs/miden-sdk', () => {
|
|
|
20
19
|
};
|
|
21
20
|
|
|
22
21
|
return {
|
|
23
|
-
|
|
22
|
+
AuthSecretKey: {
|
|
24
23
|
rpoFalconWithRNG: vi.fn().mockReturnValue(mockSecretKey),
|
|
25
24
|
},
|
|
26
25
|
Word: {
|
|
@@ -51,8 +50,8 @@ describe('FalconSigner', () => {
|
|
|
51
50
|
|
|
52
51
|
beforeEach(async () => {
|
|
53
52
|
vi.clearAllMocks();
|
|
54
|
-
const {
|
|
55
|
-
mockSecretKey =
|
|
53
|
+
const { AuthSecretKey } = await import('@miden-sdk/miden-sdk');
|
|
54
|
+
mockSecretKey = AuthSecretKey.rpoFalconWithRNG(new Uint8Array(32));
|
|
56
55
|
signer = new FalconSigner(mockSecretKey);
|
|
57
56
|
});
|
|
58
57
|
|
|
@@ -62,7 +61,6 @@ describe('FalconSigner', () => {
|
|
|
62
61
|
});
|
|
63
62
|
|
|
64
63
|
it('extracts public key without first byte', () => {
|
|
65
|
-
// Public key serialized is [0, 1, 2, ...15], slice(1) removes first byte
|
|
66
64
|
expect(signer.publicKey).toBe('0x0102030405060708090a0b0c0d0e0f');
|
|
67
65
|
});
|
|
68
66
|
|
|
@@ -72,63 +70,61 @@ describe('FalconSigner', () => {
|
|
|
72
70
|
});
|
|
73
71
|
|
|
74
72
|
describe('signAccountIdWithTimestamp', () => {
|
|
75
|
-
it('handles account ID with 0x prefix', () => {
|
|
76
|
-
const signature = signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
73
|
+
it('handles account ID with 0x prefix', async () => {
|
|
74
|
+
const signature = await signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
77
75
|
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
78
76
|
});
|
|
79
77
|
|
|
80
|
-
it('handles account ID without 0x prefix', () => {
|
|
81
|
-
const signature = signer.signAccountIdWithTimestamp('a'.repeat(30), 1700000000);
|
|
78
|
+
it('handles account ID without 0x prefix', async () => {
|
|
79
|
+
const signature = await signer.signAccountIdWithTimestamp('a'.repeat(30), 1700000000);
|
|
82
80
|
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
83
81
|
});
|
|
84
82
|
|
|
85
|
-
it('calls sign method with hashed digest', () => {
|
|
86
|
-
signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
83
|
+
it('calls sign method with hashed digest', async () => {
|
|
84
|
+
await signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
87
85
|
expect(mockSecretKey.sign).toHaveBeenCalled();
|
|
88
86
|
});
|
|
89
87
|
|
|
90
|
-
it('returns signature without first byte', () => {
|
|
91
|
-
|
|
92
|
-
const signature = signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
88
|
+
it('returns signature without first byte', async () => {
|
|
89
|
+
const signature = await signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
93
90
|
expect(signature).toBe('0x010203040506070809');
|
|
94
91
|
});
|
|
95
92
|
|
|
96
93
|
it('includes timestamp in signed payload', async () => {
|
|
97
|
-
const { Felt, FeltArray } = await import('@
|
|
98
|
-
signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
99
|
-
// Verify FeltArray was called with timestamp in the third position
|
|
94
|
+
const { Felt, FeltArray } = await import('@miden-sdk/miden-sdk');
|
|
95
|
+
await signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
100
96
|
expect(FeltArray).toHaveBeenCalledWith(expect.arrayContaining([
|
|
101
|
-
expect.anything(),
|
|
102
|
-
expect.anything(),
|
|
103
|
-
expect.anything(),
|
|
104
|
-
expect.anything(),
|
|
97
|
+
expect.anything(),
|
|
98
|
+
expect.anything(),
|
|
99
|
+
expect.anything(),
|
|
100
|
+
expect.anything(),
|
|
105
101
|
]));
|
|
106
102
|
});
|
|
107
103
|
});
|
|
108
104
|
|
|
109
105
|
describe('signCommitment', () => {
|
|
110
|
-
it('handles commitment with 0x prefix', () => {
|
|
111
|
-
const signature = signer.signCommitment('0x' + 'c'.repeat(64));
|
|
106
|
+
it('handles commitment with 0x prefix', async () => {
|
|
107
|
+
const signature = await signer.signCommitment('0x' + 'c'.repeat(64));
|
|
112
108
|
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
113
109
|
});
|
|
114
110
|
|
|
115
|
-
it('handles commitment without 0x prefix', () => {
|
|
116
|
-
const signature = signer.signCommitment('c'.repeat(64));
|
|
111
|
+
it('handles commitment without 0x prefix', async () => {
|
|
112
|
+
const signature = await signer.signCommitment('c'.repeat(64));
|
|
117
113
|
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
118
114
|
});
|
|
119
115
|
|
|
120
|
-
it('pads short commitment hex to 64 characters', () => {
|
|
121
|
-
const signature = signer.signCommitment('abc');
|
|
116
|
+
it('pads short commitment hex to 64 characters', async () => {
|
|
117
|
+
const signature = await signer.signCommitment('abc');
|
|
122
118
|
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
123
119
|
});
|
|
124
120
|
|
|
125
|
-
it('calls sign method with word from hex', () => {
|
|
126
|
-
signer.signCommitment('0x' + 'c'.repeat(64));
|
|
121
|
+
it('calls sign method with word from hex', async () => {
|
|
122
|
+
await signer.signCommitment('0x' + 'c'.repeat(64));
|
|
127
123
|
expect(mockSecretKey.sign).toHaveBeenCalled();
|
|
128
124
|
});
|
|
129
125
|
|
|
130
|
-
it('returns signature without first byte', () => {
|
|
131
|
-
const signature = signer.signCommitment('0x' + 'c'.repeat(64));
|
|
126
|
+
it('returns signature without first byte', async () => {
|
|
127
|
+
const signature = await signer.signCommitment('0x' + 'c'.repeat(64));
|
|
132
128
|
expect(signature).toBe('0x010203040506070809');
|
|
133
129
|
});
|
|
134
130
|
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AuthSecretKey } from '@miden-sdk/miden-sdk';
|
|
2
|
+
import type { Signer, SignatureScheme } from '../types.js';
|
|
3
|
+
import { bytesToHex } from '../utils/encoding.js';
|
|
4
|
+
import { AuthDigest } from '../utils/digest.js';
|
|
5
|
+
|
|
6
|
+
export class FalconSigner implements Signer {
|
|
7
|
+
readonly commitment: string;
|
|
8
|
+
readonly publicKey: string;
|
|
9
|
+
readonly scheme: SignatureScheme = 'falcon';
|
|
10
|
+
private readonly secretKey: AuthSecretKey;
|
|
11
|
+
|
|
12
|
+
constructor(secretKey: AuthSecretKey) {
|
|
13
|
+
this.secretKey = secretKey;
|
|
14
|
+
const pubKey = secretKey.publicKey();
|
|
15
|
+
this.commitment = pubKey.toCommitment().toHex();
|
|
16
|
+
const serialized = pubKey.serialize();
|
|
17
|
+
const falconPubKey = serialized.slice(1);
|
|
18
|
+
this.publicKey = bytesToHex(falconPubKey);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async signAccountIdWithTimestamp(accountId: string, timestamp: number): Promise<string> {
|
|
22
|
+
const digest = AuthDigest.fromAccountIdWithTimestamp(accountId, timestamp);
|
|
23
|
+
const signature = this.secretKey.sign(digest);
|
|
24
|
+
const signatureBytes = signature.serialize();
|
|
25
|
+
const falconSignature = signatureBytes.slice(1);
|
|
26
|
+
return bytesToHex(falconSignature);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async signCommitment(commitmentHex: string): Promise<string> {
|
|
30
|
+
const word = AuthDigest.fromCommitmentHex(commitmentHex);
|
|
31
|
+
const signature = this.secretKey.sign(word);
|
|
32
|
+
const signatureBytes = signature.serialize();
|
|
33
|
+
const falconSignature = signatureBytes.slice(1);
|
|
34
|
+
return bytesToHex(falconSignature);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { MidenWalletSigner } from './miden-wallet.js';
|
|
3
|
+
import type { WalletSigningContext } from './miden-wallet.js';
|
|
4
|
+
import type { Signer } from '../types.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
7
|
+
Word: {
|
|
8
|
+
fromHex: vi.fn((hex: string) => ({
|
|
9
|
+
toHex: () => hex,
|
|
10
|
+
toFelts: () => [
|
|
11
|
+
{ asInt: () => 1n },
|
|
12
|
+
{ asInt: () => 2n },
|
|
13
|
+
{ asInt: () => 3n },
|
|
14
|
+
{ asInt: () => 4n },
|
|
15
|
+
],
|
|
16
|
+
})),
|
|
17
|
+
},
|
|
18
|
+
AccountId: {
|
|
19
|
+
fromHex: vi.fn(() => ({
|
|
20
|
+
prefix: () => ({ asInt: () => BigInt(1) }),
|
|
21
|
+
suffix: () => ({ asInt: () => BigInt(2) }),
|
|
22
|
+
})),
|
|
23
|
+
},
|
|
24
|
+
Felt: vi.fn().mockImplementation((v: bigint) => ({ value: v })),
|
|
25
|
+
FeltArray: vi.fn().mockImplementation((arr: any[]) => arr),
|
|
26
|
+
Rpo256: {
|
|
27
|
+
hashElements: vi.fn().mockReturnValue({
|
|
28
|
+
toHex: () => '0x' + 'bb'.repeat(32),
|
|
29
|
+
toFelts: () => [
|
|
30
|
+
{ asInt: () => 10n },
|
|
31
|
+
{ asInt: () => 20n },
|
|
32
|
+
{ asInt: () => 30n },
|
|
33
|
+
{ asInt: () => 40n },
|
|
34
|
+
],
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
describe('MidenWalletSigner', () => {
|
|
40
|
+
let mockWallet: WalletSigningContext;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.clearAllMocks();
|
|
44
|
+
mockWallet = {
|
|
45
|
+
signBytes: vi.fn().mockResolvedValue(new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04])),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('constructor', () => {
|
|
50
|
+
it('should set commitment, scheme, and publicKey', () => {
|
|
51
|
+
const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'falcon');
|
|
52
|
+
expect(signer.commitment).toBe('0xcommitment');
|
|
53
|
+
expect(signer.scheme).toBe('falcon');
|
|
54
|
+
expect(signer.publicKey).toBe('0xcommitment');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should use localAuthSigner publicKey when provided', () => {
|
|
58
|
+
const localSigner: Signer = {
|
|
59
|
+
commitment: '0xlocal',
|
|
60
|
+
publicKey: '0xlocalpubkey',
|
|
61
|
+
scheme: 'ecdsa',
|
|
62
|
+
signAccountIdWithTimestamp: vi.fn(),
|
|
63
|
+
signCommitment: vi.fn(),
|
|
64
|
+
};
|
|
65
|
+
const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'ecdsa', localSigner);
|
|
66
|
+
expect(signer.publicKey).toBe('0xlocalpubkey');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('signAccountIdWithTimestamp', () => {
|
|
71
|
+
it('should delegate to localAuthSigner when present', async () => {
|
|
72
|
+
const localSigner: Signer = {
|
|
73
|
+
commitment: '0xlocal',
|
|
74
|
+
publicKey: '0xlocalpubkey',
|
|
75
|
+
scheme: 'ecdsa',
|
|
76
|
+
signAccountIdWithTimestamp: vi.fn().mockResolvedValue('0xlocalsig'),
|
|
77
|
+
signCommitment: vi.fn(),
|
|
78
|
+
};
|
|
79
|
+
const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'ecdsa', localSigner);
|
|
80
|
+
const result = await signer.signAccountIdWithTimestamp('0x' + 'aa'.repeat(15), 1700000000);
|
|
81
|
+
expect(result).toBe('0xlocalsig');
|
|
82
|
+
expect(localSigner.signAccountIdWithTimestamp).toHaveBeenCalledWith('0x' + 'aa'.repeat(15), 1700000000);
|
|
83
|
+
expect(mockWallet.signBytes).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should use wallet signing when no localAuthSigner', async () => {
|
|
87
|
+
const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'falcon');
|
|
88
|
+
const result = await signer.signAccountIdWithTimestamp('0x' + 'aa'.repeat(15), 1700000000);
|
|
89
|
+
expect(mockWallet.signBytes).toHaveBeenCalled();
|
|
90
|
+
expect(result).toMatch(/^0x/);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('signCommitment', () => {
|
|
95
|
+
it('should always use wallet signing', async () => {
|
|
96
|
+
const localSigner: Signer = {
|
|
97
|
+
commitment: '0xlocal',
|
|
98
|
+
publicKey: '0xlocalpubkey',
|
|
99
|
+
scheme: 'ecdsa',
|
|
100
|
+
signAccountIdWithTimestamp: vi.fn(),
|
|
101
|
+
signCommitment: vi.fn(),
|
|
102
|
+
};
|
|
103
|
+
const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'ecdsa', localSigner);
|
|
104
|
+
await signer.signCommitment('0x' + 'cc'.repeat(32));
|
|
105
|
+
expect(mockWallet.signBytes).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('signature byte stripping', () => {
|
|
110
|
+
it('should strip the first byte from wallet signature response', async () => {
|
|
111
|
+
(mockWallet.signBytes as ReturnType<typeof vi.fn>).mockResolvedValue(
|
|
112
|
+
new Uint8Array([0x00, 0xaa, 0xbb, 0xcc]),
|
|
113
|
+
);
|
|
114
|
+
const signer = new MidenWalletSigner(mockWallet, '0xcommitment', 'falcon');
|
|
115
|
+
const result = await signer.signCommitment('0x' + 'cc'.repeat(32));
|
|
116
|
+
expect(result).toBe('0xaabbcc');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Signer, SignatureScheme } from '../types.js';
|
|
2
|
+
import { AuthDigest } from '../utils/digest.js';
|
|
3
|
+
import { bytesToHex } from '../utils/encoding.js';
|
|
4
|
+
import { wordToBytes } from '../utils/word.js';
|
|
5
|
+
|
|
6
|
+
export interface WalletSigningContext {
|
|
7
|
+
signBytes(data: Uint8Array, kind: 'word' | 'signingInputs'): Promise<Uint8Array>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class MidenWalletSigner implements Signer {
|
|
11
|
+
readonly commitment: string;
|
|
12
|
+
readonly publicKey: string;
|
|
13
|
+
readonly scheme: SignatureScheme;
|
|
14
|
+
private readonly wallet: WalletSigningContext;
|
|
15
|
+
private readonly localAuthSigner: Signer | null;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
wallet: WalletSigningContext,
|
|
19
|
+
commitment: string,
|
|
20
|
+
scheme: SignatureScheme,
|
|
21
|
+
localAuthSigner?: Signer,
|
|
22
|
+
) {
|
|
23
|
+
this.wallet = wallet;
|
|
24
|
+
this.commitment = commitment;
|
|
25
|
+
this.scheme = scheme;
|
|
26
|
+
this.localAuthSigner = localAuthSigner ?? null;
|
|
27
|
+
this.publicKey = localAuthSigner?.publicKey ?? commitment;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async signAccountIdWithTimestamp(accountId: string, timestamp: number): Promise<string> {
|
|
31
|
+
if (this.localAuthSigner) {
|
|
32
|
+
return this.localAuthSigner.signAccountIdWithTimestamp(accountId, timestamp);
|
|
33
|
+
}
|
|
34
|
+
const word = AuthDigest.fromAccountIdWithTimestamp(accountId, timestamp);
|
|
35
|
+
return this.signWord(word);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async signCommitment(commitmentHex: string): Promise<string> {
|
|
39
|
+
const word = AuthDigest.fromCommitmentHex(commitmentHex);
|
|
40
|
+
return this.signWord(word);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async signWord(word: { toFelts: () => Array<{ asInt: () => bigint }> }): Promise<string> {
|
|
44
|
+
const bytes = wordToBytes(word);
|
|
45
|
+
const signatureBytes = await this.wallet.signBytes(bytes, 'word');
|
|
46
|
+
const rawSignature = signatureBytes.slice(1);
|
|
47
|
+
return bytesToHex(rawSignature);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { ParaSigner } from './para.js';
|
|
3
|
+
import type { ParaSigningContext } from './para.js';
|
|
4
|
+
|
|
5
|
+
vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
6
|
+
Word: {
|
|
7
|
+
fromHex: vi.fn((hex: string) => ({
|
|
8
|
+
toHex: () => hex,
|
|
9
|
+
toFelts: () => [
|
|
10
|
+
{ asInt: () => 1n },
|
|
11
|
+
{ asInt: () => 2n },
|
|
12
|
+
{ asInt: () => 3n },
|
|
13
|
+
{ asInt: () => 4n },
|
|
14
|
+
],
|
|
15
|
+
})),
|
|
16
|
+
},
|
|
17
|
+
AccountId: {
|
|
18
|
+
fromHex: vi.fn(() => ({
|
|
19
|
+
prefix: () => ({ asInt: () => BigInt(1) }),
|
|
20
|
+
suffix: () => ({ asInt: () => BigInt(2) }),
|
|
21
|
+
})),
|
|
22
|
+
},
|
|
23
|
+
Felt: vi.fn().mockImplementation((v: bigint) => ({ value: v })),
|
|
24
|
+
FeltArray: vi.fn().mockImplementation((arr: any[]) => arr),
|
|
25
|
+
Rpo256: {
|
|
26
|
+
hashElements: vi.fn().mockReturnValue({
|
|
27
|
+
toHex: () => '0x' + 'bb'.repeat(32),
|
|
28
|
+
toFelts: () => [
|
|
29
|
+
{ asInt: () => 10n },
|
|
30
|
+
{ asInt: () => 20n },
|
|
31
|
+
{ asInt: () => 30n },
|
|
32
|
+
{ asInt: () => 40n },
|
|
33
|
+
],
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
describe('ParaSigner', () => {
|
|
39
|
+
let mockPara: ParaSigningContext;
|
|
40
|
+
const validCompressedKey = '0x02' + 'aa'.repeat(32);
|
|
41
|
+
const validUncompressedKey = '0x04' + 'aa'.repeat(32) + 'bb'.repeat(31) + 'b0';
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.clearAllMocks();
|
|
45
|
+
mockPara = {
|
|
46
|
+
signMessage: vi.fn().mockResolvedValue({
|
|
47
|
+
signature: '0x' + 'cc'.repeat(64),
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('constructor', () => {
|
|
53
|
+
it('should accept a valid compressed public key', () => {
|
|
54
|
+
const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validCompressedKey);
|
|
55
|
+
expect(signer.publicKey).toBe(validCompressedKey);
|
|
56
|
+
expect(signer.commitment).toBe('0xcommitment');
|
|
57
|
+
expect(signer.scheme).toBe('ecdsa');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should compress an uncompressed public key', () => {
|
|
61
|
+
const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validUncompressedKey);
|
|
62
|
+
expect(signer.publicKey).toBe('0x02' + 'aa'.repeat(32));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should throw on invalid public key', () => {
|
|
66
|
+
expect(
|
|
67
|
+
() => new ParaSigner(mockPara, 'wallet-1', '0xcommitment', '0xinvalid'),
|
|
68
|
+
).toThrow('Invalid ECDSA public key for ParaSigner');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('signAccountIdWithTimestamp', () => {
|
|
73
|
+
it('should call para signMessage and return normalized signature', async () => {
|
|
74
|
+
const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validCompressedKey);
|
|
75
|
+
const result = await signer.signAccountIdWithTimestamp('0x' + 'aa'.repeat(15), 1700000000);
|
|
76
|
+
expect(mockPara.signMessage).toHaveBeenCalledWith(
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
walletId: 'wallet-1',
|
|
79
|
+
messageBase64: expect.any(String),
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
expect(result).toMatch(/^0x/);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('signCommitment', () => {
|
|
87
|
+
it('should call para signMessage', async () => {
|
|
88
|
+
const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validCompressedKey);
|
|
89
|
+
await signer.signCommitment('0x' + 'cc'.repeat(32));
|
|
90
|
+
expect(mockPara.signMessage).toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('signing denial', () => {
|
|
95
|
+
it('should throw when para returns no signature', async () => {
|
|
96
|
+
(mockPara.signMessage as ReturnType<typeof vi.fn>).mockResolvedValue({ signature: undefined });
|
|
97
|
+
const signer = new ParaSigner(mockPara, 'wallet-1', '0xcommitment', validCompressedKey);
|
|
98
|
+
await expect(signer.signCommitment('0x' + 'cc'.repeat(32))).rejects.toThrow(
|
|
99
|
+
'Para signing was denied by user',
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Signer, SignatureScheme } from '../types.js';
|
|
2
|
+
import { AuthDigest } from '../utils/digest.js';
|
|
3
|
+
import { EcdsaFormat } from '../utils/ecdsa.js';
|
|
4
|
+
import { hexToBytes, uint8ArrayToBase64 } from '../utils/encoding.js';
|
|
5
|
+
import { wordToBytes } from '../utils/word.js';
|
|
6
|
+
|
|
7
|
+
export interface ParaSigningContext {
|
|
8
|
+
signMessage(params: { walletId: string; messageBase64: string }): Promise<{ signature?: string }>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ParaSigner implements Signer {
|
|
12
|
+
readonly commitment: string;
|
|
13
|
+
readonly publicKey: string;
|
|
14
|
+
readonly scheme: SignatureScheme = 'ecdsa';
|
|
15
|
+
private readonly para: ParaSigningContext;
|
|
16
|
+
private readonly walletId: string;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
para: ParaSigningContext,
|
|
20
|
+
walletId: string,
|
|
21
|
+
commitment: string,
|
|
22
|
+
publicKey: string,
|
|
23
|
+
) {
|
|
24
|
+
if (!EcdsaFormat.validatePublicKeyHex(publicKey)) {
|
|
25
|
+
throw new Error('Invalid ECDSA public key for ParaSigner');
|
|
26
|
+
}
|
|
27
|
+
this.para = para;
|
|
28
|
+
this.walletId = walletId;
|
|
29
|
+
this.commitment = commitment;
|
|
30
|
+
this.publicKey = EcdsaFormat.compressPublicKey(publicKey);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async signAccountIdWithTimestamp(accountId: string, timestamp: number): Promise<string> {
|
|
34
|
+
const digest = AuthDigest.fromAccountIdWithTimestamp(accountId, timestamp);
|
|
35
|
+
return this.signWord(digest);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async signCommitment(commitmentHex: string): Promise<string> {
|
|
39
|
+
const word = AuthDigest.fromCommitmentHex(commitmentHex);
|
|
40
|
+
return this.signWord(word);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async signWord(word: { toHex: () => string; toFelts: () => Array<{ asInt: () => bigint }> }): Promise<string> {
|
|
44
|
+
const wordBytes = wordToBytes(word);
|
|
45
|
+
const hashedHex = EcdsaFormat.keccakDigestHex(wordBytes);
|
|
46
|
+
const cleanHex = hashedHex.slice(2);
|
|
47
|
+
const messageBase64 = uint8ArrayToBase64(hexToBytes(cleanHex));
|
|
48
|
+
|
|
49
|
+
const res = await this.para.signMessage({
|
|
50
|
+
walletId: this.walletId,
|
|
51
|
+
messageBase64,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!res.signature) {
|
|
55
|
+
throw new Error('Para signing was denied by user');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return EcdsaFormat.normalizeRecoveryByte(res.signature);
|
|
59
|
+
}
|
|
60
|
+
}
|