navio-sdk 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ var hashWasm = require('hash-wasm');
3
4
  var sha256 = require('@noble/hashes/sha256');
4
5
  var ripemd160 = require('@noble/hashes/ripemd160');
6
+ var bip39 = require('@scure/bip39');
7
+ var english_js = require('@scure/bip39/wordlists/english.js');
5
8
  var blsctModule = require('navio-blsct');
6
9
  var net = require('net');
7
10
 
@@ -23,21 +26,286 @@ function _interopNamespace(e) {
23
26
  return Object.freeze(n);
24
27
  }
25
28
 
29
+ var bip39__namespace = /*#__PURE__*/_interopNamespace(bip39);
26
30
  var blsctModule__namespace = /*#__PURE__*/_interopNamespace(blsctModule);
27
31
  var net__namespace = /*#__PURE__*/_interopNamespace(net);
28
32
 
33
+ var __defProp = Object.defineProperty;
34
+ var __getOwnPropNames = Object.getOwnPropertyNames;
29
35
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
30
36
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
31
37
  }) : x)(function(x) {
32
38
  if (typeof require !== "undefined") return require.apply(this, arguments);
33
39
  throw Error('Dynamic require of "' + x + '" is not supported');
34
40
  });
41
+ var __esm = (fn, res) => function __init() {
42
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
43
+ };
44
+ var __export = (target, all) => {
45
+ for (var name in all)
46
+ __defProp(target, name, { get: all[name], enumerable: true });
47
+ };
48
+ function toBufferSource(data) {
49
+ return new Uint8Array(data).buffer;
50
+ }
51
+ function getCrypto() {
52
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined" && typeof globalThis.crypto.getRandomValues === "function") {
53
+ return globalThis.crypto;
54
+ }
55
+ const nodeCrypto = __require("crypto");
56
+ if (nodeCrypto.webcrypto) {
57
+ return nodeCrypto.webcrypto;
58
+ }
59
+ throw new Error("Web Crypto API not available. Requires Node.js 15+ or a modern browser.");
60
+ }
61
+ function randomBytes(length) {
62
+ const crypto = getCrypto();
63
+ const bytes = new Uint8Array(length);
64
+ crypto.getRandomValues(bytes);
65
+ return bytes;
66
+ }
67
+ async function deriveKey(password, salt) {
68
+ const hashHex = await hashWasm.argon2id({
69
+ password,
70
+ salt,
71
+ memorySize: ARGON2_PARAMS.memorySize,
72
+ iterations: ARGON2_PARAMS.iterations,
73
+ parallelism: ARGON2_PARAMS.parallelism,
74
+ hashLength: ARGON2_PARAMS.hashLength,
75
+ outputType: "hex"
76
+ });
77
+ const hashBytes = hexToBytes(hashHex);
78
+ const crypto = getCrypto();
79
+ const key = await crypto.subtle.importKey(
80
+ "raw",
81
+ toBufferSource(hashBytes),
82
+ { name: "AES-GCM", length: 256 },
83
+ false,
84
+ // not extractable
85
+ ["encrypt", "decrypt"]
86
+ );
87
+ return key;
88
+ }
89
+ async function deriveKeyBytes(password, salt) {
90
+ const hashHex = await hashWasm.argon2id({
91
+ password,
92
+ salt,
93
+ memorySize: ARGON2_PARAMS.memorySize,
94
+ iterations: ARGON2_PARAMS.iterations,
95
+ parallelism: ARGON2_PARAMS.parallelism,
96
+ hashLength: ARGON2_PARAMS.hashLength,
97
+ outputType: "hex"
98
+ });
99
+ return hexToBytes(hashHex);
100
+ }
101
+ async function encrypt(data, password) {
102
+ const crypto = getCrypto();
103
+ const salt = randomBytes(exports.SALT_LENGTH);
104
+ const iv = randomBytes(exports.IV_LENGTH);
105
+ const key = await deriveKey(password, salt);
106
+ const ciphertext = await crypto.subtle.encrypt(
107
+ { name: "AES-GCM", iv: toBufferSource(iv) },
108
+ key,
109
+ toBufferSource(data)
110
+ );
111
+ return {
112
+ ciphertext: new Uint8Array(ciphertext),
113
+ iv,
114
+ salt
115
+ };
116
+ }
117
+ async function encryptWithKey(data, key, salt) {
118
+ const crypto = getCrypto();
119
+ const iv = randomBytes(exports.IV_LENGTH);
120
+ const ciphertext = await crypto.subtle.encrypt(
121
+ { name: "AES-GCM", iv: toBufferSource(iv) },
122
+ key,
123
+ toBufferSource(data)
124
+ );
125
+ return {
126
+ ciphertext: new Uint8Array(ciphertext),
127
+ iv,
128
+ salt
129
+ };
130
+ }
131
+ async function decrypt(encrypted, password) {
132
+ const crypto = getCrypto();
133
+ const key = await deriveKey(password, encrypted.salt);
134
+ try {
135
+ const plaintext = await crypto.subtle.decrypt(
136
+ { name: "AES-GCM", iv: toBufferSource(encrypted.iv) },
137
+ key,
138
+ toBufferSource(encrypted.ciphertext)
139
+ );
140
+ return new Uint8Array(plaintext);
141
+ } catch {
142
+ throw new Error("Decryption failed: wrong password or corrupted data");
143
+ }
144
+ }
145
+ async function decryptWithKey(encrypted, key) {
146
+ const crypto = getCrypto();
147
+ try {
148
+ const plaintext = await crypto.subtle.decrypt(
149
+ { name: "AES-GCM", iv: toBufferSource(encrypted.iv) },
150
+ key,
151
+ toBufferSource(encrypted.ciphertext)
152
+ );
153
+ return new Uint8Array(plaintext);
154
+ } catch {
155
+ throw new Error("Decryption failed: wrong key or corrupted data");
156
+ }
157
+ }
158
+ function serializeEncryptedData(encrypted) {
159
+ return {
160
+ ciphertext: bytesToBase64(encrypted.ciphertext),
161
+ iv: bytesToBase64(encrypted.iv),
162
+ salt: bytesToBase64(encrypted.salt),
163
+ version: exports.ENCRYPTION_VERSION
164
+ };
165
+ }
166
+ function deserializeEncryptedData(serialized) {
167
+ if (serialized.version > exports.ENCRYPTION_VERSION) {
168
+ throw new Error(`Unsupported encryption version: ${serialized.version}`);
169
+ }
170
+ return {
171
+ ciphertext: base64ToBytes(serialized.ciphertext),
172
+ iv: base64ToBytes(serialized.iv),
173
+ salt: base64ToBytes(serialized.salt)
174
+ };
175
+ }
176
+ async function encryptDatabase(dbBuffer, password) {
177
+ const encrypted = await encrypt(dbBuffer, password);
178
+ const combined = new Uint8Array(1 + exports.SALT_LENGTH + exports.IV_LENGTH + encrypted.ciphertext.length);
179
+ combined[0] = exports.ENCRYPTION_VERSION;
180
+ combined.set(encrypted.salt, 1);
181
+ combined.set(encrypted.iv, 1 + exports.SALT_LENGTH);
182
+ combined.set(encrypted.ciphertext, 1 + exports.SALT_LENGTH + exports.IV_LENGTH);
183
+ return combined;
184
+ }
185
+ async function decryptDatabase(encryptedBuffer, password) {
186
+ if (encryptedBuffer.length < 1 + exports.SALT_LENGTH + exports.IV_LENGTH) {
187
+ throw new Error("Invalid encrypted database: too short");
188
+ }
189
+ const version = encryptedBuffer[0];
190
+ if (version > exports.ENCRYPTION_VERSION) {
191
+ throw new Error(`Unsupported encryption version: ${version}`);
192
+ }
193
+ const salt = encryptedBuffer.slice(1, 1 + exports.SALT_LENGTH);
194
+ const iv = encryptedBuffer.slice(1 + exports.SALT_LENGTH, 1 + exports.SALT_LENGTH + exports.IV_LENGTH);
195
+ const ciphertext = encryptedBuffer.slice(1 + exports.SALT_LENGTH + exports.IV_LENGTH);
196
+ return decrypt({ ciphertext, iv, salt }, password);
197
+ }
198
+ function isEncryptedDatabase(buffer) {
199
+ if (buffer.length < 1 + exports.SALT_LENGTH + exports.IV_LENGTH) {
200
+ return false;
201
+ }
202
+ const version = buffer[0];
203
+ return version >= 1 && version <= exports.ENCRYPTION_VERSION;
204
+ }
205
+ async function createPasswordVerification(password, salt) {
206
+ const keyBytes = await deriveKeyBytes(password, salt);
207
+ const crypto = getCrypto();
208
+ const keyBuffer = new Uint8Array(keyBytes).buffer;
209
+ const verificationHash = await crypto.subtle.digest("SHA-256", keyBuffer);
210
+ return new Uint8Array(verificationHash);
211
+ }
212
+ async function verifyPassword(password, salt, storedHash) {
213
+ const computedHash = await createPasswordVerification(password, salt);
214
+ if (computedHash.length !== storedHash.length) {
215
+ return false;
216
+ }
217
+ let result = 0;
218
+ for (let i = 0; i < computedHash.length; i++) {
219
+ result |= computedHash[i] ^ storedHash[i];
220
+ }
221
+ return result === 0;
222
+ }
223
+ function hexToBytes(hex) {
224
+ const bytes = new Uint8Array(hex.length / 2);
225
+ for (let i = 0; i < bytes.length; i++) {
226
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
227
+ }
228
+ return bytes;
229
+ }
230
+ function bytesToBase64(bytes) {
231
+ if (typeof Buffer !== "undefined") {
232
+ return Buffer.from(bytes).toString("base64");
233
+ } else {
234
+ let binary = "";
235
+ for (let i = 0; i < bytes.length; i++) {
236
+ binary += String.fromCharCode(bytes[i]);
237
+ }
238
+ return btoa(binary);
239
+ }
240
+ }
241
+ function base64ToBytes(base64) {
242
+ if (typeof Buffer !== "undefined") {
243
+ return new Uint8Array(Buffer.from(base64, "base64"));
244
+ } else {
245
+ const binary = atob(base64);
246
+ const bytes = new Uint8Array(binary.length);
247
+ for (let i = 0; i < binary.length; i++) {
248
+ bytes[i] = binary.charCodeAt(i);
249
+ }
250
+ return bytes;
251
+ }
252
+ }
253
+ var ARGON2_PARAMS; exports.ENCRYPTION_VERSION = void 0; exports.IV_LENGTH = void 0; exports.SALT_LENGTH = void 0;
254
+ var init_encryption = __esm({
255
+ "src/crypto/encryption.ts"() {
256
+ ARGON2_PARAMS = {
257
+ memorySize: 65536,
258
+ // 64 MB (hash-wasm uses memorySize instead of memory)
259
+ iterations: 3,
260
+ parallelism: 4,
261
+ hashLength: 32
262
+ // 256 bits for AES-256 (hash-wasm uses hashLength instead of hashLen)
263
+ };
264
+ exports.ENCRYPTION_VERSION = 1;
265
+ exports.IV_LENGTH = 12;
266
+ exports.SALT_LENGTH = 16;
267
+ }
268
+ });
269
+
270
+ // src/crypto/index.ts
271
+ var crypto_exports = {};
272
+ __export(crypto_exports, {
273
+ ENCRYPTION_VERSION: () => exports.ENCRYPTION_VERSION,
274
+ IV_LENGTH: () => exports.IV_LENGTH,
275
+ SALT_LENGTH: () => exports.SALT_LENGTH,
276
+ createPasswordVerification: () => createPasswordVerification,
277
+ decrypt: () => decrypt,
278
+ decryptDatabase: () => decryptDatabase,
279
+ decryptWithKey: () => decryptWithKey,
280
+ deriveKey: () => deriveKey,
281
+ deriveKeyBytes: () => deriveKeyBytes,
282
+ deserializeEncryptedData: () => deserializeEncryptedData,
283
+ encrypt: () => encrypt,
284
+ encryptDatabase: () => encryptDatabase,
285
+ encryptWithKey: () => encryptWithKey,
286
+ isEncryptedDatabase: () => isEncryptedDatabase,
287
+ randomBytes: () => randomBytes,
288
+ serializeEncryptedData: () => serializeEncryptedData,
289
+ verifyPassword: () => verifyPassword
290
+ });
291
+ var init_crypto = __esm({
292
+ "src/crypto/index.ts"() {
293
+ init_encryption();
294
+ }
295
+ });
296
+
297
+ // src/key-manager.ts
298
+ init_crypto();
35
299
  var Scalar2 = blsctModule__namespace.Scalar;
