hd-wallet-wasm 0.1.0 → 0.1.5

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/dist/hd-wallet.js CHANGED
Binary file
Binary file
package/dist/index.d.ts CHANGED
@@ -9,6 +9,9 @@
9
9
  * - Transaction building and signing
10
10
  */
11
11
 
12
+ // Import aligned API types
13
+ import { AlignedAPI } from './aligned';
14
+
12
15
  // =============================================================================
13
16
  // Module Types
14
17
  // =============================================================================
@@ -62,6 +65,9 @@ export interface HDWalletModule {
62
65
 
63
66
  // Utilities
64
67
  utils: UtilsAPI;
68
+
69
+ // Aligned binary API for efficient batch operations
70
+ aligned: AlignedAPI;
65
71
  }
66
72
 
67
73
  // =============================================================================
@@ -600,10 +606,12 @@ export interface UtilsAPI {
600
606
 
601
607
  /**
602
608
  * Initialize the HD Wallet WASM module
609
+ * @param wasmPath - Optional path to WASM file
603
610
  */
604
- export default function init(): Promise<HDWalletModule>;
611
+ export default function init(wasmPath?: string): Promise<HDWalletModule>;
605
612
 
606
613
  /**
607
614
  * Create HD Wallet instance (alternative syntax)
615
+ * @param wasmPath - Optional path to WASM file
608
616
  */
609
- export function createHDWallet(): Promise<HDWalletModule>;
617
+ export function createHDWallet(wasmPath?: string): Promise<HDWalletModule>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-wallet-wasm",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "description": "Comprehensive HD Wallet implementation in WebAssembly - BIP-32/39/44, multi-curve, multi-chain support",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
@@ -13,6 +13,10 @@
13
13
  "import": "./src/index.mjs",
14
14
  "require": "./dist/hd-wallet.cjs"
15
15
  },
