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/README.md +277 -13
- package/dist/index.d.mts +435 -2
- package/dist/index.d.ts +435 -2
- package/dist/index.js +908 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +891 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -2
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
|
|
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 =
|
|
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 =
|
|
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
|