navio-sdk 0.1.0 → 0.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.
package/dist/index.mjs CHANGED
@@ -1,22 +1,282 @@
1
+ import argon2 from 'argon2-browser';
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 result = await argon2.hash({
47
+ pass: password,
48
+ salt,
49
+ type: ARGON2_PARAMS.type,
50
+ mem: ARGON2_PARAMS.memory,
51
+ time: ARGON2_PARAMS.iterations,
52
+ parallelism: ARGON2_PARAMS.parallelism,
53
+ hashLen: ARGON2_PARAMS.hashLen
54
+ });
55
+ const crypto = getCrypto();
56
+ const key = await crypto.subtle.importKey(
57
+ "raw",
58
+ toBufferSource(result.hash),
59
+ { name: "AES-GCM", length: 256 },
60
+ false,
61
+ // not extractable
62
+ ["encrypt", "decrypt"]
63
+ );
64
+ return key;
65
+ }
66
+ async function deriveKeyBytes(password, salt) {
67
+ const result = await argon2.hash({
68
+ pass: password,
69
+ salt,
70
+ type: ARGON2_PARAMS.type,
71
+ mem: ARGON2_PARAMS.memory,
72
+ time: ARGON2_PARAMS.iterations,
73
+ parallelism: ARGON2_PARAMS.parallelism,
74
+ hashLen: ARGON2_PARAMS.hashLen
75
+ });
76
+ return result.hash;
77
+ }
78
+ async function encrypt(data, password) {
79
+ const crypto = getCrypto();
80
+ const salt = randomBytes(SALT_LENGTH);
81
+ const iv = randomBytes(IV_LENGTH);
82
+ const key = await deriveKey(password, salt);
83
+ const ciphertext = await crypto.subtle.encrypt(
84
+ { name: "AES-GCM", iv: toBufferSource(iv) },
85
+ key,
86
+ toBufferSource(data)
87
+ );
88
+ return {
89
+ ciphertext: new Uint8Array(ciphertext),
90
+ iv,
91
+ salt
92
+ };
93
+ }
94
+ async function encryptWithKey(data, key, salt) {
95
+ const crypto = getCrypto();
96
+ const iv = randomBytes(IV_LENGTH);
97
+ const ciphertext = await crypto.subtle.encrypt(
98
+ { name: "AES-GCM", iv: toBufferSource(iv) },
99
+ key,
100
+ toBufferSource(data)
101
+ );
102
+ return {
103
+ ciphertext: new Uint8Array(ciphertext),
104
+ iv,
105
+ salt
106
+ };
107
+ }
108
+ async function decrypt(encrypted, password) {
109
+ const crypto = getCrypto();
110
+ const key = await deriveKey(password, encrypted.salt);
111
+ try {
112
+ const plaintext = await crypto.subtle.decrypt(
113
+ { name: "AES-GCM", iv: toBufferSource(encrypted.iv) },
114
+ key,
115
+ toBufferSource(encrypted.ciphertext)
116
+ );
117
+ return new Uint8Array(plaintext);
118
+ } catch {
119
+ throw new Error("Decryption failed: wrong password or corrupted data");
120
+ }
121
+ }
122
+ async function decryptWithKey(encrypted, key) {
123
+ const crypto = getCrypto();
124
+ try {
125
+ const plaintext = await crypto.subtle.decrypt(
126
+ { name: "AES-GCM", iv: toBufferSource(encrypted.iv) },
127
+ key,
128
+ toBufferSource(encrypted.ciphertext)
129
+ );
130
+ return new Uint8Array(plaintext);
131
+ } catch {
132
+ throw new Error("Decryption failed: wrong key or corrupted data");
133
+ }
134
+ }
135
+ function serializeEncryptedData(encrypted) {
136
+ return {
137
+ ciphertext: bytesToBase64(encrypted.ciphertext),
138
+ iv: bytesToBase64(encrypted.iv),
139
+ salt: bytesToBase64(encrypted.salt),
140
+ version: ENCRYPTION_VERSION
141
+ };
142
+ }
143
+ function deserializeEncryptedData(serialized) {
144
+ if (serialized.version > ENCRYPTION_VERSION) {
145
+ throw new Error(`Unsupported encryption version: ${serialized.version}`);
146
+ }
147
+ return {
148
+ ciphertext: base64ToBytes(serialized.ciphertext),
149
+ iv: base64ToBytes(serialized.iv),
150
+ salt: base64ToBytes(serialized.salt)
151
+ };
152
+ }
153
+ async function encryptDatabase(dbBuffer, password) {
154
+ const encrypted = await encrypt(dbBuffer, password);
155
+ const combined = new Uint8Array(1 + SALT_LENGTH + IV_LENGTH + encrypted.ciphertext.length);
156
+ combined[0] = ENCRYPTION_VERSION;
157
+ combined.set(encrypted.salt, 1);
158
+ combined.set(encrypted.iv, 1 + SALT_LENGTH);
159
+ combined.set(encrypted.ciphertext, 1 + SALT_LENGTH + IV_LENGTH);
160
+ return combined;
161
+ }
162
+ async function decryptDatabase(encryptedBuffer, password) {
163
+ if (encryptedBuffer.length < 1 + SALT_LENGTH + IV_LENGTH) {
164
+ throw new Error("Invalid encrypted database: too short");
165
+ }
166
+ const version = encryptedBuffer[0];
167
+ if (version > ENCRYPTION_VERSION) {
168
+ throw new Error(`Unsupported encryption version: ${version}`);
169
+ }
170
+ const salt = encryptedBuffer.slice(1, 1 + SALT_LENGTH);
171
+ const iv = encryptedBuffer.slice(1 + SALT_LENGTH, 1 + SALT_LENGTH + IV_LENGTH);
172
+ const ciphertext = encryptedBuffer.slice(1 + SALT_LENGTH + IV_LENGTH);
173
+ return decrypt({ ciphertext, iv, salt }, password);
174
+ }
175
+ function isEncryptedDatabase(buffer) {
176
+ if (buffer.length < 1 + SALT_LENGTH + IV_LENGTH) {
177
+ return false;
178
+ }
179
+ const version = buffer[0];
180
+ return version >= 1 && version <= ENCRYPTION_VERSION;
181
+ }
182
+ async function createPasswordVerification(password, salt) {
183
+ const keyBytes = await deriveKeyBytes(password, salt);
184
+ const crypto = getCrypto();
185
+ const keyBuffer = new Uint8Array(keyBytes).buffer;
186
+ const verificationHash = await crypto.subtle.digest("SHA-256", keyBuffer);
187
+ return new Uint8Array(verificationHash);
188
+ }
189
+ async function verifyPassword(password, salt, storedHash) {
190
+ const computedHash = await createPasswordVerification(password, salt);
191
+ if (computedHash.length !== storedHash.length) {
192
+ return false;
193
+ }
194
+ let result = 0;
195
+ for (let i = 0; i < computedHash.length; i++) {
196
+ result |= computedHash[i] ^ storedHash[i];
197
+ }
198
+ return result === 0;
199
+ }
200
+ function bytesToBase64(bytes) {
201
+ if (typeof Buffer !== "undefined") {
202
+ return Buffer.from(bytes).toString("base64");
203
+ } else {
204
+ let binary = "";
205
+ for (let i = 0; i < bytes.length; i++) {
206
+ binary += String.fromCharCode(bytes[i]);
207
+ }
208
+ return btoa(binary);
209
+ }
210
+ }
211
+ function base64ToBytes(base64) {
212
+ if (typeof Buffer !== "undefined") {
213
+ return new Uint8Array(Buffer.from(base64, "base64"));
214
+ } else {
215
+ const binary = atob(base64);
216
+ const bytes = new Uint8Array(binary.length);
217
+ for (let i = 0; i < binary.length; i++) {
218
+ bytes[i] = binary.charCodeAt(i);
219
+ }
220
+ return bytes;
221
+ }
222
+ }
223
+ var ARGON2_PARAMS, ENCRYPTION_VERSION, IV_LENGTH, SALT_LENGTH;
224
+ var init_encryption = __esm({
225
+ "src/crypto/encryption.ts"() {
226
+ ARGON2_PARAMS = {
227
+ type: argon2.ArgonType.Argon2id,
228
+ memory: 65536,
229
+ // 64 MB
230
+ iterations: 3,
231
+ parallelism: 4,
232
+ hashLen: 32
233
+ // 256 bits for AES-256
234
+ };
235
+ ENCRYPTION_VERSION = 1;
236
+ IV_LENGTH = 12;
237
+ SALT_LENGTH = 16;
238
+ }
239
+ });
240
+
241
+ // src/crypto/index.ts
242
+ var crypto_exports = {};
243
+ __export(crypto_exports, {
244
+ ENCRYPTION_VERSION: () => ENCRYPTION_VERSION,
245
+ IV_LENGTH: () => IV_LENGTH,
246
+ SALT_LENGTH: () => SALT_LENGTH,
247
+ createPasswordVerification: () => createPasswordVerification,
248
+ decrypt: () => decrypt,
249
+ decryptDatabase: () => decryptDatabase,
250
+ decryptWithKey: () => decryptWithKey,
251
+ deriveKey: () => deriveKey,
252
+ deriveKeyBytes: () => deriveKeyBytes,
253
+ deserializeEncryptedData: () => deserializeEncryptedData,
254
+ encrypt: () => encrypt,
255
+ encryptDatabase: () => encryptDatabase,
256
+ encryptWithKey: () => encryptWithKey,
257
+ isEncryptedDatabase: () => isEncryptedDatabase,
258
+ randomBytes: () => randomBytes,
259
+ serializeEncryptedData: () => serializeEncryptedData,
260
+ verifyPassword: () => verifyPassword
261
+ });
262
+ var init_crypto = __esm({
263
+ "src/crypto/index.ts"() {
264
+ init_encryption();
265
+ }
266
+ });
267
+
268
+ // src/key-manager.ts
269
+ init_crypto();
14
270
  var Scalar2 = blsctModule.Scalar;
