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.mjs CHANGED
@@ -1,22 +1,289 @@
1
+ import { argon2id } from 'hash-wasm';
1
2
  import { sha256 } from '@noble/hashes/sha256';
2
3
  import { ripemd160 } from '@noble/hashes/ripemd160';
4
+ import * as bip39 from '@scure/bip39';
5
+ import { wordlist } from '@scure/bip39/wordlists/english.js';
3
6
  import * as blsctModule from 'navio-blsct';
4
7
  import { BlsctChain, setChain } from 'navio-blsct';
5
8
  export { BlsctChain, getChain, setChain } from 'navio-blsct';
6
9
  import * as net from 'net';
7
10
 
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
13
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
14
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
15
  }) : x)(function(x) {
11
16
  if (typeof require !== "undefined") return require.apply(this, arguments);
12
17
  throw Error('Dynamic require of "' + x + '" is not supported');
13
18
  });
19
+ var __esm = (fn, res) => function __init() {
20
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
21
+ };
22
+ var __export = (target, all) => {
23
+ for (var name in all)
24
+ __defProp(target, name, { get: all[name], enumerable: true });
25
+ };
26
+ function toBufferSource(data) {
27
+ return new Uint8Array(data).buffer;
28
+ }
29
+ function getCrypto() {
30
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined" && typeof globalThis.crypto.getRandomValues === "function") {
31
+ return globalThis.crypto;
32
+ }
33
+ const nodeCrypto = __require("crypto");
34
+ if (nodeCrypto.webcrypto) {
35
+ return nodeCrypto.webcrypto;
36
+ }
37
+ throw new Error("Web Crypto API not available. Requires Node.js 15+ or a modern browser.");
38
+ }
39
+ function randomBytes(length) {
40
+ const crypto = getCrypto();
41
+ const bytes = new Uint8Array(length);
42
+ crypto.getRandomValues(bytes);
43
+ return bytes;
44
+ }
45
+ async function deriveKey(password, salt) {
46
+ const hashHex = await argon2id({
47
+ password,
48
+ salt,
49
+ memorySize: ARGON2_PARAMS.memorySize,
50
+ iterations: ARGON2_PARAMS.iterations,
51
+ parallelism: ARGON2_PARAMS.parallelism,
52
+ hashLength: ARGON2_PARAMS.hashLength,
53
+ outputType: "hex"
54
+ });
55
+ const hashBytes = hexToBytes(hashHex);
56
+ const crypto = getCrypto();
57
+ const key = await crypto.subtle.importKey(
58
+ "raw",
59
+ toBufferSource(hashBytes),
60
+ { name: "AES-GCM", length: 256 },
61
+ false,
62
+ // not extractable
63
+ ["encrypt", "decrypt"]
64
+ );
65
+ return key;
66
+ }
67
+ async function deriveKeyBytes(password, salt) {
68
+ const hashHex = await 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
+ return hexToBytes(hashHex);
78
+ }
79
+ async function encrypt(data, password) {
80
+ const crypto = getCrypto();
81
+ const salt = randomBytes(SALT_LENGTH);
82
+ const iv = randomBytes(IV_LENGTH);
83
+ const key = await deriveKey(password, salt);
84
+ const ciphertext = await crypto.subtle.encrypt(
85
+ { name: "AES-GCM", iv: toBufferSource(iv) },
86
+ key,
87
+ toBufferSource(data)
88
+ );
89
+ return {
90
+ ciphertext: new Uint8Array(ciphertext),
91
+ iv,
92
+ salt
93
+ };
94
+ }
95
+ async function encryptWithKey(data, key, salt) {
96
+ const crypto = getCrypto();
97
+ const iv = randomBytes(IV_LENGTH);
98
+ const ciphertext = await crypto.subtle.encrypt(
99
+ { name: "AES-GCM", iv: toBufferSource(iv) },
100
+ key,
101
+ toBufferSource(data)
102
+ );
103
+ return {
104
+ ciphertext: new Uint8Array(ciphertext),
105
+ iv,
106
+ salt
107
+ };
108
+ }
109
+ async function decrypt(encrypted, password) {
110
+ const crypto = getCrypto();
111
+ const key = await deriveKey(password, encrypted.salt);
112
+ try {
113
+ const plaintext = await crypto.subtle.decrypt(
114
+ { name: "AES-GCM", iv: toBufferSource(encrypted.iv) },
115
+ key,
116
+ toBufferSource(encrypted.ciphertext)
117
+ );
118
+ return new Uint8Array(plaintext);
119
+ } catch {
120
+ throw new Error("Decryption failed: wrong password or corrupted data");
121
+ }
122
+ }
123
+ async function decryptWithKey(encrypted, key) {
124
+ const crypto = getCrypto();
125
+ try {
126
+ const plaintext = await crypto.subtle.decrypt(
127
+ { name: "AES-GCM", iv: toBufferSource(encrypted.iv) },
128
+ key,
129
+ toBufferSource(encrypted.ciphertext)
130
+ );
131
+ return new Uint8Array(plaintext);
132
+ } catch {
133
+ throw new Error("Decryption failed: wrong key or corrupted data");
134
+ }
135
+ }
136
+ function serializeEncryptedData(encrypted) {
137
+ return {
138
+ ciphertext: bytesToBase64(encrypted.ciphertext),
139
+ iv: bytesToBase64(encrypted.iv),
140
+ salt: bytesToBase64(encrypted.salt),
141
+ version: ENCRYPTION_VERSION
142
+ };
143
+ }
144
+ function deserializeEncryptedData(serialized) {
145
+ if (serialized.version > ENCRYPTION_VERSION) {
146
+ throw new Error(`Unsupported encryption version: ${serialized.version}`);
147
+ }
148
+ return {
149
+ ciphertext: base64ToBytes(serialized.ciphertext),
150
+ iv: base64ToBytes(serialized.iv),
151
+ salt: base64ToBytes(serialized.salt)
152
+ };
153
+ }
154
+ async function encryptDatabase(dbBuffer, password) {
155
+ const encrypted = await encrypt(dbBuffer, password);
156
+ const combined = new Uint8Array(1 + SALT_LENGTH + IV_LENGTH + encrypted.ciphertext.length);
157
+ combined[0] = ENCRYPTION_VERSION;
158
+ combined.set(encrypted.salt, 1);
159
+ combined.set(encrypted.iv, 1 + SALT_LENGTH);
160
+ combined.set(encrypted.ciphertext, 1 + SALT_LENGTH + IV_LENGTH);
161
+ return combined;
162
+ }
163
+ async function decryptDatabase(encryptedBuffer, password) {
164
+ if (encryptedBuffer.length < 1 + SALT_LENGTH + IV_LENGTH) {
165
+ throw new Error("Invalid encrypted database: too short");
166
+ }
167
+ const version = encryptedBuffer[0];
168
+ if (version > ENCRYPTION_VERSION) {
169
+ throw new Error(`Unsupported encryption version: ${version}`);
170
+ }
171
+ const salt = encryptedBuffer.slice(1, 1 + SALT_LENGTH);
172
+ const iv = encryptedBuffer.slice(1 + SALT_LENGTH, 1 + SALT_LENGTH + IV_LENGTH);
173
+ const ciphertext = encryptedBuffer.slice(1 + SALT_LENGTH + IV_LENGTH);
174
+ return decrypt({ ciphertext, iv, salt }, password);
175
+ }
176
+ function isEncryptedDatabase(buffer) {
177
+ if (buffer.length < 1 + SALT_LENGTH + IV_LENGTH) {
178
+ return false;
179
+ }
180
+ const version = buffer[0];
181
+ return version >= 1 && version <= ENCRYPTION_VERSION;
182
+ }
183
+ async function createPasswordVerification(password, salt) {
184
+ const keyBytes = await deriveKeyBytes(password, salt);
185
+ const crypto = getCrypto();
186
+ const keyBuffer = new Uint8Array(keyBytes).buffer;
187
+ const verificationHash = await crypto.subtle.digest("SHA-256", keyBuffer);
188
+ return new Uint8Array(verificationHash);
189
+ }
190
+ async function verifyPassword(password, salt, storedHash) {
191
+ const computedHash = await createPasswordVerification(password, salt);
192
+ if (computedHash.length !== storedHash.length) {
193
+ return false;
194
+ }
195
+ let result = 0;
196
+ for (let i = 0; i < computedHash.length; i++) {
197
+ result |= computedHash[i] ^ storedHash[i];
198
+ }
199
+ return result === 0;
200
+ }
201
+ function hexToBytes(hex) {
202
+ const bytes = new Uint8Array(hex.length / 2);
203
+ for (let i = 0; i < bytes.length; i++) {
204
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
205
+ }
206
+ return bytes;
207
+ }
208
+ function bytesToBase64(bytes) {
209
+ if (typeof Buffer !== "undefined") {
210
+ return Buffer.from(bytes).toString("base64");
211
+ } else {
212
+ let binary = "";
213
+ for (let i = 0; i < bytes.length; i++) {
214
+ binary += String.fromCharCode(bytes[i]);
215
+ }
216
+ return btoa(binary);
217
+ }
218
+ }
219
+ function base64ToBytes(base64) {
220
+ if (typeof Buffer !== "undefined") {
221
+ return new Uint8Array(Buffer.from(base64, "base64"));
222
+ } else {
223
+ const binary = atob(base64);
224
+ const bytes = new Uint8Array(binary.length);
225
+ for (let i = 0; i < binary.length; i++) {
226
+ bytes[i] = binary.charCodeAt(i);
227
+ }
228
+ return bytes;
229
+ }
230
+ }
231
+ var ARGON2_PARAMS, ENCRYPTION_VERSION, IV_LENGTH, SALT_LENGTH;
232
+ var init_encryption = __esm({
233
+ "src/crypto/encryption.ts"() {
234
+ ARGON2_PARAMS = {
235
+ memorySize: 65536,
236
+ // 64 MB (hash-wasm uses memorySize instead of memory)
237
+ iterations: 3,
238
+ parallelism: 4,
239
+ hashLength: 32
240
+ // 256 bits for AES-256 (hash-wasm uses hashLength instead of hashLen)
241
+ };
242
+ ENCRYPTION_VERSION = 1;
243
+ IV_LENGTH = 12;
244
+ SALT_LENGTH = 16;
245
+ }
246
+ });
247
+
248
+ // src/crypto/index.ts
249
+ var crypto_exports = {};
250
+ __export(crypto_exports, {
251
+ ENCRYPTION_VERSION: () => ENCRYPTION_VERSION,
252
+ IV_LENGTH: () => IV_LENGTH,
253
+ SALT_LENGTH: () => SALT_LENGTH,
254
+ createPasswordVerification: () => createPasswordVerification,
255
+ decrypt: () => decrypt,
256
+ decryptDatabase: () => decryptDatabase,
257
+ decryptWithKey: () => decryptWithKey,
258
+ deriveKey: () => deriveKey,
259
+ deriveKeyBytes: () => deriveKeyBytes,
260
+ deserializeEncryptedData: () => deserializeEncryptedData,
261
+ encrypt: () => encrypt,
262
+ encryptDatabase: () => encryptDatabase,
263
+ encryptWithKey: () => encryptWithKey,
264
+ isEncryptedDatabase: () => isEncryptedDatabase,
265
+ randomBytes: () => randomBytes,
266
+ serializeEncryptedData: () => serializeEncryptedData,
267
+ verifyPassword: () => verifyPassword
268
+ });
269
+ var init_crypto = __esm({
270
+ "src/crypto/index.ts"() {
271
+ init_encryption();
272
+ }
273
+ });
274
+
275
+ // src/key-manager.ts
276
+ init_crypto();
14
277
  var Scalar2 = blsctModule.Scalar;
