@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,136 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeAll } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// Mock Miden SDK before importing transaction module
|
|
4
|
+
vi.mock('@demox-labs/miden-sdk', () => ({
|
|
5
|
+
AccountId: { fromHex: vi.fn() },
|
|
6
|
+
AdviceMap: vi.fn(),
|
|
7
|
+
Felt: vi.fn().mockImplementation((v) => ({ value: v })),
|
|
8
|
+
FeltArray: vi.fn().mockImplementation((arr) => arr),
|
|
9
|
+
Rpo256: { hashElements: vi.fn() },
|
|
10
|
+
Signature: { deserialize: vi.fn() },
|
|
11
|
+
TransactionRequestBuilder: vi.fn().mockImplementation(() => ({
|
|
12
|
+
withCustomScript: vi.fn().mockReturnThis(),
|
|
13
|
+
withScriptArg: vi.fn().mockReturnThis(),
|
|
14
|
+
extendAdviceMap: vi.fn().mockReturnThis(),
|
|
15
|
+
withAuthArg: vi.fn().mockReturnThis(),
|
|
16
|
+
build: vi.fn().mockReturnValue({}),
|
|
17
|
+
})),
|
|
18
|
+
Word: {
|
|
19
|
+
fromHex: vi.fn().mockImplementation((hex: string) => ({
|
|
20
|
+
toHex: () => hex,
|
|
21
|
+
toFelts: () => [1, 2, 3, 4],
|
|
22
|
+
})),
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Now import the module under test - use dynamic import to ensure mocks are in place
|
|
27
|
+
let normalizeHexWord: typeof import('./utils/encoding.js').normalizeHexWord;
|
|
28
|
+
let signatureHexToBytes: typeof import('./utils/signature.js').signatureHexToBytes;
|
|
29
|
+
let hexToBytes: typeof import('./utils/encoding.js').hexToBytes;
|
|
30
|
+
|
|
31
|
+
beforeAll(async () => {
|
|
32
|
+
const encoding = await import('./utils/encoding.js');
|
|
33
|
+
const sig = await import('./utils/signature.js');
|
|
34
|
+
normalizeHexWord = encoding.normalizeHexWord;
|
|
35
|
+
hexToBytes = encoding.hexToBytes;
|
|
36
|
+
signatureHexToBytes = sig.signatureHexToBytes;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('transaction utilities', () => {
|
|
40
|
+
describe('normalizeHexWord', () => {
|
|
41
|
+
it('should handle hex without 0x prefix', () => {
|
|
42
|
+
const result = normalizeHexWord('abc123');
|
|
43
|
+
expect(result).toBe('0x' + '0'.repeat(58) + 'abc123');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should handle hex with 0x prefix', () => {
|
|
47
|
+
const result = normalizeHexWord('0xabc123');
|
|
48
|
+
expect(result).toBe('0x' + '0'.repeat(58) + 'abc123');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should handle hex with 0X prefix', () => {
|
|
52
|
+
const result = normalizeHexWord('0Xabc123');
|
|
53
|
+
expect(result).toBe('0x' + '0'.repeat(58) + 'abc123');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should convert to lowercase', () => {
|
|
57
|
+
const result = normalizeHexWord('0xABC123DEF');
|
|
58
|
+
expect(result).toBe('0x' + '0'.repeat(55) + 'abc123def');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should pad to 64 characters', () => {
|
|
62
|
+
const result = normalizeHexWord('1');
|
|
63
|
+
expect(result).toBe('0x' + '0'.repeat(63) + '1');
|
|
64
|
+
expect(result.length).toBe(66); // 0x + 64 chars
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should preserve full 64-char hex', () => {
|
|
68
|
+
const fullHex = 'a'.repeat(64);
|
|
69
|
+
const result = normalizeHexWord(fullHex);
|
|
70
|
+
expect(result).toBe('0x' + fullHex);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should preserve full 64-char hex with prefix', () => {
|
|
74
|
+
const fullHex = 'b'.repeat(64);
|
|
75
|
+
const result = normalizeHexWord('0x' + fullHex);
|
|
76
|
+
expect(result).toBe('0x' + fullHex);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('hexToBytes', () => {
|
|
81
|
+
it('should convert hex string to Uint8Array', () => {
|
|
82
|
+
const result = hexToBytes('deadbeef');
|
|
83
|
+
expect(result).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should handle 0x prefix', () => {
|
|
87
|
+
const result = hexToBytes('0xdeadbeef');
|
|
88
|
+
expect(result).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle empty string', () => {
|
|
92
|
+
const result = hexToBytes('');
|
|
93
|
+
expect(result).toEqual(new Uint8Array([]));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should handle single byte', () => {
|
|
97
|
+
const result = hexToBytes('ff');
|
|
98
|
+
expect(result).toEqual(new Uint8Array([0xff]));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should handle zeros', () => {
|
|
102
|
+
const result = hexToBytes('00000000');
|
|
103
|
+
expect(result).toEqual(new Uint8Array([0, 0, 0, 0]));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle mixed case', () => {
|
|
107
|
+
const result = hexToBytes('DeAdBeEf');
|
|
108
|
+
expect(result).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('signatureHexToBytes', () => {
|
|
113
|
+
it('should prepend auth scheme byte (0 = RpoFalcon512)', () => {
|
|
114
|
+
const result = signatureHexToBytes('deadbeef');
|
|
115
|
+
expect(result[0]).toBe(0); // RpoFalcon512 scheme byte
|
|
116
|
+
expect(result.slice(1)).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle 0x prefix', () => {
|
|
120
|
+
const result = signatureHexToBytes('0xaabbccdd');
|
|
121
|
+
expect(result[0]).toBe(0);
|
|
122
|
+
expect(result.slice(1)).toEqual(new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd]));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return correct length', () => {
|
|
126
|
+
const sigHex = 'a'.repeat(128); // 64 bytes
|
|
127
|
+
const result = signatureHexToBytes(sigHex);
|
|
128
|
+
expect(result.length).toBe(65); // 64 bytes + 1 prefix byte
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should handle empty signature', () => {
|
|
132
|
+
const result = signatureHexToBytes('');
|
|
133
|
+
expect(result).toEqual(new Uint8Array([0])); // Just the prefix
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Backward-compatible entry point for transaction helpers.
|
|
2
|
+
// The implementations now live in ./transaction/.
|
|
3
|
+
export {
|
|
4
|
+
buildConsumeNotesTransactionRequest,
|
|
5
|
+
} from './transaction/consumeNotes.js';
|
|
6
|
+
export { executeForSummary } from './transaction/summary.js';
|
|
7
|
+
export {
|
|
8
|
+
buildP2idTransactionRequest,
|
|
9
|
+
} from './transaction/p2id.js';
|
|
10
|
+
export {
|
|
11
|
+
buildUpdatePsmTransactionRequest,
|
|
12
|
+
} from './transaction/updatePsm.js';
|
|
13
|
+
export {
|
|
14
|
+
buildUpdateSignersTransactionRequest,
|
|
15
|
+
} from './transaction/updateSigners.js';
|
|
16
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ProposalType as PsmProposalType } from '@openzeppelin/psm-client';
|
|
2
|
+
export type ProposalType = Exclude<PsmProposalType, 'custom'>;
|
|
3
|
+
|
|
4
|
+
export type ProposalStatus =
|
|
5
|
+
| { type: 'pending'; signaturesCollected: number; signaturesRequired: number; signers: string[] }
|
|
6
|
+
| { type: 'ready' }
|
|
7
|
+
| { type: 'finalized' };
|
|
8
|
+
|
|
9
|
+
export interface ProposalSignatureEntry {
|
|
10
|
+
signerId: string;
|
|
11
|
+
signature: { scheme: 'falcon'; signature: string };
|
|
12
|
+
timestamp: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface BaseProposalMetadata {
|
|
16
|
+
proposalType: ProposalType;
|
|
17
|
+
description: string;
|
|
18
|
+
saltHex?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UpdateSignersProposalMetadata extends BaseProposalMetadata {
|
|
22
|
+
proposalType: 'add_signer' | 'remove_signer' | 'change_threshold';
|
|
23
|
+
targetThreshold: number;
|
|
24
|
+
targetSignerCommitments: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SwitchPsmProposalMetadata extends BaseProposalMetadata {
|
|
28
|
+
proposalType: 'switch_psm';
|
|
29
|
+
newPsmPubkey: string;
|
|
30
|
+
newPsmEndpoint?: string;
|
|
31
|
+
targetThreshold?: number;
|
|
32
|
+
targetSignerCommitments?: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ConsumeNotesProposalMetadata extends BaseProposalMetadata {
|
|
36
|
+
proposalType: 'consume_notes';
|
|
37
|
+
noteIds: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface P2IdProposalMetadata extends BaseProposalMetadata {
|
|
41
|
+
proposalType: 'p2id';
|
|
42
|
+
recipientId: string;
|
|
43
|
+
faucetId: string;
|
|
44
|
+
amount: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface UnknownProposalMetadata extends BaseProposalMetadata {
|
|
48
|
+
proposalType: 'unknown';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ProposalMetadata =
|
|
52
|
+
| UpdateSignersProposalMetadata
|
|
53
|
+
| SwitchPsmProposalMetadata
|
|
54
|
+
| ConsumeNotesProposalMetadata
|
|
55
|
+
| P2IdProposalMetadata
|
|
56
|
+
| UnknownProposalMetadata;
|
|
57
|
+
|
|
58
|
+
export interface Proposal {
|
|
59
|
+
id: string;
|
|
60
|
+
accountId: string;
|
|
61
|
+
nonce: number;
|
|
62
|
+
status: ProposalStatus;
|
|
63
|
+
txSummary: string;
|
|
64
|
+
signatures: ProposalSignatureEntry[];
|
|
65
|
+
metadata: ProposalMetadata;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ExportedProposal {
|
|
69
|
+
accountId: string;
|
|
70
|
+
nonce: number;
|
|
71
|
+
commitment: string;
|
|
72
|
+
txSummaryBase64: string;
|
|
73
|
+
signatures: Array<{
|
|
74
|
+
commitment: string;
|
|
75
|
+
signatureHex: string;
|
|
76
|
+
timestamp?: string;
|
|
77
|
+
}>;
|
|
78
|
+
metadata?: ProposalMetadata;
|
|
79
|
+
}
|
|
80
|
+
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Account } from '@demox-labs/miden-sdk';
|
|
2
|
+
import type { ProcedureName } from './procedures.js';
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
Signer,
|
|
6
|
+
FalconSignature,
|
|
7
|
+
CosignerSignature,
|
|
8
|
+
AuthConfig,
|
|
9
|
+
DeltaStatus,
|
|
10
|
+
DeltaObject,
|
|
11
|
+
StateObject,
|
|
12
|
+
ConfigureRequest,
|
|
13
|
+
ConfigureResponse,
|
|
14
|
+
PubkeyResponse,
|
|
15
|
+
DeltaProposalRequest,
|
|
16
|
+
DeltaProposalResponse,
|
|
17
|
+
ProposalsResponse,
|
|
18
|
+
SignProposalRequest,
|
|
19
|
+
} from '@openzeppelin/psm-client';
|
|
20
|
+
|
|
21
|
+
export type {
|
|
22
|
+
ExportedProposal,
|
|
23
|
+
Proposal,
|
|
24
|
+
ProposalMetadata,
|
|
25
|
+
ProposalSignatureEntry,
|
|
26
|
+
ProposalStatus,
|
|
27
|
+
ProposalType,
|
|
28
|
+
} from './types/proposal.js';
|
|
29
|
+
|
|
30
|
+
export interface MultisigAccountState {
|
|
31
|
+
id: string;
|
|
32
|
+
nonce: number;
|
|
33
|
+
threshold: number;
|
|
34
|
+
cosignerCommitments: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Per-procedure threshold override.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const thresholds: ProcedureThreshold[] = [
|
|
43
|
+
* { procedure: 'receive_asset', threshold: 1 },
|
|
44
|
+
* { procedure: 'update_signers', threshold: 3 },
|
|
45
|
+
* ];
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export interface ProcedureThreshold {
|
|
49
|
+
procedure: ProcedureName;
|
|
50
|
+
/** Threshold for this procedure (1 to numSigners) */
|
|
51
|
+
threshold: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface MultisigConfig {
|
|
55
|
+
threshold: number;
|
|
56
|
+
signerCommitments: string[];
|
|
57
|
+
psmCommitment: string;
|
|
58
|
+
psmEnabled?: boolean;
|
|
59
|
+
storageMode?: 'private' | 'public';
|
|
60
|
+
procedureThresholds?: ProcedureThreshold[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CreateAccountResult {
|
|
64
|
+
account: Account;
|
|
65
|
+
seed: Uint8Array;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type TransactionType =
|
|
69
|
+
| { type: 'p2id'; recipient: string; faucetId: string; amount: bigint }
|
|
70
|
+
| { type: 'consumeNotes'; noteIds: string[] }
|
|
71
|
+
| { type: 'updateSigners'; newThreshold: number; newSignerCommitments: string[] };
|
|
72
|
+
|
|
73
|
+
export interface NoteAsset {
|
|
74
|
+
faucetId: string;
|
|
75
|
+
amount: bigint;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ConsumableNote {
|
|
79
|
+
id: string;
|
|
80
|
+
assets: NoteAsset[];
|
|
81
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
ensureHexPrefix,
|
|
4
|
+
normalizeHexWord,
|
|
5
|
+
bytesToHex,
|
|
6
|
+
hexToBytes,
|
|
7
|
+
uint8ArrayToBase64,
|
|
8
|
+
base64ToUint8Array,
|
|
9
|
+
} from './encoding.js';
|
|
10
|
+
|
|
11
|
+
describe('encoding utilities', () => {
|
|
12
|
+
describe('ensureHexPrefix', () => {
|
|
13
|
+
it('adds 0x prefix when missing', () => {
|
|
14
|
+
expect(ensureHexPrefix('abc123')).toBe('0xabc123');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('preserves existing 0x prefix', () => {
|
|
18
|
+
expect(ensureHexPrefix('0xabc123')).toBe('0xabc123');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('preserves existing 0X prefix', () => {
|
|
22
|
+
expect(ensureHexPrefix('0Xabc123')).toBe('0Xabc123');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('handles empty string', () => {
|
|
26
|
+
expect(ensureHexPrefix('')).toBe('0x');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('handles single character', () => {
|
|
30
|
+
expect(ensureHexPrefix('a')).toBe('0xa');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('normalizeHexWord', () => {
|
|
35
|
+
it('pads short hex to 64 characters', () => {
|
|
36
|
+
const result = normalizeHexWord('abc123');
|
|
37
|
+
expect(result).toBe('0x' + '0'.repeat(58) + 'abc123');
|
|
38
|
+
expect(result.length).toBe(66); // 0x + 64 chars
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('preserves full 64-char hex', () => {
|
|
42
|
+
const fullHex = 'a'.repeat(64);
|
|
43
|
+
expect(normalizeHexWord(fullHex)).toBe('0x' + fullHex);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('handles 0x prefix', () => {
|
|
47
|
+
expect(normalizeHexWord('0xabc')).toBe('0x' + '0'.repeat(61) + 'abc');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('handles 0X prefix', () => {
|
|
51
|
+
expect(normalizeHexWord('0Xabc')).toBe('0x' + '0'.repeat(61) + 'abc');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('converts to lowercase', () => {
|
|
55
|
+
expect(normalizeHexWord('ABC')).toBe('0x' + '0'.repeat(61) + 'abc');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('handles empty string', () => {
|
|
59
|
+
expect(normalizeHexWord('')).toBe('0x' + '0'.repeat(64));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('handles single digit', () => {
|
|
63
|
+
expect(normalizeHexWord('1')).toBe('0x' + '0'.repeat(63) + '1');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('handles full 64-char hex with prefix', () => {
|
|
67
|
+
const fullHex = 'b'.repeat(64);
|
|
68
|
+
expect(normalizeHexWord('0x' + fullHex)).toBe('0x' + fullHex);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('bytesToHex', () => {
|
|
73
|
+
it('converts Uint8Array to hex string', () => {
|
|
74
|
+
const bytes = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
|
|
75
|
+
expect(bytesToHex(bytes)).toBe('0xdeadbeef');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('handles empty array', () => {
|
|
79
|
+
expect(bytesToHex(new Uint8Array([]))).toBe('0x');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('handles single byte', () => {
|
|
83
|
+
expect(bytesToHex(new Uint8Array([0xff]))).toBe('0xff');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('pads single digit bytes with zero', () => {
|
|
87
|
+
const bytes = new Uint8Array([0x0a, 0x0b, 0x0c]);
|
|
88
|
+
expect(bytesToHex(bytes)).toBe('0x0a0b0c');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('handles zeros', () => {
|
|
92
|
+
const bytes = new Uint8Array([0x00, 0x00, 0x00]);
|
|
93
|
+
expect(bytesToHex(bytes)).toBe('0x000000');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('handles max byte value', () => {
|
|
97
|
+
const bytes = new Uint8Array([0xff, 0xff]);
|
|
98
|
+
expect(bytesToHex(bytes)).toBe('0xffff');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('handles long arrays', () => {
|
|
102
|
+
const bytes = new Uint8Array(32).fill(0xaa);
|
|
103
|
+
const result = bytesToHex(bytes);
|
|
104
|
+
expect(result).toBe('0x' + 'aa'.repeat(32));
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('hexToBytes', () => {
|
|
109
|
+
it('converts hex string to Uint8Array', () => {
|
|
110
|
+
expect(hexToBytes('deadbeef')).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('handles 0x prefix', () => {
|
|
114
|
+
expect(hexToBytes('0xdeadbeef')).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('handles 0X prefix', () => {
|
|
118
|
+
expect(hexToBytes('0Xdeadbeef')).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('handles empty string', () => {
|
|
122
|
+
expect(hexToBytes('')).toEqual(new Uint8Array([]));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('handles empty with prefix', () => {
|
|
126
|
+
expect(hexToBytes('0x')).toEqual(new Uint8Array([]));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('handles single byte', () => {
|
|
130
|
+
expect(hexToBytes('ff')).toEqual(new Uint8Array([0xff]));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('handles zeros', () => {
|
|
134
|
+
expect(hexToBytes('00000000')).toEqual(new Uint8Array([0, 0, 0, 0]));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('handles mixed case', () => {
|
|
138
|
+
expect(hexToBytes('DeAdBeEf')).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('handles long hex strings', () => {
|
|
142
|
+
const hex = 'aa'.repeat(32);
|
|
143
|
+
const result = hexToBytes(hex);
|
|
144
|
+
expect(result).toEqual(new Uint8Array(32).fill(0xaa));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('roundtrip bytesToHex/hexToBytes', () => {
|
|
149
|
+
it('survives roundtrip bytes -> hex -> bytes', () => {
|
|
150
|
+
const original = new Uint8Array([0xde, 0xad, 0xbe, 0xef, 0x01, 0x23, 0x45, 0x67]);
|
|
151
|
+
const hex = bytesToHex(original);
|
|
152
|
+
const roundtripped = hexToBytes(hex);
|
|
153
|
+
expect(roundtripped).toEqual(original);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('survives roundtrip hex -> bytes -> hex', () => {
|
|
157
|
+
const original = '0xdeadbeef01234567';
|
|
158
|
+
const bytes = hexToBytes(original);
|
|
159
|
+
const roundtripped = bytesToHex(bytes);
|
|
160
|
+
expect(roundtripped).toBe(original);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('uint8ArrayToBase64', () => {
|
|
165
|
+
it('converts Uint8Array to base64', () => {
|
|
166
|
+
const bytes = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
|
|
167
|
+
expect(uint8ArrayToBase64(bytes)).toBe('SGVsbG8=');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('handles empty array', () => {
|
|
171
|
+
expect(uint8ArrayToBase64(new Uint8Array([]))).toBe('');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('handles binary data', () => {
|
|
175
|
+
const bytes = new Uint8Array([0xff, 0x00, 0xaa, 0x55]);
|
|
176
|
+
const base64 = uint8ArrayToBase64(bytes);
|
|
177
|
+
expect(base64).toBeTruthy();
|
|
178
|
+
// Verify by round-tripping
|
|
179
|
+
expect(base64ToUint8Array(base64)).toEqual(bytes);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('handles single byte', () => {
|
|
183
|
+
const bytes = new Uint8Array([65]); // 'A'
|
|
184
|
+
expect(uint8ArrayToBase64(bytes)).toBe('QQ==');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('handles bytes requiring padding', () => {
|
|
188
|
+
const bytes = new Uint8Array([1]);
|
|
189
|
+
const base64 = uint8ArrayToBase64(bytes);
|
|
190
|
+
expect(base64).toBe('AQ==');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('base64ToUint8Array', () => {
|
|
195
|
+
it('converts base64 to Uint8Array', () => {
|
|
196
|
+
const base64 = 'SGVsbG8='; // "Hello"
|
|
197
|
+
expect(base64ToUint8Array(base64)).toEqual(new Uint8Array([72, 101, 108, 108, 111]));
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('handles empty string', () => {
|
|
201
|
+
expect(base64ToUint8Array('')).toEqual(new Uint8Array([]));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('handles binary data', () => {
|
|
205
|
+
const base64 = '/wCqVQ=='; // [0xff, 0x00, 0xaa, 0x55]
|
|
206
|
+
expect(base64ToUint8Array(base64)).toEqual(new Uint8Array([0xff, 0x00, 0xaa, 0x55]));
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('handles base64 without padding', () => {
|
|
210
|
+
const base64 = 'YWJj'; // "abc" - no padding needed
|
|
211
|
+
expect(base64ToUint8Array(base64)).toEqual(new Uint8Array([97, 98, 99]));
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('roundtrip uint8ArrayToBase64/base64ToUint8Array', () => {
|
|
216
|
+
it('survives roundtrip bytes -> base64 -> bytes', () => {
|
|
217
|
+
const original = new Uint8Array([0, 1, 2, 3, 255, 254, 253, 252]);
|
|
218
|
+
const base64 = uint8ArrayToBase64(original);
|
|
219
|
+
const roundtripped = base64ToUint8Array(base64);
|
|
220
|
+
expect(roundtripped).toEqual(original);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('survives roundtrip for large arrays', () => {
|
|
224
|
+
const original = new Uint8Array(1000);
|
|
225
|
+
for (let i = 0; i < 1000; i++) {
|
|
226
|
+
original[i] = i % 256;
|
|
227
|
+
}
|
|
228
|
+
const base64 = uint8ArrayToBase64(original);
|
|
229
|
+
const roundtripped = base64ToUint8Array(base64);
|
|
230
|
+
expect(roundtripped).toEqual(original);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function ensureHexPrefix(hex: string): string {
|
|
2
|
+
return hex.startsWith('0x') || hex.startsWith('0X') ? hex : `0x${hex}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function normalizeHexWord(hex: string): string {
|
|
6
|
+
let clean = ensureHexPrefix(hex).slice(2).toLowerCase();
|
|
7
|
+
clean = clean.padStart(64, '0');
|
|
8
|
+
return `0x${clean}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
12
|
+
let hex = '0x';
|
|
13
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
14
|
+
hex += bytes[i].toString(16).padStart(2, '0');
|
|
15
|
+
}
|
|
16
|
+
return hex;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function hexToBytes(hex: string): Uint8Array {
|
|
20
|
+
const cleanHex = ensureHexPrefix(hex).slice(2);
|
|
21
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
22
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
23
|
+
bytes[i] = parseInt(cleanHex.substring(i * 2, i * 2 + 2), 16);
|
|
24
|
+
}
|
|
25
|
+
return bytes;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|
29
|
+
if (typeof btoa === 'function') {
|
|
30
|
+
let binary = '';
|
|
31
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
32
|
+
binary += String.fromCharCode(bytes[i]);
|
|
33
|
+
}
|
|
34
|
+
return btoa(binary);
|
|
35
|
+
}
|
|
36
|
+
const buf: any = (globalThis as any).Buffer;
|
|
37
|
+
if (buf) {
|
|
38
|
+
return buf.from(bytes).toString('base64');
|
|
39
|
+
}
|
|
40
|
+
throw new Error('No base64 encoder available in this environment');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function base64ToUint8Array(base64: string): Uint8Array {
|
|
44
|
+
const binary =
|
|
45
|
+
typeof atob === 'function'
|
|
46
|
+
? atob(base64)
|
|
47
|
+
: (() => {
|
|
48
|
+
const buf: any = (globalThis as any).Buffer;
|
|
49
|
+
if (buf) {
|
|
50
|
+
return buf.from(base64, 'base64').toString('binary');
|
|
51
|
+
}
|
|
52
|
+
throw new Error('No base64 decoder available in this environment');
|
|
53
|
+
})();
|
|
54
|
+
const bytes = new Uint8Array(binary.length);
|
|
55
|
+
for (let i = 0; i < binary.length; i++) {
|
|
56
|
+
bytes[i] = binary.charCodeAt(i);
|
|
57
|
+
}
|
|
58
|
+
return bytes;
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Word } from '@demox-labs/miden-sdk';
|
|
2
|
+
|
|
3
|
+
export function randomWord(): Word {
|
|
4
|
+
const bytes = new Uint8Array(32);
|
|
5
|
+
crypto.getRandomValues(bytes);
|
|
6
|
+
const view = new DataView(bytes.buffer);
|
|
7
|
+
const u64s = new BigUint64Array([
|
|
8
|
+
view.getBigUint64(0, true),
|
|
9
|
+
view.getBigUint64(8, true),
|
|
10
|
+
view.getBigUint64(16, true),
|
|
11
|
+
view.getBigUint64(24, true),
|
|
12
|
+
]);
|
|
13
|
+
return new Word(u64s);
|
|
14
|
+
}
|
|
15
|
+
|