15
271
  var ChildKey2 = blsctModule.ChildKey;
16
272
  var PublicKey2 = blsctModule.PublicKey;
17
273
  var SubAddr2 = blsctModule.SubAddr;
18
274
  var SubAddrId2 = blsctModule.SubAddrId;
19
275
  var DoublePublicKey2 = blsctModule.DoublePublicKey;
276
+ var Address2 = blsctModule.Address;
277
+ var AddressEncoding2 = blsctModule.AddressEncoding;
278
+ var setChain2 = blsctModule.setChain;
279
+ var BlsctChain2 = blsctModule.BlsctChain;
20
280
  var calcPrivSpendingKey2 = blsctModule.calcPrivSpendingKey;
21
281
  var recoverAmount2 = blsctModule.recoverAmount;
22
282
  var ViewTag2 = blsctModule.ViewTag;
@@ -26,7 +286,7 @@ var getAmountRecoveryResultSize2 = blsctModule.getAmountRecoveryResultSize;
26
286
  var getAmountRecoveryResultIsSucc2 = blsctModule.getAmountRecoveryResultIsSucc;
27
287
  var getAmountRecoveryResultAmount2 = blsctModule.getAmountRecoveryResultAmount;
28
288
  var deleteAmountsRetVal2 = blsctModule.deleteAmountsRetVal;