15
278
  var ChildKey2 = blsctModule.ChildKey;
16
279
  var PublicKey2 = blsctModule.PublicKey;
17
280
  var SubAddr2 = blsctModule.SubAddr;
18
281
  var SubAddrId2 = blsctModule.SubAddrId;
19
282
  var DoublePublicKey2 = blsctModule.DoublePublicKey;
283
+ var Address2 = blsctModule.Address;
284
+ var AddressEncoding2 = blsctModule.AddressEncoding;
285
+ var setChain2 = blsctModule.setChain;
286
+ var BlsctChain2 = blsctModule.BlsctChain;
20
287
  var calcPrivSpendingKey2 = blsctModule.calcPrivSpendingKey;
21
288
  var recoverAmount2 = blsctModule.recoverAmount;
22
289
  var ViewTag2 = blsctModule.ViewTag;
@@ -26,7 +293,7 @@ var getAmountRecoveryResultSize2 = blsctModule.getAmountRecoveryResultSize;
26
293
  var getAmountRecoveryResultIsSucc2 = blsctModule.getAmountRecoveryResultIsSucc;
27
294
  var getAmountRecoveryResultAmount2 = blsctModule.getAmountRecoveryResultAmount;
28
295
  var deleteAmountsRetVal2 = blsctModule.deleteAmountsRetVal;
