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