hd-wallet-wasm 2.1.0 → 2.1.2

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.
@@ -9,7 +9,7 @@
9
9
  * - Transaction building and signing
10
10
  *
11
11
  * @module hd-wallet-wasm
12
- * @version 2.1.0
12
+ * @version 2.1.2
13
13
  */
14
14
 
15
15
  // Import aligned API for batch operations
@@ -20,6 +20,7 @@ import {
20
20
  } from './sdn-plugin.mjs';
21
21
  import { HD_WALLET_SDN_PLUGIN_MANIFEST } from './sdn-plugin-manifest-source.mjs';
22
22
  import { createSdnTypedCapabilities } from './sdn-typed.mjs';
23
+ import { createInputValidation } from './input-validation.mjs';
23
24
 
24
25
  const walletOriginCapabilitiesByModule = new WeakMap();
25
26
 
@@ -323,16 +324,77 @@ class HDWalletError extends Error {
323
324
  }
324
325
 
325
326
  /**
326
- * Check result and throw if error
327
+ * Check a status-returning export: 0 is success, any other value is an error.
327
328
  * @param {number} result - Result code from WASM function
328
329
  * @throws {HDWalletError} If result is non-zero
329
330
  */
330
331
  function checkResult(result) {
331
332
  if (result !== 0) {
332
- throw new HDWalletError(result);
333
+ throw new HDWalletError(nativeErrorCode(result));
333
334
  }
334
335
  }
335
336
 
337
+ // Native return convention: an export whose success value is 0 reports any
338
+ // other value as an error; an export whose success is a length, a count or 1/0
339
+ // reports failure as a negative -ErrorCode. The checks below accept exactly the
340
+ // documented success value, so no error code (of either sign) is ever read as a
341
+ // length and no output buffer is read after a failed call.
342
+
343
+ /**
344
+ * Map a failed native result to a library error code.
345
+ * @param {number} result - Native return value that was not success
346
+ * @returns {number} Known error code, or INTERNAL for anything else
347
+ */
348
+ function nativeErrorCode(result) {
349
+ const code = result < 0 ? -result : result;
350
+ return code !== ErrorCode.OK && ERROR_MESSAGES[code] !== undefined ? code : ErrorCode.INTERNAL;
351
+ }
352
+
353
+ /**
354
+ * Check a length-returning export: exactly `expected` bytes were written.
355
+ * @param {number} result - Native return value
356
+ * @param {number} expected - Byte length a successful call writes
357
+ * @returns {number} The length
358
+ * @throws {HDWalletError} On any other value, positive or negative
359
+ */
360
+ function checkLength(result, expected) {
361
+ if (result !== expected) {
362
+ throw new HDWalletError(nativeErrorCode(result));
363
+ }
364
+ return result;
365
+ }
366
+
367
+ /**
368
+ * Check a variable-length export: a length within [min, max].
369
+ * @param {number} result - Native return value
370
+ * @param {number} min - Smallest valid length
371
+ * @param {number} max - Largest valid length (the output capacity)
372
+ * @returns {number} The length
373
+ * @throws {HDWalletError} On any value outside the range
374
+ */
375
+ function checkLengthWithin(result, min, max) {
376
+ if (!Number.isInteger(result) || result < min || result > max) {
377
+ throw new HDWalletError(nativeErrorCode(result));
378
+ }
379
+ return result;
380
+ }
381
+
382
+ /**
383
+ * Input validation for every wrapper (input-validation.mjs): type, exact
384
+ * length, and for keys a native validity check, before anything reaches WASM.
385
+ * Verification returns false for invalid bytes and throws only for programmer
386
+ * errors (wrong argument types, unknown curves or coins).
387
+ */
388
+ const V = createInputValidation((code, message) => new HDWalletError(code, message));
389
+
390
+ /** Decode a hex string to bytes, or null when it is not valid hex. */
391
+ function hexOrNull(value) {
392
+ if (typeof value !== 'string') return null;
393
+ const hex = value.startsWith('0x') || value.startsWith('0X') ? value.slice(2) : value;
394
+ if (hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) return null;
395
+ return decodeHexFallback(hex);
396
+ }
397
+
336
398
  /**
337
399
  * Return a WASM function if exported by this build, otherwise null.
338
400
  * Some features are optional and can be omitted from lean builds.
@@ -495,6 +557,59 @@ function decodeBech32Fallback(str) {
495
557
  return { hrp, data: out };
496
558
  }
497
559
 
560
+ /** %TypedArray%.prototype.fill, captured at load (uses the internal length). */
561
+ const TYPED_ARRAY_FILL = Object.getPrototypeOf(Uint8Array.prototype).fill;
562
+
563
+ // Internal-slot getters, captured at load: they work on objects of any realm
564
+ // and cannot be overridden by the object.
565
+ const slotGetter = (prototype, name) => Object.getOwnPropertyDescriptor(prototype, name)?.get;
566
+ const TYPED_ARRAY_SLOTS = ['buffer', 'byteOffset', 'byteLength']
567
+ .map((name) => slotGetter(Object.getPrototypeOf(Uint8Array.prototype), name));
568
+ const DATA_VIEW_SLOTS = ['buffer', 'byteOffset', 'byteLength'].map((name) => slotGetter(DataView.prototype, name));
569
+ const ARRAY_BUFFER_BYTE_LENGTH = slotGetter(ArrayBuffer.prototype, 'byteLength');
570
+ const ARRAY_BUFFER_DETACHED = slotGetter(ArrayBuffer.prototype, 'detached');
571
+ const SHARED_BUFFER_BYTE_LENGTH = typeof SharedArrayBuffer === 'function'
572
+ ? slotGetter(SharedArrayBuffer.prototype, 'byteLength')
573
+ : undefined;
574
+
575
+ /**
576
+ * The bytes under binary data of any kind and realm: { buffer, offset,
577
+ * length, detached }, or null for anything that is not binary data.
578
+ */
579
+ function binaryRegion(value) {
580
+ const read = (getters) => {
581
+ try {
582
+ const [buffer, offset, length] = getters.map((get) => get.call(value));
583
+ return { buffer, offset, length };
584
+ } catch {
585
+ return null;
586
+ }
587
+ };
588
+ let region = read(TYPED_ARRAY_SLOTS) ?? read(DATA_VIEW_SLOTS);
589
+ if (region === null) {
590
+ for (const byteLength of [ARRAY_BUFFER_BYTE_LENGTH, SHARED_BUFFER_BYTE_LENGTH]) {
591
+ if (!byteLength) continue;
592
+ try {
593
+ region = { buffer: value, offset: 0, length: byteLength.call(value) };
594
+ break;
595
+ } catch {
596
+ // Not that kind of buffer.
597
+ }
598
+ }
599
+ }
600
+ if (region === null) return null;
601
+ let detached = false;
602
+ try {
603
+ detached = ARRAY_BUFFER_DETACHED ? ARRAY_BUFFER_DETACHED.call(region.buffer) : false;
604
+ } catch {
605
+ detached = false; // A SharedArrayBuffer cannot be detached.
606
+ }
607
+ return { ...region, detached };
608
+ }
609
+
610
+ /** The largest Ethereum chain ID (EIP-2294). */
611
+ const ETH_MAX_CHAIN_ID = 9223372036854775771n;
612
+
498
613
  // =============================================================================
499
614
  // Memory Helpers
500
615
  // =============================================================================
@@ -503,17 +618,23 @@ function decodeBech32Fallback(str) {
503
618
  * Allocate memory and copy data
504
619
  * @param {WebAssembly.Module} wasm - WASM module
505
620
  * @param {Uint8Array} data - Data to copy
506
- * @returns {number} Pointer to allocated memory
621
+ * @returns {{ptr: number, len: number}} Heap pointer and the copied length
507
622
  */
508
623
  function allocAndCopy(wasm, data) {
624
+ // Only binary data reaches the heap (a string or array would be coerced).
625
+ V.bytes(data, 'data');
626
+ // One copy from the array's own bytes and length: the caller's `length`
627
+ // (a subclass getter, an own property) is never read again. Callers pass
628
+ // the returned `len` to native code.
629
+ const copy = new Uint8Array(data);
630
+ const len = copy.length;
509
631
  // Many C++ implementations assume pointers are non-null even when `len == 0`.
510
632
  // Allocate a 1-byte placeholder in that case to avoid passing NULL into WASM.
511
- const len = data.length;
512
- const allocLen = len === 0 ? 1 : len;
513
- const ptr = wasm._hd_alloc(allocLen);
633
+ const ptr = wasm._hd_alloc(len === 0 ? 1 : len);
514
634
  if (!ptr) throw new HDWalletError(ErrorCode.OUT_OF_MEMORY);
515
- if (len > 0) wasm.HEAPU8.set(data, ptr);
516
- return ptr;
635
+ wasm.HEAPU8.set(copy, ptr);
636
+ copy.fill(0);
637
+ return { ptr, len };
517
638
  }
518
639
 
519
640
  /**
@@ -523,6 +644,7 @@ function allocAndCopy(wasm, data) {
523
644
  * @returns {number} Pointer to allocated memory
524
645
  */
525
646
  function allocString(wasm, str) {
647
+ V.cString(str, 'string argument');
526
648
  const len = wasm.lengthBytesUTF8(str) + 1;
527
649
  const ptr = wasm._hd_alloc(len);
528
650
  if (!ptr) throw new HDWalletError(ErrorCode.OUT_OF_MEMORY);
@@ -649,17 +771,10 @@ function callDynamicStringResult(wasm, invoke, options = {}) {
649
771
  }
650
772
  }
651
773
 
774
+ // Binary data is a Uint8Array in every family: a DataView, an ArrayBuffer or
775
+ // another typed array is refused, as everywhere else.
652
776
  function toUint8Array(data, name = 'data') {
653
- if (data instanceof Uint8Array) {
654
- return data;
655
- }
656
- if (ArrayBuffer.isView(data)) {
657
- return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
658
- }
659
- if (data instanceof ArrayBuffer) {
660
- return new Uint8Array(data.slice(0));
661
- }
662
- throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, `${name} must be binary data`);
777
+ return V.bytes(data, name);
663
778
  }
664
779
 
