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