36
300
  var ChildKey2 = blsctModule__namespace.ChildKey;
37
301
  var PublicKey2 = blsctModule__namespace.PublicKey;
38
302
  var SubAddr2 = blsctModule__namespace.SubAddr;
39
303
  var SubAddrId2 = blsctModule__namespace.SubAddrId;
40
304
  var DoublePublicKey2 = blsctModule__namespace.DoublePublicKey;
305
+ var Address2 = blsctModule__namespace.Address;
306
+ var AddressEncoding2 = blsctModule__namespace.AddressEncoding;
307
+ var setChain2 = blsctModule__namespace.setChain;
308
+ var BlsctChain2 = blsctModule__namespace.BlsctChain;
41
309
  var calcPrivSpendingKey2 = blsctModule__namespace.calcPrivSpendingKey;
42
310
  var recoverAmount2 = blsctModule__namespace.recoverAmount;
43
311
  var ViewTag2 = blsctModule__namespace.ViewTag;
@@ -47,7 +315,7 @@ var getAmountRecoveryResultSize2 = blsctModule__namespace.getAmountRecoveryResul
47
315
  var getAmountRecoveryResultIsSucc2 = blsctModule__namespace.getAmountRecoveryResultIsSucc;
48
316
  var getAmountRecoveryResultAmount2 = blsctModule__namespace.getAmountRecoveryResultAmount;