665
780
  function curveToName(curve) {
@@ -916,7 +1031,7 @@ class HDKey {
916
1031
  publicKeyUncompressed() {
917
1032
  this._checkDestroyed();
918
1033
  const compressed = this.publicKey();
919
- const inPtr = allocAndCopy(this._wasm, compressed);
1034
+ const { ptr: inPtr, len: inLen } = allocAndCopy(this._wasm, compressed);
920
1035
  const outPtr = this._wasm._hd_alloc(65);
921
1036
  try {
922
1037
  const result = this._wasm._hd_curve_decompress_pubkey(inPtr, Curve.SECP256K1, outPtr, 65);
@@ -960,6 +1075,7 @@ class HDKey {
960
1075
  */
961
1076
  deriveChild(index) {
962
1077
  this._checkDestroyed();
1078
+ V.uint32(index, 'index');
963
1079
  const childHandle = this._wasm._hd_key_derive_child(this._handle, index);
964
1080
  if (!childHandle) {
965
1081
  throw new HDWalletError(ErrorCode.KEY_DERIVATION_FAILED);
@@ -975,6 +1091,7 @@ class HDKey {
975
1091
  */
976
1092
  deriveHardened(index) {
977
1093
  this._checkDestroyed();
1094
+ V.integer(index, 'index', 0, 0x7fffffff);
978
1095
  const childHandle = this._wasm._hd_key_derive_hardened(this._handle, index);
979
1096
  if (!childHandle) {
980
1097
  throw new HDWalletError(ErrorCode.KEY_DERIVATION_FAILED);
@@ -1066,11 +1183,11 @@ class HDKey {
1066
1183
  peerId() {
1067
1184
  this._checkDestroyed();
1068
1185
  const pubKey = this.publicKey();
1069
- const pubPtr = allocAndCopy(this._wasm, pubKey);
1186
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(this._wasm, pubKey);
1070
1187
  const outPtr = this._wasm._hd_alloc(64);
1071
1188
  try {
1072
- const result = this._wasm._hd_libp2p_peer_id(pubPtr, pubKey.length, this.curve, outPtr, 64);
1073
- if (result < 0) throw new HDWalletError(result);
1189
+ const result = checkLengthWithin(
1190
+ this._wasm._hd_libp2p_peer_id(pubPtr, pubLen, this.curve, outPtr, 64), 1, 64);
1074
1191
  return readBytes(this._wasm, outPtr, result);
1075
1192
  } finally {
1076
1193
  this._wasm._hd_dealloc(pubPtr);
@@ -1085,10 +1202,10 @@ class HDKey {
1085
1202
  peerIdString() {
1086
1203
  this._checkDestroyed();
1087
1204
  const pubKey = this.publicKey();
1088
- const pubPtr = allocAndCopy(this._wasm, pubKey);
1205
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(this._wasm, pubKey);
1089
1206
  const outPtr = this._wasm._hd_alloc(128);
1090
1207
  try {
1091
- const result = this._wasm._hd_libp2p_peer_id_string(pubPtr, pubKey.length, this.curve, outPtr, 128);
1208
+ const result = this._wasm._hd_libp2p_peer_id_string(pubPtr, pubLen, this.curve, outPtr, 128);
1092
1209
  checkResult(result);
1093
1210
  return readString(this._wasm, outPtr);
1094
1211
  } finally {
@@ -1104,10 +1221,10 @@ class HDKey {
1104
1221
  ipnsHash() {
1105
1222
  this._checkDestroyed();
1106
1223
  const pubKey = this.publicKey();
1107
- const pubPtr = allocAndCopy(this._wasm, pubKey);
1224
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(this._wasm, pubKey);
1108
1225
  const outPtr = this._wasm._hd_alloc(128);
1109
1226
  try {
1110
- const result = this._wasm._hd_libp2p_ipns_hash(pubPtr, pubKey.length, this.curve, outPtr, 128);
1227
+ const result = this._wasm._hd_libp2p_ipns_hash(pubPtr, pubLen, this.curve, outPtr, 128);
1111
1228
  checkResult(result);
1112
1229
  return readString(this._wasm, outPtr);
1113
1230
  } finally {
@@ -1272,6 +1389,8 @@ function createModule(wasm) {
1272
1389
  * @throws {HDWalletError} If entropy not available
1273
1390
  */
1274
1391
  generate(wordCount = 24, language = Language.ENGLISH) {
1392
+ V.integer(wordCount, 'wordCount', 0, 0x7fffffff);
1393
+ V.integer(language, 'language', 0, 0x7fffffff);
1275
1394
  const ptr = wasm._hd_alloc(1024);
1276
1395
  try {
1277
1396
  const result = wasm._hd_mnemonic_generate(ptr, 1024, wordCount, language);
@@ -1289,6 +1408,8 @@ function createModule(wasm) {
1289
1408
  * @returns {boolean} True if valid
1290
1409
  */
1291
1410
  validate(mnemonicStr, language = Language.ENGLISH) {
1411
+ V.integer(language, 'language', 0, 0x7fffffff);
1412
+ if (!V.isCString(V.string(mnemonicStr, 'mnemonic'))) return false;
1292
1413
  const ptr = allocString(wasm, mnemonicStr);
1293
1414
  try {
1294
1415
  const result = wasm._hd_mnemonic_validate(ptr, language);
@@ -1305,6 +1426,8 @@ function createModule(wasm) {
1305
1426
  * @returns {Uint8Array} 64-byte seed
1306
1427
  */
1307
1428
  toSeed(mnemonicStr, passphrase = '') {
1429
+ V.string(mnemonicStr, 'mnemonic');
1430
+ V.string(passphrase, 'passphrase');
1308
1431
  const mnemonicPtr = allocString(wasm, mnemonicStr);
1309
1432
  const passphrasePtr = allocString(wasm, passphrase);
1310
1433
  const seedPtr = wasm._hd_alloc(64);
@@ -1330,6 +1453,7 @@ function createModule(wasm) {
1330
1453
  * @returns {Uint8Array} Entropy bytes
1331
1454
  */
1332
1455
  toEntropy(mnemonicStr, language = Language.ENGLISH) {
1456
+ V.integer(language, 'language', 0, 0x7fffffff);
1333
1457
  const mnemonicPtr = allocString(wasm, mnemonicStr);
1334
1458
  const mnemonicLen = wasm.lengthBytesUTF8(mnemonicStr) + 1;
1335
1459
  const entropyPtr = wasm._hd_alloc(33);
@@ -1338,7 +1462,7 @@ function createModule(wasm) {
1338
1462
  wasm.setValue(sizePtr, 33, 'i32');
1339
1463
  const result = wasm._hd_mnemonic_to_entropy(mnemonicPtr, language, entropyPtr, sizePtr);
1340
1464
  checkResult(result);
1341
- const size = wasm.getValue(sizePtr, 'i32');
1465
+ const size = checkLengthWithin(wasm.getValue(sizePtr, 'i32'), 16, 32);
1342
1466
  return readBytes(wasm, entropyPtr, size);
1343
1467
  } finally {
1344
1468
  // SECURITY FIX [HIGH-04]: Wipe mnemonic from memory before deallocation
@@ -1358,15 +1482,17 @@ function createModule(wasm) {
1358
1482
  * @returns {string} Mnemonic phrase
1359
1483
  */
1360
1484
  fromEntropy(entropy, language = Language.ENGLISH) {
1361
- const entropyPtr = allocAndCopy(wasm, entropy);
1485
+ V.bytes(entropy, 'entropy', ErrorCode.INVALID_ENTROPY_LENGTH);
1486
+ V.integer(language, 'language', 0, 0x7fffffff);
1487
+ const { ptr: entropyPtr, len: entropyLen } = allocAndCopy(wasm, entropy);
1362
1488
  const outputPtr = wasm._hd_alloc(1024);
1363
1489
  try {
1364
- const result = wasm._hd_entropy_to_mnemonic(entropyPtr, entropy.length, language, outputPtr, 1024);
1490
+ const result = wasm._hd_entropy_to_mnemonic(entropyPtr, entropyLen, language, outputPtr, 1024);
1365
1491
  checkResult(result);
1366
1492
  return readString(wasm, outputPtr);
1367
1493
  } finally {
1368
1494
  // SECURITY FIX [HIGH-04]: Wipe sensitive data before deallocation
1369
- wasm._hd_secure_wipe(entropyPtr, entropy.length);
1495
+ wasm._hd_secure_wipe(entropyPtr, entropyLen);
1370
1496
  wasm._hd_dealloc(entropyPtr);
1371
1497
  // Wipe mnemonic output buffer
1372
1498
  wasm._hd_secure_wipe(outputPtr, 1024);
@@ -1380,6 +1506,7 @@ function createModule(wasm) {
1380
1506
  * @returns {string[]} Array of 2048 words
1381
1507
  */
1382
1508
  getWordlist(language = Language.ENGLISH) {
1509
+ V.integer(language, 'language', 0, 0x7fffffff);
1383
1510
  const ptr = wasm._hd_mnemonic_get_wordlist(language);
1384
1511
  if (!ptr) {
1385
1512
  throw new HDWalletError(ErrorCode.NOT_SUPPORTED, 'Mnemonic wordlist not available for selected language');
@@ -1396,6 +1523,8 @@ function createModule(wasm) {
1396
1523
  * @returns {string[]} Suggested words
1397
1524
  */
1398
1525
  suggestWords(prefix, language = Language.ENGLISH, maxSuggestions = 5) {
1526
+ V.integer(language, 'language', 0, 0x7fffffff);
1527
+ V.integer(maxSuggestions, 'maxSuggestions', 0, 2048);
1399
1528
  const prefixPtr = allocString(wasm, prefix);
1400
1529
  const outputPtr = wasm._hd_alloc(1024);
1401
1530
  try {
@@ -1417,10 +1546,15 @@ function createModule(wasm) {
1417
1546
  * @returns {boolean} True if word is in wordlist
1418
1547
  */
1419
1548
  checkWord(word, language = Language.ENGLISH) {
1549
+ V.integer(language, 'language', 0, 0x7fffffff);
1420
1550
  const wordPtr = allocString(wasm, word);
1421
1551
  try {
1422
1552
  const result = wasm._hd_mnemonic_check_word(wordPtr, language);
1423
- return result >= 0;
1553
+ // Word index 0-2047, or -1 when the word is not in the list; any
1554
+ // other value is an error (invalid argument, unsupported language).
1555
+ if (result >= 0 && result <= 2047) return true;
1556
+ if (result === -1) return false;
1557
+ throw new HDWalletError(nativeErrorCode(result));
1424
1558
  } finally {
1425
1559
  wasm._hd_dealloc(wordPtr);
1426
1560
  }
@@ -1444,18 +1578,17 @@ function createModule(wasm) {
1444
1578
  */
1445
1579
  fromSeed(seed, curve = Curve.SECP256K1) {
1446
1580
  // BIP-32 allows 128-512 bits (16-64 bytes)
1447
- if (seed.length < 16 || seed.length > 64) {
1448
- throw new HDWalletError(ErrorCode.INVALID_SEED, 'Seed must be 16-64 bytes');
1449
- }
1450
- const seedPtr = allocAndCopy(wasm, seed);
1581
+ V.bytesInRange(seed, 16, 64, 'Seed', ErrorCode.INVALID_SEED);
1582
+ V.curve(curve);
1583
+ const { ptr: seedPtr, len: seedLen } = allocAndCopy(wasm, seed);
1451
1584
  try {
1452
- const handle = wasm._hd_key_from_seed(seedPtr, seed.length, curve);
1585
+ const handle = wasm._hd_key_from_seed(seedPtr, seedLen, curve);
1453
1586
  if (!handle) {
1454
1587
  throw new HDWalletError(ErrorCode.INVALID_SEED);
1455
1588
  }
1456
1589
  return new HDKey(wasm, handle, 'm');
1457
1590
  } finally {
1458
- wasm._hd_secure_wipe(seedPtr, seed.length);
1591
+ wasm._hd_secure_wipe(seedPtr, seedLen);
1459
1592
  wasm._hd_dealloc(seedPtr);
1460
1593
  }
1461
1594
  },
@@ -1506,6 +1639,11 @@ function createModule(wasm) {
1506
1639
  * @returns {string} Derivation path
1507
1640
  */
1508
1641
  buildPath(purpose, coinType, account = 0, change = 0, index = 0) {
1642
+ V.uint32(purpose, 'purpose');
1643
+ V.uint32(coinType, 'coinType');
1644
+ V.uint32(account, 'account');
1645
+ V.uint32(change, 'change');
1646
+ V.uint32(index, 'index');
1509
1647
  const ptr = wasm._hd_alloc(128);
1510
1648
  try {
1511
1649
  const result = wasm._hd_path_build(ptr, 128, purpose, coinType, account, change, index);
@@ -1570,61 +1708,39 @@ function createModule(wasm) {
1570
1708
  */
1571
1709
  publicKeyFromPrivate(privateKey, curve) {
1572
1710
  const pubFromPrivFn = requireWasmFunction(wasm, '_hd_curve_pubkey_from_privkey');
1573
- const privPtr = allocAndCopy(wasm, privateKey);
1711
+ V.privateKey(wasm, privateKey, curve);
1712
+ const { ptr: privPtr, len: privLen } = allocAndCopy(wasm, privateKey);
1574
1713
  const pubPtr = wasm._hd_alloc(65);
1575
1714
  try {
1576
- const result = pubFromPrivFn(privPtr, curve, pubPtr, 65);
1577
- if (result < 0) throw new HDWalletError(result);
1715
+ // Returns the key length; a private key outside 0 < d < n fails.
1578
1716
  const expectedLen = curve === Curve.P384 ? 49 : 33;
1579
- if (result === 0 || result === expectedLen) {
1580
- return readBytes(wasm, pubPtr, expectedLen);
1581
- }
1582
- throw new HDWalletError(result);
1717
+ checkLength(pubFromPrivFn(privPtr, curve, pubPtr, 65), expectedLen);
1718
+ return readBytes(wasm, pubPtr, expectedLen);
1583
1719
  } finally {
1584
- wasm._hd_secure_wipe(privPtr, privateKey.length);
1720
+ wasm._hd_secure_wipe(privPtr, privLen);
1585
1721
  wasm._hd_dealloc(privPtr);
1586
1722
  wasm._hd_dealloc(pubPtr);
1587
1723
  }
1588
1724
  },
1589
1725
 
1590
1726
  /**
1591
- * Compress public key
1592
- * @param {Uint8Array} publicKey - Uncompressed public key (65 bytes)
1593
- * @param {number} curve - Curve type
1594
- * @returns {Uint8Array} Compressed public key (33 bytes)
1727
+ * Compress public key. The key must be a valid uncompressed point of the
1728
+ * curve (checked by the module); the compressed form is its parity byte
1729
+ * and x-coordinate.
1730
+ * @param {Uint8Array} publicKey - Uncompressed public key (65 bytes; 97 for P-384)
1731
+ * @param {number} curve - Curve type (secp256k1, P-256 or P-384)
1732
+ * @returns {Uint8Array} Compressed public key (33 bytes; 49 for P-384)
1595
1733
  */
1596
- compressPublicKey(publicKey, curve) {
1597
- const compressFn = getWasmFunction(wasm, '_hd_curve_compress_pubkey');
1598
- if (!compressFn) {
1599
- // Lightweight JS fallback for uncompressed EC points: 0x04 || X || Y.
1600
- if (publicKey.length !== 65 && publicKey.length !== 97) {
1601
- throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Invalid uncompressed public key length');
1602
- }
1603
- if (publicKey[0] !== 0x04) {
1604
- throw new HDWalletError(ErrorCode.INVALID_PUBLIC_KEY);
1605
- }
1606
- const coordLen = (publicKey.length - 1) / 2;
1607
- const out = new Uint8Array(coordLen + 1);
1608
- out[0] = (publicKey[publicKey.length - 1] & 1) ? 0x03 : 0x02;
1609
- out.set(publicKey.slice(1, coordLen + 1), 1);
1610
- return out;
1611
- }
1612
-
1613
- const inPtr = allocAndCopy(wasm, publicKey);
1614
- const outPtr = wasm._hd_alloc(65);
1615
- try {
1616
- const result = compressFn(inPtr, curve, outPtr, 65);
1617
- if (result < 0) throw new HDWalletError(result);
1618
- const expectedLen = curve === Curve.P384 ? 49 : 33;
1619
- if (result === 0 || result === expectedLen) {
1620
- return readBytes(wasm, outPtr, expectedLen);
1621
- }
1622
- throw new HDWalletError(result);
1623
- } finally {
1624
- wasm._hd_dealloc(inPtr);
1625
- wasm._hd_dealloc(outPtr);
1626
- }
1627
- },
1734
+ compressPublicKey(publicKey, curve) {
1735
+ V.curve(curve, [Curve.SECP256K1, Curve.P256, Curve.P384]);
1736
+ const uncompressedLength = curve === Curve.P384 ? 97 : 65;
1737
+ V.publicKey(wasm, publicKey, curve, 'publicKey', [uncompressedLength]);
1738
+ const coordLen = (uncompressedLength - 1) / 2;
1739
+ const out = new Uint8Array(coordLen + 1);
1740
+ out[0] = (publicKey[uncompressedLength - 1] & 1) ? 0x03 : 0x02;
1741
+ out.set(publicKey.subarray(1, coordLen + 1), 1);
1742
+ return out;
1743
+ },
1628
1744
 
1629
1745
  /**
1630
1746
  * Decompress public key
@@ -1634,16 +1750,15 @@ function createModule(wasm) {
1634
1750
  */
1635
1751
  decompressPublicKey(publicKey, curve) {
1636
1752
  const decompressFn = requireWasmFunction(wasm, '_hd_curve_decompress_pubkey');
1637
- const inPtr = allocAndCopy(wasm, publicKey);
1753
+ // The export reads exactly 33 bytes: a valid compressed key only.
1754
+ V.publicKey(wasm, publicKey, curve, 'publicKey', [curve === Curve.P384 ? 49 : 33]);
1755
+ const { ptr: inPtr, len: inLen } = allocAndCopy(wasm, publicKey);
1638
1756
  const outPtr = wasm._hd_alloc(97);
1639
1757
  try {
1640
- const result = decompressFn(inPtr, curve, outPtr, 97);
1641
- if (result < 0) throw new HDWalletError(result);
1758
+ // Status export: 0 on success (secp256k1 only), -Error otherwise.
1759
+ checkResult(decompressFn(inPtr, curve, outPtr, 97));
1642
1760
  const expectedLen = curve === Curve.P384 ? 97 : 65;
1643
- if (result === 0 || result === expectedLen) {
1644
- return readBytes(wasm, outPtr, expectedLen);
1645
- }
1646
- throw new HDWalletError(result);
1761
+ return readBytes(wasm, outPtr, expectedLen);
1647
1762
  } finally {
1648
1763
  wasm._hd_dealloc(inPtr);
1649
1764
  wasm._hd_dealloc(outPtr);
@@ -1664,18 +1779,20 @@ function createModule(wasm) {
1664
1779
  */
1665
1780
  ecdh(curve, privateKey, publicKey) {
1666
1781
  const ecdhFn = requireWasmFunction(wasm, '_hd_ecdh');
1667
- const privPtr = allocAndCopy(wasm, privateKey);
1668
- const pubPtr = allocAndCopy(wasm, publicKey);
1782
+ V.curve(curve, [Curve.SECP256K1, Curve.P256, Curve.P384, Curve.X25519]);
1783
+ V.privateKey(wasm, privateKey, curve);
1784
+ V.publicKey(wasm, publicKey, curve);
1785
+ const { ptr: privPtr, len: privLen } = allocAndCopy(wasm, privateKey);
1786
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
1669
1787
  const secretSize = curve === Curve.P384 ? 48 : 32;
1670
1788
  const secretPtr = wasm._hd_alloc(secretSize);
1671
1789
  try {
1672
- const result = ecdhFn(curve, privPtr, privateKey.length, pubPtr, publicKey.length, secretPtr, secretSize);
1673
- if (result < 0) throw new HDWalletError(ErrorCode.INTERNAL, 'ECDH key exchange failed');
1674
- // result is the actual secret length on success
1675
- const actualLen = result > 0 ? result : secretSize;
1676
- return readBytes(wasm, secretPtr, actualLen);
1790
+ const result = ecdhFn(curve, privPtr, privLen, pubPtr, pubLen, secretPtr, secretSize);
1791
+ // Returns the secret length (the curve's field size); -1 on failure.
1792
+ if (result !== secretSize) throw new HDWalletError(ErrorCode.INTERNAL, 'ECDH key exchange failed');
1793
+ return readBytes(wasm, secretPtr, secretSize);
1677
1794
  } finally {
1678
- wasm._hd_secure_wipe(privPtr, privateKey.length);
1795
+ wasm._hd_secure_wipe(privPtr, privLen);
1679
1796
  wasm._hd_secure_wipe(secretPtr, secretSize);
1680
1797
  wasm._hd_dealloc(privPtr);
1681
1798
  wasm._hd_dealloc(pubPtr);
@@ -1697,13 +1814,15 @@ function createModule(wasm) {
1697
1814
  * @returns {Uint8Array} 64-byte compact signature r||s
1698
1815
  */
1699
1816
  sign(message, privateKey) {
1700
- const msgPtr = allocAndCopy(wasm, message);
1701
- const keyPtr = allocAndCopy(wasm, privateKey);
1817
+ V.bytes(message, 'message');
1818
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
1819
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
1820
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
1702
1821
  const sigPtr = wasm._hd_alloc(72);
1703
1822
  try {
1704
- const len = wasm._hd_secp256k1_sign(msgPtr, message.length, keyPtr, sigPtr, 72);
1705
- if (len < 0) throw new HDWalletError(len);
1706
- return readBytes(wasm, sigPtr, len);
1823
+ // Returns 64 (compact r||s); a private key outside 0 < d < n fails.
1824
+ checkLength(wasm._hd_secp256k1_sign(msgPtr, msgLen, keyPtr, sigPtr, 72), 64);
1825
+ return readBytes(wasm, sigPtr, 64);
1707
1826
  } finally {
1708
1827
  wasm._hd_secure_wipe(keyPtr, 32);
1709
1828
  wasm._hd_dealloc(msgPtr);
@@ -1722,17 +1841,19 @@ function createModule(wasm) {
1722
1841
  */
1723
1842
  signRecoverable(message, privateKey) {
1724
1843
  const signRecoverableFn = requireWasmFunction(wasm, '_hd_secp256k1_sign_recoverable');
1725
- const msgPtr = allocAndCopy(wasm, message);
1726
- const keyPtr = allocAndCopy(wasm, privateKey);
1844
+ V.bytesOfLength(message, 32, 'message hash');
1845
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
1846
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
1847
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
1727
1848
  const sigPtr = wasm._hd_alloc(65);
1728
1849
  try {
1729
1850
  // C signature: int32_t hd_secp256k1_sign_recoverable(hash, hash_len, privkey, out_sig65)
1730
- // Returns 65 on success (bytes written), negative on error.
1851
+ // Returns 65 on success (bytes written), -Error on failure.
1731
1852
  // out_sig65 = r[32] + s[32] + v[1], recovery ID is at byte[64].
1732
- const result = signRecoverableFn(msgPtr, message.length, keyPtr, sigPtr);
1733
- if (result < 0) throw new HDWalletError(result);
1734
- const signature = readBytes(wasm, sigPtr, 64);
1853
+ checkLength(signRecoverableFn(msgPtr, msgLen, keyPtr, sigPtr), 65);
1735
1854
  const recoveryId = wasm.HEAPU8[sigPtr + 64];
1855
+ if (recoveryId > 3) throw new HDWalletError(ErrorCode.INVALID_SIGNATURE);
1856
+ const signature = readBytes(wasm, sigPtr, 64);
1736
1857
  return { signature, recoveryId };
1737
1858
  } finally {
1738
1859
  wasm._hd_secure_wipe(keyPtr, 32);
@@ -1751,11 +1872,18 @@ function createModule(wasm) {
1751
1872
  * @returns {boolean} True if valid
1752
1873
  */
1753
1874
  verify(message, signature, publicKey) {
1754
- const msgPtr = allocAndCopy(wasm, message);
1755
- const sigPtr = allocAndCopy(wasm, signature);
1756
- const pubPtr = allocAndCopy(wasm, publicKey);
1875
+ V.bytes(message, 'message');
1876
+ V.bytes(signature, 'signature');
1877
+ V.bytes(publicKey, 'publicKey');
1878
+ // Compact r||s (64 bytes) or DER (8-72 bytes); a key of either SEC1 size.
1879
+ if (V.byteLength(signature) < 8 || V.byteLength(signature) > 72 || !V.hasLength(publicKey, [33, 65])) {
1880
+ return false;
1881
+ }
1882
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
1883
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, signature);
1884
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
1757
1885
  try {
1758
- const result = wasm._hd_secp256k1_verify(msgPtr, message.length, sigPtr, signature.length, pubPtr, publicKey.length);
1886
+ const result = wasm._hd_secp256k1_verify(msgPtr, msgLen, sigPtr, sigLen, pubPtr, pubLen);
1759
1887
  return result === 1;
1760
1888
  } finally {
1761
1889
  wasm._hd_dealloc(msgPtr);
@@ -1774,17 +1902,23 @@ function createModule(wasm) {
1774
1902
  */
1775
1903
  recover(message, signature, recoveryId) {
1776
1904
  const recoverFn = requireWasmFunction(wasm, '_hd_secp256k1_recover');
1777
- const msgPtr = allocAndCopy(wasm, message);
1905
+ V.bytesOfLength(message, 32, 'message hash');
1906
+ V.bytesOfLength(signature, 64, 'signature', ErrorCode.INVALID_SIGNATURE);
1907
+ if (!Number.isInteger(recoveryId) || recoveryId < 0 || recoveryId > 3) {
1908
+ throw new HDWalletError(ErrorCode.INVALID_SIGNATURE, 'recoveryId must be 0, 1, 2 or 3');
1909
+ }
1910
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
1778
1911
  // Build 65-byte sig buffer: r[32] + s[32] + v[1]
1779
1912
  // C signature: int32_t hd_secp256k1_recover(hash, hash_len, sig65, out_pubkey33)
1780
1913
  const sig65 = new Uint8Array(65);
1781
1914
  sig65.set(signature.subarray(0, 64));
1782
1915
  sig65[64] = recoveryId;
1783
- const sigPtr = allocAndCopy(wasm, sig65);
1916
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, sig65);
1784
1917
  const pubPtr = wasm._hd_alloc(33);
1785
1918
  try {
1786
- const result = recoverFn(msgPtr, message.length, sigPtr, pubPtr);
1787
- if (result < 0) throw new HDWalletError(result);
1919
+ // Returns 33 on success; a signature no key recovers from (ids 2
1920
+ // and 3 when r + n >= p included) fails, so this throws.
1921
+ checkLength(recoverFn(msgPtr, msgLen, sigPtr, pubPtr), 33);
1788
1922
  return readBytes(wasm, pubPtr, 33);
1789
1923
  } finally {
1790
1924
  wasm._hd_dealloc(msgPtr);
@@ -1800,12 +1934,13 @@ function createModule(wasm) {
1800
1934
  * @returns {Uint8Array} Shared secret
1801
1935
  */
1802
1936
  ecdh(privateKey, publicKey) {
1803
- const privPtr = allocAndCopy(wasm, privateKey);
1804
- const pubPtr = allocAndCopy(wasm, publicKey);
1937
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
1938
+ V.publicKey(wasm, publicKey, Curve.SECP256K1);
1939
+ const { ptr: privPtr, len: privLen } = allocAndCopy(wasm, privateKey);
1940
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
1805
1941
  const secretPtr = wasm._hd_alloc(32);
1806
1942
  try {
1807
- const result = wasm._hd_ecdh_secp256k1(privPtr, pubPtr, publicKey.length, secretPtr, 32);
1808
- if (result < 0) throw new HDWalletError(result);
1943
+ checkLength(wasm._hd_ecdh_secp256k1(privPtr, pubPtr, pubLen, secretPtr, 32), 32);
1809
1944
  return readBytes(wasm, secretPtr, 32);
1810
1945
  } finally {
1811
1946
  wasm._hd_secure_wipe(privPtr, 32);
@@ -1827,11 +1962,11 @@ function createModule(wasm) {
1827
1962
  * @returns {Uint8Array} 32-byte public key
1828
1963
  */
1829
1964
  publicKeyFromSeed(seed) {
1830
- const seedPtr = allocAndCopy(wasm, seed);
1965
+ V.privateKey(wasm, seed, Curve.ED25519, 'seed');
1966
+ const { ptr: seedPtr, len: seedLen } = allocAndCopy(wasm, seed);
1831
1967
  const pubPtr = wasm._hd_alloc(32);
1832
1968
  try {
1833
- const result = wasm._hd_ed25519_pubkey_from_seed(seedPtr, pubPtr, 32);
1834
- if (result < 0) throw new HDWalletError(result);
1969
+ checkResult(wasm._hd_ed25519_pubkey_from_seed(seedPtr, pubPtr, 32));
1835
1970
  return readBytes(wasm, pubPtr, 32);
1836
1971
  } finally {
1837
1972
  wasm._hd_secure_wipe(seedPtr, 32);
@@ -1847,12 +1982,13 @@ function createModule(wasm) {
1847
1982
  * @returns {Uint8Array} 64-byte signature
1848
1983
  */
1849
1984
  sign(message, privateKey) {
1850
- const msgPtr = allocAndCopy(wasm, message);
1851
- const keyPtr = allocAndCopy(wasm, privateKey);
1985
+ V.bytes(message, 'message');
1986
+ V.privateKey(wasm, privateKey, Curve.ED25519);
1987
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
1988
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
1852
1989
  const sigPtr = wasm._hd_alloc(64);
1853
1990
  try {
1854
- const len = wasm._hd_ed25519_sign(msgPtr, message.length, keyPtr, sigPtr, 64);
1855
- if (len < 0) throw new HDWalletError(len);
1991
+ checkLength(wasm._hd_ed25519_sign(msgPtr, msgLen, keyPtr, sigPtr, 64), 64);
1856
1992
  return readBytes(wasm, sigPtr, 64);
1857
1993
  } finally {
1858
1994
  wasm._hd_secure_wipe(keyPtr, 32);
@@ -1870,11 +2006,16 @@ function createModule(wasm) {
1870
2006
  * @returns {boolean} True if valid
1871
2007
  */
1872
2008
  verify(message, signature, publicKey) {
1873
- const msgPtr = allocAndCopy(wasm, message);
1874
- const sigPtr = allocAndCopy(wasm, signature);
1875
- const pubPtr = allocAndCopy(wasm, publicKey);
2009
+ V.bytes(message, 'message');
2010
+ V.bytes(signature, 'signature');
2011
+ V.bytes(publicKey, 'publicKey');
2012
+ if (V.byteLength(signature) !== 64 || V.byteLength(publicKey) !== 32) return false;
2013
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
2014
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, signature);
2015
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
1876
2016
  try {
1877
- const result = wasm._hd_ed25519_verify(msgPtr, message.length, sigPtr, signature.length, pubPtr, publicKey.length);
2017
+ // RFC 8032 strict: S < L, A and R canonical and not of small order.
2018
+ const result = wasm._hd_ed25519_verify(msgPtr, msgLen, sigPtr, sigLen, pubPtr, pubLen);
1878
2019
  return result === 1;
1879
2020
  } finally {
1880
2021
  wasm._hd_dealloc(msgPtr);
@@ -1896,13 +2037,14 @@ function createModule(wasm) {
1896
2037
  * @returns {Uint8Array} 64-byte compact signature r||s
1897
2038
  */
1898
2039
  sign(message, privateKey) {
1899
- const msgPtr = allocAndCopy(wasm, message);
1900
- const keyPtr = allocAndCopy(wasm, privateKey);
2040
+ V.bytes(message, 'message');
2041
+ V.privateKey(wasm, privateKey, Curve.P256);
2042
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
2043
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
1901
2044
  const sigPtr = wasm._hd_alloc(72);
1902
2045
  try {
1903
- const len = wasm._hd_p256_sign(msgPtr, message.length, keyPtr, sigPtr, 72);
1904
- if (len < 0) throw new HDWalletError(len);
1905
- return readBytes(wasm, sigPtr, len);
2046
+ checkLength(wasm._hd_p256_sign(msgPtr, msgLen, keyPtr, sigPtr, 72), 64);
2047
+ return readBytes(wasm, sigPtr, 64);
1906
2048
  } finally {
1907
2049
  wasm._hd_secure_wipe(keyPtr, 32);
1908
2050
  wasm._hd_dealloc(msgPtr);
@@ -1920,12 +2062,16 @@ function createModule(wasm) {
1920
2062
  */
1921
2063
  verify(message, signature, publicKey) {
1922
2064
  const verifyFn = requireWasmFunction(wasm, '_hd_p256_verify');
1923
- const msgPtr = allocAndCopy(wasm, message);
1924
- const sigPtr = allocAndCopy(wasm, signature);
1925
- const pubPtr = allocAndCopy(wasm, publicKey);
2065
+ V.bytes(message, 'message');
2066
+ V.bytes(signature, 'signature');
2067
+ // The export reads exactly 64 signature bytes and takes no length.
2068
+ if (V.byteLength(signature) !== 64 || !V.isPublicKey(wasm, publicKey, Curve.P256)) return false;
2069
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
2070
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, signature);
2071
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
1926
2072
  try {
1927
2073
  // C: hd_p256_verify(message, message_len, public_key, public_key_len, signature)
1928
- return verifyFn(msgPtr, message.length, pubPtr, publicKey.length, sigPtr) === 0;
2074
+ return verifyFn(msgPtr, msgLen, pubPtr, pubLen, sigPtr) === 0;
1929
2075
  } finally {
1930
2076
  wasm._hd_dealloc(msgPtr);
1931
2077
  wasm._hd_dealloc(sigPtr);
@@ -1934,12 +2080,13 @@ function createModule(wasm) {
1934
2080
  },
1935
2081
 
1936
2082
  ecdh(privateKey, publicKey) {
1937
- const privPtr = allocAndCopy(wasm, privateKey);
1938
- const pubPtr = allocAndCopy(wasm, publicKey);
2083
+ V.privateKey(wasm, privateKey, Curve.P256);
2084
+ V.publicKey(wasm, publicKey, Curve.P256);
2085
+ const { ptr: privPtr, len: privLen } = allocAndCopy(wasm, privateKey);
2086
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
1939
2087
  const secretPtr = wasm._hd_alloc(32);
1940
2088
  try {
1941
- const result = wasm._hd_ecdh_p256(privPtr, pubPtr, publicKey.length, secretPtr, 32);
1942
- if (result < 0) throw new HDWalletError(result);
2089
+ checkLength(wasm._hd_ecdh_p256(privPtr, pubPtr, pubLen, secretPtr, 32), 32);
1943
2090
  return readBytes(wasm, secretPtr, 32);
1944
2091
  } finally {
1945
2092
  wasm._hd_secure_wipe(privPtr, 32);
@@ -1963,13 +2110,14 @@ function createModule(wasm) {
1963
2110
  * @returns {Uint8Array} 96-byte compact signature r||s
1964
2111
  */
1965
2112
  sign(message, privateKey) {
1966
- const msgPtr = allocAndCopy(wasm, message);
1967
- const keyPtr = allocAndCopy(wasm, privateKey);
2113
+ V.bytes(message, 'message');
2114
+ V.privateKey(wasm, privateKey, Curve.P384);
2115
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
2116
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
1968
2117
  const sigPtr = wasm._hd_alloc(104);
1969
2118
  try {
1970
- const len = wasm._hd_p384_sign(msgPtr, message.length, keyPtr, sigPtr, 104);
1971
- if (len < 0) throw new HDWalletError(len);
1972
- return readBytes(wasm, sigPtr, len);
2119
+ checkLength(wasm._hd_p384_sign(msgPtr, msgLen, keyPtr, sigPtr, 104), 96);
2120
+ return readBytes(wasm, sigPtr, 96);
1973
2121
  } finally {
1974
2122
  wasm._hd_secure_wipe(keyPtr, 48);
1975
2123
  wasm._hd_dealloc(msgPtr);
@@ -1987,12 +2135,16 @@ function createModule(wasm) {
1987
2135
  */
1988
2136
  verify(message, signature, publicKey) {
1989
2137
  const verifyFn = requireWasmFunction(wasm, '_hd_p384_verify');
1990
- const msgPtr = allocAndCopy(wasm, message);
1991
- const sigPtr = allocAndCopy(wasm, signature);
1992
- const pubPtr = allocAndCopy(wasm, publicKey);
2138
+ V.bytes(message, 'message');
2139
+ V.bytes(signature, 'signature');
2140
+ // The export reads exactly 96 signature bytes and takes no length.
2141
+ if (V.byteLength(signature) !== 96 || !V.isPublicKey(wasm, publicKey, Curve.P384)) return false;
2142
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
2143
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, signature);
2144
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
1993
2145
  try {
1994
2146
  // C: hd_p384_verify(message, message_len, public_key, public_key_len, signature)
1995
- return verifyFn(msgPtr, message.length, pubPtr, publicKey.length, sigPtr) === 0;
2147
+ return verifyFn(msgPtr, msgLen, pubPtr, pubLen, sigPtr) === 0;
1996
2148
  } finally {
1997
2149
  wasm._hd_dealloc(msgPtr);
1998
2150
  wasm._hd_dealloc(sigPtr);
@@ -2001,12 +2153,13 @@ function createModule(wasm) {
2001
2153
  },
2002
2154
 
2003
2155
  ecdh(privateKey, publicKey) {
2004
- const privPtr = allocAndCopy(wasm, privateKey);
2005
- const pubPtr = allocAndCopy(wasm, publicKey);
2156
+ V.privateKey(wasm, privateKey, Curve.P384);
2157
+ V.publicKey(wasm, publicKey, Curve.P384);
2158
+ const { ptr: privPtr, len: privLen } = allocAndCopy(wasm, privateKey);
2159
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
2006
2160
  const secretPtr = wasm._hd_alloc(48);
2007
2161
  try {
2008
- const result = wasm._hd_ecdh_p384(privPtr, pubPtr, publicKey.length, secretPtr, 48);
2009
- if (result < 0) throw new HDWalletError(result);
2162
+ checkLength(wasm._hd_ecdh_p384(privPtr, pubPtr, pubLen, secretPtr, 48), 48);
2010
2163
  return readBytes(wasm, secretPtr, 48);
2011
2164
  } finally {
2012
2165
  wasm._hd_secure_wipe(privPtr, 48);
@@ -2028,11 +2181,11 @@ function createModule(wasm) {
2028
2181
  * @returns {Uint8Array} 32-byte public key
2029
2182
  */
2030
2183
  publicKey(privateKey) {
2031
- const privPtr = allocAndCopy(wasm, privateKey);
2184
+ V.privateKey(wasm, privateKey, Curve.X25519);
2185
+ const { ptr: privPtr, len: privLen } = allocAndCopy(wasm, privateKey);
2032
2186
  const pubPtr = wasm._hd_alloc(32);
2033
2187
  try {
2034
- const result = wasm._hd_x25519_pubkey(privPtr, pubPtr, 32);
2035
- if (result < 0) throw new HDWalletError(result);
2188
+ checkResult(wasm._hd_x25519_pubkey(privPtr, pubPtr, 32));
2036
2189
  return readBytes(wasm, pubPtr, 32);
2037
2190
  } finally {
2038
2191
  wasm._hd_secure_wipe(privPtr, 32);
@@ -2048,12 +2201,13 @@ function createModule(wasm) {
2048
2201
  * @returns {Uint8Array} 32-byte shared secret
2049
2202
  */
2050
2203
  ecdh(privateKey, publicKey) {
2051
- const privPtr = allocAndCopy(wasm, privateKey);
2052
- const pubPtr = allocAndCopy(wasm, publicKey);
2204
+ V.privateKey(wasm, privateKey, Curve.X25519);
2205
+ V.publicKey(wasm, publicKey, Curve.X25519);
2206
+ const { ptr: privPtr, len: privLen } = allocAndCopy(wasm, privateKey);
2207
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
2053
2208
  const secretPtr = wasm._hd_alloc(32);
2054
2209
  try {
2055
- const result = wasm._hd_ecdh_x25519(privPtr, pubPtr, secretPtr, 32);
2056
- if (result < 0) throw new HDWalletError(result);
2210
+ checkResult(wasm._hd_ecdh_x25519(privPtr, pubPtr, secretPtr, 32));
2057
2211
  return readBytes(wasm, secretPtr, 32);
2058
2212
  } finally {
2059
2213
  wasm._hd_secure_wipe(privPtr, 32);
@@ -2079,6 +2233,44 @@ function createModule(wasm) {
2079
2233
  *
2080
2234
  * @type {Object}
2081
2235
  */
2236
+ // Coins of the generic coin API and the curve of their keys.
2237
+ const COIN_CURVES = Object.freeze({
2238
+ [CoinType.BITCOIN]: Curve.SECP256K1,
2239
+ [CoinType.BITCOIN_TESTNET]: Curve.SECP256K1,
2240
+ [CoinType.ETHEREUM]: Curve.SECP256K1,
2241
+ [CoinType.COSMOS]: Curve.SECP256K1,
2242
+ [CoinType.SOLANA]: Curve.ED25519,
2243
+ [CoinType.POLKADOT]: Curve.ED25519,
2244
+ });
2245
+
2246
+ function coinCurve(coinType) {
2247
+ V.integer(coinType, 'coinType', -0x80000000, 0x7fffffff);
2248
+ const curve = COIN_CURVES[coinType];
2249
+ if (curve === undefined) {
2250
+ throw new HDWalletError(ErrorCode.NOT_SUPPORTED, `Unsupported coin type: ${coinType}`);
2251
+ }
2252
+ return curve;
2253
+ }
2254
+
2255
+ // The generic export takes 33 bytes for every coin: a compressed secp256k1
2256
+ // key, or 0x00 || an Ed25519 key.
2257
+ function coinPublicKey(coinType, publicKey) {
2258
+ const curve = coinCurve(coinType);
2259
+ if (curve === Curve.SECP256K1) {
2260
+ return V.publicKey(wasm, publicKey, Curve.SECP256K1, 'publicKey', [33]);
2261
+ }
2262
+ V.bytesOfLength(publicKey, [32, 33], 'publicKey', ErrorCode.INVALID_PUBLIC_KEY);
2263
+ if (publicKey.length === 32) {
2264
+ const padded = new Uint8Array(33);
2265
+ padded.set(publicKey, 1);
2266
+ return padded;
2267
+ }
2268
+ if (publicKey[0] !== 0x00) {
2269
+ throw new HDWalletError(ErrorCode.INVALID_PUBLIC_KEY, 'publicKey must be 32 bytes, or 33 with a 0x00 prefix');
2270
+ }
2271
+ return publicKey;
2272
+ }
2273
+
2082
2274
  const coin = {
2083
2275
  /**
2084
2276
  * Generate address from public key for any supported coin
@@ -2089,10 +2281,12 @@ function createModule(wasm) {
2089
2281
  */
2090
2282
  addressFromPubkey(coinType, publicKey, network = Network.MAINNET) {
2091
2283
  const fn = requireWasmFunction(wasm, '_hd_coin_address_from_pubkey');
2092
- const pubPtr = allocAndCopy(wasm, publicKey);
2284
+ publicKey = coinPublicKey(coinType, publicKey);
2285
+ V.oneOf(network, [Network.MAINNET, Network.TESTNET], 'network');
2286
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
2093
2287
  const outPtr = wasm._hd_alloc(128);
2094
2288
  try {
2095
- const result = fn(coinType, network, pubPtr, publicKey.length, outPtr, 128);
2289
+ const result = fn(coinType, network, pubPtr, pubLen, outPtr, 128);
2096
2290
  checkResult(result);
2097
2291
  return readString(wasm, outPtr);
2098
2292
  } finally {
@@ -2110,6 +2304,9 @@ function createModule(wasm) {
2110
2304
  */
2111
2305
  validateAddress(coinType, address, network = Network.MAINNET) {
2112
2306
  const fn = requireWasmFunction(wasm, '_hd_coin_validate_address');
2307
+ V.integer(coinType, 'coinType', -0x80000000, 0x7fffffff);
2308
+ V.integer(network, 'network', -0x80000000, 0x7fffffff);
2309
+ if (!V.isCString(V.string(address, 'address'))) return false;
2113
2310
  const addrPtr = allocString(wasm, address);
2114
2311
  try {
2115
2312
  return fn(coinType, network, addrPtr) === 0;
@@ -2127,20 +2324,22 @@ function createModule(wasm) {
2127
2324
  */
2128
2325
  signMessage(coinType, message, privateKey) {
2129
2326
  const fn = requireWasmFunction(wasm, '_hd_coin_sign_message');
2130
- const msgPtr = allocAndCopy(wasm, message);
2131
- const keyPtr = allocAndCopy(wasm, privateKey);
2327
+ V.bytes(message, 'message');
2328
+ V.privateKey(wasm, privateKey, coinCurve(coinType));
2329
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
2330
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
2132
2331
  const sigLenPtr = wasm._hd_alloc(4);
2133
2332
  // Pre-allocate enough for the largest possible signature (65 bytes for ECDSA recoverable)
2134
2333
  const maxSigLen = 128;
2135
2334
  wasm.setValue(sigLenPtr, maxSigLen, 'i32');
2136
2335
  const sigPtr = wasm._hd_alloc(maxSigLen);
2137
2336
  try {
2138
- const result = fn(coinType, msgPtr, message.length, keyPtr, sigPtr, sigLenPtr);
2337
+ const result = fn(coinType, msgPtr, msgLen, keyPtr, sigPtr, sigLenPtr);
2139
2338
  checkResult(result);
2140
- const actualLen = wasm.getValue(sigLenPtr, 'i32');
2339
+ const actualLen = checkLengthWithin(wasm.getValue(sigLenPtr, 'i32'), 1, maxSigLen);
2141
2340
  return readBytes(wasm, sigPtr, actualLen);
2142
2341
  } finally {
2143
- wasm._hd_secure_wipe(keyPtr, privateKey.length);
2342
+ wasm._hd_secure_wipe(keyPtr, keyLen);
2144
2343
  wasm._hd_dealloc(msgPtr);
2145
2344
  wasm._hd_dealloc(keyPtr);
2146
2345
  wasm._hd_dealloc(sigPtr);
@@ -2158,13 +2357,17 @@ function createModule(wasm) {
2158
2357
  */
2159
2358
  verifyMessage(coinType, message, signature, publicKey) {
2160
2359
  const fn = requireWasmFunction(wasm, '_hd_coin_verify_message');
2161
- const msgPtr = allocAndCopy(wasm, message);
2162
- const sigPtr = allocAndCopy(wasm, signature);
2163
- const pubPtr = allocAndCopy(wasm, publicKey);
2360
+ coinCurve(coinType);
2361
+ V.bytes(message, 'message');
2362
+ V.bytes(signature, 'signature');
2363
+ V.bytes(publicKey, 'publicKey');
2364
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
2365
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, signature);
2366
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
2164
2367
  try {
2165
- const result = fn(coinType, msgPtr, message.length, sigPtr, signature.length, pubPtr, publicKey.length);
2166
- if (result < 0 || result > 1) throw new HDWalletError(result);
2167
- return result === 1;
2368
+ // The export takes every length: any failure (a malformed signature
2369
+ // or key included) is "not verified".
2370
+ return fn(coinType, msgPtr, msgLen, sigPtr, sigLen, pubPtr, pubLen) === 1;
2168
2371
  } finally {
2169
2372
  wasm._hd_dealloc(msgPtr);
2170
2373
  wasm._hd_dealloc(sigPtr);
@@ -2190,7 +2393,34 @@ function createModule(wasm) {
2190
2393
  * @returns {string} Bitcoin address
2191
2394
  */
2192
2395
  getAddress(publicKey, type, network = Network.MAINNET) {
2193
- const pubPtr = allocAndCopy(wasm, publicKey);
2396
+ V.oneOf(network, [Network.MAINNET, Network.TESTNET], 'network');
2397
+ switch (type) {
2398
+ case BitcoinAddressType.P2PKH:
2399
+ V.publicKey(wasm, publicKey, Curve.SECP256K1);
2400
+ break;
2401
+ case BitcoinAddressType.P2SH:
2402
+ case BitcoinAddressType.P2WPKH:
2403
+ V.publicKey(wasm, publicKey, Curve.SECP256K1, 'publicKey', [33]);
2404
+ break;
2405
+ case BitcoinAddressType.P2TR:
2406
+ if (V.bytesOfLength(publicKey, [32, 33], 'publicKey', ErrorCode.INVALID_PUBLIC_KEY).length === 32) {
2407
+ // An x-only key: valid when 02 || x is a point.
2408
+ const even = new Uint8Array(33);
2409
+ even[0] = 0x02;
2410
+ even.set(publicKey, 1);
2411
+ V.publicKey(wasm, even, Curve.SECP256K1, 'publicKey', [33]);
2412
+ } else {
2413
+ V.publicKey(wasm, publicKey, Curve.SECP256K1, 'publicKey', [33]);
2414
+ }
2415
+ break;
2416
+ case BitcoinAddressType.P2WSH:
2417
+ // A witness script, not a key.
2418
+ V.bytesInRange(publicKey, 1, 10000, 'witnessScript');
2419
+ break;
2420
+ default:
2421
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Invalid address type');
2422
+ }
2423
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
2194
2424
  const outPtr = wasm._hd_alloc(128);
2195
2425
  try {
2196
2426
  let fnName;
@@ -2214,7 +2444,7 @@ function createModule(wasm) {
2214
2444
  throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Invalid address type');
2215
2445
  }
2216
2446
  const addressFn = requireWasmFunction(wasm, fnName);
2217
- const result = addressFn(pubPtr, publicKey.length, network, outPtr, 128);
2447
+ const result = addressFn(pubPtr, pubLen, network, outPtr, 128);
2218
2448
  checkResult(result);
2219
2449
  return readString(wasm, outPtr);
2220
2450
  } finally {
@@ -2230,6 +2460,8 @@ function createModule(wasm) {
2230
2460
  */
2231
2461
  validateAddress(address, network = Network.MAINNET) {
2232
2462
  const validateFn = requireWasmFunction(wasm, '_hd_btc_validate_address');
2463
+ V.integer(network, 'network', -0x80000000, 0x7fffffff);
2464
+ if (!V.isCString(V.string(address, 'address'))) return false;
2233
2465
  const addrPtr = allocString(wasm, address);
2234
2466
  try {
2235
2467
  const result = validateFn(addrPtr, network);
@@ -2250,7 +2482,7 @@ function createModule(wasm) {
2250
2482
  wasm.setValue(hashLenPtr, 32, 'i32');
2251
2483
  const result = decodeFn(addrPtr, typePtr, hashPtr, hashLenPtr, networkPtr);
2252
2484
  checkResult(result);
2253
- const hashLen = wasm.getValue(hashLenPtr, 'i32');
2485
+ const hashLen = checkLengthWithin(wasm.getValue(hashLenPtr, 'i32'), 1, 32);
2254
2486
  return {
2255
2487
  type: wasm.getValue(typePtr, 'i32'),
2256
2488
  hash: readBytes(wasm, hashPtr, hashLen),
@@ -2267,6 +2499,7 @@ function createModule(wasm) {
2267
2499
 
2268
2500
  detectAddressType(address, network = Network.MAINNET) {
2269
2501
  const detectFn = requireWasmFunction(wasm, '_hd_btc_detect_address_type');
2502
+ V.integer(network, 'network', -0x80000000, 0x7fffffff);
2270
2503
  const addrPtr = allocString(wasm, address);
2271
2504
  try {
2272
2505
  return detectFn(addrPtr, network);
@@ -2277,7 +2510,9 @@ function createModule(wasm) {
2277
2510
 
2278
2511
  toWIF(privateKey, compressed = true, network = Network.MAINNET) {
2279
2512
  const toWifFn = requireWasmFunction(wasm, '_hd_btc_to_wif');
2280
- const keyPtr = allocAndCopy(wasm, privateKey);
2513
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
2514
+ V.oneOf(network, [Network.MAINNET, Network.TESTNET], 'network');
2515
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
2281
2516
  const outPtr = wasm._hd_alloc(64);
2282
2517
  try {
2283
2518
  const result = toWifFn(keyPtr, compressed ? 1 : 0, network, outPtr, 64);
@@ -2312,8 +2547,10 @@ function createModule(wasm) {
2312
2547
 
2313
2548
  signMessage(message, privateKey, compressed = true) {
2314
2549
  const signFn = requireWasmFunction(wasm, '_hd_btc_sign_message');
2550
+ V.string(message, 'message');
2551
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
2315
2552
  const msgPtr = allocString(wasm, message);
2316
- const keyPtr = allocAndCopy(wasm, privateKey);
2553
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
2317
2554
  const sigPtr = wasm._hd_alloc(65);
2318
2555
  try {
2319
2556
  const result = signFn(msgPtr, keyPtr, compressed ? 1 : 0, sigPtr, 65);
@@ -2330,12 +2567,23 @@ function createModule(wasm) {
2330
2567
 
2331
2568
  verifyMessage(message, signature, address, network = Network.MAINNET) {
2332
2569
  const verifyFn = requireWasmFunction(wasm, '_hd_btc_verify_message');
2333
- const sigBytes = decodeBase64Fallback(signature);
2570
+ V.string(message, 'message');
2571
+ V.string(signature, 'signature');
2572
+ V.string(address, 'address');
2573
+ V.integer(network, 'network', -0x80000000, 0x7fffffff);
2574
+ // A signature that is not base64 of 65 bytes is "not verified".
2575
+ let sigBytes;
2576
+ try {
2577
+ sigBytes = decodeBase64Fallback(signature);
2578
+ } catch {
2579
+ return false;
2580
+ }
2581
+ if (V.byteLength(sigBytes) !== 65) return false;
2334
2582
  const msgPtr = allocString(wasm, message);
2335
- const sigPtr = allocAndCopy(wasm, sigBytes);
2583
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, sigBytes);
2336
2584
  const addrPtr = allocString(wasm, address);
2337
2585
  try {
2338
- const result = verifyFn(msgPtr, sigPtr, sigBytes.length, addrPtr, network);
2586
+ const result = verifyFn(msgPtr, sigPtr, sigLen, addrPtr, network);
2339
2587
  return result === 1;
2340
2588
  } finally {
2341
2589
  wasm._hd_dealloc(msgPtr);
@@ -2355,30 +2603,49 @@ function createModule(wasm) {
2355
2603
 
2356
2604
  addInput(input) {
2357
2605
  const addInputFn = requireWasmFunction(wasm, '_hd_btc_tx_add_input');
2358
- // Convert hex txid to 32-byte array
2359
- const txidHex = input.txid;
2360
- const txidBytes = new Uint8Array(32);
2361
- for (let i = 0; i < 32; i++) {
2362
- txidBytes[i] = parseInt(txidHex.substr(i * 2, 2), 16);
2606
+ if (input === null || typeof input !== 'object') {
2607
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'input must be an object');
2363
2608
  }
2364
- const txidPtr = allocAndCopy(wasm, txidBytes);
2365
- const pubPtr = input.pubkey ? allocAndCopy(wasm, input.pubkey) : 0;
2609
+ // Each field is read once; only these values are used below.
2610
+ const { txid, vout, amount: amountArg, scriptType: scriptTypeArg, sequence: sequenceArg, pubkey } = input;
2611
+ // The txid: 32 bytes as 64 hex digits, in the given byte order.
2612
+ const txidBytes = typeof txid === 'string' && txid.length === 64
2613
+ ? hexOrNull(txid)
2614
+ : null;
2615
+ if (txidBytes === null) {
2616
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'txid must be 64 hex digits');
2617
+ }
2618
+ V.uint32(vout, 'vout');
2619
+ const amount = V.unsignedBigInt(amountArg, 'amount', 64);
2620
+ const scriptType = scriptTypeArg || 0;
2621
+ V.integer(scriptType, 'scriptType', 0, BitcoinScriptType.MULTISIG);
2622
+ const sequence = sequenceArg || 0xffffffff;
2623
+ V.uint32(sequence, 'sequence');
2624
+ // The export reads a 33-byte compressed key.
2625
+ V.publicKey(wasm, pubkey, Curve.SECP256K1, 'pubkey', [33]);
2626
+ const { ptr: txidPtr } = allocAndCopy(wasm, txidBytes);
2627
+ const { ptr: pubPtr } = allocAndCopy(wasm, pubkey);
2366
2628
  try {
2367
2629
  const result = addInputFn(
2368
- this._handle, txidPtr, input.vout,
2369
- BigInt(input.amount), input.scriptType || 0,
2370
- pubPtr, input.sequence || 0xffffffff
2630
+ this._handle, txidPtr, vout,
2631
+ amount, scriptType,
2632
+ pubPtr, sequence
2371
2633
  );
2372
2634
  checkResult(result);
2373
2635
  this._inputCount++;
2374
2636
  } finally {
2375
2637
  wasm._hd_dealloc(txidPtr);
2376
- if (pubPtr) wasm._hd_dealloc(pubPtr);
2638
+ wasm._hd_dealloc(pubPtr);
2377
2639
  }
2378
2640
  return this;
2379
2641
  },
2380
2642
 
2381
2643
  addOutput(address, amount, network = Network.MAINNET) {
2644
+ V.string(address, 'address');
2645
+ amount = V.unsignedBigInt(amount, 'amount', 64);
2646
+ // The address must be of this network: a mainnet transaction never
2647
+ // pays a testnet address, or the reverse. Regtest is not supported.
2648
+ V.oneOf(network, [Network.MAINNET, Network.TESTNET], 'network');
2382
2649
  // Use address-based output helper: decode address to script internally
2383
2650
  const addP2wpkhFn = getWasmFunction(wasm, '_hd_btc_tx_add_p2wpkh_output');
2384
2651
  const addP2pkhFn = getWasmFunction(wasm, '_hd_btc_tx_add_p2pkh_output');
@@ -2395,44 +2662,53 @@ function createModule(wasm) {
2395
2662
  try {
2396
2663
  wasm.setValue(hashLenPtr, 32, 'i32');
2397
2664
  const decResult = decodeFn(addrPtr, typePtr, hashPtr, hashLenPtr, networkPtr);
2398
- if (decResult === 0) {
2399
- const addrType = wasm.getValue(typePtr, 'i32');
2400
- const hashLen = wasm.getValue(hashLenPtr, 'i32');
2401
- // P2WPKH = 2, P2PKH = 0
2402
- if (addrType === 2 && addP2wpkhFn) {
2403
- const result = addP2wpkhFn(this._handle, BigInt(amount), hashPtr);
2404
- checkResult(result);
2405
- return this;
2406
- } else if (addrType === 0 && addP2pkhFn) {
2407
- const result = addP2pkhFn(this._handle, BigInt(amount), hashPtr);
2408
- checkResult(result);
2409
- return this;
2410
- }
2411
- // For other types, build script manually
2412
- const hash = readBytes(wasm, hashPtr, hashLen);
2413
- let script;
2414
- if (addrType === 2) { // P2WPKH: OP_0 <20-byte-hash>
2415
- script = new Uint8Array([0x00, 0x14, ...hash]);
2416
- } else if (addrType === 0) { // P2PKH: OP_DUP OP_HASH160 <20> <hash> OP_EQUALVERIFY OP_CHECKSIG
2417
- script = new Uint8Array([0x76, 0xa9, 0x14, ...hash, 0x88, 0xac]);
2418
- } else if (addrType === 1) { // P2SH: OP_HASH160 <20> <hash> OP_EQUAL
2419
- script = new Uint8Array([0xa9, 0x14, ...hash, 0x87]);
2420
- } else if (addrType === 3) { // P2WSH: OP_0 <32-byte-hash>
2421
- script = new Uint8Array([0x00, 0x20, ...hash]);
2422
- } else if (addrType === 4) { // P2TR: OP_1 <32-byte-key>
2423
- script = new Uint8Array([0x51, 0x20, ...hash]);
2424
- } else {
2425
- throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Unsupported address type');
2426
- }
2427
- const scriptPtr = allocAndCopy(wasm, script);
2428
- try {
2429
- const result = addOutputFn(this._handle, BigInt(amount), scriptPtr, script.length);
2430
- checkResult(result);
2431
- } finally {
2432
- wasm._hd_dealloc(scriptPtr);
2433
- }
2665
+ checkResult(decResult);
2666
+ const addrType = wasm.getValue(typePtr, 'i32');
2667
+ const hashLen = checkLengthWithin(wasm.getValue(hashLenPtr, 'i32'), 1, 32);
2668
+ if (wasm.getValue(networkPtr, 'i32') !== network) {
2669
+ throw new HDWalletError(ErrorCode.INVALID_ADDRESS, 'address is not of this transaction\'s network');
2670
+ }
2671
+ // The native decoder types an address by witness version and
2672
+ // program length together; each type has one payload length
2673
+ // (the P2WPKH/P2PKH helpers read a 20-byte key hash).
2674
+ const payloadLength = [20, 20, 20, 32, 32][addrType];
2675
+ if (payloadLength === undefined || hashLen !== payloadLength) {
2676
+ throw new HDWalletError(ErrorCode.INVALID_ADDRESS);
2677
+ }
2678
+ // P2WPKH = 2, P2PKH = 0
2679
+ if (addrType === 2 && addP2wpkhFn) {
2680
+ const result = addP2wpkhFn(this._handle, BigInt(amount), hashPtr);
2681
+ checkResult(result);
2682
+ return this;
2683
+ } else if (addrType === 0 && addP2pkhFn) {
2684
+ const result = addP2pkhFn(this._handle, BigInt(amount), hashPtr);
2685
+ checkResult(result);
2434
2686
  return this;
2435
2687
  }
2688
+ // For other types, build script manually
2689
+ const hash = readBytes(wasm, hashPtr, hashLen);
2690
+ let script;
2691
+ if (addrType === 2) { // P2WPKH: OP_0 <20-byte-hash>
2692
+ script = new Uint8Array([0x00, 0x14, ...hash]);
2693
+ } else if (addrType === 0) { // P2PKH: OP_DUP OP_HASH160 <20> <hash> OP_EQUALVERIFY OP_CHECKSIG
2694
+ script = new Uint8Array([0x76, 0xa9, 0x14, ...hash, 0x88, 0xac]);
2695
+ } else if (addrType === 1) { // P2SH: OP_HASH160 <20> <hash> OP_EQUAL
2696
+ script = new Uint8Array([0xa9, 0x14, ...hash, 0x87]);
2697
+ } else if (addrType === 3) { // P2WSH: OP_0 <32-byte-hash>
2698
+ script = new Uint8Array([0x00, 0x20, ...hash]);
2699
+ } else if (addrType === 4) { // P2TR: OP_1 <32-byte-key>
2700
+ script = new Uint8Array([0x51, 0x20, ...hash]);
2701
+ } else {
2702
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Unsupported address type');
2703
+ }
2704
+ const { ptr: scriptPtr, len: scriptLen } = allocAndCopy(wasm, script);
2705
+ try {
2706
+ const result = addOutputFn(this._handle, BigInt(amount), scriptPtr, scriptLen);
2707
+ checkResult(result);
2708
+ } finally {
2709
+ wasm._hd_dealloc(scriptPtr);
2710
+ }
2711
+ return this;
2436
2712
  } finally {
2437
2713
  wasm._hd_dealloc(addrPtr);
2438
2714
  wasm._hd_dealloc(typePtr);
@@ -2446,7 +2722,8 @@ function createModule(wasm) {
2446
2722
 
2447
2723
  sign(privateKey) {
2448
2724
  const signFn = requireWasmFunction(wasm, '_hd_btc_tx_sign');
2449
- const keyPtr = allocAndCopy(wasm, privateKey);
2725
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
2726
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
2450
2727
  try {
2451
2728
  // Sign all inputs
2452
2729
  for (let i = 0; i < this._inputCount; i++) {
@@ -2468,7 +2745,7 @@ function createModule(wasm) {
2468
2745
  wasm.setValue(sizePtr, 65536, 'i32');
2469
2746
  const result = serializeFn(this._handle, outPtr, 65536, sizePtr);
2470
2747
  checkResult(result);
2471
- const size = wasm.getValue(sizePtr, 'i32');
2748
+ const size = checkLengthWithin(wasm.getValue(sizePtr, 'i32'), 1, 65536);
2472
2749
  return readBytes(wasm, outPtr, size);
2473
2750
  } finally {
2474
2751
  wasm._hd_dealloc(outPtr);
@@ -2533,9 +2810,10 @@ function createModule(wasm) {
2533
2810
 
2534
2811
  parse(data) {
2535
2812
  const parseFn = requireWasmFunction(wasm, '_hd_btc_tx_parse');
2536
- const dataPtr = allocAndCopy(wasm, data);
2813
+ V.bytes(data, 'data');
2814
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
2537
2815
  try {
2538
- const handle = parseFn(dataPtr, data.length);
2816
+ const handle = parseFn(dataPtr, dataLen);
2539
2817
  if (!handle) throw new HDWalletError(ErrorCode.INVALID_TRANSACTION);
2540
2818
  return this._makeTxObj(handle);
2541
2819
  } finally {
@@ -2553,6 +2831,12 @@ function createModule(wasm) {
2553
2831
  * Ethereum API
2554
2832
  * @type {Object}
2555
2833
  */
2834
+ // An Ethereum message: a string (UTF-8) or bytes.
2835
+ function ethereumMessageBytes(message) {
2836
+ if (typeof message === 'string') return UTF8_ENCODER.encode(message);
2837
+ return V.bytes(message, 'message');
2838
+ }
2839
+
2556
2840
  const ethereum = {
2557
2841
  /**
2558
2842
  * Get Ethereum address from public key
@@ -2561,10 +2845,20 @@ function createModule(wasm) {
2561
2845
  */
2562
2846
  getAddress(publicKey) {
2563
2847
  const getAddressFn = requireWasmFunction(wasm, '_hd_eth_address');
2564
- const pubPtr = allocAndCopy(wasm, publicKey);
2848
+ V.bytesOfLength(publicKey, [33, 64, 65], 'publicKey', ErrorCode.INVALID_PUBLIC_KEY);
2849
+ if (V.byteLength(publicKey) === 64) {
2850
+ // Raw x || y: valid when 04 || x || y is a point.
2851
+ const sec1 = new Uint8Array(65);
2852
+ sec1[0] = 0x04;
2853
+ sec1.set(publicKey, 1);
2854
+ V.publicKey(wasm, sec1, Curve.SECP256K1);
2855
+ } else {
2856
+ V.publicKey(wasm, publicKey, Curve.SECP256K1);
2857
+ }
2858
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
2565
2859
  const outPtr = wasm._hd_alloc(64);
2566
2860
  try {
2567
- const result = getAddressFn(pubPtr, publicKey.length, outPtr, 64);
2861
+ const result = getAddressFn(pubPtr, pubLen, outPtr, 64);
2568
2862
  checkResult(result);
2569
2863
  return readString(wasm, outPtr);
2570
2864
  } finally {
@@ -2599,6 +2893,7 @@ function createModule(wasm) {
2599
2893
  */
2600
2894
  validateAddress(address) {
2601
2895
  const validateFn = requireWasmFunction(wasm, '_hd_eth_validate_address');
2896
+ if (!V.isCString(V.string(address, 'address'))) return false;
2602
2897
  const addrPtr = allocString(wasm, address);
2603
2898
  try {
2604
2899
  const result = validateFn(addrPtr);
@@ -2616,13 +2911,13 @@ function createModule(wasm) {
2616
2911
  */
2617
2912
  signMessage(message, privateKey) {
2618
2913
  const signFn = requireWasmFunction(wasm, '_hd_eth_sign_message');
2619
- const enc = new TextEncoder();
2620
- const msgBytes = typeof message === 'string' ? enc.encode(message) : message;
2621
- const msgPtr = allocAndCopy(wasm, msgBytes);
2622
- const keyPtr = allocAndCopy(wasm, privateKey);
2914
+ const msgBytes = ethereumMessageBytes(message);
2915
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
2916
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, msgBytes);
2917
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
2623
2918
  const sigPtr = wasm._hd_alloc(65);
2624
2919
  try {
2625
- const result = signFn(msgPtr, msgBytes.length, keyPtr, sigPtr, 65);
2920
+ const result = signFn(msgPtr, msgLen, keyPtr, sigPtr, 65);
2626
2921
  checkResult(result);
2627
2922
  const sigBytes = readBytes(wasm, sigPtr, 65);
2628
2923
  return '0x' + encodeHexFallback(sigBytes);
@@ -2642,16 +2937,22 @@ function createModule(wasm) {
2642
2937
  */
2643
2938
  signTypedData(typedData, privateKey) {
2644
2939
  const signTypedDataFn = requireWasmFunction(wasm, '_hd_eth_sign_typed_data');
2940
+ if (typedData === null || typeof typedData !== 'object' ||
2941
+ typedData.types === null || typeof typedData.types !== 'object' ||
2942
+ typeof typedData.primaryType !== 'string' ||
2943
+ typedData.domain === null || typeof typedData.domain !== 'object') {
2944
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'typedData must have types, primaryType, domain and message');
2945
+ }
2946
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
2645
2947
 
2646
2948
  // --- EIP-712 struct hash computation (data encoding, not crypto) ---
2647
2949
  // keccak256 via WASM
2648
2950
  function k256(data) {
2649
2951
  const d = data instanceof Uint8Array ? data : new TextEncoder().encode(data);
2650
- const dPtr = allocAndCopy(wasm, d);
2952
+ const { ptr: dPtr, len: dLen } = allocAndCopy(wasm, d);
2651
2953
  const hPtr = wasm._hd_alloc(32);
2652
2954
  try {
2653
- const r = wasm._hd_hash_keccak256(dPtr, d.length, hPtr, 32);
2654
- if (r < 0) throw new HDWalletError(r);
2955
+ checkLength(wasm._hd_hash_keccak256(dPtr, dLen, hPtr, 32), 32);
2655
2956
  return readBytes(wasm, hPtr, 32);
2656
2957
  } finally {
2657
2958
  wasm._hd_dealloc(dPtr);
@@ -2743,8 +3044,8 @@ function createModule(wasm) {
2743
3044
  const namePtr = allocString(wasm, domain.name || '');
2744
3045
  const versionPtr = allocString(wasm, domain.version || '');
2745
3046
  const contractPtr = allocString(wasm, domain.verifyingContract || '');
2746
- const structHashPtr = allocAndCopy(wasm, structHash);
2747
- const keyPtr = allocAndCopy(wasm, privateKey);
3047
+ const { ptr: structHashPtr, len: structHashLen } = allocAndCopy(wasm, structHash);
3048
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
2748
3049
  const sigPtr = wasm._hd_alloc(65);
2749
3050
  try {
2750
3051
  const result = signTypedDataFn(namePtr, versionPtr, BigInt(domain.chainId || 0), contractPtr, structHashPtr, keyPtr, sigPtr, 65);
@@ -2762,17 +3063,29 @@ function createModule(wasm) {
2762
3063
  }
2763
3064
  },
2764
3065
 
3066
+ /**
3067
+ * Recover the signer's address of an EIP-191 message signature. A recover,
3068
+ * not a boolean verify: compare the result with the expected address. A
3069
+ * signature that recovers no key (malformed hex, not 65 bytes, v outside
3070
+ * 0, 1, 27, 28, r or s out of range) throws INVALID_SIGNATURE; it never
3071
+ * yields an address.
3072
+ * @param {string|Uint8Array} message
3073
+ * @param {string} signature - 0x-prefixed hex r || s || v
3074
+ * @returns {string} Recovered address
3075
+ */
2765
3076
  verifyMessage(message, signature) {
2766
3077
  const recoverFn = requireWasmFunction(wasm, '_hd_eth_recover_address');
2767
- const enc = new TextEncoder();
2768
- const msgBytes = typeof message === 'string' ? enc.encode(message) : message;
2769
- const sigHex = signature.startsWith('0x') ? signature.slice(2) : signature;
2770
- const sigBytes = decodeHexFallback(sigHex);
2771
- const msgPtr = allocAndCopy(wasm, msgBytes);
2772
- const sigPtr = allocAndCopy(wasm, sigBytes);
3078
+ const msgBytes = ethereumMessageBytes(message);
3079
+ V.string(signature, 'signature');
3080
+ const sigBytes = hexOrNull(signature);
3081
+ if (sigBytes === null || V.byteLength(sigBytes) !== 65 || ![0, 1, 27, 28].includes(sigBytes[64])) {
3082
+ throw new HDWalletError(ErrorCode.INVALID_SIGNATURE, 'signature must be 65 bytes r || s || v with v 0, 1, 27 or 28');
3083
+ }
3084
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, msgBytes);
3085
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, sigBytes);
2773
3086
  const addrPtr = wasm._hd_alloc(64);
2774
3087
  try {
2775
- const result = recoverFn(msgPtr, msgBytes.length, sigPtr, sigBytes.length, addrPtr, 64);
3088
+ const result = recoverFn(msgPtr, msgLen, sigPtr, sigLen, addrPtr, 64);
2776
3089
  checkResult(result);
2777
3090
  return readString(wasm, addrPtr);
2778
3091
  } finally {
@@ -2794,12 +3107,11 @@ function createModule(wasm) {
2794
3107
 
2795
3108
  hashMessage(message) {
2796
3109
  const hashFn = requireWasmFunction(wasm, '_hd_eth_hash_message');
2797
- const enc = new TextEncoder();
2798
- const msgBytes = typeof message === 'string' ? enc.encode(message) : message;
2799
- const msgPtr = allocAndCopy(wasm, msgBytes);
3110
+ const msgBytes = ethereumMessageBytes(message);
3111
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, msgBytes);
2800
3112
  const hashPtr = wasm._hd_alloc(32);
2801
3113
  try {
2802
- const result = hashFn(msgPtr, msgBytes.length, hashPtr);
3114
+ const result = hashFn(msgPtr, msgLen, hashPtr);
2803
3115
  checkResult(result);
2804
3116
  return readBytes(wasm, hashPtr, 32);
2805
3117
  } finally {
@@ -2815,7 +3127,8 @@ function createModule(wasm) {
2815
3127
 
2816
3128
  sign(privateKey) {
2817
3129
  const signFn = requireWasmFunction(wasm, '_hd_eth_tx_sign');
2818
- const keyPtr = allocAndCopy(wasm, privateKey);
3130
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
3131
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
2819
3132
  try {
2820
3133
  const result = signFn(this._handle, keyPtr);
2821
3134
  checkResult(result);
@@ -2834,7 +3147,7 @@ function createModule(wasm) {
2834
3147
  wasm.setValue(sizePtr, 65536, 'i32');
2835
3148
  const result = serializeFn(this._handle, outPtr, 65536, sizePtr);
2836
3149
  checkResult(result);
2837
- const size = wasm.getValue(sizePtr, 'i32');
3150
+ const size = checkLengthWithin(wasm.getValue(sizePtr, 'i32'), 1, 65536);
2838
3151
  return readBytes(wasm, outPtr, size);
2839
3152
  } finally {
2840
3153
  wasm._hd_dealloc(outPtr);
@@ -2864,62 +3177,137 @@ function createModule(wasm) {
2864
3177
  };
2865
3178
  },
2866
3179
 
2867
- createEIP1559(params) {
2868
- const createFn = requireWasmFunction(wasm, '_hd_eth_tx_create_eip1559');
2869
- const handle = createFn(
2870
- BigInt(params.chainId),
2871
- BigInt(params.maxPriorityFeePerGas),
2872
- BigInt(params.maxFeePerGas)
2873
- );
2874
- if (!handle) throw new HDWalletError(ErrorCode.NOT_SUPPORTED);
2875
-
2876
- // Set remaining fields
2877
- const setNonceFn = requireWasmFunction(wasm, '_hd_eth_tx_set_nonce');
2878
- setNonceFn(handle, BigInt(params.nonce || 0));
3180
+ // Read each field of `params` once (a getter or a Uint8Array subclass
3181
+ // could answer differently on a second read: a longer `length` put heap
3182
+ // bytes into the signed data) and check it. The nonce and the chain ID
3183
+ // have no defaults: a missing nonce signed nonce 0, a missing chain ID
3184
+ // chain 1. The native setters take uint64 (a 256-bit value as 32 bytes);
3185
+ // a larger or negative number would wrap modulo 2^64, so it is refused.
3186
+ _readParams(params) {
3187
+ if (params === null || typeof params !== 'object') {
3188
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'params must be an object');
3189
+ }
3190
+ const {
3191
+ nonce, chainId, gasLimit, gasPrice, maxFeePerGas, maxPriorityFeePerGas, to, value, data,
3192
+ } = params;
3193
+ if (nonce === undefined || nonce === null) {
3194
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'nonce is required');
3195
+ }
3196
+ if (chainId === undefined || chainId === null) {
3197
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'chainId is required');
3198
+ }
3199
+ const fields = {
3200
+ nonce: V.unsignedBigInt(nonce, 'nonce', 64),
3201
+ chainId: V.unsignedBigInt(chainId, 'chainId', 64),
3202
+ gasLimit: gasLimit === undefined ? undefined : V.unsignedBigInt(gasLimit, 'gasLimit', 64),
3203
+ gasPrice: gasPrice === undefined ? undefined : V.unsignedBigInt(gasPrice, 'gasPrice', 64),
3204
+ maxFeePerGas,
3205
+ maxPriorityFeePerGas,
3206
+ to: to === undefined || to === null || to === '' ? undefined : V.string(to, 'to'),
3207
+ value: value === undefined ? undefined : V.unsignedBigInt(value, 'value', 256),
3208
+ // A copy made from the array's own bytes and length.
3209
+ data: data === undefined || data === null ? undefined : new Uint8Array(V.bytes(data, 'data')),
3210
+ };
3211
+ // EIP-2294: 1 <= chainId <= floor(MAX_UINT64 / 2) - 36, so a legacy
3212
+ // v = 2 * chainId + 35 + recovery id fits 64 bits.
3213
+ if (fields.chainId === 0n || fields.chainId > ETH_MAX_CHAIN_ID) {
3214
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, `chainId must be from 1 to ${ETH_MAX_CHAIN_ID}`);
3215
+ }
3216
+ return fields;
3217
+ },
2879
3218
 
2880
- const setGasLimitFn = requireWasmFunction(wasm, '_hd_eth_tx_set_gas_limit');
2881
- setGasLimitFn(handle, BigInt(params.gasLimit));
3219
+ _setCommonFields(handle, fields) {
3220
+ requireWasmFunction(wasm, '_hd_eth_tx_set_nonce')(handle, fields.nonce);
3221
+ if (fields.gasLimit !== undefined) {
3222
+ requireWasmFunction(wasm, '_hd_eth_tx_set_gas_limit')(handle, fields.gasLimit);
3223
+ }
2882
3224
 
2883
- if (params.to) {
3225
+ if (fields.to !== undefined) {
2884
3226
  const setToFn = requireWasmFunction(wasm, '_hd_eth_tx_set_to_hex');
2885
- const toPtr = allocString(wasm, params.to);
3227
+ const toPtr = allocString(wasm, fields.to);
2886
3228
  try {
2887
- setToFn(handle, toPtr);
3229
+ // An address that does not parse fails here. Without a recipient
3230
+ // the transaction does not serialize or sign (it would otherwise
3231
+ // send the value to 0x00..00).
3232
+ checkResult(setToFn(handle, toPtr));
2888
3233
  } finally {
2889
3234
  wasm._hd_dealloc(toPtr);
2890
3235
  }
2891
3236
  }
2892
3237
 
2893
- if (params.value !== undefined) {
2894
- const setValueFn = requireWasmFunction(wasm, '_hd_eth_tx_set_value');
2895
- setValueFn(handle, BigInt(params.value));
3238
+ if (fields.value !== undefined) {
3239
+ // Always the 256-bit setter: wei amounts reach 2^64 at ~18.45 ETH.
3240
+ const setValueFn = requireWasmFunction(wasm, '_hd_eth_tx_set_value_256');
3241
+ const valueBytes = new Uint8Array(32);
3242
+ for (let i = 31, v = fields.value; i >= 0 && v > 0n; i--, v >>= 8n) valueBytes[i] = Number(v & 0xffn);
3243
+ const { ptr: valuePtr, len: valueLen } = allocAndCopy(wasm, valueBytes);
3244
+ try {
3245
+ setValueFn(handle, valuePtr);
3246
+ } finally {
3247
+ wasm._hd_dealloc(valuePtr);
3248
+ }
2896
3249
  }
2897
3250
 
2898
- if (params.data) {
3251
+ if (fields.data !== undefined) {
2899
3252
  const setDataFn = requireWasmFunction(wasm, '_hd_eth_tx_set_data');
2900
- const dataPtr = allocAndCopy(wasm, params.data);
3253
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, fields.data);
2901
3254
  try {
2902
- setDataFn(handle, dataPtr, params.data.length);
3255
+ setDataFn(handle, dataPtr, dataLen);
2903
3256
  } finally {
2904
3257
  wasm._hd_dealloc(dataPtr);
2905
3258
  }
2906
3259
  }
3260
+ },
3261
+
3262
+ // EIP-1559 transaction. nonce, chainId (>= 1), gasLimit, maxFeePerGas
3263
+ // and maxPriorityFeePerGas are required.
3264
+ createEIP1559(params) {
3265
+ const createFn = requireWasmFunction(wasm, '_hd_eth_tx_create_eip1559');
3266
+ const fields = this._readParams(params);
3267
+ if (fields.gasLimit === undefined) {
3268
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'gasLimit is required');
3269
+ }
3270
+ const maxPriorityFeePerGas = V.unsignedBigInt(fields.maxPriorityFeePerGas, 'maxPriorityFeePerGas', 64);
3271
+ const maxFeePerGas = V.unsignedBigInt(fields.maxFeePerGas, 'maxFeePerGas', 64);
3272
+ const handle = createFn(fields.chainId, maxPriorityFeePerGas, maxFeePerGas);
3273
+ if (!handle) throw new HDWalletError(ErrorCode.NOT_SUPPORTED);
3274
+
3275
+ try {
3276
+ this._setCommonFields(handle, fields);
3277
+ } catch (error) {
3278
+ requireWasmFunction(wasm, '_hd_eth_tx_destroy')(handle);
3279
+ throw error;
3280
+ }
2907
3281
 
2908
3282
  return this._makeTxObj(handle);
2909
3283
  },
2910
3284
 
2911
- create(params) {
3285
+ // Legacy (EIP-155) transaction. nonce and chainId (>= 1) are required.
3286
+ // Before 2.1.1 every field but chainId was ignored, so the declared
3287
+ // to/value/gasPrice never reached the signed bytes.
3288
+ create(params = {}) {
2912
3289
  const createFn = requireWasmFunction(wasm, '_hd_eth_tx_create');
2913
- const handle = createFn(BigInt(params?.chainId || 1));
3290
+ const fields = this._readParams(params);
3291
+ const handle = createFn(fields.chainId);
2914
3292
  if (!handle) throw new HDWalletError(ErrorCode.NOT_SUPPORTED);
3293
+
3294
+ try {
3295
+ if (fields.gasPrice !== undefined) requireWasmFunction(wasm, '_hd_eth_tx_set_gas_price')(handle, fields.gasPrice);
3296
+ this._setCommonFields(handle, fields);
3297
+ } catch (error) {
3298
+ requireWasmFunction(wasm, '_hd_eth_tx_destroy')(handle);
3299
+ throw error;
3300
+ }
3301
+
2915
3302
  return this._makeTxObj(handle);
2916
3303
  },
2917
3304
 
2918
3305
  parse(data) {
2919
3306
  const parseFn = requireWasmFunction(wasm, '_hd_eth_tx_parse');
2920
- const dataPtr = allocAndCopy(wasm, data);
3307
+ V.bytes(data, 'data');
3308
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
2921
3309
  try {
2922
- const handle = parseFn(dataPtr, data.length);
3310
+ const handle = parseFn(dataPtr, dataLen);
2923
3311
  if (!handle) throw new HDWalletError(ErrorCode.INVALID_TRANSACTION);
2924
3312
  return this._makeTxObj(handle);
2925
3313
  } finally {
@@ -2940,11 +3328,13 @@ function createModule(wasm) {
2940
3328
  const cosmos = {
2941
3329
  getAddress(publicKey, prefix = 'cosmos') {
2942
3330
  const getAddressFn = requireWasmFunction(wasm, '_hd_cosmos_address');
2943
- const pubPtr = allocAndCopy(wasm, publicKey);
3331
+ V.publicKey(wasm, publicKey, Curve.SECP256K1, 'publicKey', [33]);
3332
+ V.string(prefix, 'prefix');
3333
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
2944
3334
  const prefixPtr = allocString(wasm, prefix);
2945
3335
  const outPtr = wasm._hd_alloc(128);
2946
3336
  try {
2947
- const result = getAddressFn(pubPtr, publicKey.length, prefixPtr, outPtr, 128);
3337
+ const result = getAddressFn(pubPtr, pubLen, prefixPtr, outPtr, 128);
2948
3338
  checkResult(result);
2949
3339
  return readString(wasm, outPtr);
2950
3340
  } finally {
@@ -2956,6 +3346,9 @@ function createModule(wasm) {
2956
3346
 
2957
3347
  validateAddress(address, prefix) {
2958
3348
  const validateFn = requireWasmFunction(wasm, '_hd_cosmos_validate_address');
3349
+ if (prefix !== undefined && prefix !== null && prefix !== '') V.string(prefix, 'prefix');
3350
+ if (!V.isCString(V.string(address, 'address'))) return false;
3351
+ if (prefix && !V.isCString(prefix)) return false;
2959
3352
  const addrPtr = allocString(wasm, address);
2960
3353
  const prefixPtr = prefix ? allocString(wasm, prefix) : 0;
2961
3354
  try {
@@ -2984,15 +3377,19 @@ function createModule(wasm) {
2984
3377
 
2985
3378
  signAmino(doc, privateKey) {
2986
3379
  const signAminoFn = requireWasmFunction(wasm, '_hd_cosmos_sign_amino');
3380
+ if (doc === null || typeof doc !== 'object') {
3381
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'sign doc must be an object');
3382
+ }
3383
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
2987
3384
  // Serialize the sign doc to canonical JSON bytes
2988
3385
  const jsonStr = JSON.stringify(doc);
2989
3386
  const enc = new TextEncoder();
2990
3387
  const docBytes = enc.encode(jsonStr);
2991
- const docPtr = allocAndCopy(wasm, docBytes);
2992
- const keyPtr = allocAndCopy(wasm, privateKey);
3388
+ const { ptr: docPtr, len: docLen } = allocAndCopy(wasm, docBytes);
3389
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
2993
3390
  const sigPtr = wasm._hd_alloc(64);
2994
3391
  try {
2995
- const result = signAminoFn(docPtr, docBytes.length, keyPtr, sigPtr, 64);
3392
+ const result = signAminoFn(docPtr, docLen, keyPtr, sigPtr, 64);
2996
3393
  checkResult(result);
2997
3394
  return readBytes(wasm, sigPtr, 64);
2998
3395
  } finally {
@@ -3009,11 +3406,13 @@ function createModule(wasm) {
3009
3406
  // (bodyBytes, authInfoBytes, chainId, accountNumber, privateKey) forms
3010
3407
  if (args.length === 2) {
3011
3408
  const [signDocBytes, privateKey] = args;
3012
- const docPtr = allocAndCopy(wasm, signDocBytes);
3013
- const keyPtr = allocAndCopy(wasm, privateKey);
3409
+ V.bytes(signDocBytes, 'signDocBytes');
3410
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
3411
+ const { ptr: docPtr, len: docLen } = allocAndCopy(wasm, signDocBytes);
3412
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
3014
3413
  const sigPtr = wasm._hd_alloc(64);
3015
3414
  try {
3016
- const result = signDirectFn(docPtr, signDocBytes.length, keyPtr, sigPtr, 64);
3415
+ const result = signDirectFn(docPtr, docLen, keyPtr, sigPtr, 64);
3017
3416
  checkResult(result);
3018
3417
  return readBytes(wasm, sigPtr, 64);
3019
3418
  } finally {
@@ -3024,7 +3423,16 @@ function createModule(wasm) {
3024
3423
  }
3025
3424
  } else {
3026
3425
  // 5-arg form: build SignDoc from body, authInfo, chainId, accountNumber
3027
- const [bodyBytes, authInfoBytes, chainId, accountNumber, privateKey] = args;
3426
+ if (args.length !== 5) {
3427
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'signDirect takes (signDocBytes, privateKey) or (bodyBytes, authInfoBytes, chainId, accountNumber, privateKey)');
3428
+ }
3429
+ const [bodyArg, authInfoArg, chainId, accountNumber, privateKey] = args;
3430
+ // Copies read once: the lengths below size the SignDoc.
3431
+ const bodyBytes = V.snapshot(bodyArg, 'bodyBytes');
3432
+ const authInfoBytes = V.snapshot(authInfoArg, 'authInfoBytes');
3433
+ V.string(chainId, 'chainId');
3434
+ V.unsignedBigInt(accountNumber, 'accountNumber', 64);
3435
+ V.privateKey(wasm, privateKey, Curve.SECP256K1);
3028
3436
  // Build a minimal SignDoc by concatenating the fields
3029
3437
  // The C function takes raw signDoc bytes, so we need to construct them
3030
3438
  // For protobuf SignDoc: bodyBytes + authInfoBytes + chainId + accountNumber
@@ -3043,11 +3451,11 @@ function createModule(wasm) {
3043
3451
  for (let i = 0; i < 8; i++) {
3044
3452
  signDoc[off + i] = Number((accNum >> BigInt(i * 8)) & 0xFFn);
3045
3453
  }
3046
- const docPtr = allocAndCopy(wasm, signDoc);
3047
- const keyPtr = allocAndCopy(wasm, privateKey);
3454
+ const { ptr: docPtr, len: docLen } = allocAndCopy(wasm, signDoc);
3455
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
3048
3456
  const sigPtr = wasm._hd_alloc(64);
3049
3457
  try {
3050
- const result = signDirectFn(docPtr, signDoc.length, keyPtr, sigPtr, 64);
3458
+ const result = signDirectFn(docPtr, docLen, keyPtr, sigPtr, 64);
3051
3459
  checkResult(result);
3052
3460
  return readBytes(wasm, sigPtr, 64);
3053
3461
  } finally {
@@ -3061,11 +3469,15 @@ function createModule(wasm) {
3061
3469
 
3062
3470
  verifySignature(message, signature, publicKey) {
3063
3471
  const verifyFn = requireWasmFunction(wasm, '_hd_cosmos_verify_signature');
3064
- const msgPtr = allocAndCopy(wasm, message);
3065
- const sigPtr = allocAndCopy(wasm, signature);
3066
- const pubPtr = allocAndCopy(wasm, publicKey);
3472
+ V.bytes(message, 'message');
3473
+ V.bytes(signature, 'signature');
3474
+ V.bytes(publicKey, 'publicKey');
3475
+ if (V.byteLength(signature) !== 64 || V.byteLength(publicKey) !== 33) return false;
3476
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
3477
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, signature);
3478
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
3067
3479
  try {
3068
- return verifyFn(msgPtr, message.length, sigPtr, signature.length, pubPtr, publicKey.length) === 1;
3480
+ return verifyFn(msgPtr, msgLen, sigPtr, sigLen, pubPtr, pubLen) === 1;
3069
3481
  } finally {
3070
3482
  wasm._hd_dealloc(msgPtr);
3071
3483
  wasm._hd_dealloc(sigPtr);
@@ -3089,10 +3501,12 @@ function createModule(wasm) {
3089
3501
  const solana = {
3090
3502
  getAddress(publicKey) {
3091
3503
  const getAddressFn = requireWasmFunction(wasm, '_hd_sol_address');
3092
- const pubPtr = allocAndCopy(wasm, publicKey);
3504
+ // Any 32 bytes: Solana addresses include off-curve program addresses.
3505
+ V.bytesOfLength(publicKey, 32, 'publicKey', ErrorCode.INVALID_PUBLIC_KEY);
3506
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
3093
3507
  const outPtr = wasm._hd_alloc(64);
3094
3508
  try {
3095
- const result = getAddressFn(pubPtr, publicKey.length, outPtr, 64);
3509
+ const result = getAddressFn(pubPtr, pubLen, outPtr, 64);
3096
3510
  checkResult(result);
3097
3511
  return readString(wasm, outPtr);
3098
3512
  } finally {
@@ -3103,6 +3517,7 @@ function createModule(wasm) {
3103
3517
 
3104
3518
  validateAddress(address) {
3105
3519
  const validateFn = requireWasmFunction(wasm, '_hd_sol_validate_address');
3520
+ if (!V.isCString(V.string(address, 'address'))) return false;
3106
3521
  const addrPtr = allocString(wasm, address);
3107
3522
  try {
3108
3523
  return validateFn(addrPtr) === 0;
@@ -3113,11 +3528,13 @@ function createModule(wasm) {
3113
3528
 
3114
3529
  signMessage(message, privateKey) {
3115
3530
  const signFn = requireWasmFunction(wasm, '_hd_sol_sign_message');
3116
- const msgPtr = allocAndCopy(wasm, message);
3117
- const keyPtr = allocAndCopy(wasm, privateKey);
3531
+ V.bytes(message, 'message');
3532
+ V.privateKey(wasm, privateKey, Curve.ED25519);
3533
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
3534
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
3118
3535
  const sigPtr = wasm._hd_alloc(64);
3119
3536
  try {
3120
- const result = signFn(msgPtr, message.length, keyPtr, sigPtr, 64);
3537
+ const result = signFn(msgPtr, msgLen, keyPtr, sigPtr, 64);
3121
3538
  checkResult(result);
3122
3539
  return readBytes(wasm, sigPtr, 64);
3123
3540
  } finally {
@@ -3130,11 +3547,15 @@ function createModule(wasm) {
3130
3547
 
3131
3548
  verifyMessage(message, signature, publicKey) {
3132
3549
  const verifyFn = requireWasmFunction(wasm, '_hd_sol_verify_message');
3133
- const msgPtr = allocAndCopy(wasm, message);
3134
- const sigPtr = allocAndCopy(wasm, signature);
3135
- const pubPtr = allocAndCopy(wasm, publicKey);
3550
+ V.bytes(message, 'message');
3551
+ V.bytes(signature, 'signature');
3552
+ V.bytes(publicKey, 'publicKey');
3553
+ if (V.byteLength(signature) !== 64 || V.byteLength(publicKey) !== 32) return false;
3554
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
3555
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, signature);
3556
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
3136
3557
  try {
3137
- return verifyFn(msgPtr, message.length, sigPtr, signature.length, pubPtr, publicKey.length) === 1;
3558
+ return verifyFn(msgPtr, msgLen, sigPtr, sigLen, pubPtr, pubLen) === 1;
3138
3559
  } finally {
3139
3560
  wasm._hd_dealloc(msgPtr);
3140
3561
  wasm._hd_dealloc(sigPtr);
@@ -3158,7 +3579,8 @@ function createModule(wasm) {
3158
3579
 
3159
3580
  derivePubkey(privateKey) {
3160
3581
  const fn = requireWasmFunction(wasm, '_hd_sol_derive_pubkey');
3161
- const keyPtr = allocAndCopy(wasm, privateKey);
3582
+ V.privateKey(wasm, privateKey, Curve.ED25519);
3583
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
3162
3584
  const outPtr = wasm._hd_alloc(32);
3163
3585
  try {
3164
3586
  const result = fn(keyPtr, outPtr, 32);
@@ -3173,21 +3595,34 @@ function createModule(wasm) {
3173
3595
 
3174
3596
  findPDA(programId, seeds) {
3175
3597
  const fn = requireWasmFunction(wasm, '_hd_sol_find_pda');
3176
- const progPtr = allocAndCopy(wasm, programId);
3598
+ // The export reads a 32-byte program id; Solana allows up to 16 seeds
3599
+ // of up to 32 bytes.
3600
+ V.bytesOfLength(programId, 32, 'programId', ErrorCode.INVALID_PUBLIC_KEY);
3601
+ // The array and each seed are read once (an array or a Proxy could
3602
+ // answer a second read differently): the copies' lengths go to the heap.
3603
+ const count = Array.isArray(seeds) ? seeds.length : -1;
3604
+ if (!Number.isInteger(count) || count < 0 || count > 16) {
3605
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'seeds must be an array of at most 16 seeds');
3606
+ }
3607
+ const seedCopies = [];
3608
+ for (let i = 0; i < count; i++) {
3609
+ seedCopies.push(V.snapshot(V.bytesInRange(seeds[i], 0, 32, 'seed'), 'seed'));
3610
+ }
3611
+ const { ptr: progPtr } = allocAndCopy(wasm, programId);
3177
3612
  let totalLen = 0;
3178
- for (const s of seeds) totalLen += s.length;
3613
+ for (const s of seedCopies) totalLen += s.length;
3179
3614
  const flatSeeds = new Uint8Array(totalLen);
3180
3615
  let off = 0;
3181
- for (const s of seeds) { flatSeeds.set(s, off); off += s.length; }
3182
- const seedsPtr = totalLen > 0 ? allocAndCopy(wasm, flatSeeds) : 0;
3183
- const seedLensPtr = wasm._hd_alloc(seeds.length * 4);
3184
- for (let i = 0; i < seeds.length; i++) {
3185
- wasm.setValue(seedLensPtr + i * 4, seeds[i].length, 'i32');
3616
+ for (const s of seedCopies) { flatSeeds.set(s, off); off += s.length; }
3617
+ const { ptr: seedsPtr } = totalLen > 0 ? allocAndCopy(wasm, flatSeeds) : { ptr: 0 };
3618
+ const seedLensPtr = wasm._hd_alloc(seedCopies.length * 4);
3619
+ for (let i = 0; i < seedCopies.length; i++) {
3620
+ wasm.setValue(seedLensPtr + i * 4, seedCopies[i].length, 'i32');
3186
3621
  }
3187
3622
  const addrPtr = wasm._hd_alloc(64);
3188
3623
  const bumpPtr = wasm._hd_alloc(1);
3189
3624
  try {
3190
- const result = fn(progPtr, seedsPtr || 0, totalLen, seedLensPtr, seeds.length, addrPtr, 64, bumpPtr);
3625
+ const result = fn(progPtr, seedsPtr || 0, totalLen, seedLensPtr, seedCopies.length, addrPtr, 64, bumpPtr);
3191
3626
  checkResult(result);
3192
3627
  return {
3193
3628
  address: readString(wasm, addrPtr),
@@ -3230,10 +3665,12 @@ function createModule(wasm) {
3230
3665
  const polkadot = {
3231
3666
  getAddress(publicKey, ss58Prefix = 0) {
3232
3667
  const getAddressFn = requireWasmFunction(wasm, '_hd_dot_address');
3233
- const pubPtr = allocAndCopy(wasm, publicKey);
3668
+ V.bytesOfLength(publicKey, 32, 'publicKey', ErrorCode.INVALID_PUBLIC_KEY);
3669
+ V.integer(ss58Prefix, 'ss58Prefix', 0, 16383);
3670
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
3234
3671
  const outPtr = wasm._hd_alloc(64);
3235
3672
  try {
3236
- const result = getAddressFn(pubPtr, publicKey.length, ss58Prefix, outPtr, 64);
3673
+ const result = getAddressFn(pubPtr, pubLen, ss58Prefix, outPtr, 64);
3237
3674
  checkResult(result);
3238
3675
  return readString(wasm, outPtr);
3239
3676
  } finally {
@@ -3244,6 +3681,8 @@ function createModule(wasm) {
3244
3681
 
3245
3682
  validateAddress(address, prefix) {
3246
3683
  const validateFn = requireWasmFunction(wasm, '_hd_dot_validate_address');
3684
+ if (prefix !== undefined) V.integer(prefix, 'prefix', 0, 16383);
3685
+ if (!V.isCString(V.string(address, 'address'))) return false;
3247
3686
  const addrPtr = allocString(wasm, address);
3248
3687
  try {
3249
3688
  return validateFn(addrPtr, prefix !== undefined ? prefix : -1) === 0;
@@ -3273,6 +3712,7 @@ function createModule(wasm) {
3273
3712
 
3274
3713
  convertPrefix(address, newPrefix) {
3275
3714
  const convertFn = requireWasmFunction(wasm, '_hd_dot_convert_prefix');
3715
+ V.integer(newPrefix, 'newPrefix', 0, 16383);
3276
3716
  const addrPtr = allocString(wasm, address);
3277
3717
  const outPtr = wasm._hd_alloc(64);
3278
3718
  try {
@@ -3287,7 +3727,8 @@ function createModule(wasm) {
3287
3727
 
3288
3728
  derivePubkey(privateKey) {
3289
3729
  const fn = requireWasmFunction(wasm, '_hd_dot_derive_pubkey');
3290
- const keyPtr = allocAndCopy(wasm, privateKey);
3730
+ V.privateKey(wasm, privateKey, Curve.ED25519);
3731
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
3291
3732
  const outPtr = wasm._hd_alloc(32);
3292
3733
  try {
3293
3734
  const result = fn(keyPtr, outPtr, 32);
@@ -3302,11 +3743,13 @@ function createModule(wasm) {
3302
3743
 
3303
3744
  signMessage(message, privateKey) {
3304
3745
  const signFn = requireWasmFunction(wasm, '_hd_dot_sign_message');
3305
- const msgPtr = allocAndCopy(wasm, message);
3306
- const keyPtr = allocAndCopy(wasm, privateKey);
3746
+ V.bytes(message, 'message');
3747
+ V.privateKey(wasm, privateKey, Curve.ED25519);
3748
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
3749
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, privateKey);
3307
3750
  const sigPtr = wasm._hd_alloc(64);
3308
3751
  try {
3309
- const result = signFn(msgPtr, message.length, keyPtr, sigPtr, 64);
3752
+ const result = signFn(msgPtr, msgLen, keyPtr, sigPtr, 64);
3310
3753
  checkResult(result);
3311
3754
  return readBytes(wasm, sigPtr, 64);
3312
3755
  } finally {
@@ -3319,11 +3762,15 @@ function createModule(wasm) {
3319
3762
 
3320
3763
  verifyMessage(message, signature, publicKey) {
3321
3764
  const verifyFn = requireWasmFunction(wasm, '_hd_dot_verify_message');
3322
- const msgPtr = allocAndCopy(wasm, message);
3323
- const sigPtr = allocAndCopy(wasm, signature);
3324
- const pubPtr = allocAndCopy(wasm, publicKey);
3765
+ V.bytes(message, 'message');
3766
+ V.bytes(signature, 'signature');
3767
+ V.bytes(publicKey, 'publicKey');
3768
+ if (V.byteLength(signature) !== 64 || V.byteLength(publicKey) !== 32) return false;
3769
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
3770
+ const { ptr: sigPtr, len: sigLen } = allocAndCopy(wasm, signature);
3771
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
3325
3772
  try {
3326
- return verifyFn(msgPtr, message.length, sigPtr, signature.length, pubPtr, publicKey.length) === 1;
3773
+ return verifyFn(msgPtr, msgLen, sigPtr, sigLen, pubPtr, pubLen) === 1;
3327
3774
  } finally {
3328
3775
  wasm._hd_dealloc(msgPtr);
3329
3776
  wasm._hd_dealloc(sigPtr);
@@ -3419,10 +3866,10 @@ function createModule(wasm) {
3419
3866
 
3420
3867
  async signTransaction(path, transaction) {
3421
3868
  const pathPtr = allocString(wasm, path);
3422
- const txPtr = allocAndCopy(wasm, transaction);
3869
+ const { ptr: txPtr, len: txLen } = allocAndCopy(wasm, transaction);
3423
3870
  const sigPtr = wasm._hd_alloc(128);
3424
3871
  try {
3425
- const result = wasm._hd_hw_sign_transaction(this._handle, pathPtr, txPtr, transaction.length, sigPtr, 128);
3872
+ const result = wasm._hd_hw_sign_transaction(this._handle, pathPtr, txPtr, txLen, sigPtr, 128);
3426
3873
  checkResult(result);
3427
3874
  return readBytes(wasm, sigPtr, 64);
3428
3875
  } finally {
@@ -3487,13 +3934,13 @@ function createModule(wasm) {
3487
3934
  addWallet(seed, name) {
3488
3935
  const addWalletFn = getWasmFunction(wasm, '_hd_keyring_add_wallet');
3489
3936
  if (!addWalletFn) return '';
3490
- const seedPtr = allocAndCopy(wasm, seed);
3937
+ const { ptr: seedPtr, len: seedLen } = allocAndCopy(wasm, seed);
3491
3938
  const namePtr = name ? allocString(wasm, name) : 0;
3492
3939
  try {
3493
- const ptr = addWalletFn(this._handle, seedPtr, seed.length, namePtr);
3940
+ const ptr = addWalletFn(this._handle, seedPtr, seedLen, namePtr);
3494
3941
  return readString(wasm, ptr);
3495
3942
  } finally {
3496
- wasm._hd_secure_wipe(seedPtr, seed.length);
3943
+ wasm._hd_secure_wipe(seedPtr, seedLen);
3497
3944
  wasm._hd_dealloc(seedPtr);
3498
3945
  if (namePtr) wasm._hd_dealloc(namePtr);
3499
3946
  }
@@ -3531,10 +3978,10 @@ function createModule(wasm) {
3531
3978
  const signTransactionFn = requireWasmFunction(wasm, '_hd_keyring_sign_transaction', ErrorCode.NOT_SUPPORTED, 'Keyring is not available in the WASM build');
3532
3979
  const idPtr = allocString(wasm, walletId);
3533
3980
  const pathPtr = allocString(wasm, path);
3534
- const txPtr = allocAndCopy(wasm, transaction);
3981
+ const { ptr: txPtr, len: txLen } = allocAndCopy(wasm, transaction);
3535
3982
  const sigPtr = wasm._hd_alloc(128);
3536
3983
  try {
3537
- const result = signTransactionFn(this._handle, idPtr, pathPtr, txPtr, transaction.length, sigPtr, 128);
3984
+ const result = signTransactionFn(this._handle, idPtr, pathPtr, txPtr, txLen, sigPtr, 128);
3538
3985
  checkResult(result);
3539
3986
  return readBytes(wasm, sigPtr, 64);
3540
3987
  } finally {
@@ -3549,10 +3996,10 @@ function createModule(wasm) {
3549
3996
  const signMessageFn = requireWasmFunction(wasm, '_hd_keyring_sign_message', ErrorCode.NOT_SUPPORTED, 'Keyring is not available in the WASM build');
3550
3997
  const idPtr = allocString(wasm, walletId);
3551
3998
  const pathPtr = allocString(wasm, path);
3552
- const msgPtr = allocAndCopy(wasm, message);
3999
+ const { ptr: msgPtr, len: msgLen } = allocAndCopy(wasm, message);
3553
4000
  const sigPtr = wasm._hd_alloc(128);
3554
4001
  try {
3555
- const result = signMessageFn(this._handle, idPtr, pathPtr, msgPtr, message.length, sigPtr, 128);
4002
+ const result = signMessageFn(this._handle, idPtr, pathPtr, msgPtr, msgLen, sigPtr, 128);
3556
4003
  checkResult(result);
3557
4004
  return readBytes(wasm, sigPtr, 64);
3558
4005
  } finally {
@@ -3587,11 +4034,10 @@ function createModule(wasm) {
3587
4034
  const utils = {
3588
4035
  // Hashing
3589
4036
  sha256(data) {
3590
- const dataPtr = allocAndCopy(wasm, data);
4037
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3591
4038
  const hashPtr = wasm._hd_alloc(32);
3592
4039
  try {
3593
- const result = wasm._hd_hash_sha256(dataPtr, data.length, hashPtr, 32);
3594
- if (result < 0) throw new HDWalletError(result);
4040
+ checkLength(wasm._hd_hash_sha256(dataPtr, dataLen, hashPtr, 32), 32);
3595
4041
  return readBytes(wasm, hashPtr, 32);
3596
4042
  } finally {
3597
4043
  wasm._hd_dealloc(dataPtr);
@@ -3600,11 +4046,10 @@ function createModule(wasm) {
3600
4046
  },
3601
4047
 
3602
4048
  sha512(data) {
3603
- const dataPtr = allocAndCopy(wasm, data);
4049
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3604
4050
  const hashPtr = wasm._hd_alloc(64);
3605
4051
  try {
3606
- const result = wasm._hd_hash_sha512(dataPtr, data.length, hashPtr, 64);
3607
- if (result < 0) throw new HDWalletError(result);
4052
+ checkLength(wasm._hd_hash_sha512(dataPtr, dataLen, hashPtr, 64), 64);
3608
4053
  return readBytes(wasm, hashPtr, 64);
3609
4054
  } finally {
3610
4055
  wasm._hd_dealloc(dataPtr);
@@ -3613,11 +4058,10 @@ function createModule(wasm) {
3613
4058
  },
3614
4059
 
3615
4060
  keccak256(data) {
3616
- const dataPtr = allocAndCopy(wasm, data);
4061
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3617
4062
  const hashPtr = wasm._hd_alloc(32);
3618
4063
  try {
3619
- const result = wasm._hd_hash_keccak256(dataPtr, data.length, hashPtr, 32);
3620
- if (result < 0) throw new HDWalletError(result);
4064
+ checkLength(wasm._hd_hash_keccak256(dataPtr, dataLen, hashPtr, 32), 32);
3621
4065
  return readBytes(wasm, hashPtr, 32);
3622
4066
  } finally {
3623
4067
  wasm._hd_dealloc(dataPtr);
@@ -3626,11 +4070,10 @@ function createModule(wasm) {
3626
4070
  },
3627
4071
 
3628
4072
  ripemd160(data) {
3629
- const dataPtr = allocAndCopy(wasm, data);
4073
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3630
4074
  const hashPtr = wasm._hd_alloc(20);
3631
4075
  try {
3632
- const result = wasm._hd_hash_ripemd160(dataPtr, data.length, hashPtr, 20);
3633
- if (result < 0) throw new HDWalletError(result);
4076
+ checkLength(wasm._hd_hash_ripemd160(dataPtr, dataLen, hashPtr, 20), 20);
3634
4077
  return readBytes(wasm, hashPtr, 20);
3635
4078
  } finally {
3636
4079
  wasm._hd_dealloc(dataPtr);
@@ -3639,11 +4082,10 @@ function createModule(wasm) {
3639
4082
  },
3640
4083
 
3641
4084
  hash160(data) {
3642
- const dataPtr = allocAndCopy(wasm, data);
4085
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3643
4086
  const hashPtr = wasm._hd_alloc(20);
3644
4087
  try {
3645
- const result = wasm._hd_hash_hash160(dataPtr, data.length, hashPtr, 20);
3646
- if (result < 0) throw new HDWalletError(result);
4088
+ checkLength(wasm._hd_hash_hash160(dataPtr, dataLen, hashPtr, 20), 20);
3647
4089
  return readBytes(wasm, hashPtr, 20);
3648
4090
  } finally {
3649
4091
  wasm._hd_dealloc(dataPtr);
@@ -3652,11 +4094,12 @@ function createModule(wasm) {
3652
4094
  },
3653
4095
 
3654
4096
  blake2b(data, outputLength = 32) {
3655
- const dataPtr = allocAndCopy(wasm, data);
4097
+ // The export checks the size against its 64-byte buffer.
4098
+ V.integer(outputLength, 'outputLength', 0, 0x7fffffff);
4099
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3656
4100
  const hashPtr = wasm._hd_alloc(64);
3657
4101
  try {
3658
- const result = wasm._hd_hash_blake2b(dataPtr, data.length, hashPtr, 64, outputLength);
3659
- if (result < 0) throw new HDWalletError(result);
4102
+ checkLength(wasm._hd_hash_blake2b(dataPtr, dataLen, hashPtr, 64, outputLength), outputLength);
3660
4103
  return readBytes(wasm, hashPtr, outputLength);
3661
4104
  } finally {
3662
4105
  wasm._hd_dealloc(dataPtr);
@@ -3665,11 +4108,12 @@ function createModule(wasm) {
3665
4108
  },
3666
4109
 
3667
4110
  blake2s(data, outputLength = 32) {
3668
- const dataPtr = allocAndCopy(wasm, data);
4111
+ // The export checks the size against its 32-byte buffer.
4112
+ V.integer(outputLength, 'outputLength', 0, 0x7fffffff);
4113
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3669
4114
  const hashPtr = wasm._hd_alloc(32);
3670
4115
  try {
3671
- const result = wasm._hd_hash_blake2s(dataPtr, data.length, hashPtr, 32, outputLength);
3672
- if (result < 0) throw new HDWalletError(result);
4116
+ checkLength(wasm._hd_hash_blake2s(dataPtr, dataLen, hashPtr, 32, outputLength), outputLength);
3673
4117
  return readBytes(wasm, hashPtr, outputLength);
3674
4118
  } finally {
3675
4119
  wasm._hd_dealloc(dataPtr);
@@ -3679,16 +4123,20 @@ function createModule(wasm) {
3679
4123
 
3680
4124
  // Key derivation (sync - uses Crypto++ in WASM)
3681
4125
  hkdf(ikm, salt, info, length) {
3682
- const ikmPtr = allocAndCopy(wasm, ikm);
3683
- const saltPtr = allocAndCopy(wasm, salt);
3684
- const infoPtr = allocAndCopy(wasm, info);
4126
+ V.bytes(ikm, 'ikm');
4127
+ V.bytes(salt, 'salt');
4128
+ V.bytes(info, 'info');
4129
+ // The export refuses more than 255 blocks of output.
4130
+ V.integer(length, 'length', 1, 0x3fffffff);
4131
+ const { ptr: ikmPtr, len: ikmLen } = allocAndCopy(wasm, ikm);
4132
+ const { ptr: saltPtr, len: saltLen } = allocAndCopy(wasm, salt);
4133
+ const { ptr: infoPtr, len: infoLen } = allocAndCopy(wasm, info);
3685
4134
  const outPtr = wasm._hd_alloc(length);
3686
4135
  try {
3687
- const result = wasm._hd_kdf_hkdf(ikmPtr, ikm.length, saltPtr, salt.length, infoPtr, info.length, outPtr, length);
3688
- if (result < 0) throw new HDWalletError(result);
4136
+ checkLength(wasm._hd_kdf_hkdf(ikmPtr, ikmLen, saltPtr, saltLen, infoPtr, infoLen, outPtr, length), length);
3689
4137
  return readBytes(wasm, outPtr, length);
3690
4138
  } finally {
3691
- wasm._hd_secure_wipe(ikmPtr, ikm.length);
4139
+ wasm._hd_secure_wipe(ikmPtr, ikmLen);
3692
4140
  wasm._hd_dealloc(ikmPtr);
3693
4141
  wasm._hd_dealloc(saltPtr);
3694
4142
  wasm._hd_dealloc(infoPtr);
@@ -3707,32 +4155,35 @@ function createModule(wasm) {
3707
4155
  * @returns {{ciphertext: Uint8Array, tag: Uint8Array}}
3708
4156
  */
3709
4157
  encrypt(key, plaintext, iv, aad = new Uint8Array(0)) {
3710
- if (key.length !== 32) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Key must be 32 bytes');
3711
- if (iv.length !== 12) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'IV must be 12 bytes');
3712
-
3713
- const keyPtr = allocAndCopy(wasm, key);
3714
- const ptPtr = allocAndCopy(wasm, plaintext);
3715
- const ivPtr = allocAndCopy(wasm, iv);
3716
- const aadPtr = aad.length > 0 ? allocAndCopy(wasm, aad) : 0;
3717
- const ctPtr = wasm._hd_alloc(plaintext.length);
4158
+ V.bytesOfLength(key, 32, 'Key');
4159
+ V.bytes(plaintext, 'plaintext');
4160
+ V.bytesOfLength(iv, 12, 'IV');
4161
+ V.bytes(aad, 'aad');
4162
+
4163
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, key);
4164
+ const { ptr: ptPtr, len: ptLen } = allocAndCopy(wasm, plaintext);
4165
+ const { ptr: ivPtr, len: ivLen } = allocAndCopy(wasm, iv);
4166
+ const { ptr: aadPtr, len: aadLen } = V.byteLength(aad) > 0 ? allocAndCopy(wasm, aad) : { ptr: 0, len: 0 };
4167
+ const ctPtr = wasm._hd_alloc(ptLen);
3718
4168
  const tagPtr = wasm._hd_alloc(16);
3719
4169
 
3720
4170
  try {
3721
4171
  const result = wasm._hd_aes_gcm_encrypt(
3722
- keyPtr, key.length,
3723
- ptPtr, plaintext.length,
3724
- ivPtr, iv.length,
3725
- aadPtr, aad.length,
4172
+ keyPtr, keyLen,
4173
+ ptPtr, ptLen,
4174
+ ivPtr, ivLen,
4175
+ aadPtr, aadLen,
3726
4176
  ctPtr, tagPtr
3727
4177
  );
3728
- if (result < 0) throw new HDWalletError(-result);
4178
+ checkLength(result, ptLen);
3729
4179
 
3730
4180
  return {
3731
- ciphertext: readBytes(wasm, ctPtr, plaintext.length),
4181
+ ciphertext: readBytes(wasm, ctPtr, ptLen),
3732
4182
  tag: readBytes(wasm, tagPtr, 16)
3733
4183
  };
3734
4184
  } finally {
3735
- wasm._hd_secure_wipe(keyPtr, key.length);
4185
+ wasm._hd_secure_wipe(keyPtr, keyLen);
4186
+ wasm._hd_secure_wipe(ptPtr, ptLen);
3736
4187
  wasm._hd_dealloc(keyPtr);
3737
4188
  wasm._hd_dealloc(ptPtr);
3738
4189
  wasm._hd_dealloc(ivPtr);
@@ -3753,40 +4204,44 @@ function createModule(wasm) {
3753
4204
  * @throws {HDWalletError} If authentication fails
3754
4205
  */
3755
4206
  decrypt(key, ciphertext, tag, iv, aad = new Uint8Array(0)) {
3756
- if (key.length !== 32) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Key must be 32 bytes');
3757
- if (iv.length !== 12) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'IV must be 12 bytes');
3758
- if (tag.length !== 16) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Tag must be 16 bytes');
3759
-
3760
- const keyPtr = allocAndCopy(wasm, key);
3761
- const ctPtr = allocAndCopy(wasm, ciphertext);
3762
- const ivPtr = allocAndCopy(wasm, iv);
3763
- const tagPtr = allocAndCopy(wasm, tag);
3764
- const aadPtr = aad.length > 0 ? allocAndCopy(wasm, aad) : 0;
3765
- const ptPtr = wasm._hd_alloc(ciphertext.length);
4207
+ V.bytesOfLength(key, 32, 'Key');
4208
+ V.bytes(ciphertext, 'ciphertext');
4209
+ V.bytesOfLength(tag, 16, 'Tag');
4210
+ V.bytesOfLength(iv, 12, 'IV');
4211
+ V.bytes(aad, 'aad');
4212
+
4213
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, key);
4214
+ const { ptr: ctPtr, len: ctLen } = allocAndCopy(wasm, ciphertext);
4215
+ const { ptr: ivPtr, len: ivLen } = allocAndCopy(wasm, iv);
4216
+ const { ptr: tagPtr, len: tagLen } = allocAndCopy(wasm, tag);
4217
+ const { ptr: aadPtr, len: aadLen } = V.byteLength(aad) > 0 ? allocAndCopy(wasm, aad) : { ptr: 0, len: 0 };
4218
+ const ptPtr = wasm._hd_alloc(ctLen);
3766
4219
 
3767
4220
  try {
3768
4221
  const result = wasm._hd_aes_gcm_decrypt(
3769
- keyPtr, key.length,
3770
- ctPtr, ciphertext.length,
3771
- ivPtr, iv.length,
3772
- aadPtr, aad.length,
4222
+ keyPtr, keyLen,
4223
+ ctPtr, ctLen,
4224
+ ivPtr, ivLen,
4225
+ aadPtr, aadLen,
3773
4226
  tagPtr, ptPtr
3774
4227
  );
3775
- if (result < 0) {
4228
+ if (result !== ctLen) {
4229
+ const code = nativeErrorCode(result);
3776
4230
  throw new HDWalletError(
3777
- result === -2 ? ErrorCode.VERIFICATION_FAILED : -result,
3778
- result === -2 ? 'AES-GCM authentication failed' : undefined
4231
+ code,
4232
+ code === ErrorCode.VERIFICATION_FAILED ? 'AES-GCM authentication failed' : undefined
3779
4233
  );
3780
4234
  }
3781
4235
 
3782
- return readBytes(wasm, ptPtr, ciphertext.length);
4236
+ return readBytes(wasm, ptPtr, ctLen);
3783
4237
  } finally {
3784
- wasm._hd_secure_wipe(keyPtr, key.length);
4238
+ wasm._hd_secure_wipe(keyPtr, keyLen);
3785
4239
  wasm._hd_dealloc(keyPtr);
3786
4240
  wasm._hd_dealloc(ctPtr);
3787
4241
  wasm._hd_dealloc(ivPtr);
3788
4242
  wasm._hd_dealloc(tagPtr);
3789
4243
  if (aadPtr) wasm._hd_dealloc(aadPtr);
4244
+ wasm._hd_secure_wipe(ptPtr, ctLen);
3790
4245
  wasm._hd_dealloc(ptPtr);
3791
4246
  }
3792
4247
  }
@@ -3794,6 +4249,7 @@ function createModule(wasm) {
3794
4249
 
3795
4250
  /** Generate cryptographically secure random bytes */
3796
4251
  getRandomBytes(length) {
4252
+ V.integer(length, 'length', 0, 0x7fffffff);
3797
4253
  const bytes = new Uint8Array(length);
3798
4254
  if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues) {
3799
4255
  globalThis.crypto.getRandomValues(bytes);
@@ -3817,15 +4273,18 @@ function createModule(wasm) {
3817
4273
  },
3818
4274
 
3819
4275
  pbkdf2(password, salt, iterations, length) {
3820
- const pwdPtr = allocAndCopy(wasm, password);
3821
- const saltPtr = allocAndCopy(wasm, salt);
4276
+ V.bytes(password, 'password');
4277
+ V.bytes(salt, 'salt');
4278
+ V.integer(iterations, 'iterations', 1, 0x7fffffff);
4279
+ V.integer(length, 'length', 1, 0x3fffffff);
4280
+ const { ptr: pwdPtr, len: pwdLen } = allocAndCopy(wasm, password);
4281
+ const { ptr: saltPtr, len: saltLen } = allocAndCopy(wasm, salt);
3822
4282
  const outPtr = wasm._hd_alloc(length);
3823
4283
  try {
3824
- const result = wasm._hd_kdf_pbkdf2(pwdPtr, password.length, saltPtr, salt.length, iterations, outPtr, length);
3825
- if (result < 0) throw new HDWalletError(result);
4284
+ checkLength(wasm._hd_kdf_pbkdf2(pwdPtr, pwdLen, saltPtr, saltLen, iterations, outPtr, length), length);
3826
4285
  return readBytes(wasm, outPtr, length);
3827
4286
  } finally {
3828
- wasm._hd_secure_wipe(pwdPtr, password.length);
4287
+ wasm._hd_secure_wipe(pwdPtr, pwdLen);
3829
4288
  wasm._hd_dealloc(pwdPtr);
3830
4289
  wasm._hd_dealloc(saltPtr);
3831
4290
  wasm._hd_dealloc(outPtr);
@@ -3833,15 +4292,20 @@ function createModule(wasm) {
3833
4292
  },
3834
4293
 
3835
4294
  scrypt(password, salt, n, r, p, length) {
3836
- const pwdPtr = allocAndCopy(wasm, password);
3837
- const saltPtr = allocAndCopy(wasm, salt);
4295
+ V.bytes(password, 'password');
4296
+ V.bytes(salt, 'salt');
4297
+ V.integer(n, 'n', 2, Number.MAX_SAFE_INTEGER);
4298
+ V.integer(r, 'r', 1, 0x7fffffff);
4299
+ V.integer(p, 'p', 1, 0x7fffffff);
4300
+ V.integer(length, 'length', 1, 0x3fffffff);
4301
+ const { ptr: pwdPtr, len: pwdLen } = allocAndCopy(wasm, password);
4302
+ const { ptr: saltPtr, len: saltLen } = allocAndCopy(wasm, salt);
3838
4303
  const outPtr = wasm._hd_alloc(length);
3839
4304
  try {
3840
- const result = wasm._hd_kdf_scrypt(pwdPtr, password.length, saltPtr, salt.length, BigInt(n), r, p, outPtr, length);
3841
- if (result < 0) throw new HDWalletError(result);
4305
+ checkLength(wasm._hd_kdf_scrypt(pwdPtr, pwdLen, saltPtr, saltLen, BigInt(n), r, p, outPtr, length), length);
3842
4306
  return readBytes(wasm, outPtr, length);
3843
4307
  } finally {
3844
- wasm._hd_secure_wipe(pwdPtr, password.length);
4308
+ wasm._hd_secure_wipe(pwdPtr, pwdLen);
3845
4309
  wasm._hd_dealloc(pwdPtr);
3846
4310
  wasm._hd_dealloc(saltPtr);
3847
4311
  wasm._hd_dealloc(outPtr);
@@ -3850,13 +4314,14 @@ function createModule(wasm) {
3850
4314
 
3851
4315
  // Encoding
3852
4316
  encodeBase58(data) {
4317
+ V.bytes(data, 'data');
3853
4318
  const encodeBase58Fn = getWasmFunction(wasm, '_hd_encode_base58');
3854
4319
  if (!encodeBase58Fn) {
3855
4320
  return encodeBase58Fallback(data);
3856
4321
  }
3857
- const dataPtr = allocAndCopy(wasm, data);
4322
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3858
4323
  try {
3859
- const ptr = encodeBase58Fn(dataPtr, data.length);
4324
+ const ptr = encodeBase58Fn(dataPtr, dataLen);
3860
4325
  return readString(wasm, ptr);
3861
4326
  } finally {
3862
4327
  wasm._hd_dealloc(dataPtr);
@@ -3864,6 +4329,7 @@ function createModule(wasm) {
3864
4329
  },
3865
4330
 
3866
4331
  decodeBase58(str) {
4332
+ V.string(str, 'str');
3867
4333
  const decodeBase58Fn = getWasmFunction(wasm, '_hd_decode_base58');
3868
4334
  if (!decodeBase58Fn) {
3869
4335
  return decodeBase58Fallback(str);
@@ -3885,17 +4351,18 @@ function createModule(wasm) {
3885
4351
  },
3886
4352
 
3887
4353
  encodeBase58Check(data) {
4354
+ V.bytes(data, 'data');
3888
4355
  const encodeBase58CheckFn = getWasmFunction(wasm, '_hd_encode_base58check');
3889
4356
  if (!encodeBase58CheckFn) {
3890
4357
  const checksum = this.sha256(this.sha256(data)).slice(0, 4);
3891
- const payload = new Uint8Array(data.length + checksum.length);
4358
+ const payload = new Uint8Array(V.byteLength(data) + checksum.length);
3892
4359
  payload.set(data, 0);
3893
- payload.set(checksum, data.length);
4360
+ payload.set(checksum, V.byteLength(data));
3894
4361
  return encodeBase58Fallback(payload);
3895
4362
  }
3896
- const dataPtr = allocAndCopy(wasm, data);
4363
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3897
4364
  try {
3898
- const ptr = encodeBase58CheckFn(dataPtr, data.length);
4365
+ const ptr = encodeBase58CheckFn(dataPtr, dataLen);
3899
4366
  return readString(wasm, ptr);
3900
4367
  } finally {
3901
4368
  wasm._hd_dealloc(dataPtr);
@@ -3903,6 +4370,7 @@ function createModule(wasm) {
3903
4370
  },
3904
4371
 
3905
4372
  decodeBase58Check(str) {
4373
+ V.string(str, 'str');
3906
4374
  const decodeBase58CheckFn = getWasmFunction(wasm, '_hd_decode_base58check');
3907
4375
  if (!decodeBase58CheckFn) {
3908
4376
  const decoded = decodeBase58Fallback(str);
@@ -3936,14 +4404,16 @@ function createModule(wasm) {
3936
4404
  },
3937
4405
 
3938
4406
  encodeBech32(hrp, data) {
4407
+ V.string(hrp, 'hrp');
4408
+ V.bytes(data, 'data');
3939
4409
  const encodeBech32Fn = getWasmFunction(wasm, '_hd_encode_bech32');
3940
4410
  if (!encodeBech32Fn) {
3941
4411
  return encodeBech32Fallback(hrp, data);
3942
4412
  }
3943
4413
  const hrpPtr = allocString(wasm, hrp);
3944
- const dataPtr = allocAndCopy(wasm, data);
4414
+ const { ptr: dataPtr, len: dataLen } = allocAndCopy(wasm, data);
3945
4415
  try {
3946
- const ptr = encodeBech32Fn(hrpPtr, dataPtr, data.length);
4416
+ const ptr = encodeBech32Fn(hrpPtr, dataPtr, dataLen);
3947
4417
  return readString(wasm, ptr);
3948
4418
  } finally {
3949
4419
  wasm._hd_dealloc(hrpPtr);
@@ -3952,6 +4422,7 @@ function createModule(wasm) {
3952
4422
  },
3953
4423
 
3954
4424
  decodeBech32(str) {
4425
+ V.string(str, 'str');
3955
4426
  const decodeBech32Fn = getWasmFunction(wasm, '_hd_decode_bech32');
3956
4427
  if (!decodeBech32Fn) {
3957
4428
  return decodeBech32Fallback(str);
@@ -3978,29 +4449,38 @@ function createModule(wasm) {
3978
4449
  },
3979
4450
 
3980
4451
  encodeHex(data) {
3981
- return encodeHexFallback(data);
4452
+ return encodeHexFallback(V.bytes(data, 'data'));
3982
4453
  },
3983
4454
 
3984
4455
  decodeHex(str) {
3985
- return decodeHexFallback(str);
4456
+ return decodeHexFallback(V.string(str, 'str'));
3986
4457
  },
3987
4458
 
3988
4459
  encodeBase64(data) {
3989
- return encodeBase64Fallback(data);
4460
+ return encodeBase64Fallback(V.bytes(data, 'data'));
3990
4461
  },
3991
4462
 
3992
4463
  decodeBase64(str) {
3993
- return decodeBase64Fallback(str);
4464
+ return decodeBase64Fallback(V.string(str, 'str'));
3994
4465
  },
3995
4466
 
3996
4467
  // Memory
4468
+ /**
4469
+ * Zero, in place, the bytes under any typed array, DataView, ArrayBuffer
4470
+ * or SharedArrayBuffer (any realm), read from its internal slots, never
4471
+ * from overridable properties. Throws INVALID_ARGUMENT for anything else
4472
+ * and for a detached buffer (its bytes are no longer here to wipe).
4473
+ */
3997
4474
  secureWipe(data) {
3998
- if (data instanceof Uint8Array) {
3999
- // Wipe in-place
4000
- for (let i = 0; i < data.length; i++) {
4001
- data[i] = 0;
4002
- }
4475
+ const region = binaryRegion(data);
4476
+ if (region === null) {
4477
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT,
4478
+ 'secureWipe takes a typed array, DataView, ArrayBuffer or SharedArrayBuffer');
4003
4479
  }
4480
+ if (region.detached) {
4481
+ throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'secureWipe: the buffer is detached');
4482
+ }
4483
+ TYPED_ARRAY_FILL.call(new Uint8Array(region.buffer, region.offset, region.length), 0);
4004
4484
  }
4005
4485
  };
4006
4486
 
@@ -4020,6 +4500,7 @@ function createModule(wasm) {
4020
4500
 
4021
4501
  generatePrivateKey(curve) {
4022
4502
  const fn = requireWasmFunction(wasm, '_hd_x509_generate_private_key');
4503
+ V.curve(curve);
4023
4504
  return callDynamicBytesResult(
4024
4505
  wasm,
4025
4506
  (outPtr, outLenPtr) => fn(curve, outPtr, outLenPtr),
@@ -4029,22 +4510,23 @@ function createModule(wasm) {
4029
4510
 
4030
4511
  exportPrivateKeyPem(curve, privateKey) {
4031
4512
  const fn = requireWasmFunction(wasm, '_hd_x509_export_private_key_pem');
4032
- const keyBytes = toUint8Array(privateKey, 'privateKey');
4033
- const keyPtr = allocAndCopy(wasm, keyBytes);
4513
+ const keyBytes = V.privateKey(wasm, toUint8Array(privateKey, 'privateKey'), curve);
4514
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, keyBytes);
4034
4515
  try {
4035
4516
  return callDynamicStringResult(
4036
4517
  wasm,
4037
- (outPtr, outLenPtr) => fn(curve, keyPtr, keyBytes.length, outPtr, outLenPtr),
4518
+ (outPtr, outLenPtr) => fn(curve, keyPtr, keyLen, outPtr, outLenPtr),
4038
4519
  { wipeOutput: true }
4039
4520
  );
4040
4521
  } finally {
4041
- wasm._hd_secure_wipe(keyPtr, keyBytes.length);
4522
+ wasm._hd_secure_wipe(keyPtr, keyLen);
4042
4523
  wasm._hd_dealloc(keyPtr);
4043
4524
  }
4044
4525
  },
4045
4526
 
4046
4527
  importPrivateKeyPem(curve, pem) {
4047
4528
  const fn = requireWasmFunction(wasm, '_hd_x509_import_private_key_pem');
4529
+ V.curve(curve);
4048
4530
  const pemPtr = allocString(wasm, pem);
4049
4531
  const pemLen = wasm.lengthBytesUTF8(pem) + 1;
4050
4532
  try {
@@ -4064,8 +4546,9 @@ function createModule(wasm) {
4064
4546
  const optionsSpec = serializeX509Options(options);
4065
4547
  const optionsPtr = allocString(wasm, optionsSpec);
4066
4548
  const optionsLen = wasm.lengthBytesUTF8(optionsSpec) + 1;
4067
- const keyBytes = toUint8Array(certificatePrivateKey, 'certificatePrivateKey');
4068
- const keyPtr = allocAndCopy(wasm, keyBytes);
4549
+ const keyBytes = V.privateKey(wasm, toUint8Array(certificatePrivateKey, 'certificatePrivateKey'),
4550
+ certificateCurve, 'certificatePrivateKey');
4551
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, keyBytes);
4069
4552
 
4070
4553
  try {
4071
4554
  const bytes = callDynamicBytesResult(
@@ -4074,7 +4557,7 @@ function createModule(wasm) {
4074
4557
  optionsPtr,
4075
4558
  certificateCurve,
4076
4559
  keyPtr,
4077
- keyBytes.length,
4560
+ keyLen,
4078
4561
  outputEncoding,
4079
4562
  outPtr,
4080
4563
  outLenPtr
@@ -4083,7 +4566,7 @@ function createModule(wasm) {
4083
4566
  return decodeX509CertificateOutput(bytes, outputEncoding);
4084
4567
  } finally {
4085
4568
  wasm._hd_secure_wipe(optionsPtr, optionsLen);
4086
- wasm._hd_secure_wipe(keyPtr, keyBytes.length);
4569
+ wasm._hd_secure_wipe(keyPtr, keyLen);
4087
4570
  wasm._hd_dealloc(optionsPtr);
4088
4571
  wasm._hd_dealloc(keyPtr);
4089
4572
  }
@@ -4104,16 +4587,18 @@ function createModule(wasm) {
4104
4587
  const optionsSpec = serializeX509Options(options);
4105
4588
  const optionsPtr = allocString(wasm, optionsSpec);
4106
4589
  const optionsLen = wasm.lengthBytesUTF8(optionsSpec) + 1;
4107
- const issuerKeyBytes = toUint8Array(issuerPrivateKey, 'issuerPrivateKey');
4108
- const issuerKeyPtr = allocAndCopy(wasm, issuerKeyBytes);
4590
+ const issuerKeyBytes = V.privateKey(wasm, toUint8Array(issuerPrivateKey, 'issuerPrivateKey'),
4591
+ issuerCurve, 'issuerPrivateKey');
4592
+ const { ptr: issuerKeyPtr, len: issuerKeyLen } = allocAndCopy(wasm, issuerKeyBytes);
4109
4593
  const issuerCertBytes = normalizeX509CertificateInput(
4110
4594
  issuerCertificate,
4111
4595
  issuerCertificateEncoding,
4112
4596
  'issuerCertificate'
4113
4597
  );
4114
- const issuerCertPtr = allocAndCopy(wasm, issuerCertBytes);
4115
- const subjectKeyBytes = toUint8Array(subjectPrivateKey, 'subjectPrivateKey');
4116
- const subjectKeyPtr = allocAndCopy(wasm, subjectKeyBytes);
4598
+ const { ptr: issuerCertPtr, len: issuerCertLen } = allocAndCopy(wasm, issuerCertBytes);
4599
+ const subjectKeyBytes = V.privateKey(wasm, toUint8Array(subjectPrivateKey, 'subjectPrivateKey'),
4600
+ subjectCurve, 'subjectPrivateKey');
4601
+ const { ptr: subjectKeyPtr, len: subjectKeyLen } = allocAndCopy(wasm, subjectKeyBytes);
4117
4602
 
4118
4603
  try {
4119
4604
  const bytes = callDynamicBytesResult(
@@ -4122,13 +4607,13 @@ function createModule(wasm) {
4122
4607
  optionsPtr,
4123
4608
  issuerCurve,
4124
4609
  issuerKeyPtr,
4125
- issuerKeyBytes.length,
4610
+ issuerKeyLen,
4126
4611
  issuerCertPtr,
4127
- issuerCertBytes.length,
4612
+ issuerCertLen,
4128
4613
  issuerCertificateEncoding,
4129
4614
  subjectCurve,
4130
4615
  subjectKeyPtr,
4131
- subjectKeyBytes.length,
4616
+ subjectKeyLen,
4132
4617
  outputEncoding,
4133
4618
  outPtr,
4134
4619
  outLenPtr
@@ -4137,8 +4622,8 @@ function createModule(wasm) {
4137
4622
  return decodeX509CertificateOutput(bytes, outputEncoding);
4138
4623
  } finally {
4139
4624
  wasm._hd_secure_wipe(optionsPtr, optionsLen);
4140
- wasm._hd_secure_wipe(issuerKeyPtr, issuerKeyBytes.length);
4141
- wasm._hd_secure_wipe(subjectKeyPtr, subjectKeyBytes.length);
4625
+ wasm._hd_secure_wipe(issuerKeyPtr, issuerKeyLen);
4626
+ wasm._hd_secure_wipe(subjectKeyPtr, subjectKeyLen);
4142
4627
  wasm._hd_dealloc(optionsPtr);
4143
4628
  wasm._hd_dealloc(issuerKeyPtr);
4144
4629
  wasm._hd_dealloc(issuerCertPtr);
@@ -4151,14 +4636,14 @@ function createModule(wasm) {
4151
4636
  assertX509CertificateEncoding(outputEncoding, 'outputEncoding');
4152
4637
  const fn = requireWasmFunction(wasm, '_hd_x509_convert_certificate');
4153
4638
  const certBytes = normalizeX509CertificateInput(certificate, inputEncoding);
4154
- const certPtr = allocAndCopy(wasm, certBytes);
4639
+ const { ptr: certPtr, len: certLen } = allocAndCopy(wasm, certBytes);
4155
4640
 
4156
4641
  try {
4157
4642
  const bytes = callDynamicBytesResult(
4158
4643
  wasm,
4159
4644
  (outPtr, outLenPtr) => fn(
4160
4645
  certPtr,
4161
- certBytes.length,
4646
+ certLen,
4162
4647
  inputEncoding,
4163
4648
  outputEncoding,
4164
4649
  outPtr,
@@ -4175,13 +4660,13 @@ function createModule(wasm) {
4175
4660
  assertX509CertificateEncoding(inputEncoding, 'inputEncoding');
4176
4661
  const fn = requireWasmFunction(wasm, '_hd_x509_parse_certificate_json');
4177
4662
  const certBytes = normalizeX509CertificateInput(certificate, inputEncoding);
4178
- const certPtr = allocAndCopy(wasm, certBytes);
4663
+ const { ptr: certPtr, len: certLen } = allocAndCopy(wasm, certBytes);
4179
4664
 
4180
4665
  try {
4181
4666
  return normalizeParsedCertificateJson(JSON.parse(
4182
4667
  callDynamicStringResult(
4183
4668
  wasm,
4184
- (outPtr, outLenPtr) => fn(certPtr, certBytes.length, inputEncoding, outPtr, outLenPtr)
4669
+ (outPtr, outLenPtr) => fn(certPtr, certLen, inputEncoding, outPtr, outLenPtr)
4185
4670
  )
4186
4671
  ));
4187
4672
  } finally {
@@ -4193,10 +4678,10 @@ function createModule(wasm) {
4193
4678
  assertX509CertificateEncoding(inputEncoding, 'inputEncoding');
4194
4679
  const fn = requireWasmFunction(wasm, '_hd_x509_verify_wallet_attestation');
4195
4680
  const certBytes = normalizeX509CertificateInput(certificate, inputEncoding);
4196
- const certPtr = allocAndCopy(wasm, certBytes);
4681
+ const { ptr: certPtr, len: certLen } = allocAndCopy(wasm, certBytes);
4197
4682
 
4198
4683
  try {
4199
- const result = fn(certPtr, certBytes.length, inputEncoding);
4684
+ const result = fn(certPtr, certLen, inputEncoding);
4200
4685
  if (result < 0) {
4201
4686
  throw new HDWalletError(result);
4202
4687
  }
@@ -4218,36 +4703,38 @@ function createModule(wasm) {
4218
4703
  assertX509CertificateEncoding(certificateEncoding, 'certificateEncoding');
4219
4704
  const fn = requireWasmFunction(wasm, '_hd_x509_export_pkcs12');
4220
4705
  const certBytes = normalizeX509CertificateInput(certificate, certificateEncoding);
4221
- const certPtr = allocAndCopy(wasm, certBytes);
4222
- const keyBytes = toUint8Array(privateKey, 'privateKey');
4223
- const keyPtr = allocAndCopy(wasm, keyBytes);
4706
+ const { ptr: certPtr, len: certLen } = allocAndCopy(wasm, certBytes);
4707
+ const keyBytes = V.privateKey(wasm, toUint8Array(privateKey, 'privateKey'), privateKeyCurve);
4708
+ V.string(password, 'password');
4709
+ V.string(friendlyName, 'friendlyName');
4710
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, keyBytes);
4224
4711
  const passwordPtr = allocString(wasm, password);
4225
4712
  const passwordLen = wasm.lengthBytesUTF8(password) + 1;
4226
4713
  const friendlyNamePtr = allocString(wasm, friendlyName);
4227
4714
  const chainBytes = normalizePemInput(chainPem, 'chainPem');
4228
- const chainPtr = chainBytes.length > 0 ? allocAndCopy(wasm, chainBytes) : 0;
4715
+ const { ptr: chainPtr, len: chainLen } = V.byteLength(chainBytes) > 0 ? allocAndCopy(wasm, chainBytes) : { ptr: 0, len: 0 };
4229
4716
 
4230
4717
  try {
4231
4718
  return callDynamicBytesResult(
4232
4719
  wasm,
4233
4720
  (outPtr, outLenPtr) => fn(
4234
4721
  certPtr,
4235
- certBytes.length,
4722
+ certLen,
4236
4723
  certificateEncoding,
4237
4724
  privateKeyCurve,
4238
4725
  keyPtr,
4239
- keyBytes.length,
4726
+ keyLen,
4240
4727
  passwordPtr,
4241
4728
  friendlyNamePtr,
4242
4729
  chainPtr,
4243
- chainBytes.length,
4730
+ chainLen,
4244
4731
  outPtr,
4245
4732
  outLenPtr
4246
4733
  ),
4247
4734
  { wipeOutput: true }
4248
4735
  );
4249
4736
  } finally {
4250
- wasm._hd_secure_wipe(keyPtr, keyBytes.length);
4737
+ wasm._hd_secure_wipe(keyPtr, keyLen);
4251
4738
  wasm._hd_secure_wipe(passwordPtr, passwordLen);
4252
4739
  wasm._hd_dealloc(certPtr);
4253
4740
  wasm._hd_dealloc(keyPtr);
@@ -4262,7 +4749,8 @@ function createModule(wasm) {
4262
4749
  importPkcs12(pkcs12Bundle, password) {
4263
4750
  const fn = requireWasmFunction(wasm, '_hd_x509_import_pkcs12_json');
4264
4751
  const bundleBytes = toUint8Array(pkcs12Bundle, 'pkcs12Bundle');
4265
- const bundlePtr = allocAndCopy(wasm, bundleBytes);
4752
+ V.string(password, 'password');
4753
+ const { ptr: bundlePtr, len: bundleLen } = allocAndCopy(wasm, bundleBytes);
4266
4754
  const passwordPtr = allocString(wasm, password);
4267
4755
  const passwordLen = wasm.lengthBytesUTF8(password) + 1;
4268
4756
 
@@ -4270,12 +4758,12 @@ function createModule(wasm) {
4270
4758
  return normalizePkcs12ImportJson(JSON.parse(
4271
4759
  callDynamicStringResult(
4272
4760
  wasm,
4273
- (outPtr, outLenPtr) => fn(bundlePtr, bundleBytes.length, passwordPtr, outPtr, outLenPtr),
4761
+ (outPtr, outLenPtr) => fn(bundlePtr, bundleLen, passwordPtr, outPtr, outLenPtr),
4274
4762
  { wipeOutput: true }
4275
4763
  )
4276
4764
  ));
4277
4765
  } finally {
4278
- wasm._hd_secure_wipe(bundlePtr, bundleBytes.length);
4766
+ wasm._hd_secure_wipe(bundlePtr, bundleLen);
4279
4767
  wasm._hd_secure_wipe(passwordPtr, passwordLen);
4280
4768
  wasm._hd_dealloc(bundlePtr);
4281
4769
  wasm._hd_dealloc(passwordPtr);
@@ -4287,26 +4775,41 @@ function createModule(wasm) {
4287
4775
  // ECIES API
4288
4776
  // ==========================================================================
4289
4777
 
4290
- const CURVE_NAME_MAP = { secp256k1: 0, ed25519: 1, p256: 2, p384: 3, x25519: 4 };
4778
+ const CURVE_NAME_MAP = Object.freeze({ secp256k1: 0, ed25519: 1, p256: 2, p384: 3, x25519: 4 });
4779
+
4780
+ // An ECIES curve: a Curve id or its name; an unknown name is an error, never
4781
+ // secp256k1 by default.
4782
+ function eciesCurve(curve) {
4783
+ const curveId = typeof curve === 'string' && Object.hasOwn(CURVE_NAME_MAP, curve)
4784
+ ? CURVE_NAME_MAP[curve]
4785
+ : curve;
4786
+ return V.curve(curveId, [Curve.SECP256K1, Curve.P256, Curve.P384, Curve.X25519]);
4787
+ }
4291
4788
 
4292
4789
  const ecies = {
4293
4790
  encrypt(curve, recipientPublicKey, plaintext, aad = new Uint8Array(0)) {
4294
- const curveId = typeof curve === 'number' ? curve : (CURVE_NAME_MAP[curve] ?? 0);
4791
+ const curveId = eciesCurve(curve);
4792
+ V.publicKey(wasm, recipientPublicKey, curveId, 'recipientPublicKey');
4793
+ V.bytes(plaintext, 'plaintext');
4794
+ V.bytes(aad, 'aad');
4295
4795
  const encryptFn = requireWasmFunction(wasm, '_hd_ecies_encrypt');
4296
4796
  const overheadFn = requireWasmFunction(wasm, '_hd_ecies_overhead');
4297
4797
  const overhead = overheadFn(curveId);
4298
- const outSize = plaintext.length + overhead;
4299
- const pubPtr = allocAndCopy(wasm, recipientPublicKey);
4300
- const ptPtr = allocAndCopy(wasm, plaintext);
4301
- const aadPtr = aad.length > 0 ? allocAndCopy(wasm, aad) : 0;
4798
+ if (!Number.isInteger(overhead) || overhead <= 0) {
4799
+ throw new HDWalletError(ErrorCode.NOT_SUPPORTED, 'ECIES is not supported on this curve');
4800
+ }
4801
+ const outSize = V.byteLength(plaintext) + overhead;
4802
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, recipientPublicKey);
4803
+ const { ptr: ptPtr, len: ptLen } = allocAndCopy(wasm, plaintext);
4804
+ const { ptr: aadPtr, len: aadLen } = V.byteLength(aad) > 0 ? allocAndCopy(wasm, aad) : { ptr: 0, len: 0 };
4302
4805
  const outPtr = wasm._hd_alloc(outSize);
4303
4806
  try {
4304
- const result = encryptFn(curveId, pubPtr, recipientPublicKey.length,
4305
- ptPtr, plaintext.length, aadPtr, aad.length, outPtr, outSize);
4306
- if (result < 0) throw new HDWalletError(-result);
4807
+ const result = checkLength(encryptFn(curveId, pubPtr, pubLen,
4808
+ ptPtr, ptLen, aadPtr, aadLen, outPtr, outSize), outSize);
4307
4809
  return readBytes(wasm, outPtr, result);
4308
4810
  } finally {
4309
4811
  wasm._hd_dealloc(pubPtr);
4812
+ wasm._hd_secure_wipe(ptPtr, ptLen);
4310
4813
  wasm._hd_dealloc(ptPtr);
4311
4814
  if (aadPtr) wasm._hd_dealloc(aadPtr);
4312
4815
  wasm._hd_dealloc(outPtr);
@@ -4314,22 +4817,25 @@ function createModule(wasm) {
4314
4817
  },
4315
4818
 
4316
4819
  decrypt(curve, recipientPrivateKey, ciphertext, aad = new Uint8Array(0)) {
4317
- const curveId = typeof curve === 'number' ? curve : (CURVE_NAME_MAP[curve] ?? 0);
4820
+ const curveId = eciesCurve(curve);
4821
+ V.privateKey(wasm, recipientPrivateKey, curveId, 'recipientPrivateKey');
4822
+ V.bytes(ciphertext, 'ciphertext');
4823
+ V.bytes(aad, 'aad');
4318
4824
  const decryptFn = requireWasmFunction(wasm, '_hd_ecies_decrypt');
4319
- const privPtr = allocAndCopy(wasm, recipientPrivateKey);
4320
- const ctPtr = allocAndCopy(wasm, ciphertext);
4321
- const aadPtr = aad.length > 0 ? allocAndCopy(wasm, aad) : 0;
4322
- const outPtr = wasm._hd_alloc(ciphertext.length);
4825
+ const { ptr: privPtr, len: privLen } = allocAndCopy(wasm, recipientPrivateKey);
4826
+ const { ptr: ctPtr, len: ctLen } = allocAndCopy(wasm, ciphertext);
4827
+ const { ptr: aadPtr, len: aadLen } = V.byteLength(aad) > 0 ? allocAndCopy(wasm, aad) : { ptr: 0, len: 0 };
4828
+ const outPtr = wasm._hd_alloc(ctLen);
4323
4829
  try {
4324
- const result = decryptFn(curveId, privPtr, recipientPrivateKey.length,
4325
- ctPtr, ciphertext.length, aadPtr, aad.length, outPtr, ciphertext.length);
4326
- if (result < 0) throw new HDWalletError(-result);
4830
+ const result = checkLengthWithin(decryptFn(curveId, privPtr, privLen,
4831
+ ctPtr, ctLen, aadPtr, aadLen, outPtr, ctLen), 0, ctLen);
4327
4832
  return readBytes(wasm, outPtr, result);
4328
4833
  } finally {
4329
- wasm._hd_secure_wipe(privPtr, recipientPrivateKey.length);
4834
+ wasm._hd_secure_wipe(privPtr, privLen);
4330
4835
  wasm._hd_dealloc(privPtr);
4331
4836
  wasm._hd_dealloc(ctPtr);
4332
4837
  if (aadPtr) wasm._hd_dealloc(aadPtr);
4838
+ wasm._hd_secure_wipe(outPtr, ctLen);
4333
4839
  wasm._hd_dealloc(outPtr);
4334
4840
  }
4335
4841
  }
@@ -4342,16 +4848,19 @@ function createModule(wasm) {
4342
4848
  const aesCtr = {
4343
4849
  encrypt(key, plaintext, iv) {
4344
4850
  const encryptFn = requireWasmFunction(wasm, '_hd_aes_ctr_encrypt');
4345
- const keyPtr = allocAndCopy(wasm, key);
4346
- const ptPtr = allocAndCopy(wasm, plaintext);
4347
- const ivPtr = allocAndCopy(wasm, iv);
4348
- const outPtr = wasm._hd_alloc(plaintext.length);
4851
+ V.bytesOfLength(key, [16, 24, 32], 'key');
4852
+ V.bytes(plaintext, 'plaintext');
4853
+ V.bytesOfLength(iv, 16, 'iv');
4854
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, key);
4855
+ const { ptr: ptPtr, len: ptLen } = allocAndCopy(wasm, plaintext);
4856
+ const { ptr: ivPtr, len: ivLen } = allocAndCopy(wasm, iv);
4857
+ const outPtr = wasm._hd_alloc(ptLen);
4349
4858
  try {
4350
- const result = encryptFn(keyPtr, key.length, ptPtr, plaintext.length, ivPtr, iv.length, outPtr, plaintext.length);
4351
- if (result < 0) throw new HDWalletError(-result);
4352
- return readBytes(wasm, outPtr, plaintext.length);
4859
+ checkLength(encryptFn(keyPtr, keyLen, ptPtr, ptLen, ivPtr, ivLen, outPtr, ptLen), ptLen);
4860
+ return readBytes(wasm, outPtr, ptLen);
4353
4861
  } finally {
4354
- wasm._hd_secure_wipe(keyPtr, key.length);
4862
+ wasm._hd_secure_wipe(keyPtr, keyLen);
4863
+ wasm._hd_secure_wipe(ptPtr, ptLen);
4355
4864
  wasm._hd_dealloc(keyPtr);
4356
4865
  wasm._hd_dealloc(ptPtr);
4357
4866
  wasm._hd_dealloc(ivPtr);
@@ -4361,19 +4870,22 @@ function createModule(wasm) {
4361
4870
 
4362
4871
  decrypt(key, ciphertext, iv) {
4363
4872
  const decryptFn = requireWasmFunction(wasm, '_hd_aes_ctr_decrypt');
4364
- const keyPtr = allocAndCopy(wasm, key);
4365
- const ctPtr = allocAndCopy(wasm, ciphertext);
4366
- const ivPtr = allocAndCopy(wasm, iv);
4367
- const outPtr = wasm._hd_alloc(ciphertext.length);
4873
+ V.bytesOfLength(key, [16, 24, 32], 'key');
4874
+ V.bytes(ciphertext, 'ciphertext');
4875
+ V.bytesOfLength(iv, 16, 'iv');
4876
+ const { ptr: keyPtr, len: keyLen } = allocAndCopy(wasm, key);
4877
+ const { ptr: ctPtr, len: ctLen } = allocAndCopy(wasm, ciphertext);
4878
+ const { ptr: ivPtr, len: ivLen } = allocAndCopy(wasm, iv);
4879
+ const outPtr = wasm._hd_alloc(ctLen);
4368
4880
  try {
4369
- const result = decryptFn(keyPtr, key.length, ctPtr, ciphertext.length, ivPtr, iv.length, outPtr, ciphertext.length);
4370
- if (result < 0) throw new HDWalletError(-result);
4371
- return readBytes(wasm, outPtr, ciphertext.length);
4881
+ checkLength(decryptFn(keyPtr, keyLen, ctPtr, ctLen, ivPtr, ivLen, outPtr, ctLen), ctLen);
4882
+ return readBytes(wasm, outPtr, ctLen);
4372
4883
  } finally {
4373
- wasm._hd_secure_wipe(keyPtr, key.length);
4884
+ wasm._hd_secure_wipe(keyPtr, keyLen);
4374
4885
  wasm._hd_dealloc(keyPtr);
4375
4886
  wasm._hd_dealloc(ctPtr);
4376
4887
  wasm._hd_dealloc(ivPtr);
4888
+ wasm._hd_secure_wipe(outPtr, ctLen);
4377
4889
  wasm._hd_dealloc(outPtr);
4378
4890
  }
4379
4891
  }
@@ -4386,19 +4898,21 @@ function createModule(wasm) {
4386
4898
  const slip10 = {
4387
4899
  deriveEd25519Path(seed, path) {
4388
4900
  const deriveFn = requireWasmFunction(wasm, '_hd_slip10_ed25519_derive_path');
4389
- const seedPtr = allocAndCopy(wasm, seed);
4901
+ V.bytesOfLength(seed, 64, 'seed', ErrorCode.INVALID_SEED);
4902
+ V.string(path, 'path');
4903
+ const { ptr: seedPtr, len: seedLen } = allocAndCopy(wasm, seed);
4390
4904
  const pathPtr = allocString(wasm, path);
4391
4905
  const keyPtr = wasm._hd_alloc(32);
4392
4906
  const chainCodePtr = wasm._hd_alloc(32);
4393
4907
  try {
4394
- const result = deriveFn(seedPtr, seed.length, pathPtr, keyPtr, chainCodePtr);
4908
+ const result = deriveFn(seedPtr, seedLen, pathPtr, keyPtr, chainCodePtr);
4395
4909
  checkResult(result);
4396
4910
  return {
4397
4911
  privateKey: readBytes(wasm, keyPtr, 32),
4398
4912
  chainCode: readBytes(wasm, chainCodePtr, 32)
4399
4913
  };
4400
4914
  } finally {
4401
- wasm._hd_secure_wipe(seedPtr, seed.length);
4915
+ wasm._hd_secure_wipe(seedPtr, seedLen);
4402
4916
  wasm._hd_secure_wipe(keyPtr, 32);
4403
4917
  wasm._hd_dealloc(seedPtr);
4404
4918
  wasm._hd_dealloc(pathPtr);
@@ -4420,11 +4934,15 @@ function createModule(wasm) {
4420
4934
  * @returns {Uint8Array} PeerID as raw multihash bytes
4421
4935
  */
4422
4936
  peerIdFromPublicKey(publicKey, curve) {
4423
- const pubPtr = allocAndCopy(wasm, publicKey);
4937
+ // libp2p key forms: secp256k1 compressed, Ed25519 32 bytes, P-256 SEC1.
4938
+ V.curve(curve, [Curve.SECP256K1, Curve.ED25519, Curve.P256]);
4939
+ V.publicKey(wasm, publicKey, curve, 'publicKey',
4940
+ curve === Curve.SECP256K1 ? [33] : V.publicKeyLengths(curve));
4941
+ const { ptr: pubPtr, len: pubLen } = allocAndCopy(wasm, publicKey);
4424
4942
  const outPtr = wasm._hd_alloc(64);
4425
4943
  try {
4426
- const result = wasm._hd_libp2p_peer_id(pubPtr, publicKey.length, curve, outPtr, 64);
4427
- if (result < 0) throw new HDWalletError(result);
4944
+ const result = checkLengthWithin(
4945
+ wasm._hd_libp2p_peer_id(pubPtr, pubLen, curve, outPtr, 64), 1, 64);
4428
4946
  return readBytes(wasm, outPtr, result);
4429
4947
  } finally {
4430
4948
  wasm._hd_dealloc(pubPtr);
@@ -4453,20 +4971,20 @@ function createModule(wasm) {
4453
4971
  // We could also re-derive from pubkey, but for the namespace API we accept
4454
4972
  // pre-computed peerID bytes and just do the CID wrapping + encoding here.
4455
4973
  // We still use WASM for base36 encoding.
4456
- const dataPtr = allocAndCopy(wasm, peerIdBytes);
4974
+ // A peer ID multihash: identity or SHA-256, at most 64 bytes.
4975
+ const peerId = V.snapshot(V.bytesInRange(peerIdBytes, 2, 64, 'peerIdBytes'), 'peerIdBytes');
4457
4976
  const outPtr = wasm._hd_alloc(128);
4458
4977
  // Build CID in JS (trivial: 2-byte prefix + multihash)
4459
- const cid = new Uint8Array(2 + peerIdBytes.length);
4978
+ const cid = new Uint8Array(2 + peerId.length);
4460
4979
  cid[0] = 0x01; // CIDv1
4461
4980
  cid[1] = 0x72; // libp2p-key codec
4462
- cid.set(peerIdBytes, 2);
4463
- const cidPtr = allocAndCopy(wasm, cid);
4981
+ cid.set(peerId, 2);
4982
+ const { ptr: cidPtr, len: cidLen } = allocAndCopy(wasm, cid);
4464
4983
  try {
4465
- const result = wasm._hd_encode_base36lower(cidPtr, cid.length, outPtr, 128);
4984
+ const result = wasm._hd_encode_base36lower(cidPtr, cidLen, outPtr, 128);
4466
4985
  checkResult(result);
4467
4986
  return 'k' + readString(wasm, outPtr);
4468
4987
  } finally {
4469
- wasm._hd_dealloc(dataPtr);
4470
4988
  wasm._hd_dealloc(cidPtr);
4471
4989
  wasm._hd_dealloc(outPtr);
4472
4990
  }
@@ -4478,14 +4996,15 @@ function createModule(wasm) {
4478
4996
  * @returns {string}
4479
4997
  */
4480
4998
  ipnsHashBase32(peerIdBytes) {
4481
- const cid = new Uint8Array(2 + peerIdBytes.length);
4999
+ const peerId = V.snapshot(V.bytesInRange(peerIdBytes, 2, 64, 'peerIdBytes'), 'peerIdBytes');
5000
+ const cid = new Uint8Array(2 + peerId.length);
4482
5001
  cid[0] = 0x01; // CIDv1
4483
5002
  cid[1] = 0x72; // libp2p-key codec
4484
- cid.set(peerIdBytes, 2);
4485
- const cidPtr = allocAndCopy(wasm, cid);
5003
+ cid.set(peerId, 2);
5004
+ const { ptr: cidPtr, len: cidLen } = allocAndCopy(wasm, cid);
4486
5005
  const outPtr = wasm._hd_alloc(128);
4487
5006
  try {
4488
- const result = wasm._hd_encode_base32lower(cidPtr, cid.length, outPtr, 128);
5007
+ const result = wasm._hd_encode_base32lower(cidPtr, cidLen, outPtr, 128);
4489
5008
  checkResult(result);
4490
5009
  return 'b' + readString(wasm, outPtr);
4491
5010
  } finally {
@@ -4575,11 +5094,12 @@ function createModule(wasm) {
4575
5094
 
4576
5095
  // Entropy
4577
5096
  injectEntropy(entropy) {
4578
- const entropyPtr = allocAndCopy(wasm, entropy);
5097
+ V.bytesInRange(entropy, 1, 0x100000, 'entropy');
5098
+ const { ptr: entropyPtr, len: entropyLen } = allocAndCopy(wasm, entropy);
4579
5099
  try {
4580
- wasm._hd_inject_entropy(entropyPtr, entropy.length);
5100
+ wasm._hd_inject_entropy(entropyPtr, entropyLen);
4581
5101
  } finally {
4582
- wasm._hd_secure_wipe(entropyPtr, entropy.length);
5102
+ wasm._hd_secure_wipe(entropyPtr, entropyLen);
4583
5103
  wasm._hd_dealloc(entropyPtr);
4584
5104
  }
4585
5105
  },