29
- var KeyManager = class {
289
+ var KeyManager = class _KeyManager {
30
290
  constructor() {
31
291
  this.hdChain = null;
32
292
  this.viewKey = null;
@@ -63,6 +323,11 @@ var KeyManager = class {
63
323
  // Flags
64
324
  this.fViewKeyDefined = false;
65
325
  this.fSpendKeyDefined = false;
326
+ // Encryption state
327
+ this.encrypted = false;
328
+ this.encryptionSalt = null;
329
+ this.passwordVerificationHash = null;
330
+ this.encryptionKey = null;
66
331
  }
67
332
  /**
68
333
  * Check if HD is enabled (has a seed)
@@ -78,6 +343,196 @@ var KeyManager = class {
78
343
  canGenerateKeys() {
79
344
  return this.isHDEnabled();
80
345
  }
346
+ // ============================================================================
347
+ // Encryption Methods
348
+ // ============================================================================
349
+ /**
350
+ * Check if the wallet is encrypted
351
+ * @returns True if the wallet has been encrypted with a password
352
+ */
353
+ isEncrypted() {
354
+ return this.encrypted;
355
+ }
356
+ /**
357
+ * Check if the wallet is unlocked (decryption key is cached)
358
+ * @returns True if the wallet is unlocked and can access private keys
359
+ */
360
+ isUnlocked() {
361
+ return !this.encrypted || this.encryptionKey !== null;
362
+ }
363
+ /**
364
+ * Set a password to encrypt the wallet
365
+ * This encrypts all private keys and stores them in encrypted form
366
+ *
367
+ * @param password - The password to encrypt the wallet with
368
+ * @throws Error if wallet is already encrypted
369
+ */
370
+ async setPassword(password) {
371
+ if (this.encrypted) {
372
+ throw new Error("Wallet is already encrypted. Use changePassword() to change the password.");
373
+ }
374
+ this.encryptionSalt = randomBytes(SALT_LENGTH);
375
+ this.encryptionKey = await deriveKey(password, this.encryptionSalt);
376
+ this.passwordVerificationHash = await createPasswordVerification(password, this.encryptionSalt);
377
+ await this.encryptAllKeys();
378
+ this.encrypted = true;
379
+ }
380
+ /**
381
+ * Unlock an encrypted wallet with the password
382
+ * This derives the encryption key and caches it for decrypting private keys
383
+ *
384
+ * @param password - The wallet password
385
+ * @returns True if the password is correct and wallet is unlocked
386
+ */
387
+ async unlock(password) {
388
+ if (!this.encrypted) {
389
+ return true;
390
+ }
391
+ if (!this.encryptionSalt || !this.passwordVerificationHash) {
392
+ throw new Error("Wallet encryption state is corrupted");
393
+ }
394
+ const isValid = await verifyPassword(password, this.encryptionSalt, this.passwordVerificationHash);
395
+ if (!isValid) {
396
+ return false;
397
+ }
398
+ this.encryptionKey = await deriveKey(password, this.encryptionSalt);
399
+ await this.decryptEssentialKeys();
400
+ return true;
401
+ }
402
+ /**
403
+ * Lock the wallet, clearing the cached encryption key
404
+ * After locking, private keys cannot be accessed without unlocking again
405
+ */
406
+ lock() {
407
+ if (!this.encrypted) {
408
+ return;
409
+ }
410
+ this.encryptionKey = null;
411
+ }
412
+ /**
413
+ * Change the wallet password
414
+ *
415
+ * @param oldPassword - The current password
416
+ * @param newPassword - The new password
417
+ * @returns True if password was changed successfully
418
+ * @throws Error if wallet is not encrypted or old password is incorrect
419
+ */
420
+ async changePassword(oldPassword, newPassword) {
421
+ if (!this.encrypted) {
422
+ throw new Error("Wallet is not encrypted. Use setPassword() first.");
423
+ }
424
+ const unlocked = await this.unlock(oldPassword);
425
+ if (!unlocked) {
426
+ return false;
427
+ }
428
+ const newSalt = randomBytes(SALT_LENGTH);
429
+ const newKey = await deriveKey(newPassword, newSalt);
430
+ await this.reEncryptAllKeys(newKey, newSalt);
431
+ this.encryptionSalt = newSalt;
432
+ this.encryptionKey = newKey;
433
+ this.passwordVerificationHash = await createPasswordVerification(newPassword, newSalt);
434
+ return true;
435
+ }
436
+ /**
437
+ * Get encryption parameters for storage
438
+ * @returns Encryption salt and verification hash, or null if not encrypted
439
+ */
440
+ getEncryptionParams() {
441
+ if (!this.encrypted || !this.encryptionSalt || !this.passwordVerificationHash) {
442
+ return null;
443
+ }
444
+ return {
445
+ salt: this.bytesToHex(this.encryptionSalt),
446
+ verificationHash: this.bytesToHex(this.passwordVerificationHash)
447
+ };
448
+ }
449
+ /**
450
+ * Set encryption parameters from storage (for loading encrypted wallet)
451
+ * @param salt - Hex-encoded salt
452
+ * @param verificationHash - Hex-encoded verification hash
453
+ */
454
+ setEncryptionParams(salt, verificationHash) {
455
+ this.encryptionSalt = this.hexToBytes(salt);
456
+ this.passwordVerificationHash = this.hexToBytes(verificationHash);
457
+ this.encrypted = true;
458
+ }
459
+ /**
460
+ * Encrypt all private keys in the wallet
461
+ * Internal method called when setting password
462
+ */
463
+ async encryptAllKeys() {
464
+ if (!this.encryptionKey || !this.encryptionSalt) {
465
+ throw new Error("Encryption key not available");
466
+ }
467
+ for (const [keyIdHex, secretKey] of this.keys) {
468
+ const keyBytes = this.hexToBytes(secretKey.serialize());
469
+ const encrypted = await encryptWithKey(keyBytes, this.encryptionKey, this.encryptionSalt);
470
+ const publicKey = PublicKey2.fromScalar(secretKey);
471
+ this.cryptedKeys.set(keyIdHex, {
472
+ publicKey,
473
+ encryptedSecret: this.serializeEncryptedToBytes(encrypted)
474
+ });
475
+ }
476
+ for (const [outIdHex, secretKey] of this.outKeys) {
477
+ const keyBytes = this.hexToBytes(secretKey.serialize());
478
+ const encrypted = await encryptWithKey(keyBytes, this.encryptionKey, this.encryptionSalt);
479
+ const publicKey = PublicKey2.fromScalar(secretKey);
480
+ this.cryptedOutKeys.set(outIdHex, {
481
+ publicKey,
482
+ encryptedSecret: this.serializeEncryptedToBytes(encrypted)
483
+ });
484
+ }
485
+ }
486
+ /**
487
+ * Decrypt essential keys needed for wallet operations
488
+ * Called after successful unlock
489
+ */
490
+ async decryptEssentialKeys() {
491
+ }
492
+ /**
493
+ * Re-encrypt all keys with a new key (for password change)
494
+ */
495
+ async reEncryptAllKeys(newKey, newSalt) {
496
+ if (!this.encryptionKey) {
497
+ throw new Error("Wallet must be unlocked to change password");
498
+ }
499
+ for (const [keyIdHex, cryptedData] of this.cryptedKeys) {
500
+ const encrypted = this.deserializeEncryptedFromBytes(cryptedData.encryptedSecret);
501
+ const decrypted = await decryptWithKey(encrypted, this.encryptionKey);
502
+ const reEncrypted = await encryptWithKey(decrypted, newKey, newSalt);
503
+ this.cryptedKeys.set(keyIdHex, {
504
+ publicKey: cryptedData.publicKey,
505
+ encryptedSecret: this.serializeEncryptedToBytes(reEncrypted)
506
+ });
507
+ }
508
+ for (const [outIdHex, cryptedData] of this.cryptedOutKeys) {
509
+ const encrypted = this.deserializeEncryptedFromBytes(cryptedData.encryptedSecret);
510
+ const decrypted = await decryptWithKey(encrypted, this.encryptionKey);
511
+ const reEncrypted = await encryptWithKey(decrypted, newKey, newSalt);
512
+ this.cryptedOutKeys.set(outIdHex, {
513
+ publicKey: cryptedData.publicKey,
514
+ encryptedSecret: this.serializeEncryptedToBytes(reEncrypted)
515
+ });
516
+ }
517
+ }
518
+ /**
519
+ * Serialize EncryptedData to bytes for storage
520
+ */
521
+ serializeEncryptedToBytes(encrypted) {
522
+ const serialized = serializeEncryptedData(encrypted);
523
+ return new TextEncoder().encode(JSON.stringify(serialized));
524
+ }
525
+ /**
526
+ * Deserialize EncryptedData from bytes
527
+ */
528
+ deserializeEncryptedFromBytes(bytes) {
529
+ const json = new TextDecoder().decode(bytes);
530
+ const serialized = JSON.parse(json);
531
+ return deserializeEncryptedData(serialized);
532
+ }
533
+ // ============================================================================
534
+ // End Encryption Methods
535
+ // ============================================================================
81
536
  /**
82
537
  * Generate a new random seed
83
538
  * @returns A new random Scalar (seed)
@@ -171,6 +626,108 @@ var KeyManager = class {
171
626
  }
172
627
  return this.masterSeed;
173
628
  }
629
+ // ============================================================
630
+ // Mnemonic Support (BIP39)
631
+ // ============================================================
632
+ /**
633
+ * Generate a new random mnemonic phrase (24 words)
634
+ * @param strength - Entropy strength in bits (128=12 words, 160=15 words, 192=18 words, 224=21 words, 256=24 words)
635
+ * @returns A BIP39 mnemonic phrase
636
+ */
637
+ static generateMnemonic(strength = 256) {
638
+ return bip39.generateMnemonic(wordlist, strength);
639
+ }
640
+ /**
641
+ * Validate a mnemonic phrase
642
+ * @param mnemonic - The mnemonic phrase to validate
643
+ * @returns True if the mnemonic is valid
644
+ */
645
+ static validateMnemonic(mnemonic) {
646
+ return bip39.validateMnemonic(mnemonic, wordlist);
647
+ }
648
+ /**
649
+ * Convert a mnemonic phrase to seed bytes
650
+ * Uses BIP39 standard derivation with optional passphrase
651
+ * @param mnemonic - The mnemonic phrase
652
+ * @param passphrase - Optional passphrase for additional security
653
+ * @returns 64-byte seed derived from mnemonic
654
+ */
655
+ static mnemonicToSeedBytes(mnemonic, passphrase = "") {
656
+ if (!_KeyManager.validateMnemonic(mnemonic)) {
657
+ throw new Error("Invalid mnemonic phrase");
658
+ }
659
+ return bip39.mnemonicToSeedSync(mnemonic, passphrase);
660
+ }
661
+ /**
662
+ * Convert a seed (Scalar) to a mnemonic phrase
663
+ * Note: This converts the 32-byte scalar to mnemonic using it as entropy
664
+ * @param seed - The seed Scalar
665
+ * @returns A 24-word mnemonic phrase
666
+ */
667
+ static seedToMnemonic(seed) {
668
+ const seedHex = seed.serialize().padStart(64, "0");
669
+ const seedBytes = Buffer.from(seedHex, "hex");
670
+ if (seedBytes.length !== 32) {
671
+ throw new Error(`Invalid seed length: ${seedBytes.length}, expected 32 bytes`);
672
+ }
673
+ return bip39.entropyToMnemonic(seedBytes, wordlist);
674
+ }
675
+ /**
676
+ * Convert a mnemonic phrase to a Scalar seed
677
+ * For direct entropy-based conversion (mnemonic as entropy, not BIP39 derivation)
678
+ * @param mnemonic - The mnemonic phrase
679
+ * @returns A Scalar seed
680
+ */
681
+ static mnemonicToScalar(mnemonic) {
682
+ if (!_KeyManager.validateMnemonic(mnemonic)) {
683
+ throw new Error("Invalid mnemonic phrase");
684
+ }
685
+ const entropy = bip39.mnemonicToEntropy(mnemonic, wordlist);
686
+ const entropyHex = Buffer.from(entropy).toString("hex");
687
+ return Scalar2.deserialize(entropyHex);
688
+ }
689
+ /**
690
+ * Generate a new mnemonic and set it as the HD seed
691
+ * @param strength - Entropy strength in bits (default: 256 for 24 words)
692
+ * @returns The mnemonic phrase that can be used to recover this wallet
693
+ * @note The returned mnemonic is derived from the stored seed, which ensures
694
+ * it will produce the same wallet when used for restoration. Due to BLS
695
+ * Scalar normalization, this may differ from the initially generated entropy.
696
+ */
697
+ generateNewMnemonic(strength = 256) {
698
+ const mnemonic = _KeyManager.generateMnemonic(strength);
699
+ this.setHDSeedFromMnemonic(mnemonic);
700
+ return this.getMnemonic();
701
+ }
702
+ /**
703
+ * Set the HD seed from a mnemonic phrase
704
+ * Uses direct entropy conversion (mnemonic words encode the seed directly)
705
+ * @param mnemonic - The mnemonic phrase
706
+ */
707
+ setHDSeedFromMnemonic(mnemonic) {
708
+ const seed = _KeyManager.mnemonicToScalar(mnemonic);
709
+ this.setHDSeed(seed);
710
+ }
711
+ /**
712
+ * Get the mnemonic phrase for the current seed
713
+ * @returns The mnemonic phrase for the current master seed
714
+ */
715
+ getMnemonic() {
716
+ if (!this.masterSeed) {
717
+ throw new Error("No master seed available");
718
+ }
719
+ return _KeyManager.seedToMnemonic(this.masterSeed);
720
+ }
721
+ /**
722
+ * Get the master seed as a hex string
723
+ * @returns The master seed as a 64-character hex string (32 bytes)
724
+ */
725
+ getMasterSeedHex() {
726
+ if (!this.masterSeed) {
727
+ throw new Error("No master seed available");
728
+ }
729
+ return this.masterSeed.serialize().padStart(64, "0");
730
+ }
174
731
  /**
175
732
  * Get the private view key
176
733
  * @returns The view key
@@ -204,6 +761,37 @@ var KeyManager = class {
204
761
  const subAddrId = SubAddrId2.generate(id.account, id.address);
205
762
  return SubAddr2.generate(this.viewKey, this.spendPublicKey, subAddrId);
206
763
  }
764
+ /**
765
+ * Get a bech32m encoded address string for the given sub-address identifier
766
+ * @param id - The sub-address identifier (defaults to account 0, address 0)
767
+ * @param network - The network type ('mainnet' or 'testnet', defaults to 'mainnet')
768
+ * @returns The bech32m encoded address string
769
+ * @example
770
+ * ```typescript
771
+ * const keyManager = new KeyManager();
772
+ * keyManager.setHDSeed(seed);
773
+ *
774
+ * // Get mainnet address
775
+ * const mainnetAddress = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'mainnet');
776
+ *
777
+ * // Get testnet address
778
+ * const testnetAddress = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'testnet');
779
+ * ```
780
+ */
781
+ getSubAddressBech32m(id = { account: 0, address: 0 }, network = "mainnet") {
782
+ const chain = network === "mainnet" ? BlsctChain2.Mainnet : BlsctChain2.Testnet;
783
+ setChain2(chain);
784
+ const subAddress = this.getSubAddress(id);
785
+ const serialized = subAddress.serialize();
786
+ const dpk = DoublePublicKey2.deserialize(serialized);
787
+ const address = Address2.encode(dpk, AddressEncoding2.Bech32M);
788
+ if (!address || address.length === 0) {
789
+ throw new Error(
790
+ `Address encoding returned empty. This may indicate a navio-blsct WASM issue. Serialized subAddress (${serialized.length} chars): ${serialized.substring(0, 32)}...`
791
+ );
792
+ }
793
+ return address;
794
+ }
207
795
  /**
208
796
  * Generate a new sub-address for the given account
209
797
  * @param account - The account number (0 for main, -1 for change, -2 for staking)
@@ -665,6 +1253,13 @@ var KeyManager = class {
665
1253
  bytesToHex(bytes) {
666
1254
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
667
1255
  }
1256
+ hexToBytes(hex) {
1257
+ const bytes = new Uint8Array(hex.length / 2);
1258
+ for (let i = 0; i < hex.length; i += 2) {
1259
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
1260
+ }
1261
+ return bytes;
1262
+ }
668
1263
  /**
669
1264
  * Get the private spending key
670
1265
  * Replicates GetSpendingKey from keyman.cpp
@@ -1362,7 +1957,7 @@ async function loadSQL() {
1362
1957
  throw new Error(`Failed to load SQL.js: ${error}. Please install sql.js: npm install sql.js`);
1363
1958
  }
1364
1959
  }
1365
- var WalletDB = class {
1960
+ var WalletDB = class _WalletDB {
1366
1961
  /**
1367
1962
  * Create a new WalletDB instance
1368
1963
  * @param dbPath - Path to the database file (or name for in-memory)
@@ -1530,6 +2125,15 @@ var WalletDB = class {
1530
2125
  version INTEGER NOT NULL DEFAULT 1
1531
2126
  )
1532
2127
  `);
2128
+ this.db.run(`
2129
+ CREATE TABLE IF NOT EXISTS encryption_metadata (
2130
+ id INTEGER PRIMARY KEY,
2131
+ is_encrypted INTEGER NOT NULL DEFAULT 0,
2132
+ salt TEXT,
2133
+ verification_hash TEXT,
2134
+ encryption_version INTEGER NOT NULL DEFAULT 1
2135
+ )
2136
+ `);
1533
2137
  this.db.run(`
1534
2138
  CREATE TABLE IF NOT EXISTS wallet_outputs (
1535
2139
  output_hash TEXT PRIMARY KEY,
@@ -1703,6 +2307,28 @@ var WalletDB = class {
1703
2307
  this.keyManager = keyManager;
1704
2308
  return keyManager;
1705
2309
  }
2310
+ /**
2311
+ * Restore wallet from mnemonic phrase
2312
+ * @param mnemonic - The BIP39 mnemonic phrase (12-24 words)
2313
+ * @param creationHeight - Optional block height to start scanning from (for faster restore)
2314
+ * @returns The restored KeyManager instance
2315
+ */
2316
+ async restoreWalletFromMnemonic(mnemonic, creationHeight) {
2317
+ await this.initDatabase();
2318
+ const keyManager = new KeyManager();
2319
+ keyManager.setHDSeedFromMnemonic(mnemonic);
2320
+ keyManager.newSubAddressPool(0);
2321
+ keyManager.newSubAddressPool(-1);
2322
+ keyManager.newSubAddressPool(-2);
2323
+ await this.saveWallet(keyManager);
2324
+ await this.saveWalletMetadata({
2325
+ creationHeight: creationHeight ?? 0,
2326
+ creationTime: Date.now(),
2327
+ restoredFromSeed: true
2328
+ });
2329
+ this.keyManager = keyManager;
2330
+ return keyManager;
2331
+ }
1706
2332
  /**
1707
2333
  * Save wallet metadata to database
1708
2334
  */
@@ -2031,6 +2657,255 @@ var WalletDB = class {
2031
2657
  stmt.free();
2032
2658
  return outputs;
2033
2659
  }
2660
+ // ============================================================================
2661
+ // Encryption Methods
2662
+ // ============================================================================
2663
+ /**
2664
+ * Check if the database has encryption enabled
2665
+ * @returns True if encryption is enabled
2666
+ */
2667
+ isEncrypted() {
2668
+ if (!this.isOpen) {
2669
+ return false;
2670
+ }
2671
+ const stmt = this.db.prepare("SELECT is_encrypted FROM encryption_metadata WHERE id = 0");
2672
+ if (stmt.step()) {
2673
+ const result = stmt.getAsObject().is_encrypted === 1;
2674
+ stmt.free();
2675
+ return result;
2676
+ }
2677
+ stmt.free();
2678
+ return false;
2679
+ }
2680
+ /**
2681
+ * Save encryption metadata to the database
2682
+ * @param salt - Hex-encoded salt
2683
+ * @param verificationHash - Hex-encoded password verification hash
2684
+ */
2685
+ saveEncryptionMetadata(salt, verificationHash) {
2686
+ if (!this.isOpen) {
2687
+ throw new Error("Database not initialized");
2688
+ }
2689
+ this.db.run(`
2690
+ INSERT OR REPLACE INTO encryption_metadata (id, is_encrypted, salt, verification_hash, encryption_version)
2691
+ VALUES (0, 1, ?, ?, 1)
2692
+ `, [salt, verificationHash]);
2693
+ }
2694
+ /**
2695
+ * Load encryption metadata from the database
2696
+ * @returns Encryption metadata or null if not encrypted
2697
+ */
2698
+ getEncryptionMetadata() {
2699
+ if (!this.isOpen) {
2700
+ throw new Error("Database not initialized");
2701
+ }
2702
+ const stmt = this.db.prepare(`
2703
+ SELECT salt, verification_hash FROM encryption_metadata WHERE id = 0 AND is_encrypted = 1
2704
+ `);
2705
+ if (stmt.step()) {
2706
+ const row = stmt.getAsObject();
2707
+ stmt.free();
2708
+ if (row.salt && row.verification_hash) {
2709
+ return {
2710
+ salt: row.salt,
2711
+ verificationHash: row.verification_hash
2712
+ };
2713
+ }
2714
+ }
2715
+ stmt.free();
2716
+ return null;
2717
+ }
2718
+ /**
2719
+ * Save an encrypted key to the database
2720
+ * @param keyId - Key identifier (hex)
2721
+ * @param publicKey - Public key (hex)
2722
+ * @param encryptedSecret - Encrypted secret (JSON string)
2723
+ */
2724
+ saveEncryptedKey(keyId, publicKey, encryptedSecret) {
2725
+ if (!this.isOpen) {
2726
+ throw new Error("Database not initialized");
2727
+ }
2728
+ this.db.run(`
2729
+ INSERT OR REPLACE INTO crypted_keys (key_id, public_key, encrypted_secret)
2730
+ VALUES (?, ?, ?)
2731
+ `, [keyId, publicKey, encryptedSecret]);
2732
+ }
2733
+ /**
2734
+ * Load an encrypted key from the database
2735
+ * @param keyId - Key identifier (hex)
2736
+ * @returns Encrypted key data or null if not found
2737
+ */
2738
+ getEncryptedKey(keyId) {
2739
+ if (!this.isOpen) {
2740
+ throw new Error("Database not initialized");
2741
+ }
2742
+ const stmt = this.db.prepare(`
2743
+ SELECT public_key, encrypted_secret FROM crypted_keys WHERE key_id = ?
2744
+ `);
2745
+ stmt.bind([keyId]);
2746
+ if (stmt.step()) {
2747
+ const row = stmt.getAsObject();
2748
+ stmt.free();
2749
+ return {
2750
+ publicKey: row.public_key,
2751
+ encryptedSecret: row.encrypted_secret
2752
+ };
2753
+ }
2754
+ stmt.free();
2755
+ return null;
2756
+ }
2757
+ /**
2758
+ * Get all encrypted keys from the database
2759
+ * @returns Array of encrypted key data
2760
+ */
2761
+ getAllEncryptedKeys() {
2762
+ if (!this.isOpen) {
2763
+ throw new Error("Database not initialized");
2764
+ }
2765
+ const stmt = this.db.prepare("SELECT key_id, public_key, encrypted_secret FROM crypted_keys");
2766
+ const keys = [];
2767
+ while (stmt.step()) {
2768
+ const row = stmt.getAsObject();
2769
+ keys.push({
2770
+ keyId: row.key_id,
2771
+ publicKey: row.public_key,
2772
+ encryptedSecret: row.encrypted_secret
2773
+ });
2774
+ }
2775
+ stmt.free();
2776
+ return keys;
2777
+ }
2778
+ /**
2779
+ * Save an encrypted output key to the database
2780
+ * @param outId - Output identifier (hex)
2781
+ * @param publicKey - Public key (hex)
2782
+ * @param encryptedSecret - Encrypted secret (JSON string)
2783
+ */
2784
+ saveEncryptedOutKey(outId, publicKey, encryptedSecret) {
2785
+ if (!this.isOpen) {
2786
+ throw new Error("Database not initialized");
2787
+ }
2788
+ this.db.run(`
2789
+ INSERT OR REPLACE INTO crypted_out_keys (out_id, public_key, encrypted_secret)
2790
+ VALUES (?, ?, ?)
2791
+ `, [outId, publicKey, encryptedSecret]);
2792
+ }
2793
+ /**
2794
+ * Load an encrypted output key from the database
2795
+ * @param outId - Output identifier (hex)
2796
+ * @returns Encrypted output key data or null if not found
2797
+ */
2798
+ getEncryptedOutKey(outId) {
2799
+ if (!this.isOpen) {
2800
+ throw new Error("Database not initialized");
2801
+ }
2802
+ const stmt = this.db.prepare(`
2803
+ SELECT public_key, encrypted_secret FROM crypted_out_keys WHERE out_id = ?
2804
+ `);
2805
+ stmt.bind([outId]);
2806
+ if (stmt.step()) {
2807
+ const row = stmt.getAsObject();
2808
+ stmt.free();
2809
+ return {
2810
+ publicKey: row.public_key,
2811
+ encryptedSecret: row.encrypted_secret
2812
+ };
2813
+ }
2814
+ stmt.free();
2815
+ return null;
2816
+ }
2817
+ /**
2818
+ * Get all encrypted output keys from the database
2819
+ * @returns Array of encrypted output key data
2820
+ */
2821
+ getAllEncryptedOutKeys() {
2822
+ if (!this.isOpen) {
2823
+ throw new Error("Database not initialized");
2824
+ }
2825
+ const stmt = this.db.prepare("SELECT out_id, public_key, encrypted_secret FROM crypted_out_keys");
2826
+ const keys = [];
2827
+ while (stmt.step()) {
2828
+ const row = stmt.getAsObject();
2829
+ keys.push({
2830
+ outId: row.out_id,
2831
+ publicKey: row.public_key,
2832
+ encryptedSecret: row.encrypted_secret
2833
+ });
2834
+ }
2835
+ stmt.free();
2836
+ return keys;
2837
+ }
2838
+ /**
2839
+ * Delete plaintext keys (after encryption)
2840
+ * This removes the unencrypted keys from the database
2841
+ */
2842
+ deletePlaintextKeys() {
2843
+ if (!this.isOpen) {
2844
+ throw new Error("Database not initialized");
2845
+ }
2846
+ this.db.run("DELETE FROM keys");
2847
+ this.db.run("DELETE FROM out_keys");
2848
+ }
2849
+ /**
2850
+ * Export the database as an encrypted binary blob
2851
+ * Uses the encryption module to encrypt the entire SQLite database
2852
+ *
2853
+ * @param password - Password to encrypt the export
2854
+ * @returns Encrypted database bytes
2855
+ */
2856
+ async exportEncrypted(password) {
2857
+ if (!this.isOpen) {
2858
+ throw new Error("Database not initialized");
2859
+ }
2860
+ const { encryptDatabase: encryptDatabase2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
2861
+ const dbBuffer = this.db.export();
2862
+ const encrypted = await encryptDatabase2(new Uint8Array(dbBuffer), password);
2863
+ return encrypted;
2864
+ }
2865
+ /**
2866
+ * Load a database from an encrypted binary blob
2867
+ *
2868
+ * @param encryptedData - Encrypted database bytes
2869
+ * @param password - Password to decrypt
2870
+ * @param dbPath - Path for the new database instance
2871
+ * @returns New WalletDB instance with decrypted data
2872
+ */
2873
+ static async loadEncrypted(encryptedData, password, dbPath = ":memory:") {
2874
+ const { decryptDatabase: decryptDatabase2, isEncryptedDatabase: isEncryptedDatabase2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
2875
+ if (!isEncryptedDatabase2(encryptedData)) {
2876
+ throw new Error("Data does not appear to be an encrypted database");
2877
+ }
2878
+ const decryptedBuffer = await decryptDatabase2(encryptedData, password);
2879
+ const walletDb = new _WalletDB(dbPath);
2880
+ const SQL2 = await loadSQL();
2881
+ walletDb.db = new SQL2.Database(decryptedBuffer);
2882
+ walletDb.isOpen = true;
2883
+ return walletDb;
2884
+ }
2885
+ /**
2886
+ * Get the raw database bytes (unencrypted)
2887
+ * @returns Database bytes
2888
+ */
2889
+ export() {
2890
+ if (!this.isOpen) {
2891
+ throw new Error("Database not initialized");
2892
+ }
2893
+ return new Uint8Array(this.db.export());
2894
+ }
2895
+ /**
2896
+ * Load a database from raw bytes
2897
+ *
2898
+ * @param data - Raw database bytes
2899
+ * @param dbPath - Path for the new database instance
2900
+ * @returns New WalletDB instance
2901
+ */
2902
+ static async loadFromBytes(data, dbPath = ":memory:") {
2903
+ const walletDb = new _WalletDB(dbPath);
2904
+ const SQL2 = await loadSQL();
2905
+ walletDb.db = new SQL2.Database(data);
2906
+ walletDb.isOpen = true;
2907
+ return walletDb;
2908
+ }
2034
2909
  };
2035
2910
  var WebSocketClass;
2036
2911
  var isBrowserWebSocket = false;
@@ -2422,7 +3297,7 @@ var ElectrumClient = class {
2422
3297
  }
2423
3298
  };
2424
3299
  var TransactionKeysSync = class {
2425
- // Keep last 1k block hashes by default
3300
+ // Keep last 10k block hashes by default
2426
3301
  /**
2427
3302
  * Create a new TransactionKeysSync instance
2428
3303
  * @param walletDB - The wallet database
@@ -2431,7 +3306,7 @@ var TransactionKeysSync = class {
2431
3306
  constructor(walletDB, provider) {
2432
3307
  this.keyManager = null;
2433
3308
  this.syncState = null;
2434
- this.blockHashRetention = 1e3;
3309
+ this.blockHashRetention = 1e4;
2435
3310
  this.walletDB = walletDB;
2436
3311
  if ("type" in provider && (provider.type === "electrum" || provider.type === "p2p" || provider.type === "custom")) {
2437
3312
  this.syncProvider = provider;
@@ -2539,7 +3414,7 @@ var TransactionKeysSync = class {
2539
3414
  verifyHashes = true,
2540
3415
  saveInterval = 100,
2541
3416
  keepTxKeys = false,
2542
- blockHashRetention = 1e3
3417
+ blockHashRetention = 1e4
2543
3418
  } = options;
2544
3419
  this.blockHashRetention = blockHashRetention;
2545
3420
  const lastSynced = this.syncState?.lastSyncedHeight ?? -1;
@@ -4659,6 +5534,13 @@ var _NavioClient = class _NavioClient {
4659
5534
  );
4660
5535
  this.syncManager.setKeyManager(this.keyManager);
4661
5536
  await this.syncProvider.connect();
5537
+ } else if (this.config.restoreFromMnemonic) {
5538
+ this.keyManager = await this.walletDB.restoreWalletFromMnemonic(
5539
+ this.config.restoreFromMnemonic,
5540
+ this.config.restoreFromHeight
5541
+ );
5542
+ this.syncManager.setKeyManager(this.keyManager);
5543
+ await this.syncProvider.connect();
4662
5544
  } else {
4663
5545
  try {
4664
5546
  this.keyManager = await this.walletDB.loadWallet();
@@ -4997,6 +5879,9 @@ var _NavioClient = class _NavioClient {
4997
5879
  _NavioClient.CREATION_HEIGHT_MARGIN = 100;
4998
5880
  var NavioClient = _NavioClient;
4999
5881
 
5000
- export { BaseSyncProvider, DefaultPorts, ElectrumClient, ElectrumError, ElectrumSyncProvider, InvType, KeyManager, MessageType, NavioClient, NetworkMagic, P2PClient, P2PSyncProvider, PROTOCOL_VERSION, ServiceFlags, TransactionKeysSync, WalletDB };
5882
+ // src/index.ts
5883
+ init_crypto();
5884
+
5885
+ 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
5886
  //# sourceMappingURL=index.mjs.map
5002
5887
  //# sourceMappingURL=index.mjs.map