29
- var KeyManager = class {
296
+ var KeyManager = class _KeyManager {
30
297
  constructor() {
31
298
  this.hdChain = null;
32
299
  this.viewKey = null;
@@ -63,6 +330,11 @@ var KeyManager = class {
63
330
  // Flags
64
331
  this.fViewKeyDefined = false;
65
332
  this.fSpendKeyDefined = false;
333
+ // Encryption state
334
+ this.encrypted = false;
335
+ this.encryptionSalt = null;
336
+ this.passwordVerificationHash = null;
337
+ this.encryptionKey = null;
66
338
  }
67
339
  /**
68
340
  * Check if HD is enabled (has a seed)
@@ -78,6 +350,212 @@ var KeyManager = class {
78
350
  canGenerateKeys() {
79
351
  return this.isHDEnabled();
80
352
  }
353
+ // ============================================================================
354
+ // Encryption Methods
355
+ // ============================================================================
356
+ /**
357
+ * Check if the wallet is encrypted
358
+ * @returns True if the wallet has been encrypted with a password
359
+ */
360
+ isEncrypted() {
361
+ return this.encrypted;
362
+ }
363
+ /**
364
+ * Check if the wallet is unlocked (decryption key is cached)
365
+ * @returns True if the wallet is unlocked and can access private keys
366
+ */
367
+ isUnlocked() {
368
+ return !this.encrypted || this.encryptionKey !== null;
369
+ }
370
+ /**
371
+ * Set a password to encrypt the wallet
372
+ * This encrypts all private keys and stores them in encrypted form
373
+ *
374
+ * @param password - The password to encrypt the wallet with
375
+ * @throws Error if wallet is already encrypted
376
+ */
377
+ async setPassword(password) {
378
+ if (this.encrypted) {
379
+ throw new Error("Wallet is already encrypted. Use changePassword() to change the password.");
380
+ }
381
+ this.encryptionSalt = randomBytes(SALT_LENGTH);
382
+ this.encryptionKey = await deriveKey(password, this.encryptionSalt);
383
+ this.passwordVerificationHash = await createPasswordVerification(password, this.encryptionSalt);
384
+ await this.encryptAllKeys();
385
+ this.encrypted = true;
386
+ }
387
+ /**
388
+ * Unlock an encrypted wallet with the password
389
+ * This derives the encryption key and caches it for decrypting private keys
390
+ *
391
+ * @param password - The wallet password
392
+ * @returns True if the password is correct and wallet is unlocked
393
+ */
394
+ async unlock(password) {
395
+ if (!this.encrypted) {
396
+ return true;
397
+ }
398
+ if (!this.encryptionSalt || !this.passwordVerificationHash) {
399
+ throw new Error("Wallet encryption state is corrupted");
400
+ }
401
+ const isValid = await verifyPassword(password, this.encryptionSalt, this.passwordVerificationHash);
402
+ if (!isValid) {
403
+ return false;
404
+ }
405
+ this.encryptionKey = await deriveKey(password, this.encryptionSalt);
406
+ await this.decryptEssentialKeys();
407
+ return true;
408
+ }
409
+ /**
410
+ * Lock the wallet, clearing the cached encryption key and unencrypted keys
411
+ * After locking, private keys cannot be accessed without unlocking again
412
+ */
413
+ lock() {
414
+ if (!this.encrypted) {
415
+ return;
416
+ }
417
+ this.encryptionKey = null;
418
+ this.keys.clear();
419
+ this.outKeys.clear();
420
+ }
421
+ /**
422
+ * Change the wallet password
423
+ *
424
+ * @param oldPassword - The current password
425
+ * @param newPassword - The new password
426
+ * @returns True if password was changed successfully
427
+ * @throws Error if wallet is not encrypted or old password is incorrect
428
+ */
429
+ async changePassword(oldPassword, newPassword) {
430
+ if (!this.encrypted) {
431
+ throw new Error("Wallet is not encrypted. Use setPassword() first.");
432
+ }
433
+ const unlocked = await this.unlock(oldPassword);
434
+ if (!unlocked) {
435
+ return false;
436
+ }
437
+ const newSalt = randomBytes(SALT_LENGTH);
438
+ const newKey = await deriveKey(newPassword, newSalt);
439
+ await this.reEncryptAllKeys(newKey, newSalt);
440
+ this.encryptionSalt = newSalt;
441
+ this.encryptionKey = newKey;
442
+ this.passwordVerificationHash = await createPasswordVerification(newPassword, newSalt);
443
+ return true;
444
+ }
445
+ /**
446
+ * Get encryption parameters for storage
447
+ * @returns Encryption salt and verification hash, or null if not encrypted
448
+ */
449
+ getEncryptionParams() {
450
+ if (!this.encrypted || !this.encryptionSalt || !this.passwordVerificationHash) {
451
+ return null;
452
+ }
453
+ return {
454
+ salt: this.bytesToHex(this.encryptionSalt),
455
+ verificationHash: this.bytesToHex(this.passwordVerificationHash)
456
+ };
457
+ }
458
+ /**
459
+ * Set encryption parameters from storage (for loading encrypted wallet)
460
+ * @param salt - Hex-encoded salt
461
+ * @param verificationHash - Hex-encoded verification hash
462
+ */
463
+ setEncryptionParams(salt, verificationHash) {
464
+ this.encryptionSalt = this.hexToBytes(salt);
465
+ this.passwordVerificationHash = this.hexToBytes(verificationHash);
466
+ this.encrypted = true;
467
+ }
468
+ /**
469
+ * Get key storage statistics (for testing/debugging)
470
+ * @returns Object with counts of plain and encrypted keys
471
+ */
472
+ getKeyStats() {
473
+ return {
474
+ plainKeys: this.keys.size,
475
+ plainOutKeys: this.outKeys.size,
476
+ encryptedKeys: this.cryptedKeys.size,
477
+ encryptedOutKeys: this.cryptedOutKeys.size
478
+ };
479
+ }
480
+ /**
481
+ * Encrypt all private keys in the wallet
482
+ * Internal method called when setting password
483
+ */
484
+ async encryptAllKeys() {
485
+ if (!this.encryptionKey || !this.encryptionSalt) {
486
+ throw new Error("Encryption key not available");
487
+ }
488
+ for (const [keyIdHex, secretKey] of this.keys) {
489
+ const keyBytes = this.hexToBytes(secretKey.serialize());
490
+ const encrypted = await encryptWithKey(keyBytes, this.encryptionKey, this.encryptionSalt);
491
+ const publicKey = PublicKey2.fromScalar(secretKey);
492
+ this.cryptedKeys.set(keyIdHex, {
493
+ publicKey,
494
+ encryptedSecret: this.serializeEncryptedToBytes(encrypted)
495
+ });
496
+ }
497
+ for (const [outIdHex, secretKey] of this.outKeys) {
498
+ const keyBytes = this.hexToBytes(secretKey.serialize());
499
+ const encrypted = await encryptWithKey(keyBytes, this.encryptionKey, this.encryptionSalt);
500
+ const publicKey = PublicKey2.fromScalar(secretKey);
501
+ this.cryptedOutKeys.set(outIdHex, {
502
+ publicKey,
503
+ encryptedSecret: this.serializeEncryptedToBytes(encrypted)
504
+ });
505
+ }
506
+ this.keys.clear();
507
+ this.outKeys.clear();
508
+ }
509
+ /**
510
+ * Decrypt essential keys needed for wallet operations
511
+ * Called after successful unlock
512
+ */
513
+ async decryptEssentialKeys() {
514
+ }
515
+ /**
516
+ * Re-encrypt all keys with a new key (for password change)
517
+ */
518
+ async reEncryptAllKeys(newKey, newSalt) {
519
+ if (!this.encryptionKey) {
520
+ throw new Error("Wallet must be unlocked to change password");
521
+ }
522
+ for (const [keyIdHex, cryptedData] of this.cryptedKeys) {
523
+ const encrypted = this.deserializeEncryptedFromBytes(cryptedData.encryptedSecret);
524
+ const decrypted = await decryptWithKey(encrypted, this.encryptionKey);
525
+ const reEncrypted = await encryptWithKey(decrypted, newKey, newSalt);
526
+ this.cryptedKeys.set(keyIdHex, {
527
+ publicKey: cryptedData.publicKey,
528
+ encryptedSecret: this.serializeEncryptedToBytes(reEncrypted)
529
+ });
530
+ }
531
+ for (const [outIdHex, cryptedData] of this.cryptedOutKeys) {
532
+ const encrypted = this.deserializeEncryptedFromBytes(cryptedData.encryptedSecret);
533
+ const decrypted = await decryptWithKey(encrypted, this.encryptionKey);
534
+ const reEncrypted = await encryptWithKey(decrypted, newKey, newSalt);
535
+ this.cryptedOutKeys.set(outIdHex, {
536
+ publicKey: cryptedData.publicKey,
537
+ encryptedSecret: this.serializeEncryptedToBytes(reEncrypted)
538
+ });
539
+ }
540
+ }
541
+ /**
542
+ * Serialize EncryptedData to bytes for storage
543
+ */
544
+ serializeEncryptedToBytes(encrypted) {
545
+ const serialized = serializeEncryptedData(encrypted);
546
+ return new TextEncoder().encode(JSON.stringify(serialized));
547
+ }
548
+ /**
549
+ * Deserialize EncryptedData from bytes
550
+ */
551
+ deserializeEncryptedFromBytes(bytes) {
552
+ const json = new TextDecoder().decode(bytes);
553
+ const serialized = JSON.parse(json);
554
+ return deserializeEncryptedData(serialized);
555
+ }
556
+ // ============================================================================
557
+ // End Encryption Methods
558
+ // ============================================================================
81
559
  /**
82
560
  * Generate a new random seed
83
561
  * @returns A new random Scalar (seed)
@@ -171,6 +649,108 @@ var KeyManager = class {
171
649
  }
172
650
  return this.masterSeed;
173
651
  }
652
+ // ============================================================
653
+ // Mnemonic Support (BIP39)
654
+ // ============================================================
655
+ /**
656
+ * Generate a new random mnemonic phrase (24 words)
657
+ * @param strength - Entropy strength in bits (128=12 words, 160=15 words, 192=18 words, 224=21 words, 256=24 words)
658
+ * @returns A BIP39 mnemonic phrase
659
+ */
660
+ static generateMnemonic(strength = 256) {
661
+ return bip39.generateMnemonic(wordlist, strength);
662
+ }
663
+ /**
664
+ * Validate a mnemonic phrase
665
+ * @param mnemonic - The mnemonic phrase to validate
666
+ * @returns True if the mnemonic is valid
667
+ */
668
+ static validateMnemonic(mnemonic) {
669
+ return bip39.validateMnemonic(mnemonic, wordlist);
670
+ }
671
+ /**
672
+ * Convert a mnemonic phrase to seed bytes
673
+ * Uses BIP39 standard derivation with optional passphrase
674
+ * @param mnemonic - The mnemonic phrase
675
+ * @param passphrase - Optional passphrase for additional security
676
+ * @returns 64-byte seed derived from mnemonic
677
+ */
678
+ static mnemonicToSeedBytes(mnemonic, passphrase = "") {
679
+ if (!_KeyManager.validateMnemonic(mnemonic)) {
680
+ throw new Error("Invalid mnemonic phrase");
681
+ }
682
+ return bip39.mnemonicToSeedSync(mnemonic, passphrase);
683
+ }
684
+ /**
685
+ * Convert a seed (Scalar) to a mnemonic phrase
686
+ * Note: This converts the 32-byte scalar to mnemonic using it as entropy
687
+ * @param seed - The seed Scalar
688
+ * @returns A 24-word mnemonic phrase
689
+ */
690
+ static seedToMnemonic(seed) {
691
+ const seedHex = seed.serialize().padStart(64, "0");
692
+ const seedBytes = Buffer.from(seedHex, "hex");
693
+ if (seedBytes.length !== 32) {
694
+ throw new Error(`Invalid seed length: ${seedBytes.length}, expected 32 bytes`);
695
+ }
696
+ return bip39.entropyToMnemonic(seedBytes, wordlist);
697
+ }
698
+ /**
699
+ * Convert a mnemonic phrase to a Scalar seed
700
+ * For direct entropy-based conversion (mnemonic as entropy, not BIP39 derivation)
701
+ * @param mnemonic - The mnemonic phrase
702
+ * @returns A Scalar seed
703
+ */
704
+ static mnemonicToScalar(mnemonic) {
705
+ if (!_KeyManager.validateMnemonic(mnemonic)) {
706
+ throw new Error("Invalid mnemonic phrase");
707
+ }
708
+ const entropy = bip39.mnemonicToEntropy(mnemonic, wordlist);
709
+ const entropyHex = Buffer.from(entropy).toString("hex");
710
+ return Scalar2.deserialize(entropyHex);
711
+ }
712
+ /**
713
+ * Generate a new mnemonic and set it as the HD seed
714
+ * @param strength - Entropy strength in bits (default: 256 for 24 words)
715
+ * @returns The mnemonic phrase that can be used to recover this wallet
716
+ * @note The returned mnemonic is derived from the stored seed, which ensures
717
+ * it will produce the same wallet when used for restoration. Due to BLS
718
+ * Scalar normalization, this may differ from the initially generated entropy.
719
+ */
720
+ generateNewMnemonic(strength = 256) {
721
+ const mnemonic = _KeyManager.generateMnemonic(strength);
722
+ this.setHDSeedFromMnemonic(mnemonic);
723
+ return this.getMnemonic();
724
+ }
725
+ /**
726
+ * Set the HD seed from a mnemonic phrase
727
+ * Uses direct entropy conversion (mnemonic words encode the seed directly)
728
+ * @param mnemonic - The mnemonic phrase
729
+ */
730
+ setHDSeedFromMnemonic(mnemonic) {
731
+ const seed = _KeyManager.mnemonicToScalar(mnemonic);
732
+ this.setHDSeed(seed);
733
+ }
734
+ /**
735
+ * Get the mnemonic phrase for the current seed
736
+ * @returns The mnemonic phrase for the current master seed
737
+ */
738
+ getMnemonic() {
739
+ if (!this.masterSeed) {
740
+ throw new Error("No master seed available");
741
+ }
742
+ return _KeyManager.seedToMnemonic(this.masterSeed);
743
+ }
744
+ /**
745
+ * Get the master seed as a hex string
746
+ * @returns The master seed as a 64-character hex string (32 bytes)
747
+ */
748
+ getMasterSeedHex() {
749
+ if (!this.masterSeed) {
750
+ throw new Error("No master seed available");
751
+ }
752
+ return this.masterSeed.serialize().padStart(64, "0");
753
+ }
174
754
  /**
175
755
  * Get the private view key
176
756
  * @returns The view key
@@ -204,6 +784,37 @@ var KeyManager = class {
204
784
  const subAddrId = SubAddrId2.generate(id.account, id.address);
205
785
  return SubAddr2.generate(this.viewKey, this.spendPublicKey, subAddrId);
206
786
  }
787
+ /**
788
+ * Get a bech32m encoded address string for the given sub-address identifier
789
+ * @param id - The sub-address identifier (defaults to account 0, address 0)
790
+ * @param network - The network type ('mainnet' or 'testnet', defaults to 'mainnet')
791
+ * @returns The bech32m encoded address string
792
+ * @example
793
+ * ```typescript
794
+ * const keyManager = new KeyManager();
795
+ * keyManager.setHDSeed(seed);
796
+ *
797
+ * // Get mainnet address
798
+ * const mainnetAddress = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'mainnet');
799
+ *
800
+ * // Get testnet address
801
+ * const testnetAddress = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'testnet');
802
+ * ```
803
+ */
804
+ getSubAddressBech32m(id = { account: 0, address: 0 }, network = "mainnet") {
805
+ const chain = network === "mainnet" ? BlsctChain2.Mainnet : BlsctChain2.Testnet;
806
+ setChain2(chain);
807
+ const subAddress = this.getSubAddress(id);
808
+ const serialized = subAddress.serialize();
809
+ const dpk = DoublePublicKey2.deserialize(serialized);
810
+ const address = Address2.encode(dpk, AddressEncoding2.Bech32M);
811
+ if (!address || address.length === 0) {
812
+ throw new Error(
813
+ `Address encoding returned empty. This may indicate a navio-blsct WASM issue. Serialized subAddress (${serialized.length} chars): ${serialized.substring(0, 32)}...`
814
+ );
815
+ }
816
+ return address;
817
+ }
207
818
  /**
208
819
  * Generate a new sub-address for the given account
209
820
  * @param account - The account number (0 for main, -1 for change, -2 for staking)
@@ -665,6 +1276,13 @@ var KeyManager = class {
665
1276
  bytesToHex(bytes) {
666
1277
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
667
1278
  }
1279
+ hexToBytes(hex) {
1280
+ const bytes = new Uint8Array(hex.length / 2);
1281
+ for (let i = 0; i < hex.length; i += 2) {
1282
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
1283
+ }
1284
+ return bytes;
1285
+ }
668
1286
  /**
669
1287
  * Get the private spending key
670
1288
  * Replicates GetSpendingKey from keyman.cpp
@@ -1362,7 +1980,7 @@ async function loadSQL() {
1362
1980
  throw new Error(`Failed to load SQL.js: ${error}. Please install sql.js: npm install sql.js`);
1363
1981
  }
1364
1982
  }
1365
- var WalletDB = class {
1983
+ var WalletDB = class _WalletDB {
1366
1984
  /**
1367
1985
  * Create a new WalletDB instance
1368
1986
  * @param dbPath - Path to the database file (or name for in-memory)
@@ -1530,6 +2148,15 @@ var WalletDB = class {
1530
2148
  version INTEGER NOT NULL DEFAULT 1
1531
2149
  )
1532
2150
  `);
2151
+ this.db.run(`
2152
+ CREATE TABLE IF NOT EXISTS encryption_metadata (
2153
+ id INTEGER PRIMARY KEY,
2154
+ is_encrypted INTEGER NOT NULL DEFAULT 0,
2155
+ salt TEXT,
2156
+ verification_hash TEXT,
2157
+ encryption_version INTEGER NOT NULL DEFAULT 1
2158
+ )
2159
+ `);
1533
2160
  this.db.run(`
1534
2161
  CREATE TABLE IF NOT EXISTS wallet_outputs (
1535
2162
  output_hash TEXT PRIMARY KEY,
@@ -1703,6 +2330,28 @@ var WalletDB = class {
1703
2330
  this.keyManager = keyManager;
1704
2331
  return keyManager;
1705
2332
  }
2333
+ /**
2334
+ * Restore wallet from mnemonic phrase
2335
+ * @param mnemonic - The BIP39 mnemonic phrase (12-24 words)
2336
+ * @param creationHeight - Optional block height to start scanning from (for faster restore)
2337
+ * @returns The restored KeyManager instance
2338
+ */
2339
+ async restoreWalletFromMnemonic(mnemonic, creationHeight) {
2340
+ await this.initDatabase();
2341
+ const keyManager = new KeyManager();
2342
+ keyManager.setHDSeedFromMnemonic(mnemonic);
2343
+ keyManager.newSubAddressPool(0);
2344
+ keyManager.newSubAddressPool(-1);
2345
+ keyManager.newSubAddressPool(-2);
2346
+ await this.saveWallet(keyManager);
2347
+ await this.saveWalletMetadata({
2348
+ creationHeight: creationHeight ?? 0,
2349
+ creationTime: Date.now(),
2350
+ restoredFromSeed: true
2351
+ });
2352
+ this.keyManager = keyManager;
2353
+ return keyManager;
2354
+ }
1706
2355
  /**
1707
2356
  * Save wallet metadata to database
1708
2357
  */
@@ -2031,6 +2680,255 @@ var WalletDB = class {
2031
2680
  stmt.free();
2032
2681
  return outputs;
2033
2682
  }
2683
+ // ============================================================================
2684
+ // Encryption Methods
2685
+ // ============================================================================
2686
+ /**
2687
+ * Check if the database has encryption enabled
2688
+ * @returns True if encryption is enabled
2689
+ */
2690
+ isEncrypted() {
2691
+ if (!this.isOpen) {
2692
+ return false;
2693
+ }
2694
+ const stmt = this.db.prepare("SELECT is_encrypted FROM encryption_metadata WHERE id = 0");
2695
+ if (stmt.step()) {
2696
+ const result = stmt.getAsObject().is_encrypted === 1;
2697
+ stmt.free();
2698
+ return result;
2699
+ }
2700
+ stmt.free();
2701
+ return false;
2702
+ }
2703
+ /**
2704
+ * Save encryption metadata to the database
2705
+ * @param salt - Hex-encoded salt
2706
+ * @param verificationHash - Hex-encoded password verification hash
2707
+ */
2708
+ saveEncryptionMetadata(salt, verificationHash) {
2709
+ if (!this.isOpen) {
2710
+ throw new Error("Database not initialized");
2711
+ }
2712
+ this.db.run(`
2713
+ INSERT OR REPLACE INTO encryption_metadata (id, is_encrypted, salt, verification_hash, encryption_version)
2714
+ VALUES (0, 1, ?, ?, 1)
2715
+ `, [salt, verificationHash]);
2716
+ }
2717
+ /**
2718
+ * Load encryption metadata from the database
2719
+ * @returns Encryption metadata or null if not encrypted
2720
+ */
2721
+ getEncryptionMetadata() {
2722
+ if (!this.isOpen) {
2723
+ throw new Error("Database not initialized");
2724
+ }
2725
+ const stmt = this.db.prepare(`
2726
+ SELECT salt, verification_hash FROM encryption_metadata WHERE id = 0 AND is_encrypted = 1
2727
+ `);
2728
+ if (stmt.step()) {
2729
+ const row = stmt.getAsObject();
2730
+ stmt.free();
2731
+ if (row.salt && row.verification_hash) {
2732
+ return {
2733
+ salt: row.salt,
2734
+ verificationHash: row.verification_hash
2735
+ };
2736
+ }
2737
+ }
2738
+ stmt.free();
2739
+ return null;
2740
+ }
2741
+ /**
2742
+ * Save an encrypted key to the database
2743
+ * @param keyId - Key identifier (hex)
2744
+ * @param publicKey - Public key (hex)
2745
+ * @param encryptedSecret - Encrypted secret (JSON string)
2746
+ */
2747
+ saveEncryptedKey(keyId, publicKey, encryptedSecret) {
2748
+ if (!this.isOpen) {
2749
+ throw new Error("Database not initialized");
2750
+ }
2751
+ this.db.run(`
2752
+ INSERT OR REPLACE INTO crypted_keys (key_id, public_key, encrypted_secret)
2753
+ VALUES (?, ?, ?)
2754
+ `, [keyId, publicKey, encryptedSecret]);
2755
+ }
2756
+ /**
2757
+ * Load an encrypted key from the database
2758
+ * @param keyId - Key identifier (hex)
2759
+ * @returns Encrypted key data or null if not found
2760
+ */
2761
+ getEncryptedKey(keyId) {
2762
+ if (!this.isOpen) {
2763
+ throw new Error("Database not initialized");
2764
+ }
2765
+ const stmt = this.db.prepare(`
2766
+ SELECT public_key, encrypted_secret FROM crypted_keys WHERE key_id = ?
2767
+ `);
2768
+ stmt.bind([keyId]);
2769
+ if (stmt.step()) {
2770
+ const row = stmt.getAsObject();
2771
+ stmt.free();
2772
+ return {
2773
+ publicKey: row.public_key,
2774
+ encryptedSecret: row.encrypted_secret
2775
+ };
2776
+ }
2777
+ stmt.free();
2778
+ return null;
2779
+ }
2780
+ /**
2781
+ * Get all encrypted keys from the database
2782
+ * @returns Array of encrypted key data
2783
+ */
2784
+ getAllEncryptedKeys() {
2785
+ if (!this.isOpen) {
2786
+ throw new Error("Database not initialized");
2787
+ }
2788
+ const stmt = this.db.prepare("SELECT key_id, public_key, encrypted_secret FROM crypted_keys");
2789
+ const keys = [];
2790
+ while (stmt.step()) {
2791
+ const row = stmt.getAsObject();
2792
+ keys.push({
2793
+ keyId: row.key_id,
2794
+ publicKey: row.public_key,
2795
+ encryptedSecret: row.encrypted_secret
2796
+ });
2797
+ }
2798
+ stmt.free();
2799
+ return keys;
2800
+ }
2801
+ /**
2802
+ * Save an encrypted output key to the database
2803
+ * @param outId - Output identifier (hex)
2804
+ * @param publicKey - Public key (hex)
2805
+ * @param encryptedSecret - Encrypted secret (JSON string)
2806
+ */
2807
+ saveEncryptedOutKey(outId, publicKey, encryptedSecret) {
2808
+ if (!this.isOpen) {
2809
+ throw new Error("Database not initialized");
2810
+ }
2811
+ this.db.run(`
2812
+ INSERT OR REPLACE INTO crypted_out_keys (out_id, public_key, encrypted_secret)
2813
+ VALUES (?, ?, ?)
2814
+ `, [outId, publicKey, encryptedSecret]);
2815
+ }
2816
+ /**
2817
+ * Load an encrypted output key from the database
2818
+ * @param outId - Output identifier (hex)
2819
+ * @returns Encrypted output key data or null if not found
2820
+ */
2821
+ getEncryptedOutKey(outId) {
2822
+ if (!this.isOpen) {
2823
+ throw new Error("Database not initialized");
2824
+ }
2825
+ const stmt = this.db.prepare(`
2826
+ SELECT public_key, encrypted_secret FROM crypted_out_keys WHERE out_id = ?
2827
+ `);
2828
+ stmt.bind([outId]);
2829
+ if (stmt.step()) {
2830
+ const row = stmt.getAsObject();
2831
+ stmt.free();
2832
+ return {
2833
+ publicKey: row.public_key,
2834
+ encryptedSecret: row.encrypted_secret
2835
+ };
2836
+ }
2837
+ stmt.free();
2838
+ return null;
2839
+ }
2840
+ /**
2841
+ * Get all encrypted output keys from the database
2842
+ * @returns Array of encrypted output key data
2843
+ */
2844
+ getAllEncryptedOutKeys() {
2845
+ if (!this.isOpen) {
2846
+ throw new Error("Database not initialized");
2847
+ }
2848
+ const stmt = this.db.prepare("SELECT out_id, public_key, encrypted_secret FROM crypted_out_keys");
2849
+ const keys = [];
2850
+ while (stmt.step()) {
2851
+ const row = stmt.getAsObject();
2852
+ keys.push({
2853
+ outId: row.out_id,
2854
+ publicKey: row.public_key,
2855
+ encryptedSecret: row.encrypted_secret
2856
+ });
2857
+ }
2858
+ stmt.free();
2859
+ return keys;
2860
+ }
2861
+ /**
2862
+ * Delete plaintext keys (after encryption)
2863
+ * This removes the unencrypted keys from the database
2864
+ */
2865
+ deletePlaintextKeys() {
2866
+ if (!this.isOpen) {
2867
+ throw new Error("Database not initialized");
2868
+ }
2869
+ this.db.run("DELETE FROM keys");
2870
+ this.db.run("DELETE FROM out_keys");
2871
+ }
2872
+ /**
2873
+ * Export the database as an encrypted binary blob
2874
+ * Uses the encryption module to encrypt the entire SQLite database
2875
+ *
2876
+ * @param password - Password to encrypt the export
2877
+ * @returns Encrypted database bytes
2878
+ */
2879
+ async exportEncrypted(password) {
2880
+ if (!this.isOpen) {
2881
+ throw new Error("Database not initialized");
2882
+ }
2883
+ const { encryptDatabase: encryptDatabase2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
2884
+ const dbBuffer = this.db.export();
2885
+ const encrypted = await encryptDatabase2(new Uint8Array(dbBuffer), password);
2886
+ return encrypted;
2887
+ }
2888
+ /**
2889
+ * Load a database from an encrypted binary blob
2890
+ *
2891
+ * @param encryptedData - Encrypted database bytes
2892
+ * @param password - Password to decrypt
2893
+ * @param dbPath - Path for the new database instance
2894
+ * @returns New WalletDB instance with decrypted data
2895
+ */
2896
+ static async loadEncrypted(encryptedData, password, dbPath = ":memory:") {
2897
+ const { decryptDatabase: decryptDatabase2, isEncryptedDatabase: isEncryptedDatabase2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
2898
+ if (!isEncryptedDatabase2(encryptedData)) {
2899
+ throw new Error("Data does not appear to be an encrypted database");
2900
+ }
2901
+ const decryptedBuffer = await decryptDatabase2(encryptedData, password);
2902
+ const walletDb = new _WalletDB(dbPath);
2903
+ const SQL2 = await loadSQL();
2904
+ walletDb.db = new SQL2.Database(decryptedBuffer);
2905
+ walletDb.isOpen = true;
2906
+ return walletDb;
2907
+ }
2908
+ /**
2909
+ * Get the raw database bytes (unencrypted)
2910
+ * @returns Database bytes
2911
+ */
2912
+ export() {
2913
+ if (!this.isOpen) {
2914
+ throw new Error("Database not initialized");
2915
+ }
2916
+ return new Uint8Array(this.db.export());
2917
+ }
2918
+ /**
2919
+ * Load a database from raw bytes
2920
+ *
2921
+ * @param data - Raw database bytes
2922
+ * @param dbPath - Path for the new database instance
2923
+ * @returns New WalletDB instance
2924
+ */
2925
+ static async loadFromBytes(data, dbPath = ":memory:") {
2926
+ const walletDb = new _WalletDB(dbPath);
2927
+ const SQL2 = await loadSQL();
2928
+ walletDb.db = new SQL2.Database(data);
2929
+ walletDb.isOpen = true;
2930
+ return walletDb;
2931
+ }
2034
2932
  };
2035
2933
  var WebSocketClass;
2036
2934
  var isBrowserWebSocket = false;
@@ -2422,7 +3320,7 @@ var ElectrumClient = class {
2422
3320
  }
2423
3321
  };
2424
3322
  var TransactionKeysSync = class {
2425
- // Keep last 1k block hashes by default
3323
+ // Keep last 10k block hashes by default
2426
3324
  /**
2427
3325
  * Create a new TransactionKeysSync instance
2428
3326
  * @param walletDB - The wallet database
@@ -2431,7 +3329,7 @@ var TransactionKeysSync = class {
2431
3329
  constructor(walletDB, provider) {
2432
3330
  this.keyManager = null;
2433
3331
  this.syncState = null;
2434
- this.blockHashRetention = 1e3;
3332
+ this.blockHashRetention = 1e4;
2435
3333
  this.walletDB = walletDB;
2436
3334
  if ("type" in provider && (provider.type === "electrum" || provider.type === "p2p" || provider.type === "custom")) {
2437
3335
  this.syncProvider = provider;
@@ -2539,7 +3437,7 @@ var TransactionKeysSync = class {
2539
3437
  verifyHashes = true,
2540
3438
  saveInterval = 100,
2541
3439
  keepTxKeys = false,
2542
- blockHashRetention = 1e3
3440
+ blockHashRetention = 1e4
2543
3441
  } = options;
2544
3442
  this.blockHashRetention = blockHashRetention;
2545
3443
  const lastSynced = this.syncState?.lastSyncedHeight ?? -1;
@@ -4659,6 +5557,13 @@ var _NavioClient = class _NavioClient {
4659
5557
  );
4660
5558
  this.syncManager.setKeyManager(this.keyManager);
4661
5559
  await this.syncProvider.connect();
5560
+ } else if (this.config.restoreFromMnemonic) {
5561
+ this.keyManager = await this.walletDB.restoreWalletFromMnemonic(
5562
+ this.config.restoreFromMnemonic,
5563
+ this.config.restoreFromHeight
5564
+ );
5565
+ this.syncManager.setKeyManager(this.keyManager);
5566
+ await this.syncProvider.connect();
4662
5567
  } else {
4663
5568
  try {
4664
5569
  this.keyManager = await this.walletDB.loadWallet();
@@ -4997,6 +5902,9 @@ var _NavioClient = class _NavioClient {
4997
5902
  _NavioClient.CREATION_HEIGHT_MARGIN = 100;
4998
5903
  var NavioClient = _NavioClient;
4999
5904
 
5000
- export { BaseSyncProvider, DefaultPorts, ElectrumClient, ElectrumError, ElectrumSyncProvider, InvType, KeyManager, MessageType, NavioClient, NetworkMagic, P2PClient, P2PSyncProvider, PROTOCOL_VERSION, ServiceFlags, TransactionKeysSync, WalletDB };
5905
+ // src/index.ts
5906
+ init_crypto();
5907
+
5908
+ export { BaseSyncProvider, DefaultPorts, ENCRYPTION_VERSION, ElectrumClient, ElectrumError, ElectrumSyncProvider, IV_LENGTH, InvType, KeyManager, MessageType, NavioClient, NetworkMagic, P2PClient, P2PSyncProvider, PROTOCOL_VERSION, SALT_LENGTH, ServiceFlags, TransactionKeysSync, WalletDB, createPasswordVerification, decrypt, decryptDatabase, decryptWithKey, deriveKey, deriveKeyBytes, deserializeEncryptedData, encrypt, encryptDatabase, encryptWithKey, isEncryptedDatabase, randomBytes, serializeEncryptedData, verifyPassword };
5001
5909
  //# sourceMappingURL=index.mjs.map
5002
5910
  //# sourceMappingURL=index.mjs.map