49
317
  var deleteAmountsRetVal2 = blsctModule__namespace.deleteAmountsRetVal;
50
- var KeyManager = class {
318
+ var KeyManager = class _KeyManager {
51
319
  constructor() {
52
320
  this.hdChain = null;
53
321
  this.viewKey = null;
@@ -84,6 +352,11 @@ var KeyManager = class {
84
352
  // Flags
85
353
  this.fViewKeyDefined = false;
86
354
  this.fSpendKeyDefined = false;
355
+ // Encryption state
356
+ this.encrypted = false;
357
+ this.encryptionSalt = null;
358
+ this.passwordVerificationHash = null;
359
+ this.encryptionKey = null;
87
360
  }
88
361
  /**
89
362
  * Check if HD is enabled (has a seed)
@@ -99,6 +372,212 @@ var KeyManager = class {
99
372
  canGenerateKeys() {
100
373
  return this.isHDEnabled();
101
374
  }
375
+ // ============================================================================
376
+ // Encryption Methods
377
+ // ============================================================================
378
+ /**
379
+ * Check if the wallet is encrypted
380
+ * @returns True if the wallet has been encrypted with a password
381
+ */
382
+ isEncrypted() {
383
+ return this.encrypted;
384
+ }
385
+ /**
386
+ * Check if the wallet is unlocked (decryption key is cached)
387
+ * @returns True if the wallet is unlocked and can access private keys
388
+ */
389
+ isUnlocked() {
390
+ return !this.encrypted || this.encryptionKey !== null;
391
+ }
392
+ /**
393
+ * Set a password to encrypt the wallet
394
+ * This encrypts all private keys and stores them in encrypted form
395
+ *
396
+ * @param password - The password to encrypt the wallet with
397
+ * @throws Error if wallet is already encrypted
398
+ */
399
+ async setPassword(password) {
400
+ if (this.encrypted) {
401
+ throw new Error("Wallet is already encrypted. Use changePassword() to change the password.");
402
+ }
403
+ this.encryptionSalt = randomBytes(exports.SALT_LENGTH);
404
+ this.encryptionKey = await deriveKey(password, this.encryptionSalt);
405
+ this.passwordVerificationHash = await createPasswordVerification(password, this.encryptionSalt);
406
+ await this.encryptAllKeys();
407
+ this.encrypted = true;
408
+ }
409
+ /**
410
+ * Unlock an encrypted wallet with the password
411
+ * This derives the encryption key and caches it for decrypting private keys
412
+ *
413
+ * @param password - The wallet password
414
+ * @returns True if the password is correct and wallet is unlocked
415
+ */
416
+ async unlock(password) {
417
+ if (!this.encrypted) {
418
+ return true;
419
+ }
420
+ if (!this.encryptionSalt || !this.passwordVerificationHash) {
421
+ throw new Error("Wallet encryption state is corrupted");
422
+ }
423
+ const isValid = await verifyPassword(password, this.encryptionSalt, this.passwordVerificationHash);
424
+ if (!isValid) {
425
+ return false;
426
+ }
427
+ this.encryptionKey = await deriveKey(password, this.encryptionSalt);
428
+ await this.decryptEssentialKeys();
429
+ return true;
430
+ }
431
+ /**
432
+ * Lock the wallet, clearing the cached encryption key and unencrypted keys
433
+ * After locking, private keys cannot be accessed without unlocking again
434
+ */
435
+ lock() {
436
+ if (!this.encrypted) {
437
+ return;
438
+ }
439
+ this.encryptionKey = null;
440
+ this.keys.clear();
441
+ this.outKeys.clear();
442
+ }
443
+ /**
444
+ * Change the wallet password
445
+ *
446
+ * @param oldPassword - The current password
447
+ * @param newPassword - The new password
448
+ * @returns True if password was changed successfully
449
+ * @throws Error if wallet is not encrypted or old password is incorrect
450
+ */
451
+ async changePassword(oldPassword, newPassword) {
452
+ if (!this.encrypted) {
453
+ throw new Error("Wallet is not encrypted. Use setPassword() first.");
454
+ }
455
+ const unlocked = await this.unlock(oldPassword);
456
+ if (!unlocked) {
457
+ return false;
458
+ }
459
+ const newSalt = randomBytes(exports.SALT_LENGTH);
460
+ const newKey = await deriveKey(newPassword, newSalt);
461
+ await this.reEncryptAllKeys(newKey, newSalt);
462
+ this.encryptionSalt = newSalt;
463
+ this.encryptionKey = newKey;
464
+ this.passwordVerificationHash = await createPasswordVerification(newPassword, newSalt);
465
+ return true;
466
+ }
467
+ /**
468
+ * Get encryption parameters for storage
469
+ * @returns Encryption salt and verification hash, or null if not encrypted
470
+ */
471
+ getEncryptionParams() {
472
+ if (!this.encrypted || !this.encryptionSalt || !this.passwordVerificationHash) {
473
+ return null;
474
+ }
475
+ return {
476
+ salt: this.bytesToHex(this.encryptionSalt),
477
+ verificationHash: this.bytesToHex(this.passwordVerificationHash)
478
+ };
479
+ }
480
+ /**
481
+ * Set encryption parameters from storage (for loading encrypted wallet)
482
+ * @param salt - Hex-encoded salt
483
+ * @param verificationHash - Hex-encoded verification hash
484
+ */
485
+ setEncryptionParams(salt, verificationHash) {
486
+ this.encryptionSalt = this.hexToBytes(salt);
487
+ this.passwordVerificationHash = this.hexToBytes(verificationHash);
488
+ this.encrypted = true;
489
+ }
490
+ /**
491
+ * Get key storage statistics (for testing/debugging)
492
+ * @returns Object with counts of plain and encrypted keys
493
+ */
494
+ getKeyStats() {
495
+ return {
496
+ plainKeys: this.keys.size,
497
+ plainOutKeys: this.outKeys.size,
498
+ encryptedKeys: this.cryptedKeys.size,
499
+ encryptedOutKeys: this.cryptedOutKeys.size
500
+ };
501
+ }
502
+ /**
503
+ * Encrypt all private keys in the wallet
504
+ * Internal method called when setting password
505
+ */
506
+ async encryptAllKeys() {
507
+ if (!this.encryptionKey || !this.encryptionSalt) {
508
+ throw new Error("Encryption key not available");
509
+ }
510
+ for (const [keyIdHex, secretKey] of this.keys) {
511
+ const keyBytes = this.hexToBytes(secretKey.serialize());
512
+ const encrypted = await encryptWithKey(keyBytes, this.encryptionKey, this.encryptionSalt);
513
+ const publicKey = PublicKey2.fromScalar(secretKey);
514
+ this.cryptedKeys.set(keyIdHex, {
515
+ publicKey,
516
+ encryptedSecret: this.serializeEncryptedToBytes(encrypted)
517
+ });
518
+ }
519
+ for (const [outIdHex, secretKey] of this.outKeys) {
520
+ const keyBytes = this.hexToBytes(secretKey.serialize());
521
+ const encrypted = await encryptWithKey(keyBytes, this.encryptionKey, this.encryptionSalt);
522
+ const publicKey = PublicKey2.fromScalar(secretKey);
523
+ this.cryptedOutKeys.set(outIdHex, {
524
+ publicKey,
525
+ encryptedSecret: this.serializeEncryptedToBytes(encrypted)
526
+ });
527
+ }
528
+ this.keys.clear();
529
+ this.outKeys.clear();
530
+ }
531
+ /**
532
+ * Decrypt essential keys needed for wallet operations
533
+ * Called after successful unlock
534
+ */
535
+ async decryptEssentialKeys() {
536
+ }
537
+ /**
538
+ * Re-encrypt all keys with a new key (for password change)
539
+ */
540
+ async reEncryptAllKeys(newKey, newSalt) {
541
+ if (!this.encryptionKey) {
542
+ throw new Error("Wallet must be unlocked to change password");
543
+ }
544
+ for (const [keyIdHex, cryptedData] of this.cryptedKeys) {
545
+ const encrypted = this.deserializeEncryptedFromBytes(cryptedData.encryptedSecret);
546
+ const decrypted = await decryptWithKey(encrypted, this.encryptionKey);
547
+ const reEncrypted = await encryptWithKey(decrypted, newKey, newSalt);
548
+ this.cryptedKeys.set(keyIdHex, {
549
+ publicKey: cryptedData.publicKey,
550
+ encryptedSecret: this.serializeEncryptedToBytes(reEncrypted)
551
+ });
552
+ }
553
+ for (const [outIdHex, cryptedData] of this.cryptedOutKeys) {
554
+ const encrypted = this.deserializeEncryptedFromBytes(cryptedData.encryptedSecret);
555
+ const decrypted = await decryptWithKey(encrypted, this.encryptionKey);
556
+ const reEncrypted = await encryptWithKey(decrypted, newKey, newSalt);
557
+ this.cryptedOutKeys.set(outIdHex, {
558
+ publicKey: cryptedData.publicKey,
559
+ encryptedSecret: this.serializeEncryptedToBytes(reEncrypted)
560
+ });
561
+ }
562
+ }
563
+ /**
564
+ * Serialize EncryptedData to bytes for storage
565
+ */
566
+ serializeEncryptedToBytes(encrypted) {
567
+ const serialized = serializeEncryptedData(encrypted);
568
+ return new TextEncoder().encode(JSON.stringify(serialized));
569
+ }
570
+ /**
571
+ * Deserialize EncryptedData from bytes
572
+ */
573
+ deserializeEncryptedFromBytes(bytes) {
574
+ const json = new TextDecoder().decode(bytes);
575
+ const serialized = JSON.parse(json);
576
+ return deserializeEncryptedData(serialized);
577
+ }
578
+ // ============================================================================
579
+ // End Encryption Methods
580
+ // ============================================================================
102
581
  /**
103
582
  * Generate a new random seed
104
583
  * @returns A new random Scalar (seed)
@@ -192,6 +671,108 @@ var KeyManager = class {
192
671
  }
193
672
  return this.masterSeed;
194
673
  }
674
+ // ============================================================
675
+ // Mnemonic Support (BIP39)
676
+ // ============================================================
677
+ /**
678
+ * Generate a new random mnemonic phrase (24 words)
679
+ * @param strength - Entropy strength in bits (128=12 words, 160=15 words, 192=18 words, 224=21 words, 256=24 words)
680
+ * @returns A BIP39 mnemonic phrase
681
+ */
682
+ static generateMnemonic(strength = 256) {
683
+ return bip39__namespace.generateMnemonic(english_js.wordlist, strength);
684
+ }
685
+ /**
686
+ * Validate a mnemonic phrase
687
+ * @param mnemonic - The mnemonic phrase to validate
688
+ * @returns True if the mnemonic is valid
689
+ */
690
+ static validateMnemonic(mnemonic) {
691
+ return bip39__namespace.validateMnemonic(mnemonic, english_js.wordlist);
692
+ }
693
+ /**
694
+ * Convert a mnemonic phrase to seed bytes
695
+ * Uses BIP39 standard derivation with optional passphrase
696
+ * @param mnemonic - The mnemonic phrase
697
+ * @param passphrase - Optional passphrase for additional security
698
+ * @returns 64-byte seed derived from mnemonic
699
+ */
700
+ static mnemonicToSeedBytes(mnemonic, passphrase = "") {
701
+ if (!_KeyManager.validateMnemonic(mnemonic)) {
702
+ throw new Error("Invalid mnemonic phrase");
703
+ }
704
+ return bip39__namespace.mnemonicToSeedSync(mnemonic, passphrase);
705
+ }
706
+ /**
707
+ * Convert a seed (Scalar) to a mnemonic phrase
708
+ * Note: This converts the 32-byte scalar to mnemonic using it as entropy
709
+ * @param seed - The seed Scalar
710
+ * @returns A 24-word mnemonic phrase
711
+ */
712
+ static seedToMnemonic(seed) {
713
+ const seedHex = seed.serialize().padStart(64, "0");
714
+ const seedBytes = Buffer.from(seedHex, "hex");
715
+ if (seedBytes.length !== 32) {
716
+ throw new Error(`Invalid seed length: ${seedBytes.length}, expected 32 bytes`);
717
+ }
718
+ return bip39__namespace.entropyToMnemonic(seedBytes, english_js.wordlist);
719
+ }
720
+ /**
721
+ * Convert a mnemonic phrase to a Scalar seed
722
+ * For direct entropy-based conversion (mnemonic as entropy, not BIP39 derivation)
723
+ * @param mnemonic - The mnemonic phrase
724
+ * @returns A Scalar seed
725
+ */
726
+ static mnemonicToScalar(mnemonic) {
727
+ if (!_KeyManager.validateMnemonic(mnemonic)) {
728
+ throw new Error("Invalid mnemonic phrase");
729
+ }
730
+ const entropy = bip39__namespace.mnemonicToEntropy(mnemonic, english_js.wordlist);
731
+ const entropyHex = Buffer.from(entropy).toString("hex");
732
+ return Scalar2.deserialize(entropyHex);
733
+ }
734
+ /**
735
+ * Generate a new mnemonic and set it as the HD seed
736
+ * @param strength - Entropy strength in bits (default: 256 for 24 words)
737
+ * @returns The mnemonic phrase that can be used to recover this wallet
738
+ * @note The returned mnemonic is derived from the stored seed, which ensures
739
+ * it will produce the same wallet when used for restoration. Due to BLS
740
+ * Scalar normalization, this may differ from the initially generated entropy.
741
+ */
742
+ generateNewMnemonic(strength = 256) {
743
+ const mnemonic = _KeyManager.generateMnemonic(strength);
744
+ this.setHDSeedFromMnemonic(mnemonic);
745
+ return this.getMnemonic();
746
+ }
747
+ /**
748
+ * Set the HD seed from a mnemonic phrase
749
+ * Uses direct entropy conversion (mnemonic words encode the seed directly)
750
+ * @param mnemonic - The mnemonic phrase
751
+ */
752
+ setHDSeedFromMnemonic(mnemonic) {
753
+ const seed = _KeyManager.mnemonicToScalar(mnemonic);
754
+ this.setHDSeed(seed);
755
+ }
756
+ /**
757
+ * Get the mnemonic phrase for the current seed
758
+ * @returns The mnemonic phrase for the current master seed
759
+ */
760
+ getMnemonic() {
761
+ if (!this.masterSeed) {
762
+ throw new Error("No master seed available");
763
+ }
764
+ return _KeyManager.seedToMnemonic(this.masterSeed);
765
+ }
766
+ /**
767
+ * Get the master seed as a hex string
768
+ * @returns The master seed as a 64-character hex string (32 bytes)
769
+ */
770
+ getMasterSeedHex() {
771
+ if (!this.masterSeed) {
772
+ throw new Error("No master seed available");
773
+ }
774
+ return this.masterSeed.serialize().padStart(64, "0");
775
+ }
195
776
  /**
196
777
  * Get the private view key
197
778
  * @returns The view key
@@ -225,6 +806,37 @@ var KeyManager = class {
225
806
  const subAddrId = SubAddrId2.generate(id.account, id.address);
226
807
  return SubAddr2.generate(this.viewKey, this.spendPublicKey, subAddrId);
227
808
  }
809
+ /**
810
+ * Get a bech32m encoded address string for the given sub-address identifier
811
+ * @param id - The sub-address identifier (defaults to account 0, address 0)
812
+ * @param network - The network type ('mainnet' or 'testnet', defaults to 'mainnet')
813
+ * @returns The bech32m encoded address string
814
+ * @example
815
+ * ```typescript
816
+ * const keyManager = new KeyManager();
817
+ * keyManager.setHDSeed(seed);
818
+ *
819
+ * // Get mainnet address
820
+ * const mainnetAddress = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'mainnet');
821
+ *
822
+ * // Get testnet address
823
+ * const testnetAddress = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'testnet');
824
+ * ```
825
+ */
826
+ getSubAddressBech32m(id = { account: 0, address: 0 }, network = "mainnet") {
827
+ const chain = network === "mainnet" ? BlsctChain2.Mainnet : BlsctChain2.Testnet;
828
+ setChain2(chain);
829
+ const subAddress = this.getSubAddress(id);
830
+ const serialized = subAddress.serialize();
831
+ const dpk = DoublePublicKey2.deserialize(serialized);
832
+ const address = Address2.encode(dpk, AddressEncoding2.Bech32M);
833
+ if (!address || address.length === 0) {
834
+ throw new Error(
835
+ `Address encoding returned empty. This may indicate a navio-blsct WASM issue. Serialized subAddress (${serialized.length} chars): ${serialized.substring(0, 32)}...`
836
+ );
837
+ }
838
+ return address;
839
+ }
228
840
  /**
229
841
  * Generate a new sub-address for the given account
230
842
  * @param account - The account number (0 for main, -1 for change, -2 for staking)
@@ -686,6 +1298,13 @@ var KeyManager = class {
686
1298
  bytesToHex(bytes) {
687
1299
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
688
1300
  }
1301
+ hexToBytes(hex) {
1302
+ const bytes = new Uint8Array(hex.length / 2);
1303
+ for (let i = 0; i < hex.length; i += 2) {
1304
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
1305
+ }
1306
+ return bytes;
1307
+ }
689
1308
  /**
690
1309
  * Get the private spending key
691
1310
  * Replicates GetSpendingKey from keyman.cpp
@@ -1383,7 +2002,7 @@ async function loadSQL() {
1383
2002
  throw new Error(`Failed to load SQL.js: ${error}. Please install sql.js: npm install sql.js`);
1384
2003
  }
1385
2004
  }
1386
- var WalletDB = class {
2005
+ var WalletDB = class _WalletDB {
1387
2006
  /**
1388
2007
  * Create a new WalletDB instance
1389
2008
  * @param dbPath - Path to the database file (or name for in-memory)
@@ -1551,6 +2170,15 @@ var WalletDB = class {
1551
2170
  version INTEGER NOT NULL DEFAULT 1
1552
2171
  )
1553
2172
  `);
2173
+ this.db.run(`
2174
+ CREATE TABLE IF NOT EXISTS encryption_metadata (
2175
+ id INTEGER PRIMARY KEY,
2176
+ is_encrypted INTEGER NOT NULL DEFAULT 0,
2177
+ salt TEXT,
2178
+ verification_hash TEXT,
2179
+ encryption_version INTEGER NOT NULL DEFAULT 1
2180
+ )
2181
+ `);
1554
2182
  this.db.run(`
1555
2183
  CREATE TABLE IF NOT EXISTS wallet_outputs (
1556
2184
  output_hash TEXT PRIMARY KEY,
@@ -1724,6 +2352,28 @@ var WalletDB = class {
1724
2352
  this.keyManager = keyManager;
1725
2353
  return keyManager;
1726
2354
  }
2355
+ /**
2356
+ * Restore wallet from mnemonic phrase
2357
+ * @param mnemonic - The BIP39 mnemonic phrase (12-24 words)
2358
+ * @param creationHeight - Optional block height to start scanning from (for faster restore)
2359
+ * @returns The restored KeyManager instance
2360
+ */
2361
+ async restoreWalletFromMnemonic(mnemonic, creationHeight) {
2362
+ await this.initDatabase();
2363
+ const keyManager = new KeyManager();
2364
+ keyManager.setHDSeedFromMnemonic(mnemonic);
2365
+ keyManager.newSubAddressPool(0);
2366
+ keyManager.newSubAddressPool(-1);
2367
+ keyManager.newSubAddressPool(-2);
2368
+ await this.saveWallet(keyManager);
2369
+ await this.saveWalletMetadata({
2370
+ creationHeight: creationHeight ?? 0,
2371
+ creationTime: Date.now(),
2372
+ restoredFromSeed: true
2373
+ });
2374
+ this.keyManager = keyManager;
2375
+ return keyManager;
2376
+ }
1727
2377
  /**
1728
2378
  * Save wallet metadata to database
1729
2379
  */
@@ -2052,6 +2702,255 @@ var WalletDB = class {
2052
2702
  stmt.free();
2053
2703
  return outputs;
2054
2704
  }
2705
+ // ============================================================================
2706
+ // Encryption Methods
2707
+ // ============================================================================
2708
+ /**
2709
+ * Check if the database has encryption enabled
2710
+ * @returns True if encryption is enabled
2711
+ */
2712
+ isEncrypted() {
2713
+ if (!this.isOpen) {
2714
+ return false;
2715
+ }
2716
+ const stmt = this.db.prepare("SELECT is_encrypted FROM encryption_metadata WHERE id = 0");
2717
+ if (stmt.step()) {
2718
+ const result = stmt.getAsObject().is_encrypted === 1;
2719
+ stmt.free();
2720
+ return result;
2721
+ }
2722
+ stmt.free();
2723
+ return false;
2724
+ }
2725
+ /**
2726
+ * Save encryption metadata to the database
2727
+ * @param salt - Hex-encoded salt
2728
+ * @param verificationHash - Hex-encoded password verification hash
2729
+ */
2730
+ saveEncryptionMetadata(salt, verificationHash) {
2731
+ if (!this.isOpen) {
2732
+ throw new Error("Database not initialized");
2733
+ }
2734
+ this.db.run(`
2735
+ INSERT OR REPLACE INTO encryption_metadata (id, is_encrypted, salt, verification_hash, encryption_version)
2736
+ VALUES (0, 1, ?, ?, 1)
2737
+ `, [salt, verificationHash]);
2738
+ }
2739
+ /**
2740
+ * Load encryption metadata from the database
2741
+ * @returns Encryption metadata or null if not encrypted
2742
+ */
2743
+ getEncryptionMetadata() {
2744
+ if (!this.isOpen) {
2745
+ throw new Error("Database not initialized");
2746
+ }
2747
+ const stmt = this.db.prepare(`
2748
+ SELECT salt, verification_hash FROM encryption_metadata WHERE id = 0 AND is_encrypted = 1
2749
+ `);
2750
+ if (stmt.step()) {
2751
+ const row = stmt.getAsObject();
2752
+ stmt.free();
2753
+ if (row.salt && row.verification_hash) {
2754
+ return {
2755
+ salt: row.salt,
2756
+ verificationHash: row.verification_hash
2757
+ };
2758
+ }
2759
+ }
2760
+ stmt.free();
2761
+ return null;
2762
+ }
2763
+ /**
2764
+ * Save an encrypted key to the database
2765
+ * @param keyId - Key identifier (hex)
2766
+ * @param publicKey - Public key (hex)
2767
+ * @param encryptedSecret - Encrypted secret (JSON string)
2768
+ */
2769
+ saveEncryptedKey(keyId, publicKey, encryptedSecret) {
2770
+ if (!this.isOpen) {
2771
+ throw new Error("Database not initialized");
2772
+ }
2773
+ this.db.run(`
2774
+ INSERT OR REPLACE INTO crypted_keys (key_id, public_key, encrypted_secret)
2775
+ VALUES (?, ?, ?)
2776
+ `, [keyId, publicKey, encryptedSecret]);
2777
+ }
2778
+ /**
2779
+ * Load an encrypted key from the database
2780
+ * @param keyId - Key identifier (hex)
2781
+ * @returns Encrypted key data or null if not found
2782
+ */
2783
+ getEncryptedKey(keyId) {
2784
+ if (!this.isOpen) {
2785
+ throw new Error("Database not initialized");
2786
+ }
2787
+ const stmt = this.db.prepare(`
2788
+ SELECT public_key, encrypted_secret FROM crypted_keys WHERE key_id = ?
2789
+ `);
2790
+ stmt.bind([keyId]);
2791
+ if (stmt.step()) {
2792
+ const row = stmt.getAsObject();
2793
+ stmt.free();
2794
+ return {
2795
+ publicKey: row.public_key,
2796
+ encryptedSecret: row.encrypted_secret
2797
+ };
2798
+ }
2799
+ stmt.free();
2800
+ return null;
2801
+ }
2802
+ /**
2803
+ * Get all encrypted keys from the database
2804
+ * @returns Array of encrypted key data
2805
+ */
2806
+ getAllEncryptedKeys() {
2807
+ if (!this.isOpen) {
2808
+ throw new Error("Database not initialized");
2809
+ }
2810
+ const stmt = this.db.prepare("SELECT key_id, public_key, encrypted_secret FROM crypted_keys");
2811
+ const keys = [];
2812
+ while (stmt.step()) {
2813
+ const row = stmt.getAsObject();
2814
+ keys.push({
2815
+ keyId: row.key_id,
2816
+ publicKey: row.public_key,
2817
+ encryptedSecret: row.encrypted_secret
2818
+ });
2819
+ }
2820
+ stmt.free();
2821
+ return keys;
2822
+ }
2823
+ /**
2824
+ * Save an encrypted output key to the database
2825
+ * @param outId - Output identifier (hex)
2826
+ * @param publicKey - Public key (hex)
2827
+ * @param encryptedSecret - Encrypted secret (JSON string)
2828
+ */
2829
+ saveEncryptedOutKey(outId, publicKey, encryptedSecret) {
2830
+ if (!this.isOpen) {
2831
+ throw new Error("Database not initialized");
2832
+ }
2833
+ this.db.run(`
2834
+ INSERT OR REPLACE INTO crypted_out_keys (out_id, public_key, encrypted_secret)
2835
+ VALUES (?, ?, ?)
2836
+ `, [outId, publicKey, encryptedSecret]);
2837
+ }
2838
+ /**
2839
+ * Load an encrypted output key from the database
2840
+ * @param outId - Output identifier (hex)
2841
+ * @returns Encrypted output key data or null if not found
2842
+ */
2843
+ getEncryptedOutKey(outId) {
2844
+ if (!this.isOpen) {
2845
+ throw new Error("Database not initialized");
2846
+ }
2847
+ const stmt = this.db.prepare(`
2848
+ SELECT public_key, encrypted_secret FROM crypted_out_keys WHERE out_id = ?
2849
+ `);
2850
+ stmt.bind([outId]);
2851
+ if (stmt.step()) {
2852
+ const row = stmt.getAsObject();
2853
+ stmt.free();
2854
+ return {
2855
+ publicKey: row.public_key,
2856
+ encryptedSecret: row.encrypted_secret
2857
+ };
2858
+ }
2859
+ stmt.free();
2860
+ return null;
2861
+ }
2862
+ /**
2863
+ * Get all encrypted output keys from the database
2864
+ * @returns Array of encrypted output key data
2865
+ */
2866
+ getAllEncryptedOutKeys() {
2867
+ if (!this.isOpen) {
2868
+ throw new Error("Database not initialized");
2869
+ }
2870
+ const stmt = this.db.prepare("SELECT out_id, public_key, encrypted_secret FROM crypted_out_keys");
2871
+ const keys = [];
2872
+ while (stmt.step()) {
2873
+ const row = stmt.getAsObject();
2874
+ keys.push({
2875
+ outId: row.out_id,
2876
+ publicKey: row.public_key,
2877
+ encryptedSecret: row.encrypted_secret
2878
+ });
2879
+ }
2880
+ stmt.free();
2881
+ return keys;
2882
+ }
2883
+ /**
2884
+ * Delete plaintext keys (after encryption)
2885
+ * This removes the unencrypted keys from the database
2886
+ */
2887
+ deletePlaintextKeys() {
2888
+ if (!this.isOpen) {
2889
+ throw new Error("Database not initialized");
2890
+ }
2891
+ this.db.run("DELETE FROM keys");
2892
+ this.db.run("DELETE FROM out_keys");
2893
+ }
2894
+ /**
2895
+ * Export the database as an encrypted binary blob
2896
+ * Uses the encryption module to encrypt the entire SQLite database
2897
+ *
2898
+ * @param password - Password to encrypt the export
2899
+ * @returns Encrypted database bytes
2900
+ */
2901
+ async exportEncrypted(password) {
2902
+ if (!this.isOpen) {
2903
+ throw new Error("Database not initialized");
2904
+ }
2905
+ const { encryptDatabase: encryptDatabase2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
2906
+ const dbBuffer = this.db.export();
2907
+ const encrypted = await encryptDatabase2(new Uint8Array(dbBuffer), password);
2908
+ return encrypted;
2909
+ }
2910
+ /**
2911
+ * Load a database from an encrypted binary blob
2912
+ *
2913
+ * @param encryptedData - Encrypted database bytes
2914
+ * @param password - Password to decrypt
2915
+ * @param dbPath - Path for the new database instance
2916
+ * @returns New WalletDB instance with decrypted data
2917
+ */
2918
+ static async loadEncrypted(encryptedData, password, dbPath = ":memory:") {
2919
+ const { decryptDatabase: decryptDatabase2, isEncryptedDatabase: isEncryptedDatabase2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
2920
+ if (!isEncryptedDatabase2(encryptedData)) {
2921
+ throw new Error("Data does not appear to be an encrypted database");
2922
+ }
2923
+ const decryptedBuffer = await decryptDatabase2(encryptedData, password);
2924
+ const walletDb = new _WalletDB(dbPath);
2925
+ const SQL2 = await loadSQL();
2926
+ walletDb.db = new SQL2.Database(decryptedBuffer);
2927
+ walletDb.isOpen = true;
2928
+ return walletDb;
2929
+ }
2930
+ /**
2931
+ * Get the raw database bytes (unencrypted)
2932
+ * @returns Database bytes
2933
+ */
2934
+ export() {
2935
+ if (!this.isOpen) {
2936
+ throw new Error("Database not initialized");
2937
+ }
2938
+ return new Uint8Array(this.db.export());
2939
+ }
2940
+ /**
2941
+ * Load a database from raw bytes
2942
+ *
2943
+ * @param data - Raw database bytes
2944
+ * @param dbPath - Path for the new database instance
2945
+ * @returns New WalletDB instance
2946
+ */
2947
+ static async loadFromBytes(data, dbPath = ":memory:") {
2948
+ const walletDb = new _WalletDB(dbPath);
2949
+ const SQL2 = await loadSQL();
2950
+ walletDb.db = new SQL2.Database(data);
2951
+ walletDb.isOpen = true;
2952
+ return walletDb;
2953
+ }
2055
2954
  };
2056
2955
  var WebSocketClass;
2057
2956
  var isBrowserWebSocket = false;
@@ -2443,7 +3342,7 @@ var ElectrumClient = class {
2443
3342
  }
2444
3343
  };
2445
3344
  var TransactionKeysSync = class {
2446
- // Keep last 1k block hashes by default
3345
+ // Keep last 10k block hashes by default
2447
3346
  /**
2448
3347
  * Create a new TransactionKeysSync instance
2449
3348
  * @param walletDB - The wallet database
@@ -2452,7 +3351,7 @@ var TransactionKeysSync = class {
2452
3351
  constructor(walletDB, provider) {
2453
3352
  this.keyManager = null;
2454
3353
  this.syncState = null;
2455
- this.blockHashRetention = 1e3;
3354
+ this.blockHashRetention = 1e4;
2456
3355
  this.walletDB = walletDB;
2457
3356
  if ("type" in provider && (provider.type === "electrum" || provider.type === "p2p" || provider.type === "custom")) {
2458
3357
  this.syncProvider = provider;
@@ -2560,7 +3459,7 @@ var TransactionKeysSync = class {
2560
3459
  verifyHashes = true,
2561
3460
  saveInterval = 100,
2562
3461
  keepTxKeys = false,
2563
- blockHashRetention = 1e3
3462
+ blockHashRetention = 1e4
2564
3463
  } = options;
2565
3464
  this.blockHashRetention = blockHashRetention;
2566
3465
  const lastSynced = this.syncState?.lastSyncedHeight ?? -1;
@@ -4680,6 +5579,13 @@ var _NavioClient = class _NavioClient {
4680
5579
  );
4681
5580
  this.syncManager.setKeyManager(this.keyManager);
4682
5581
  await this.syncProvider.connect();
5582
+ } else if (this.config.restoreFromMnemonic) {
5583
+ this.keyManager = await this.walletDB.restoreWalletFromMnemonic(
5584
+ this.config.restoreFromMnemonic,
5585
+ this.config.restoreFromHeight
5586
+ );
5587
+ this.syncManager.setKeyManager(this.keyManager);
5588
+ await this.syncProvider.connect();
4683
5589
  } else {
4684
5590
  try {
4685
5591
  this.keyManager = await this.walletDB.loadWallet();
@@ -5018,6 +5924,9 @@ var _NavioClient = class _NavioClient {
5018
5924
  _NavioClient.CREATION_HEIGHT_MARGIN = 100;
5019
5925
  var NavioClient = _NavioClient;
5020
5926
 
5927
+ // src/index.ts
5928
+ init_crypto();
5929
+
5021
5930
  Object.defineProperty(exports, "BlsctChain", {
5022
5931
  enumerable: true,
5023
5932
  get: function () { return blsctModule.BlsctChain; }
@@ -5046,5 +5955,19 @@ exports.PROTOCOL_VERSION = PROTOCOL_VERSION;
5046
5955
  exports.ServiceFlags = ServiceFlags;
5047
5956
  exports.TransactionKeysSync = TransactionKeysSync;
5048
5957
  exports.WalletDB = WalletDB;
5958
+ exports.createPasswordVerification = createPasswordVerification;
5959
+ exports.decrypt = decrypt;
5960
+ exports.decryptDatabase = decryptDatabase;
5961
+ exports.decryptWithKey = decryptWithKey;
5962
+ exports.deriveKey = deriveKey;
5963
+ exports.deriveKeyBytes = deriveKeyBytes;
5964
+ exports.deserializeEncryptedData = deserializeEncryptedData;
5965
+ exports.encrypt = encrypt;
5966
+ exports.encryptDatabase = encryptDatabase;
5967
+ exports.encryptWithKey = encryptWithKey;
5968
+ exports.isEncryptedDatabase = isEncryptedDatabase;
5969
+ exports.randomBytes = randomBytes;
5970
+ exports.serializeEncryptedData = serializeEncryptedData;
5971
+ exports.verifyPassword = verifyPassword;
5049
5972
  //# sourceMappingURL=index.js.map
5050
5973
  //# sourceMappingURL=index.js.map