16
+ "./aligned": {
17
+ "types": "./src/aligned.d.ts",
18
+ "import": "./src/aligned.mjs"
19
+ },
16
20
  "./wasm": {
17
21
  "types": "./dist/index.d.ts",
18
22
  "import": "./dist/hd-wallet.js"
@@ -21,6 +25,8 @@
21
25
  "files": [
22
26
  "src/index.mjs",
23
27
  "src/index.d.ts",
28
+ "src/aligned.mjs",
29
+ "src/aligned.d.ts",
24
30
  "dist/hd-wallet.js",
25
31
  "dist/hd-wallet.wasm",
26
32
  "dist/hd-wallet.cjs",
@@ -28,11 +34,13 @@
28
34
  "README.md"
29
35
  ],
30
36
  "scripts": {
31
- "build": "cmake --build ../build-wasm --target hd_wallet_wasm_npm -j",
37
+ "generate": "node ../scripts/generate-aligned.mjs",
38
+ "build": "npm run generate && cmake --build ../build-wasm --target hd_wallet_wasm_npm -j",
32
39
  "test": "node test/test_all.mjs",
33
40
  "test:bip39": "node test/test_bip39.mjs",
34
41
  "test:bip32": "node test/test_bip32.mjs",
35
42
  "test:vectors": "node test/test_vectors.mjs",
43
+ "test:aligned": "node test/test_aligned.mjs",
36
44
  "prepublishOnly": "npm run build && npm test"
37
45
  },
38
46
  "repository": {
@@ -0,0 +1,349 @@
1
+ /**
2
+ * HD Wallet WASM - Aligned Binary API Type Definitions
3
+ *
4
+ * Provides efficient batch operations using aligned, fixed-size structs
5
+ * for zero-copy WASM interop.
6
+ *
7
+ * @module hd-wallet-wasm/aligned
8
+ */
9
+
10
+ // =============================================================================
11
+ // Enums
12
+ // =============================================================================
13
+
14
+ /**
15
+ * Elliptic curve types for aligned operations
16
+ */
17
+ export enum AlignedCurve {
18
+ SECP256K1 = 0,
19
+ ED25519 = 1,
20
+ P256 = 2,
21
+ P384 = 3,
22
+ X25519 = 4
23
+ }
24
+
25
+ /**
26
+ * Error codes for aligned operations
27
+ */
28
+ export enum AlignedError {
29
+ OK = 0,
30
+ UNKNOWN = 1,
31
+ INVALID_ARGUMENT = 2,
32
+ NOT_SUPPORTED = 3,
33
+ OUT_OF_MEMORY = 4,
34
+ INTERNAL = 5,
35
+ NO_ENTROPY = 100,
36
+ INSUFFICIENT_ENTROPY = 101,
37
+ INVALID_WORD = 200,
38
+ INVALID_CHECKSUM = 201,
39
+ INVALID_MNEMONIC_LENGTH = 202,
40
+ INVALID_ENTROPY_LENGTH = 203,
41
+ INVALID_SEED = 300,
42
+ INVALID_PATH = 301,
43
+ INVALID_CHILD_INDEX = 302,
44
+ HARDENED_FROM_PUBLIC = 303,
45
+ INVALID_EXTENDED_KEY = 304,
46
+ INVALID_PRIVATE_KEY = 400,
47
+ INVALID_PUBLIC_KEY = 401,
48
+ INVALID_SIGNATURE = 402,
49
+ VERIFICATION_FAILED = 403,
50
+ KEY_DERIVATION_FAILED = 404
51
+ }
52
+
53
+ // =============================================================================
54
+ // Size Constants
55
+ // =============================================================================
56
+
57
+ export const EXTENDEDKEYDATA_SIZE: number;
58
+ export const BATCHDERIVEREQUEST_SIZE: number;
59
+ export const DERIVEDKEYENTRY_SIZE: number;
60
+
61
+ // =============================================================================
62
+ // Result Types
63
+ // =============================================================================
64
+
65
+ /**
66
+ * Result of a single key derivation
67
+ */
68
+ export interface DerivedKey {
69
+ /** Child index that was derived */
70
+ index: number;
71
+ /** Error code (AlignedError.OK on success) */
72
+ error: AlignedError;
73
+ /** Compressed public key (33 bytes), null on error */
74
+ publicKey: Uint8Array | null;
75
+ /** Private key (32 bytes), null on error or if base was neutered */
76
+ privateKey: Uint8Array | null;
77
+ }
78
+
79
+ /**
80
+ * Result of a single signing operation
81
+ */
82
+ export interface SignatureResult {
83
+ /** Index of the hash that was signed */
84
+ index: number;
85
+ /** Error code (AlignedError.OK on success) */
86
+ error: AlignedError;
87
+ /** Signature (64 bytes r||s format), null on error */
88
+ signature: Uint8Array | null;
89
+ /** Recovery ID for recoverable signatures (-1 if not applicable) */
90
+ recoveryId: number;
91
+ }
92
+
93
+ /**
94
+ * Result of a single signature verification
95
+ */
96
+ export interface VerifyResult {
97
+ /** Index of the entry that was verified */
98
+ index: number;
99
+ /** Whether the signature is valid */
100
+ valid: boolean;
101
+ /** Error code */
102
+ error: AlignedError;
103
+ }
104
+
105
+ /**
106
+ * Input for signature verification
107
+ */
108
+ export interface VerifyEntry {
109
+ /** 32-byte message hash */
110
+ hash: Uint8Array;
111
+ /** 64-byte signature */
112
+ signature: Uint8Array;
113
+ }
114
+
115
+ /**
116
+ * Base key data for batch operations
117
+ */
118
+ export interface BaseKeyData {
119
+ /** Elliptic curve */
120
+ curve?: AlignedCurve;
121
+ /** Key depth in derivation tree */
122
+ depth?: number;
123
+ /** Parent fingerprint */
124
+ parentFingerprint?: number;
125
+ /** Child index */
126
+ childIndex?: number;
127
+ /** Chain code (32 bytes) */
128
+ chainCode: Uint8Array;
129
+ /** Compressed public key (33 bytes) */
130
+ publicKey: Uint8Array;
131
+ /** Private key (32 bytes) */
132
+ privateKey: Uint8Array;
133
+ }
134
+
135
+ // =============================================================================
136
+ // View Classes (from generated code)
137
+ // =============================================================================
138
+
139
+ export class ExtendedKeyDataView {
140
+ constructor(buffer: ArrayBuffer, byteOffset?: number);
141
+ static fromMemory(memory: WebAssembly.Memory, ptr: number): ExtendedKeyDataView;
142
+ static fromBytes(bytes: Uint8Array, offset?: number): ExtendedKeyDataView;
143
+ static allocate(): ExtendedKeyDataView;
144
+
145
+ get curve(): AlignedCurve;
146
+ set curve(v: AlignedCurve);
147
+ get depth(): number;
148
+ set depth(v: number);
149
+ get parent_fingerprint(): number;
150
+ set parent_fingerprint(v: number);
151
+ get child_index(): number;
152
+ set child_index(v: number);
153
+ get chain_code_data(): Uint8Array;
154
+ get public_key_data(): Uint8Array;
155
+ get private_key_data(): Uint8Array;
156
+ get has_private_key(): number;
157
+ set has_private_key(v: number);
158
+
159
+ toObject(): Record<string, unknown>;
160
+ copyFrom(obj: Partial<Record<string, unknown>>): void;
161
+ copyTo(dest: Uint8Array, offset?: number): void;
162
+ getBytes(): Uint8Array;
163
+ }
164
+
165
+ export class BatchDeriveRequestView {
166
+ constructor(buffer: ArrayBuffer, byteOffset?: number);
167
+ static fromMemory(memory: WebAssembly.Memory, ptr: number): BatchDeriveRequestView;
168
+ static fromBytes(bytes: Uint8Array, offset?: number): BatchDeriveRequestView;
169
+ static allocate(): BatchDeriveRequestView;
170
+
171
+ get base_key_curve(): AlignedCurve;
172
+ set base_key_curve(v: AlignedCurve);
173
+ get base_key_depth(): number;
174
+ set base_key_depth(v: number);
175
+ get base_key_parent_fingerprint(): number;
176
+ set base_key_parent_fingerprint(v: number);
177
+ get base_key_child_index(): number;
178
+ set base_key_child_index(v: number);
179
+ get base_key_chain_code_data(): Uint8Array;
180
+ get base_key_public_key_data(): Uint8Array;
181
+ get base_key_private_key_data(): Uint8Array;
182
+ get base_key_has_private_key(): number;
183
+ set base_key_has_private_key(v: number);
184
+ get start_index(): number;
185
+ set start_index(v: number);
186
+ get count(): number;
187
+ set count(v: number);
188
+ get hardened(): number;
189
+ set hardened(v: number);
190
+
191
+ toObject(): Record<string, unknown>;
192
+ copyFrom(obj: Partial<Record<string, unknown>>): void;
193
+ copyTo(dest: Uint8Array, offset?: number): void;
194
+ getBytes(): Uint8Array;
195
+ }
196
+
197
+ export class DerivedKeyEntryView {
198
+ constructor(buffer: ArrayBuffer, byteOffset?: number);
199
+ static fromMemory(memory: WebAssembly.Memory, ptr: number): DerivedKeyEntryView;
200
+ static fromBytes(bytes: Uint8Array, offset?: number): DerivedKeyEntryView;
201
+ static allocate(): DerivedKeyEntryView;
202
+
203
+ get index(): number;
204
+ set index(v: number);
205
+ get error(): AlignedError;
206
+ set error(v: AlignedError);
207
+ get public_key_data(): Uint8Array;
208
+ get private_key_data(): Uint8Array;
209
+
210
+ toObject(): Record<string, unknown>;
211
+ copyFrom(obj: Partial<Record<string, unknown>>): void;
212
+ copyTo(dest: Uint8Array, offset?: number): void;
213
+ getBytes(): Uint8Array;
214
+ }
215
+
216
+ export class DerivedKeyEntryArrayView implements Iterable<DerivedKeyEntryView> {
217
+ constructor(buffer: ArrayBuffer, byteOffset: number, count: number);
218
+ static fromMemory(memory: WebAssembly.Memory, ptr: number, count: number): DerivedKeyEntryArrayView;
219
+
220
+ readonly length: number;
221
+ at(index: number): DerivedKeyEntryView;
222
+ [Symbol.iterator](): Iterator<DerivedKeyEntryView>;
223
+ }
224
+
225
+ // =============================================================================
226
+ // AlignedKeyDeriver
227
+ // =============================================================================
228
+
229
+ /**
230
+ * Efficient batch key derivation using aligned binary format.
231
+ */
232
+ export class AlignedKeyDeriver {
233
+ /**
234
+ * @param wasm - WASM module instance
235
+ */
236
+ constructor(wasm: unknown);
237
+
238
+ /**
239
+ * Derive multiple child keys from a base key in batch.
240
+ *
241
+ * @param baseKey - Base HDKey or BaseKeyData
242
+ * @param startIndex - Starting child index
243
+ * @param count - Number of keys to derive
244
+ * @param hardened - Use hardened derivation
245
+ * @returns Array of derived keys
246
+ */
247
+ deriveBatch(
248
+ baseKey: BaseKeyData | { _handle: unknown },
249
+ startIndex: number,
250
+ count: number,
251
+ hardened?: boolean
252
+ ): DerivedKey[];
253
+
254
+ /**
255
+ * Generator for streaming key derivation.
256
+ *
257
+ * @param baseKey - Base HDKey or BaseKeyData
258
+ * @param startIndex - Starting child index
259
+ * @param batchSize - Keys per batch
260
+ * @param hardened - Use hardened derivation
261
+ * @yields Batches of derived keys
262
+ */
263
+ streamKeys(
264
+ baseKey: BaseKeyData | { _handle: unknown },
265
+ startIndex?: number,
266
+ batchSize?: number,
267
+ hardened?: boolean
268
+ ): Generator<DerivedKey[], void, unknown>;
269
+ }
270
+
271
+ // =============================================================================
272
+ // AlignedSigner
273
+ // =============================================================================
274
+
275
+ /**
276
+ * Efficient batch signing using aligned binary format.
277
+ */
278
+ export class AlignedSigner {
279
+ /**
280
+ * @param wasm - WASM module instance
281
+ */
282
+ constructor(wasm: unknown);
283
+
284
+ /**
285
+ * Sign multiple message hashes with the same private key.
286
+ *
287
+ * @param privateKey - 32-byte private key
288
+ * @param hashes - Array of 32-byte message hashes
289
+ * @param curve - Elliptic curve
290
+ * @returns Array of signature results
291
+ */
292
+ signBatch(
293
+ privateKey: Uint8Array,
294
+ hashes: Uint8Array[],
295
+ curve?: AlignedCurve
296
+ ): SignatureResult[];
297
+
298
+ /**
299
+ * Verify multiple signatures against the same public key.
300
+ *
301
+ * @param publicKey - 33-byte compressed public key
302
+ * @param entries - Hash/signature pairs
303
+ * @param curve - Elliptic curve
304
+ * @returns Array of verification results
305
+ */
306
+ verifyBatch(
307
+ publicKey: Uint8Array,
308
+ entries: VerifyEntry[],
309
+ curve?: AlignedCurve
310
+ ): VerifyResult[];
311
+ }
312
+
313
+ // =============================================================================
314
+ // AlignedAPI
315
+ // =============================================================================
316
+
317
+ /**
318
+ * Combined aligned API providing access to batch operations.
319
+ */
320
+ export class AlignedAPI {
321
+ /**
322
+ * @param wasm - WASM module instance
323
+ */
324
+ constructor(wasm: unknown);
325
+
326
+ /** Key deriver for batch derivation */
327
+ readonly keyDeriver: AlignedKeyDeriver;
328
+
329
+ /** Signer for batch signing/verification */
330
+ readonly signer: AlignedSigner;
331
+
332
+ /**
333
+ * Convert HDKey to ExtendedKeyData bytes
334
+ * @param hdKey - HDKey instance
335
+ * @returns Extended key data bytes
336
+ */
337
+ keyToBytes(hdKey: { _handle: unknown }): Uint8Array;
338
+
339
+ /** Size of DerivedKeyEntry struct */
340
+ readonly derivedKeyEntrySize: number;
341
+
342
+ /** Size of ExtendedKeyData struct */
343
+ readonly extendedKeyDataSize: number;
344
+
345
+ /** Size of BatchDeriveRequest struct */
346
+ readonly batchDeriveRequestSize: number;
347
+ }
348
+
349
+ export default AlignedAPI;
@@ -0,0 +1,627 @@
1
+ /**
2
+ * HD Wallet WASM - Aligned Binary API
3
+ *
4
+ * Provides efficient batch operations using aligned, fixed-size structs
5
+ * for zero-copy WASM interop. This module wraps the C++ aligned API
6
+ * with JavaScript-friendly interfaces.
7
+ *
8
+ * Features:
9
+ * - Batch key derivation (derive 100s of keys efficiently)
10
+ * - Batch signing (sign multiple hashes with same key)
11
+ * - Batch verification
12
+ * - Streaming key derivation (generators for unlimited sequences)
13
+ *
14
+ * @module hd-wallet-wasm/aligned
15
+ */
16
+
17
+ // Import generated aligned struct definitions
18
+ import {
19
+ // Size constants
20
+ EXTENDEDKEYDATA_SIZE,
21
+ BATCHDERIVEREQUEST_SIZE,
22
+ DERIVEDKEYENTRY_SIZE,
23
+ BATCHSIGNREQUEST_SIZE,
24
+ HASHENTRY_SIZE,
25
+ SIGNATUREENTRY_SIZE,
26
+ BATCHVERIFYREQUEST_SIZE,
27
+ VERIFYENTRY_SIZE,
28
+ VERIFYRESULTENTRY_SIZE,
29
+ STREAMDERIVECONFIG_SIZE,
30
+ STREAMSTATUS_SIZE,
31
+
32
+ // View classes
33
+ ExtendedKeyDataView,
34
+ BatchDeriveRequestView,
35
+ DerivedKeyEntryView,
36
+ DerivedKeyEntryArrayView,
37
+ BatchSignRequestView,
38
+ HashEntryView,
39
+ HashEntryArrayView,
40
+ SignatureEntryView,
41
+ SignatureEntryArrayView,
42
+ BatchVerifyRequestView,
43
+ VerifyEntryView,
44
+ VerifyEntryArrayView,
45
+ VerifyResultEntryView,
46
+ VerifyResultEntryArrayView,
47
+ StreamDeriveConfigView,
48
+ StreamStatusView,
49
+
50
+ // Enums
51
+ Curve as AlignedCurve,
52
+ Error as AlignedErrorCode,
53
+ } from '../../generated/aligned/hd_wallet_aligned.mjs';
54
+
55
+ // Re-export for external use
56
+ export { AlignedCurve, AlignedErrorCode };
57
+ export {
58
+ EXTENDEDKEYDATA_SIZE,
59
+ BATCHDERIVEREQUEST_SIZE,
60
+ DERIVEDKEYENTRY_SIZE,
61
+ ExtendedKeyDataView,
62
+ BatchDeriveRequestView,
63
+ DerivedKeyEntryView,
64
+ DerivedKeyEntryArrayView,
65
+ };
66
+
67
+ // =============================================================================
68
+ // Error Handling
69
+ // =============================================================================
70
+
71
+ const ERROR_MESSAGES = {
72
+ [AlignedErrorCode.OK]: 'Success',
73
+ [AlignedErrorCode.UNKNOWN]: 'Unknown error',
74
+ [AlignedErrorCode.INVALID_ARGUMENT]: 'Invalid argument',
75
+ [AlignedErrorCode.NOT_SUPPORTED]: 'Operation not supported',
76
+ [AlignedErrorCode.OUT_OF_MEMORY]: 'Out of memory',
77
+ [AlignedErrorCode.INTERNAL]: 'Internal error',
78
+ [AlignedErrorCode.NO_ENTROPY]: 'No entropy available',
79
+ [AlignedErrorCode.INSUFFICIENT_ENTROPY]: 'Insufficient entropy',
80
+ [AlignedErrorCode.INVALID_PRIVATE_KEY]: 'Invalid private key',
81
+ [AlignedErrorCode.INVALID_PUBLIC_KEY]: 'Invalid public key',
82
+ [AlignedErrorCode.INVALID_SIGNATURE]: 'Invalid signature',
83
+ [AlignedErrorCode.VERIFICATION_FAILED]: 'Signature verification failed',
84
+ [AlignedErrorCode.KEY_DERIVATION_FAILED]: 'Key derivation failed',
85
+ [AlignedErrorCode.HARDENED_FROM_PUBLIC]: 'Cannot derive hardened from public key',
86
+ };
87
+
88
+ /**
89
+ * Aligned API Error
90
+ */
91
+ export class AlignedError extends Error {
92
+ constructor(code, message) {
93
+ super(message || ERROR_MESSAGES[code] || `Error code: ${code}`);
94
+ this.name = 'AlignedError';
95
+ this.code = code;
96
+ }
97
+ }
98
+
99
+ // =============================================================================
100
+ // AlignedKeyDeriver - Batch Key Derivation
101
+ // =============================================================================
102
+
103
+ /**
104
+ * Efficient batch key derivation using aligned binary format.
105
+ *
106
+ * @example
107
+ * ```js
108
+ * const deriver = new AlignedKeyDeriver(wasm);
109
+ *
110
+ * // Derive 100 keys at once
111
+ * const keys = deriver.deriveBatch(masterKey, 0, 100);
112
+ * for (const key of keys) {
113
+ * console.log(key.index, key.publicKey);
114
+ * }
115
+ *
116
+ * // Stream unlimited keys
117
+ * for (const batch of deriver.streamKeys(masterKey, 0, 50)) {
118
+ * for (const key of batch) {
119
+ * processKey(key);
120
+ * }
121
+ * }
122
+ * ```
123
+ */
124
+ export class AlignedKeyDeriver {
125
+ #wasm;
126
+
127
+ /**
128
+ * @param {Object} wasm - WASM module instance
129
+ */
130
+ constructor(wasm) {
131
+ this.#wasm = wasm;
132
+ }
133
+
134
+ /**
135
+ * Derive multiple child keys from a base key in batch.
136
+ *
137
+ * @param {Object} baseKey - Base HDKey or ExtendedKeyData
138
+ * @param {number} startIndex - Starting child index
139
+ * @param {number} count - Number of keys to derive
140
+ * @param {boolean} [hardened=false] - Use hardened derivation
141
+ * @returns {Array<DerivedKey>} Array of derived keys
142
+ */
143
+ deriveBatch(baseKey, startIndex, count, hardened = false) {
144
+ const wasm = this.#wasm;
145
+
146
+ // Allocate request buffer
147
+ const requestPtr = wasm._hd_alloc(BATCHDERIVEREQUEST_SIZE);
148
+ if (!requestPtr) {
149
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
150
+ }
151
+
152
+ try {
153
+ // Fill request
154
+ const requestView = BatchDeriveRequestView.fromMemory(wasm.wasmMemory, requestPtr);
155
+ this.#fillBaseKey(requestView, baseKey);
156
+ requestView.start_index = startIndex >>> 0;
157
+ requestView.count = count >>> 0;
158
+ requestView.hardened = hardened ? 1 : 0;
159
+
160
+ // Allocate results buffer
161
+ const resultsSize = count * DERIVEDKEYENTRY_SIZE;
162
+ const resultsPtr = wasm._hd_alloc(resultsSize);
163
+ if (!resultsPtr) {
164
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
165
+ }
166
+
167
+ try {
168
+ // Call WASM function
169
+ const derivedCount = wasm._hd_aligned_derive_batch(
170
+ requestPtr,
171
+ resultsPtr,
172
+ count
173
+ );
174
+
175
+ if (derivedCount < 0) {
176
+ throw new AlignedError(derivedCount);
177
+ }
178
+
179
+ // Read results
180
+ const results = [];
181
+ const resultsArray = DerivedKeyEntryArrayView.fromMemory(
182
+ wasm.wasmMemory, resultsPtr, derivedCount
183
+ );
184
+
185
+ for (let i = 0; i < derivedCount; i++) {
186
+ const entry = resultsArray.at(i);
187
+ results.push({
188
+ index: entry.index,
189
+ error: entry.error,
190
+ publicKey: entry.error === AlignedErrorCode.OK
191
+ ? new Uint8Array(entry.public_key_data)
192
+ : null,
193
+ privateKey: entry.error === AlignedErrorCode.OK
194
+ ? new Uint8Array(entry.private_key_data)
195
+ : null,
196
+ });
197
+ }
198
+
199
+ return results;
200
+ } finally {
201
+ wasm._hd_dealloc(resultsPtr);
202
+ }
203
+ } finally {
204
+ wasm._hd_dealloc(requestPtr);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Generator for streaming key derivation.
210
+ *
211
+ * @param {Object} baseKey - Base HDKey or ExtendedKeyData
212
+ * @param {number} [startIndex=0] - Starting child index
213
+ * @param {number} [batchSize=100] - Keys per batch
214
+ * @param {boolean} [hardened=false] - Use hardened derivation
215
+ * @yields {Array<DerivedKey>} Batches of derived keys
216
+ */
217
+ *streamKeys(baseKey, startIndex = 0, batchSize = 100, hardened = false) {
218
+ const wasm = this.#wasm;
219
+
220
+ // Allocate config buffer
221
+ const configPtr = wasm._hd_alloc(STREAMDERIVECONFIG_SIZE);
222
+ if (!configPtr) {
223
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
224
+ }
225
+
226
+ // Fill config
227
+ const configView = StreamDeriveConfigView.fromMemory(wasm.wasmMemory, configPtr);
228
+ this.#fillBaseKey(configView, baseKey);
229
+ configView.start_index = startIndex >>> 0;
230
+ configView.batch_size = batchSize >>> 0;
231
+ configView.hardened = hardened ? 1 : 0;
232
+
233
+ // Create stream
234
+ const streamHandle = wasm._hd_aligned_stream_create(configPtr);
235
+ wasm._hd_dealloc(configPtr);
236
+
237
+ if (!streamHandle) {
238
+ throw new AlignedError(AlignedErrorCode.INTERNAL, 'Failed to create stream');
239
+ }
240
+
241
+ // Allocate results buffer
242
+ const resultsSize = batchSize * DERIVEDKEYENTRY_SIZE;
243
+ const resultsPtr = wasm._hd_alloc(resultsSize);
244
+ if (!resultsPtr) {
245
+ wasm._hd_aligned_stream_destroy(streamHandle);
246
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
247
+ }
248
+
249
+ try {
250
+ while (true) {
251
+ const derivedCount = wasm._hd_aligned_stream_next(
252
+ streamHandle,
253
+ resultsPtr,
254
+ batchSize
255
+ );
256
+
257
+ if (derivedCount <= 0) {
258
+ break;
259
+ }
260
+
261
+ // Read batch results
262
+ const batch = [];
263
+ const resultsArray = DerivedKeyEntryArrayView.fromMemory(
264
+ wasm.wasmMemory, resultsPtr, derivedCount
265
+ );
266
+
267
+ for (let i = 0; i < derivedCount; i++) {
268
+ const entry = resultsArray.at(i);
269
+ batch.push({
270
+ index: entry.index,
271
+ error: entry.error,
272
+ publicKey: entry.error === AlignedErrorCode.OK
273
+ ? new Uint8Array(entry.public_key_data)
274
+ : null,
275
+ privateKey: entry.error === AlignedErrorCode.OK
276
+ ? new Uint8Array(entry.private_key_data)
277
+ : null,
278
+ });
279
+ }
280
+
281
+ yield batch;
282
+ }
283
+ } finally {
284
+ wasm._hd_dealloc(resultsPtr);
285
+ wasm._hd_aligned_stream_destroy(streamHandle);
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Fill base key data in request/config view
291
+ * @private
292
+ */
293
+ #fillBaseKey(view, baseKey) {
294
+ if (baseKey._handle) {
295
+ // HDKey object - use conversion function
296
+ const keyDataPtr = this.#wasm._hd_alloc(EXTENDEDKEYDATA_SIZE);
297
+ try {
298
+ this.#wasm._hd_aligned_from_extended_key(baseKey._handle, keyDataPtr);
299
+ const keyDataView = ExtendedKeyDataView.fromMemory(
300
+ this.#wasm.wasmMemory, keyDataPtr
301
+ );
302
+ // Copy to request view's base_key fields
303
+ view.base_key_curve = keyDataView.curve;
304
+ view.base_key_depth = keyDataView.depth;
305
+ view.base_key_parent_fingerprint = keyDataView.parent_fingerprint;
306
+ view.base_key_child_index = keyDataView.child_index;
307
+ view.base_key_chain_code_data.set(keyDataView.chain_code_data);
308
+ view.base_key_public_key_data.set(keyDataView.public_key_data);
309
+ view.base_key_private_key_data.set(keyDataView.private_key_data);
310
+ view.base_key_has_private_key = keyDataView.has_private_key;
311
+ } finally {
312
+ this.#wasm._hd_dealloc(keyDataPtr);
313
+ }
314
+ } else if (baseKey.privateKey && baseKey.chainCode) {
315
+ // Plain object with key data
316
+ view.base_key_curve = baseKey.curve ?? AlignedCurve.SECP256K1;
317
+ view.base_key_depth = baseKey.depth ?? 0;
318
+ view.base_key_parent_fingerprint = baseKey.parentFingerprint ?? 0;
319
+ view.base_key_child_index = baseKey.childIndex ?? 0;
320
+ view.base_key_chain_code_data.set(baseKey.chainCode);
321
+ view.base_key_public_key_data.set(baseKey.publicKey);
322
+ view.base_key_private_key_data.set(baseKey.privateKey);
323
+ view.base_key_has_private_key = 1;
324
+ } else {
325
+ throw new AlignedError(
326
+ AlignedErrorCode.INVALID_ARGUMENT,
327
+ 'baseKey must be an HDKey or have privateKey and chainCode'
328
+ );
329
+ }
330
+ }
331
+ }
332
+
333
+ // =============================================================================
334
+ // AlignedSigner - Batch Signing
335
+ // =============================================================================
336
+
337
+ /**
338
+ * Efficient batch signing using aligned binary format.
339
+ *
340
+ * @example
341
+ * ```js
342
+ * const signer = new AlignedSigner(wasm);
343
+ *
344
+ * // Sign multiple hashes
345
+ * const hashes = [hash1, hash2, hash3];
346
+ * const signatures = signer.signBatch(privateKey, hashes);
347
+ * ```
348
+ */
349
+ export class AlignedSigner {
350
+ #wasm;
351
+
352
+ /**
353
+ * @param {Object} wasm - WASM module instance
354
+ */
355
+ constructor(wasm) {
356
+ this.#wasm = wasm;
357
+ }
358
+
359
+ /**
360
+ * Sign multiple message hashes with the same private key.
361
+ *
362
+ * @param {Uint8Array} privateKey - 32-byte private key
363
+ * @param {Array<Uint8Array>} hashes - Array of 32-byte message hashes
364
+ * @param {number} [curve=AlignedCurve.SECP256K1] - Elliptic curve
365
+ * @returns {Array<SignatureResult>} Array of signature results
366
+ */
367
+ signBatch(privateKey, hashes, curve = AlignedCurve.SECP256K1) {
368
+ const wasm = this.#wasm;
369
+ const count = hashes.length;
370
+
371
+ // Allocate request
372
+ const requestPtr = wasm._hd_alloc(BATCHSIGNREQUEST_SIZE);
373
+ if (!requestPtr) {
374
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
375
+ }
376
+
377
+ try {
378
+ // Fill request
379
+ const requestView = BatchSignRequestView.fromMemory(wasm.wasmMemory, requestPtr);
380
+ requestView.private_key_data.set(privateKey);
381
+ requestView.curve = curve;
382
+ requestView.count = count;
383
+
384
+ // Allocate and fill hashes
385
+ const hashesPtr = wasm._hd_alloc(count * HASHENTRY_SIZE);
386
+ if (!hashesPtr) {
387
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
388
+ }
389
+
390
+ try {
391
+ const hashesArray = HashEntryArrayView.fromMemory(wasm.wasmMemory, hashesPtr, count);
392
+ for (let i = 0; i < count; i++) {
393
+ const entry = hashesArray.at(i);
394
+ entry.index = i;
395
+ entry.hash_data.set(hashes[i]);
396
+ }
397
+
398
+ // Allocate results
399
+ const resultsPtr = wasm._hd_alloc(count * SIGNATUREENTRY_SIZE);
400
+ if (!resultsPtr) {
401
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
402
+ }
403
+
404
+ try {
405
+ // Call WASM function
406
+ const signedCount = wasm._hd_aligned_sign_batch(
407
+ requestPtr,
408
+ hashesPtr,
409
+ count,
410
+ resultsPtr,
411
+ count
412
+ );
413
+
414
+ if (signedCount < 0) {
415
+ throw new AlignedError(signedCount);
416
+ }
417
+
418
+ // Read results
419
+ const results = [];
420
+ const resultsArray = SignatureEntryArrayView.fromMemory(
421
+ wasm.wasmMemory, resultsPtr, signedCount
422
+ );
423
+
424
+ for (let i = 0; i < signedCount; i++) {
425
+ const entry = resultsArray.at(i);
426
+ results.push({
427
+ index: entry.index,
428
+ error: entry.error,
429
+ signature: entry.error === AlignedErrorCode.OK
430
+ ? new Uint8Array(entry.signature_data)
431
+ : null,
432
+ recoveryId: entry.recovery_id,
433
+ });
434
+ }
435
+
436
+ return results;
437
+ } finally {
438
+ wasm._hd_dealloc(resultsPtr);
439
+ }
440
+ } finally {
441
+ wasm._hd_dealloc(hashesPtr);
442
+ }
443
+ } finally {
444
+ wasm._hd_dealloc(requestPtr);
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Verify multiple signatures against the same public key.
450
+ *
451
+ * @param {Uint8Array} publicKey - 33-byte compressed public key
452
+ * @param {Array<{hash: Uint8Array, signature: Uint8Array}>} entries - Hash/signature pairs
453
+ * @param {number} [curve=AlignedCurve.SECP256K1] - Elliptic curve
454
+ * @returns {Array<VerifyResult>} Array of verification results
455
+ */
456
+ verifyBatch(publicKey, entries, curve = AlignedCurve.SECP256K1) {
457
+ const wasm = this.#wasm;
458
+ const count = entries.length;
459
+
460
+ // Allocate request
461
+ const requestPtr = wasm._hd_alloc(BATCHVERIFYREQUEST_SIZE);
462
+ if (!requestPtr) {
463
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
464
+ }
465
+
466
+ try {
467
+ // Fill request
468
+ const requestView = BatchVerifyRequestView.fromMemory(wasm.wasmMemory, requestPtr);
469
+ requestView.public_key_data.set(publicKey);
470
+ requestView.curve = curve;
471
+ requestView.count = count;
472
+
473
+ // Allocate and fill entries
474
+ const entriesPtr = wasm._hd_alloc(count * VERIFYENTRY_SIZE);
475
+ if (!entriesPtr) {
476
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
477
+ }
478
+
479
+ try {
480
+ const entriesArray = VerifyEntryArrayView.fromMemory(wasm.wasmMemory, entriesPtr, count);
481
+ for (let i = 0; i < count; i++) {
482
+ const view = entriesArray.at(i);
483
+ view.index = i;
484
+ view.hash_data.set(entries[i].hash);
485
+ view.signature_data.set(entries[i].signature);
486
+ }
487
+
488
+ // Allocate results
489
+ const resultsPtr = wasm._hd_alloc(count * VERIFYRESULTENTRY_SIZE);
490
+ if (!resultsPtr) {
491
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
492
+ }
493
+
494
+ try {
495
+ // Call WASM function
496
+ const verifiedCount = wasm._hd_aligned_verify_batch(
497
+ requestPtr,
498
+ entriesPtr,
499
+ count,
500
+ resultsPtr,
501
+ count
502
+ );
503
+
504
+ if (verifiedCount < 0) {
505
+ throw new AlignedError(verifiedCount);
506
+ }
507
+
508
+ // Read results
509
+ const results = [];
510
+ const resultsArray = VerifyResultEntryArrayView.fromMemory(
511
+ wasm.wasmMemory, resultsPtr, verifiedCount
512
+ );
513
+
514
+ for (let i = 0; i < verifiedCount; i++) {
515
+ const entry = resultsArray.at(i);
516
+ results.push({
517
+ index: entry.index,
518
+ valid: entry.error === AlignedErrorCode.OK,
519
+ error: entry.error,
520
+ });
521
+ }
522
+
523
+ return results;
524
+ } finally {
525
+ wasm._hd_dealloc(resultsPtr);
526
+ }
527
+ } finally {
528
+ wasm._hd_dealloc(entriesPtr);
529
+ }
530
+ } finally {
531
+ wasm._hd_dealloc(requestPtr);
532
+ }
533
+ }
534
+ }
535
+
536
+ // =============================================================================
537
+ // AlignedAPI - Combined API
538
+ // =============================================================================
539
+
540
+ /**
541
+ * Combined aligned API providing access to batch operations.
542
+ */
543
+ export class AlignedAPI {
544
+ #wasm;
545
+ #keyDeriver;
546
+ #signer;
547
+
548
+ /**
549
+ * @param {Object} wasm - WASM module instance
550
+ */
551
+ constructor(wasm) {
552
+ this.#wasm = wasm;
553
+ this.#keyDeriver = null;
554
+ this.#signer = null;
555
+ }
556
+
557
+ /**
558
+ * Get key deriver instance
559
+ * @returns {AlignedKeyDeriver}
560
+ */
561
+ get keyDeriver() {
562
+ if (!this.#keyDeriver) {
563
+ this.#keyDeriver = new AlignedKeyDeriver(this.#wasm);
564
+ }
565
+ return this.#keyDeriver;
566
+ }
567
+
568
+ /**
569
+ * Get signer instance
570
+ * @returns {AlignedSigner}
571
+ */
572
+ get signer() {
573
+ if (!this.#signer) {
574
+ this.#signer = new AlignedSigner(this.#wasm);
575
+ }
576
+ return this.#signer;
577
+ }
578
+
579
+ /**
580
+ * Convert HDKey to ExtendedKeyData bytes
581
+ * @param {Object} hdKey - HDKey instance
582
+ * @returns {Uint8Array} Extended key data bytes
583
+ */
584
+ keyToBytes(hdKey) {
585
+ const wasm = this.#wasm;
586
+ const ptr = wasm._hd_alloc(EXTENDEDKEYDATA_SIZE);
587
+ if (!ptr) {
588
+ throw new AlignedError(AlignedErrorCode.OUT_OF_MEMORY);
589
+ }
590
+
591
+ try {
592
+ const result = wasm._hd_aligned_from_extended_key(hdKey._handle, ptr);
593
+ if (result !== 0) {
594
+ throw new AlignedError(result);
595
+ }
596
+ return new Uint8Array(wasm.HEAPU8.buffer, ptr, EXTENDEDKEYDATA_SIZE).slice();
597
+ } finally {
598
+ wasm._hd_dealloc(ptr);
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Get size of DerivedKeyEntry struct
604
+ * @returns {number}
605
+ */
606
+ get derivedKeyEntrySize() {
607
+ return DERIVEDKEYENTRY_SIZE;
608
+ }
609
+
610
+ /**
611
+ * Get size of ExtendedKeyData struct
612
+ * @returns {number}
613
+ */
614
+ get extendedKeyDataSize() {
615
+ return EXTENDEDKEYDATA_SIZE;
616
+ }
617
+
618
+ /**
619
+ * Get size of BatchDeriveRequest struct
620
+ * @returns {number}
621
+ */
622
+ get batchDeriveRequestSize() {
623
+ return BATCHDERIVEREQUEST_SIZE;
624
+ }
625
+ }
626
+
627
+ export default AlignedAPI;
package/src/index.d.ts CHANGED
@@ -9,6 +9,9 @@
9
9
  * - Transaction building and signing
10
10
  */
11
11
 
12
+ // Import aligned API types
13
+ import { AlignedAPI } from './aligned';
14
+
12
15
  // =============================================================================
13
16
  // Module Types
14
17
  // =============================================================================
@@ -62,6 +65,9 @@ export interface HDWalletModule {
62
65
 
63
66
  // Utilities
64
67
  utils: UtilsAPI;
68
+
69
+ // Aligned binary API for efficient batch operations
70
+ aligned: AlignedAPI;
65
71
  }
66
72
 
67
73
  // =============================================================================
@@ -600,10 +606,12 @@ export interface UtilsAPI {
600
606
 
601
607
  /**
602
608
  * Initialize the HD Wallet WASM module
609
+ * @param wasmPath - Optional path to WASM file
603
610
  */
604
- export default function init(): Promise<HDWalletModule>;
611
+ export default function init(wasmPath?: string): Promise<HDWalletModule>;
605
612
 
606
613
  /**
607
614
  * Create HD Wallet instance (alternative syntax)
615
+ * @param wasmPath - Optional path to WASM file
608
616
  */
609
- export function createHDWallet(): Promise<HDWalletModule>;
617
+ export function createHDWallet(wasmPath?: string): Promise<HDWalletModule>;
package/src/index.mjs CHANGED
@@ -12,6 +12,9 @@
12
12
  * @version 0.1.0
13
13
  */
14
14
 
15
+ // Import aligned API for batch operations
16
+ import { AlignedAPI } from './aligned.mjs';
17
+
15
18
  // =============================================================================
16
19
  // Enums (matching TypeScript definitions)
17
20
  // =============================================================================
@@ -604,7 +607,7 @@ async function loadWasmModule(wasmPath) {
604
607
 
605
608
  try {
606
609
  // Try dynamic import (works in Node.js and bundlers)
607
- const module = await import('./hd-wallet.js');
610
+ const module = await import('../dist/hd-wallet.js');
608
611
  HDWalletWasm = module.default;
609
612
  } catch (e) {
610
613
  // Fallback for browsers without ES module support
@@ -768,8 +771,9 @@ function createModule(wasm) {
768
771
  const outputPtr = wasm._hd_alloc(1024);
769
772
  try {
770
773
  wasm._hd_mnemonic_suggest_word(prefixPtr, language, outputPtr, 1024, maxSuggestions);
771
- const json = readString(wasm, outputPtr);
772
- return JSON.parse(json);
774
+ const text = readString(wasm, outputPtr);
775
+ // C API returns newline-separated words
776
+ return text ? text.split('\n').filter(w => w.length > 0) : [];
773
777
  } finally {
774
778
  wasm._hd_dealloc(prefixPtr);
775
779
  wasm._hd_dealloc(outputPtr);
@@ -804,23 +808,24 @@ function createModule(wasm) {
804
808
  const hdkey = {
805
809
  /**
806
810
  * Create master key from seed
807
- * @param {Uint8Array} seed - 64-byte seed
811
+ * @param {Uint8Array} seed - 16-64 byte seed (BIP-32 allows 128-512 bits)
808
812
  * @param {number} [curve=Curve.SECP256K1] - Elliptic curve
809
813
  * @returns {HDKey} Master HD key
810
814
  */
811
815
  fromSeed(seed, curve = Curve.SECP256K1) {
812
- if (seed.length !== 64) {
813
- throw new HDWalletError(ErrorCode.INVALID_SEED, 'Seed must be 64 bytes');
816
+ // BIP-32 allows 128-512 bits (16-64 bytes)
817
+ if (seed.length < 16 || seed.length > 64) {
818
+ throw new HDWalletError(ErrorCode.INVALID_SEED, 'Seed must be 16-64 bytes');
814
819
  }
815
820
  const seedPtr = allocAndCopy(wasm, seed);
816
821
  try {
817
- const handle = wasm._hd_key_from_seed(seedPtr, 64, curve);
822
+ const handle = wasm._hd_key_from_seed(seedPtr, seed.length, curve);
818
823
  if (!handle) {
819
824
  throw new HDWalletError(ErrorCode.INVALID_SEED);
820
825
  }
821
826
  return new HDKey(wasm, handle, 'm');
822
827
  } finally {
823
- wasm._hd_secure_wipe(seedPtr, 64);
828
+ wasm._hd_secure_wipe(seedPtr, seed.length);
824
829
  wasm._hd_dealloc(seedPtr);
825
830
  }
826
831
  },
@@ -2444,7 +2449,20 @@ function createModule(wasm) {
2444
2449
  polkadot,
2445
2450
  hardware,
2446
2451
  keyring,
2447
- utils
2452
+ utils,
2453
+
2454
+ // Aligned binary API for efficient batch operations
2455
+ get aligned() {
2456
+ if (!this._aligned) {
2457
+ // Create a wrapper that exposes wasmMemory for the aligned API
2458
+ const wasmWithMemory = Object.create(wasm);
2459
+ Object.defineProperty(wasmWithMemory, 'wasmMemory', {
2460
+ get: () => ({ buffer: wasm.HEAPU8.buffer })
2461
+ });
2462
+ this._aligned = new AlignedAPI(wasmWithMemory);
2463
+ }
2464
+ return this._aligned;
2465
+ }
2448
2466
  };
2449
2467
  }
2450
2468