@openzeppelin/miden-multisig-client 0.12.6
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 +189 -0
- package/dist/account/builder.d.ts +23 -0
- package/dist/account/builder.d.ts.map +1 -0
- package/dist/account/builder.js +85 -0
- package/dist/account/builder.js.map +1 -0
- package/dist/account/index.d.ts +6 -0
- package/dist/account/index.d.ts.map +1 -0
- package/dist/account/index.js +6 -0
- package/dist/account/index.js.map +1 -0
- package/dist/account/masm.d.ts +3 -0
- package/dist/account/masm.d.ts.map +1 -0
- package/dist/account/masm.js +605 -0
- package/dist/account/masm.js.map +1 -0
- package/dist/account/storage.d.ts +10 -0
- package/dist/account/storage.d.ts.map +1 -0
- package/dist/account/storage.js +56 -0
- package/dist/account/storage.js.map +1 -0
- package/dist/account.d.ts +104 -0
- package/dist/account.d.ts.map +1 -0
- package/dist/account.js +136 -0
- package/dist/account.js.map +1 -0
- package/dist/builder.d.ts +54 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +78 -0
- package/dist/builder.js.map +1 -0
- package/dist/client.d.ts +73 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +104 -0
- package/dist/client.js.map +1 -0
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/client.test.js +150 -0
- package/dist/client.test.js.map +1 -0
- package/dist/http.d.ts +64 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +162 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector.d.ts +49 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +120 -0
- package/dist/inspector.js.map +1 -0
- package/dist/inspector.test.d.ts +2 -0
- package/dist/inspector.test.d.ts.map +1 -0
- package/dist/inspector.test.js +205 -0
- package/dist/inspector.test.js.map +1 -0
- package/dist/keystore.d.ts +65 -0
- package/dist/keystore.d.ts.map +1 -0
- package/dist/keystore.js +121 -0
- package/dist/keystore.js.map +1 -0
- package/dist/miden.d.ts +9 -0
- package/dist/miden.d.ts.map +1 -0
- package/dist/miden.js +26 -0
- package/dist/miden.js.map +1 -0
- package/dist/multisig/helpers.d.ts +4 -0
- package/dist/multisig/helpers.d.ts.map +1 -0
- package/dist/multisig/helpers.js +21 -0
- package/dist/multisig/helpers.js.map +1 -0
- package/dist/multisig/helpers.test.d.ts +2 -0
- package/dist/multisig/helpers.test.d.ts.map +1 -0
- package/dist/multisig/helpers.test.js +94 -0
- package/dist/multisig/helpers.test.js.map +1 -0
- package/dist/multisig/metadata.d.ts +18 -0
- package/dist/multisig/metadata.d.ts.map +1 -0
- package/dist/multisig/metadata.js +100 -0
- package/dist/multisig/metadata.js.map +1 -0
- package/dist/multisig/metadata.test.d.ts +2 -0
- package/dist/multisig/metadata.test.d.ts.map +1 -0
- package/dist/multisig/metadata.test.js +323 -0
- package/dist/multisig/metadata.test.js.map +1 -0
- package/dist/multisig.d.ts +199 -0
- package/dist/multisig.d.ts.map +1 -0
- package/dist/multisig.js +821 -0
- package/dist/multisig.js.map +1 -0
- package/dist/multisig.test.d.ts +2 -0
- package/dist/multisig.test.d.ts.map +1 -0
- package/dist/multisig.test.js +1121 -0
- package/dist/multisig.test.js.map +1 -0
- package/dist/procedures.d.ts +57 -0
- package/dist/procedures.d.ts.map +1 -0
- package/dist/procedures.js +62 -0
- package/dist/procedures.js.map +1 -0
- package/dist/signer.d.ts +36 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signer.js +68 -0
- package/dist/signer.js.map +1 -0
- package/dist/signer.test.d.ts +2 -0
- package/dist/signer.test.d.ts.map +1 -0
- package/dist/signer.test.js +117 -0
- package/dist/signer.test.js.map +1 -0
- package/dist/transaction/consumeNotes.d.ts +7 -0
- package/dist/transaction/consumeNotes.d.ts.map +1 -0
- package/dist/transaction/consumeNotes.js +28 -0
- package/dist/transaction/consumeNotes.js.map +1 -0
- package/dist/transaction/index.d.ts +7 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +6 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/options.d.ts +6 -0
- package/dist/transaction/options.d.ts.map +1 -0
- package/dist/transaction/options.js +2 -0
- package/dist/transaction/options.js.map +1 -0
- package/dist/transaction/p2id.d.ts +7 -0
- package/dist/transaction/p2id.d.ts.map +1 -0
- package/dist/transaction/p2id.js +43 -0
- package/dist/transaction/p2id.js.map +1 -0
- package/dist/transaction/summary.d.ts +3 -0
- package/dist/transaction/summary.d.ts.map +1 -0
- package/dist/transaction/summary.js +6 -0
- package/dist/transaction/summary.js.map +1 -0
- package/dist/transaction/updatePsm.d.ts +7 -0
- package/dist/transaction/updatePsm.d.ts.map +1 -0
- package/dist/transaction/updatePsm.js +43 -0
- package/dist/transaction/updatePsm.js.map +1 -0
- package/dist/transaction/updateSigners.d.ts +8 -0
- package/dist/transaction/updateSigners.d.ts.map +1 -0
- package/dist/transaction/updateSigners.js +60 -0
- package/dist/transaction/updateSigners.js.map +1 -0
- package/dist/transaction.d.ts +6 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +8 -0
- package/dist/transaction.js.map +1 -0
- package/dist/transaction.test.d.ts +2 -0
- package/dist/transaction.test.d.ts.map +1 -0
- package/dist/transaction.test.js +117 -0
- package/dist/transaction.test.js.map +1 -0
- package/dist/transport/http.d.ts +64 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +162 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/index.d.ts +5 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +5 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/types/proposal.d.ts +74 -0
- package/dist/types/proposal.d.ts.map +1 -0
- package/dist/types/proposal.js +2 -0
- package/dist/types/proposal.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/encoding.d.ts +7 -0
- package/dist/utils/encoding.d.ts.map +1 -0
- package/dist/utils/encoding.js +54 -0
- package/dist/utils/encoding.js.map +1 -0
- package/dist/utils/encoding.test.d.ts +2 -0
- package/dist/utils/encoding.test.d.ts.map +1 -0
- package/dist/utils/encoding.test.js +185 -0
- package/dist/utils/encoding.test.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/random.d.ts +3 -0
- package/dist/utils/random.d.ts.map +1 -0
- package/dist/utils/random.js +14 -0
- package/dist/utils/random.js.map +1 -0
- package/dist/utils/signature.d.ts +12 -0
- package/dist/utils/signature.d.ts.map +1 -0
- package/dist/utils/signature.js +28 -0
- package/dist/utils/signature.js.map +1 -0
- package/dist/utils/signature.test.d.ts +2 -0
- package/dist/utils/signature.test.d.ts.map +1 -0
- package/dist/utils/signature.test.js +149 -0
- package/dist/utils/signature.test.js.map +1 -0
- package/dist/utils/word.d.ts +4 -0
- package/dist/utils/word.d.ts.map +1 -0
- package/dist/utils/word.js +8 -0
- package/dist/utils/word.js.map +1 -0
- package/dist/utils/word.test.d.ts +2 -0
- package/dist/utils/word.test.d.ts.map +1 -0
- package/dist/utils/word.test.js +54 -0
- package/dist/utils/word.test.js.map +1 -0
- package/masm/multisig.masm +423 -0
- package/masm/psm.masm +178 -0
- package/package.json +42 -0
- package/src/account/builder.ts +112 -0
- package/src/account/index.ts +15 -0
- package/src/account/masm.ts +606 -0
- package/src/account/storage.ts +70 -0
- package/src/client.test.ts +177 -0
- package/src/client.ts +129 -0
- package/src/index.ts +118 -0
- package/src/inspector.test.ts +237 -0
- package/src/inspector.ts +142 -0
- package/src/multisig/helpers.test.ts +114 -0
- package/src/multisig/helpers.ts +23 -0
- package/src/multisig.test.ts +1294 -0
- package/src/multisig.ts +1097 -0
- package/src/procedures.ts +72 -0
- package/src/signer.test.ts +135 -0
- package/src/signer.ts +75 -0
- package/src/transaction/consumeNotes.ts +41 -0
- package/src/transaction/index.ts +7 -0
- package/src/transaction/options.ts +7 -0
- package/src/transaction/p2id.ts +105 -0
- package/src/transaction/summary.ts +12 -0
- package/src/transaction/updatePsm.ts +69 -0
- package/src/transaction/updateSigners.ts +96 -0
- package/src/transaction.test.ts +136 -0
- package/src/transaction.ts +16 -0
- package/src/types/proposal.ts +80 -0
- package/src/types.ts +81 -0
- package/src/utils/encoding.test.ts +233 -0
- package/src/utils/encoding.ts +60 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/random.ts +15 -0
- package/src/utils/signature.test.ts +187 -0
- package/src/utils/signature.ts +36 -0
- package/src/utils/word.test.ts +64 -0
- package/src/utils/word.ts +11 -0
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
*/
|
|
8
|
+
|
|
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 */
|
|
17
|
+
update_signers: '0x53d0ad381a193de0cf6af3730141e498274103dfc3b8c8e7367bd49d4a66c72b',
|
|
18
|
+
/** Authenticate transaction with multisig (Falcon512) */
|
|
19
|
+
auth_tx: '0x474c613b38001cc36d68557e9d881495d6a461a9027033445c7672c586509026',
|
|
20
|
+
|
|
21
|
+
// PSM component procedures
|
|
22
|
+
/** Update PSM public key */
|
|
23
|
+
update_psm: '0xb103236807e5bf09c27efc2c5287ca8b03ab9efc1d852b62ebd31f5f1927ec26',
|
|
24
|
+
/** Verify PSM signature */
|
|
25
|
+
verify_psm: '0x30727fc23c6105a678fea8b4c1920f35fa85c03f16a0cf98372c8f56701f8a87',
|
|
26
|
+
|
|
27
|
+
// BasicWallet procedures
|
|
28
|
+
/** Send assets from account (move_asset_to_note) */
|
|
29
|
+
send_asset: '0x0e406b067ed2bcd7de745ca6517f519fd1a9be245f913347ac673ca1db30c1d6',
|
|
30
|
+
/** Receive assets into account */
|
|
31
|
+
receive_asset: '0x6f4bdbdc4b13d7ed933d590d88ac9dfb98020c9e917697845b5e169395b76a01',
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Valid procedure names that can be used for threshold overrides.
|
|
36
|
+
*/
|
|
37
|
+
export type ProcedureName = keyof typeof PROCEDURE_ROOTS;
|
|
38
|
+
|
|
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];
|
|
53
|
+
}
|
|
54
|
+
|
|
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
|
+
export function isProcedureName(name: string): name is ProcedureName {
|
|
62
|
+
return name in PROCEDURE_ROOTS;
|
|
63
|
+
}
|
|
64
|
+
|
|
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[];
|
|
72
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { FalconSigner } from './signer.js';
|
|
3
|
+
|
|
4
|
+
// Mock the Miden SDK
|
|
5
|
+
vi.mock('@demox-labs/miden-sdk', () => {
|
|
6
|
+
const mockSignature = {
|
|
7
|
+
serialize: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const mockPublicKey = {
|
|
11
|
+
toCommitment: () => ({
|
|
12
|
+
toHex: () => '0x' + 'a'.repeat(64),
|
|
13
|
+
}),
|
|
14
|
+
serialize: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const mockSecretKey = {
|
|
18
|
+
publicKey: vi.fn().mockReturnValue(mockPublicKey),
|
|
19
|
+
sign: vi.fn().mockReturnValue(mockSignature),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
SecretKey: {
|
|
24
|
+
rpoFalconWithRNG: vi.fn().mockReturnValue(mockSecretKey),
|
|
25
|
+
},
|
|
26
|
+
Word: {
|
|
27
|
+
fromHex: vi.fn((hex: string) => ({
|
|
28
|
+
toHex: () => hex,
|
|
29
|
+
toFelts: () => [1, 2, 3, 4],
|
|
30
|
+
})),
|
|
31
|
+
},
|
|
32
|
+
AccountId: {
|
|
33
|
+
fromHex: vi.fn((hex: string) => ({
|
|
34
|
+
prefix: () => ({ asInt: () => BigInt(1) }),
|
|
35
|
+
suffix: () => ({ asInt: () => BigInt(2) }),
|
|
36
|
+
})),
|
|
37
|
+
},
|
|
38
|
+
Felt: vi.fn().mockImplementation((v: bigint) => ({ value: v })),
|
|
39
|
+
FeltArray: vi.fn().mockImplementation((arr: any[]) => arr),
|
|
40
|
+
Rpo256: {
|
|
41
|
+
hashElements: vi.fn().mockReturnValue({
|
|
42
|
+
toHex: () => '0x' + 'b'.repeat(64),
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('FalconSigner', () => {
|
|
49
|
+
let mockSecretKey: any;
|
|
50
|
+
let signer: FalconSigner;
|
|
51
|
+
|
|
52
|
+
beforeEach(async () => {
|
|
53
|
+
vi.clearAllMocks();
|
|
54
|
+
const { SecretKey } = await import('@demox-labs/miden-sdk');
|
|
55
|
+
mockSecretKey = SecretKey.rpoFalconWithRNG(new Uint8Array(32));
|
|
56
|
+
signer = new FalconSigner(mockSecretKey);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('constructor', () => {
|
|
60
|
+
it('extracts commitment from public key', () => {
|
|
61
|
+
expect(signer.commitment).toBe('0x' + 'a'.repeat(64));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('extracts public key without first byte', () => {
|
|
65
|
+
// Public key serialized is [0, 1, 2, ...15], slice(1) removes first byte
|
|
66
|
+
expect(signer.publicKey).toBe('0x0102030405060708090a0b0c0d0e0f');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('calls publicKey method on secret key', () => {
|
|
70
|
+
expect(mockSecretKey.publicKey).toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('signAccountIdWithTimestamp', () => {
|
|
75
|
+
it('handles account ID with 0x prefix', () => {
|
|
76
|
+
const signature = signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
77
|
+
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('handles account ID without 0x prefix', () => {
|
|
81
|
+
const signature = signer.signAccountIdWithTimestamp('a'.repeat(30), 1700000000);
|
|
82
|
+
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('calls sign method with hashed digest', () => {
|
|
86
|
+
signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
87
|
+
expect(mockSecretKey.sign).toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('returns signature without first byte', () => {
|
|
91
|
+
// Signature serialized is [0, 1, 2, ...9], slice(1) returns [1, 2, ...9]
|
|
92
|
+
const signature = signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
93
|
+
expect(signature).toBe('0x010203040506070809');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('includes timestamp in signed payload', async () => {
|
|
97
|
+
const { Felt, FeltArray } = await import('@demox-labs/miden-sdk');
|
|
98
|
+
signer.signAccountIdWithTimestamp('0x' + 'a'.repeat(30), 1700000000);
|
|
99
|
+
// Verify FeltArray was called with timestamp in the third position
|
|
100
|
+
expect(FeltArray).toHaveBeenCalledWith(expect.arrayContaining([
|
|
101
|
+
expect.anything(), // prefix
|
|
102
|
+
expect.anything(), // suffix
|
|
103
|
+
expect.anything(), // timestamp
|
|
104
|
+
expect.anything(), // zero padding
|
|
105
|
+
]));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('signCommitment', () => {
|
|
110
|
+
it('handles commitment with 0x prefix', () => {
|
|
111
|
+
const signature = signer.signCommitment('0x' + 'c'.repeat(64));
|
|
112
|
+
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('handles commitment without 0x prefix', () => {
|
|
116
|
+
const signature = signer.signCommitment('c'.repeat(64));
|
|
117
|
+
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('pads short commitment hex to 64 characters', () => {
|
|
121
|
+
const signature = signer.signCommitment('abc');
|
|
122
|
+
expect(signature).toMatch(/^0x[a-f0-9]+$/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('calls sign method with word from hex', () => {
|
|
126
|
+
signer.signCommitment('0x' + 'c'.repeat(64));
|
|
127
|
+
expect(mockSecretKey.sign).toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('returns signature without first byte', () => {
|
|
131
|
+
const signature = signer.signCommitment('0x' + 'c'.repeat(64));
|
|
132
|
+
expect(signature).toBe('0x010203040506070809');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
package/src/signer.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import { SecretKey, Word, AccountId, Felt, FeltArray, Rpo256 } from '@demox-labs/miden-sdk';
|
|
9
|
+
import type { Signer } from './types.js';
|
|
10
|
+
import { bytesToHex } from './utils/encoding.js';
|
|
11
|
+
|
|
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
|
+
export class FalconSigner implements Signer {
|
|
23
|
+
readonly commitment: string;
|
|
24
|
+
readonly publicKey: string;
|
|
25
|
+
private readonly secretKey: SecretKey;
|
|
26
|
+
|
|
27
|
+
constructor(secretKey: SecretKey) {
|
|
28
|
+
this.secretKey = secretKey;
|
|
29
|
+
const pubKey = secretKey.publicKey();
|
|
30
|
+
this.commitment = pubKey.toCommitment().toHex();
|
|
31
|
+
const serialized = pubKey.serialize();
|
|
32
|
+
const falconPubKey = serialized.slice(1);
|
|
33
|
+
this.publicKey = bytesToHex(falconPubKey);
|
|
34
|
+
}
|
|
35
|
+
|
|
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
|
+
signAccountIdWithTimestamp(accountId: string, timestamp: number): string {
|
|
43
|
+
// Parse the account ID from hex
|
|
44
|
+
const paddedHex = accountId.startsWith('0x') ? accountId : `0x${accountId}`;
|
|
45
|
+
const parsedAccountId = AccountId.fromHex(paddedHex);
|
|
46
|
+
const prefix = parsedAccountId.prefix();
|
|
47
|
+
const suffix = parsedAccountId.suffix();
|
|
48
|
+
|
|
49
|
+
const feltArray = new FeltArray([
|
|
50
|
+
prefix,
|
|
51
|
+
suffix,
|
|
52
|
+
new Felt(BigInt(timestamp)),
|
|
53
|
+
new Felt(BigInt(0)),
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const digest = Rpo256.hashElements(feltArray);
|
|
57
|
+
const signature = this.secretKey.sign(digest);
|
|
58
|
+
const signatureBytes = signature.serialize();
|
|
59
|
+
const falconSignature = signatureBytes.slice(1);
|
|
60
|
+
return bytesToHex(falconSignature);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Signs a commitment/word for proposal signing.
|
|
65
|
+
*/
|
|
66
|
+
signCommitment(commitmentHex: string): string {
|
|
67
|
+
const paddedHex = commitmentHex.startsWith('0x') ? commitmentHex : `0x${commitmentHex}`;
|
|
68
|
+
const cleanHex = paddedHex.slice(2).padStart(64, '0');
|
|
69
|
+
const word = Word.fromHex(`0x${cleanHex}`);
|
|
70
|
+
const signature = this.secretKey.sign(word);
|
|
71
|
+
const signatureBytes = signature.serialize();
|
|
72
|
+
const falconSignature = signatureBytes.slice(1);
|
|
73
|
+
return bytesToHex(falconSignature);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { TransactionRequest, Word } from '@demox-labs/miden-sdk';
|
|
2
|
+
import { NoteId, NoteIdAndArgs, NoteIdAndArgsArray, TransactionRequestBuilder, Word as WordType } from '@demox-labs/miden-sdk';
|
|
3
|
+
import { randomWord } from '../utils/random.js';
|
|
4
|
+
import { normalizeHexWord } from '../utils/encoding.js';
|
|
5
|
+
import type { SignatureOptions } from './options.js';
|
|
6
|
+
|
|
7
|
+
export function buildConsumeNotesTransactionRequest(
|
|
8
|
+
noteIds: string[],
|
|
9
|
+
options: SignatureOptions = {},
|
|
10
|
+
): { request: TransactionRequest; salt: Word } {
|
|
11
|
+
if (noteIds.length === 0) {
|
|
12
|
+
throw new Error('At least one note ID is required');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const noteIdAndArgsArray = new NoteIdAndArgsArray();
|
|
16
|
+
for (const noteIdHex of noteIds) {
|
|
17
|
+
const noteId = NoteId.fromHex(noteIdHex);
|
|
18
|
+
const noteIdAndArgs = new NoteIdAndArgs(noteId, null);
|
|
19
|
+
noteIdAndArgsArray.push(noteIdAndArgs);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
|
|
23
|
+
|
|
24
|
+
const authSaltForBuilder = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
25
|
+
|
|
26
|
+
let txBuilder = new TransactionRequestBuilder();
|
|
27
|
+
txBuilder = txBuilder.withAuthenticatedInputNotes(noteIdAndArgsArray);
|
|
28
|
+
txBuilder = txBuilder.withAuthArg(authSaltForBuilder);
|
|
29
|
+
|
|
30
|
+
if (options.signatureAdviceMap) {
|
|
31
|
+
txBuilder = txBuilder.extendAdviceMap(options.signatureAdviceMap);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const authSaltForReturn = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
request: txBuilder.build(),
|
|
38
|
+
salt: authSaltForReturn,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { executeForSummary } from './summary.js';
|
|
2
|
+
export { buildUpdateSignersTransactionRequest } from './updateSigners.js';
|
|
3
|
+
export { buildUpdatePsmTransactionRequest } from './updatePsm.js';
|
|
4
|
+
export { buildConsumeNotesTransactionRequest } from './consumeNotes.js';
|
|
5
|
+
export { buildP2idTransactionRequest } from './p2id.js';
|
|
6
|
+
export type { SignatureOptions } from './options.js';
|
|
7
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { TransactionRequest, Word } from '@demox-labs/miden-sdk';
|
|
2
|
+
import {
|
|
3
|
+
AccountId,
|
|
4
|
+
Felt,
|
|
5
|
+
FeltArray,
|
|
6
|
+
FungibleAsset,
|
|
7
|
+
MidenArrays,
|
|
8
|
+
Note,
|
|
9
|
+
NoteAssets,
|
|
10
|
+
NoteExecutionHint,
|
|
11
|
+
NoteInputs,
|
|
12
|
+
NoteMetadata,
|
|
13
|
+
NoteRecipient,
|
|
14
|
+
NoteScript,
|
|
15
|
+
NoteTag,
|
|
16
|
+
NoteType,
|
|
17
|
+
OutputNote,
|
|
18
|
+
Rpo256,
|
|
19
|
+
TransactionRequestBuilder,
|
|
20
|
+
Word as WordType,
|
|
21
|
+
} from '@demox-labs/miden-sdk';
|
|
22
|
+
import { randomWord } from '../utils/random.js';
|
|
23
|
+
import { normalizeHexWord } from '../utils/encoding.js';
|
|
24
|
+
import type { SignatureOptions } from './options.js';
|
|
25
|
+
|
|
26
|
+
function buildP2idNote(
|
|
27
|
+
sender: AccountId,
|
|
28
|
+
recipient: AccountId,
|
|
29
|
+
noteAssets: NoteAssets,
|
|
30
|
+
noteType: NoteType,
|
|
31
|
+
aux: Felt,
|
|
32
|
+
saltHex: string,
|
|
33
|
+
): Note {
|
|
34
|
+
const salt = WordType.fromHex(normalizeHexWord(saltHex));
|
|
35
|
+
const serialNum = Rpo256.hashElements(new FeltArray([
|
|
36
|
+
...salt.toFelts(),
|
|
37
|
+
new Felt(0n),
|
|
38
|
+
]));
|
|
39
|
+
|
|
40
|
+
const noteScript = NoteScript.p2id();
|
|
41
|
+
const noteInputs = new NoteInputs(new FeltArray([
|
|
42
|
+
recipient.suffix(),
|
|
43
|
+
recipient.prefix(),
|
|
44
|
+
]));
|
|
45
|
+
|
|
46
|
+
const noteRecipient = new NoteRecipient(serialNum, noteScript, noteInputs);
|
|
47
|
+
const noteTag = NoteTag.fromAccountId(recipient);
|
|
48
|
+
|
|
49
|
+
const noteMetadata = new NoteMetadata(
|
|
50
|
+
sender,
|
|
51
|
+
noteType,
|
|
52
|
+
noteTag,
|
|
53
|
+
NoteExecutionHint.always(),
|
|
54
|
+
aux,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return new Note(noteAssets, noteMetadata, noteRecipient);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildP2idTransactionRequest(
|
|
61
|
+
senderId: string,
|
|
62
|
+
recipientId: string,
|
|
63
|
+
faucetId: string,
|
|
64
|
+
amount: bigint,
|
|
65
|
+
options: SignatureOptions = {},
|
|
66
|
+
): { request: TransactionRequest; salt: Word } {
|
|
67
|
+
const sender = AccountId.fromHex(senderId);
|
|
68
|
+
const recipient = AccountId.fromHex(recipientId);
|
|
69
|
+
const faucet = AccountId.fromHex(faucetId);
|
|
70
|
+
|
|
71
|
+
const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
|
|
72
|
+
|
|
73
|
+
const asset = new FungibleAsset(faucet, amount);
|
|
74
|
+
const noteAssets = new NoteAssets([asset]);
|
|
75
|
+
|
|
76
|
+
const note = buildP2idNote(
|
|
77
|
+
sender,
|
|
78
|
+
recipient,
|
|
79
|
+
noteAssets,
|
|
80
|
+
NoteType.Public,
|
|
81
|
+
new Felt(0n),
|
|
82
|
+
authSaltHex,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const outputNote = OutputNote.full(note);
|
|
86
|
+
const outputNotes = new MidenArrays.OutputNoteArray([outputNote]);
|
|
87
|
+
|
|
88
|
+
const authSaltForBuilder = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
89
|
+
|
|
90
|
+
let txBuilder = new TransactionRequestBuilder();
|
|
91
|
+
txBuilder = txBuilder.withOwnOutputNotes(outputNotes);
|
|
92
|
+
txBuilder = txBuilder.withAuthArg(authSaltForBuilder);
|
|
93
|
+
|
|
94
|
+
if (options.signatureAdviceMap) {
|
|
95
|
+
txBuilder = txBuilder.extendAdviceMap(options.signatureAdviceMap);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const authSaltForReturn = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
request: txBuilder.build(),
|
|
102
|
+
salt: authSaltForReturn,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TransactionRequest, TransactionSummary, WebClient } from '@demox-labs/miden-sdk';
|
|
2
|
+
import { AccountId } from '@demox-labs/miden-sdk';
|
|
3
|
+
|
|
4
|
+
export async function executeForSummary(
|
|
5
|
+
client: WebClient,
|
|
6
|
+
accountId: string,
|
|
7
|
+
txRequest: TransactionRequest,
|
|
8
|
+
): Promise<TransactionSummary> {
|
|
9
|
+
const acc = AccountId.fromHex(accountId);
|
|
10
|
+
return (client as any).executeForSummary(acc, txRequest);
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AdviceMap,
|
|
3
|
+
FeltArray,
|
|
4
|
+
TransactionRequest,
|
|
5
|
+
TransactionRequestBuilder,
|
|
6
|
+
TransactionScript,
|
|
7
|
+
WebClient,
|
|
8
|
+
Word,
|
|
9
|
+
Word as WordType,
|
|
10
|
+
} from '@demox-labs/miden-sdk';
|
|
11
|
+
import { PSM_MASM } from '../account/masm.js';
|
|
12
|
+
import { normalizeHexWord } from '../utils/encoding.js';
|
|
13
|
+
import { randomWord } from '../utils/random.js';
|
|
14
|
+
import type { SignatureOptions } from './options.js';
|
|
15
|
+
|
|
16
|
+
function buildUpdatePsmScript(webClient: WebClient): TransactionScript {
|
|
17
|
+
const libBuilder = webClient.createScriptBuilder();
|
|
18
|
+
const psmLib = libBuilder.buildLibrary('openzeppelin::psm', PSM_MASM);
|
|
19
|
+
libBuilder.linkDynamicLibrary(psmLib);
|
|
20
|
+
|
|
21
|
+
const scriptSource = `
|
|
22
|
+
use.openzeppelin::psm
|
|
23
|
+
|
|
24
|
+
begin
|
|
25
|
+
adv.push_mapval
|
|
26
|
+
dropw
|
|
27
|
+
call.psm::update_psm_public_key
|
|
28
|
+
end
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
return libBuilder.compileTxScript(scriptSource);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function buildUpdatePsmTransactionRequest(
|
|
35
|
+
webClient: WebClient,
|
|
36
|
+
newPsmPubkey: string,
|
|
37
|
+
options: SignatureOptions = {},
|
|
38
|
+
): Promise<{ request: TransactionRequest; salt: Word }> {
|
|
39
|
+
const script = buildUpdatePsmScript(webClient);
|
|
40
|
+
|
|
41
|
+
const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
|
|
42
|
+
|
|
43
|
+
const pubkeyWordForAdvice = WordType.fromHex(normalizeHexWord(newPsmPubkey));
|
|
44
|
+
const pubkeyWordForFelts = WordType.fromHex(normalizeHexWord(newPsmPubkey));
|
|
45
|
+
const pubkeyWordForScript = WordType.fromHex(normalizeHexWord(newPsmPubkey));
|
|
46
|
+
|
|
47
|
+
const advice = new AdviceMap();
|
|
48
|
+
advice.insert(pubkeyWordForAdvice, new FeltArray(pubkeyWordForFelts.toFelts()));
|
|
49
|
+
|
|
50
|
+
const authSaltForBuilder = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
51
|
+
|
|
52
|
+
let txBuilder = new TransactionRequestBuilder();
|
|
53
|
+
txBuilder = txBuilder.withCustomScript(script);
|
|
54
|
+
txBuilder = txBuilder.withScriptArg(pubkeyWordForScript);
|
|
55
|
+
txBuilder = txBuilder.extendAdviceMap(advice);
|
|
56
|
+
txBuilder = txBuilder.withAuthArg(authSaltForBuilder);
|
|
57
|
+
|
|
58
|
+
if (options.signatureAdviceMap) {
|
|
59
|
+
txBuilder = txBuilder.extendAdviceMap(options.signatureAdviceMap);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const authSaltForReturn = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
request: txBuilder.build(),
|
|
66
|
+
salt: authSaltForReturn,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AdviceMap,
|
|
3
|
+
Felt,
|
|
4
|
+
FeltArray,
|
|
5
|
+
Rpo256,
|
|
6
|
+
TransactionRequest,
|
|
7
|
+
TransactionRequestBuilder,
|
|
8
|
+
TransactionScript,
|
|
9
|
+
WebClient,
|
|
10
|
+
Word,
|
|
11
|
+
Word as WordType,
|
|
12
|
+
} from '@demox-labs/miden-sdk';
|
|
13
|
+
import { MULTISIG_MASM, PSM_MASM } from '../account/masm.js';
|
|
14
|
+
import { normalizeHexWord } from '../utils/encoding.js';
|
|
15
|
+
import { randomWord } from '../utils/random.js';
|
|
16
|
+
import type { SignatureOptions } from './options.js';
|
|
17
|
+
|
|
18
|
+
function buildMultisigConfigAdvice(
|
|
19
|
+
threshold: number,
|
|
20
|
+
signerCommitments: string[],
|
|
21
|
+
): { configHash: Word; payload: FeltArray } {
|
|
22
|
+
const numApprovers = signerCommitments.length;
|
|
23
|
+
const felts: Felt[] = [
|
|
24
|
+
new Felt(BigInt(threshold)),
|
|
25
|
+
new Felt(BigInt(numApprovers)),
|
|
26
|
+
new Felt(0n),
|
|
27
|
+
new Felt(0n),
|
|
28
|
+
];
|
|
29
|
+
for (const commitment of [...signerCommitments].reverse()) {
|
|
30
|
+
const word = WordType.fromHex(normalizeHexWord(commitment));
|
|
31
|
+
felts.push(...word.toFelts());
|
|
32
|
+
}
|
|
33
|
+
const payload = new FeltArray(felts);
|
|
34
|
+
const configHash = Rpo256.hashElements(payload);
|
|
35
|
+
return { configHash, payload };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildUpdateSignersScript(webClient: WebClient): TransactionScript {
|
|
39
|
+
const libBuilder = webClient.createScriptBuilder();
|
|
40
|
+
const psmLib = libBuilder.buildLibrary('openzeppelin::psm', PSM_MASM);
|
|
41
|
+
libBuilder.linkStaticLibrary(psmLib);
|
|
42
|
+
|
|
43
|
+
const multisigLib = libBuilder.buildLibrary('auth::multisig', MULTISIG_MASM);
|
|
44
|
+
libBuilder.linkDynamicLibrary(multisigLib);
|
|
45
|
+
|
|
46
|
+
const scriptSource = `
|
|
47
|
+
use.auth::multisig
|
|
48
|
+
|
|
49
|
+
begin
|
|
50
|
+
call.multisig::update_signers_and_threshold
|
|
51
|
+
end
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
return libBuilder.compileTxScript(scriptSource);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function buildUpdateSignersTransactionRequest(
|
|
58
|
+
webClient: WebClient,
|
|
59
|
+
threshold: number,
|
|
60
|
+
signerCommitments: string[],
|
|
61
|
+
options: SignatureOptions = {},
|
|
62
|
+
): Promise<{ request: TransactionRequest; salt: Word; configHash: Word }> {
|
|
63
|
+
const { configHash: configHashForAdvice, payload } = buildMultisigConfigAdvice(threshold, signerCommitments);
|
|
64
|
+
|
|
65
|
+
const { configHash: configHashForScript } = buildMultisigConfigAdvice(threshold, signerCommitments);
|
|
66
|
+
|
|
67
|
+
const { configHash: configHashForReturn } = buildMultisigConfigAdvice(threshold, signerCommitments);
|
|
68
|
+
|
|
69
|
+
const advice = new AdviceMap();
|
|
70
|
+
advice.insert(configHashForAdvice, payload);
|
|
71
|
+
|
|
72
|
+
const script = buildUpdateSignersScript(webClient);
|
|
73
|
+
|
|
74
|
+
const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
|
|
75
|
+
|
|
76
|
+
const authSaltForBuilder = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
77
|
+
|
|
78
|
+
let txBuilder = new TransactionRequestBuilder();
|
|
79
|
+
txBuilder = txBuilder.withCustomScript(script);
|
|
80
|
+
txBuilder = txBuilder.withScriptArg(configHashForScript);
|
|
81
|
+
txBuilder = txBuilder.extendAdviceMap(advice);
|
|
82
|
+
txBuilder = txBuilder.withAuthArg(authSaltForBuilder);
|
|
83
|
+
|
|
84
|
+
if (options.signatureAdviceMap) {
|
|
85
|
+
txBuilder = txBuilder.extendAdviceMap(options.signatureAdviceMap);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const authSaltForReturn = WordType.fromHex(normalizeHexWord(authSaltHex));
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
request: txBuilder.build(),
|
|
92
|
+
salt: authSaltForReturn,
|
|
93
|
+
configHash: configHashForReturn,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|