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 CHANGED
@@ -5,6 +5,7 @@ TypeScript SDK for interacting with the Navio blockchain. Provides wallet manage
5
5
  ## Features
6
6
 
7
7
  - **Wallet Management** - HD key derivation with BLS CT support
8
+ - **Wallet Encryption** - Password-based encryption with Argon2id + AES-256-GCM
8
9
  - **Dual Sync Backends** - Electrum protocol or direct P2P node connections
9
10
  - **Automatic Output Detection** - BLSCT output scanning with view tag optimization
10
11
  - **Amount Recovery** - Decrypt confidential transaction amounts
@@ -43,8 +44,8 @@ await client.initialize();
43
44
 
44
45
  // Sync transaction keys
45
46
  await client.sync({
46
- onProgress: (height, tip, blocks, txKeys) => {
47
- console.log(`Syncing: ${height}/${tip} (${blocks} blocks, ${txKeys} TX keys)`);
47
+ onProgress: (height, tip, blocks, txKeys, isReorg) => {
48
+ console.log(`Syncing: ${height}/${tip} (${blocks} blocks, ${txKeys} TX keys${isReorg ? ' [REORG]' : ''})`);
48
49
  },
49
50
  });
50
51
 
@@ -101,6 +102,7 @@ interface NavioClientConfig {
101
102
  // Wallet options
102
103
  createWalletIfNotExists?: boolean; // Create wallet if missing (default: false)
103
104
  restoreFromSeed?: string; // Restore from seed (hex string)
105
+ restoreFromMnemonic?: string; // Restore from BIP39 mnemonic (12-24 words)
104
106
  restoreFromHeight?: number; // Block height when wallet was created (for restore)
105
107
  creationHeight?: number; // Creation height for new wallets (default: chainTip - 100)
106
108
  }
@@ -167,6 +169,16 @@ Returns the last synced block height, or -1 if never synced.
167
169
 
168
170
  Returns current sync state with statistics.
169
171
 
172
+ ```typescript
173
+ interface SyncState {
174
+ lastSyncedHeight: number; // Last synced block height
175
+ lastSyncedHash: string; // Last synced block hash
176
+ totalTxKeysSynced: number; // Total transaction keys synced
177
+ lastSyncTime: number; // Last sync timestamp (ms)
178
+ chainTipAtLastSync: number; // Chain tip at last sync
179
+ }
180
+ ```
181
+
170
182
  #### Background Sync
171
183
 
172
184
  ##### `startBackgroundSync(options?: BackgroundSyncOptions): Promise<void>`
@@ -272,10 +284,6 @@ Check if client is connected to the backend.
272
284
 
273
285
  #### Connection
274
286
 
275
- ##### `connect(): Promise<void>`
276
-
277
- Connect to the backend.
278
-
279
287
  ##### `disconnect(): Promise<void>`
280
288
 
281
289
  Disconnect from backend and close database.
@@ -300,12 +308,48 @@ const masterSeed = keyManager.getMasterSeedKey();
300
308
  console.log('Seed:', masterSeed.serialize()); // hex string
301
309
  ```
302
310
 
311
+ #### Mnemonic Support (BIP39)
312
+
313
+ ```typescript
314
+ // Generate a new 24-word mnemonic and set as seed
315
+ const mnemonic = keyManager.generateNewMnemonic();
316
+ console.log('Mnemonic:', mnemonic);
317
+ // "abandon ability able about above absent absorb abstract absurd abuse access accident..."
318
+
319
+ // Or generate mnemonic with different word counts
320
+ const mnemonic12 = KeyManager.generateMnemonic(128); // 12 words
321
+ const mnemonic24 = KeyManager.generateMnemonic(256); // 24 words (default)
322
+
323
+ // Validate a mnemonic
324
+ const isValid = KeyManager.validateMnemonic(mnemonic);
325
+ console.log('Valid:', isValid); // true
326
+
327
+ // Restore from mnemonic
328
+ keyManager.setHDSeedFromMnemonic(mnemonic);
329
+
330
+ // Get mnemonic from current seed (for backup)
331
+ const backupMnemonic = keyManager.getMnemonic();
332
+ console.log('Backup mnemonic:', backupMnemonic);
333
+
334
+ // Static conversion methods
335
+ const scalar = KeyManager.mnemonicToScalar(mnemonic); // Convert to Scalar
336
+ const recovered = KeyManager.seedToMnemonic(scalar); // Convert back to mnemonic
337
+ ```
338
+
303
339
  #### Sub-addresses
304
340
 
305
341
  ```typescript
306
342
  // Get sub-address by identifier
307
343
  const subAddr = keyManager.getSubAddress({ account: 0, address: 0 });
308
344
 
345
+ // Get bech32m encoded address string (recommended)
346
+ const address = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'testnet');
347
+ console.log('Address:', address); // tnav1... (Node.js) or hex fallback (browser)
348
+
349
+ // Get mainnet address
350
+ const mainnetAddress = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'mainnet');
351
+ console.log('Address:', mainnetAddress); // nav1...
352
+
309
353
  // Generate new sub-address
310
354
  const { subAddress, id } = keyManager.generateNewSubAddress(0); // account 0
311
355
 
@@ -315,6 +359,9 @@ keyManager.newSubAddressPool(-1); // Change
315
359
  keyManager.newSubAddressPool(-2); // Staking
316
360
  ```
317
361
 
362
+ > **Note:** In browser environments using navio-blsct WASM, bech32m encoding may fall back to hex
363
+ > representation due to a known issue. This will be addressed in a future navio-blsct release.
364
+
318
365
  #### Output Detection
319
366
 
320
367
  ```typescript
@@ -331,6 +378,38 @@ const hashId = keyManager.calculateHashId(blindingKey, spendingKey);
331
378
  const nonce = keyManager.calculateNonce(blindingKey);
332
379
  ```
333
380
 
381
+ #### Wallet Encryption
382
+
383
+ The KeyManager supports password-based encryption using Argon2id for key derivation and AES-256-GCM for encryption.
384
+
385
+ ```typescript
386
+ // Check encryption status
387
+ const isEncrypted = keyManager.isEncrypted();
388
+ const isUnlocked = keyManager.isUnlocked();
389
+
390
+ // Set a password (encrypts the wallet)
391
+ await keyManager.setPassword('my-secure-password');
392
+
393
+ // Lock the wallet (clears cached encryption key)
394
+ keyManager.lock();
395
+
396
+ // Unlock the wallet
397
+ const success = await keyManager.unlock('my-secure-password');
398
+ if (!success) {
399
+ console.log('Wrong password');
400
+ }
401
+
402
+ // Change password
403
+ const changed = await keyManager.changePassword('old-password', 'new-password');
404
+
405
+ // Get encryption parameters (for database storage)
406
+ const params = keyManager.getEncryptionParams();
407
+ // { salt: 'hex...', verificationHash: 'hex...' }
408
+
409
+ // Restore encryption state from database
410
+ keyManager.setEncryptionParams(params.salt, params.verificationHash);
411
+ ```
412
+
334
413
  ---
335
414
 
336
415
  ### WalletDB
@@ -381,6 +460,50 @@ const utxos = await walletDB.getUnspentOutputs();
381
460
  const outputs = await walletDB.getAllOutputs();
382
461
  ```
383
462
 
463
+ #### Database Encryption
464
+
465
+ The WalletDB supports full database encryption for secure backup and export.
466
+
467
+ ```typescript
468
+ // Export database as encrypted blob
469
+ const encryptedData = await walletDB.exportEncrypted('backup-password');
470
+ // encryptedData is a Uint8Array that can be saved to file
471
+
472
+ // Load database from encrypted blob
473
+ const restoredDb = await WalletDB.loadEncrypted(encryptedData, 'backup-password');
474
+
475
+ // Check if a buffer is an encrypted database
476
+ import { isEncryptedDatabase } from 'navio-sdk';
477
+ const isEncrypted = isEncryptedDatabase(someBuffer);
478
+
479
+ // Export unencrypted database (for backup without password)
480
+ const rawData = walletDB.export();
481
+
482
+ // Load from raw bytes
483
+ const db = await WalletDB.loadFromBytes(rawData);
484
+ ```
485
+
486
+ #### Encryption Metadata
487
+
488
+ ```typescript
489
+ // Check if encryption is enabled
490
+ const isEncrypted = walletDB.isEncrypted();
491
+
492
+ // Save encryption metadata
493
+ walletDB.saveEncryptionMetadata(salt, verificationHash);
494
+
495
+ // Get encryption metadata
496
+ const metadata = walletDB.getEncryptionMetadata();
497
+ // { salt: 'hex...', verificationHash: 'hex...' } or null
498
+
499
+ // Get/save encrypted keys
500
+ walletDB.saveEncryptedKey(keyId, publicKey, encryptedSecret);
501
+ const key = walletDB.getEncryptedKey(keyId);
502
+
503
+ // Delete plaintext keys after encryption
504
+ walletDB.deletePlaintextKeys();
505
+ ```
506
+
384
507
  ---
385
508
 
386
509
  ### SyncProvider Interface
@@ -396,6 +519,7 @@ interface SyncProvider {
396
519
  isConnected(): boolean;
397
520
 
398
521
  getChainTipHeight(): Promise<number>;
522
+ getChainTip(): Promise<{ height: number; hash: string }>;
399
523
  getBlockHeader(height: number): Promise<string>;
400
524
  getBlockHeaders(startHeight: number, count: number): Promise<HeadersResult>;
401
525
  getBlockTransactionKeysRange(startHeight: number): Promise<TransactionKeysRangeResult>;
@@ -436,7 +560,7 @@ import { P2PSyncProvider } from 'navio-sdk';
436
560
 
437
561
  const provider = new P2PSyncProvider({
438
562
  host: 'localhost',
439
- port: 33670,
563
+ port: 43670, // testnet port (mainnet: 33670)
440
564
  network: 'testnet',
441
565
  debug: true,
442
566
  });
@@ -468,12 +592,9 @@ const keyManager = client.getKeyManager();
468
592
  const seed = keyManager.getMasterSeedKey();
469
593
  console.log('SAVE THIS SEED:', seed.serialize());
470
594
 
471
- // Get receiving address
472
- const { DoublePublicKey, Address, AddressEncoding } = require('navio-blsct');
473
- const subAddress = keyManager.getSubAddress({ account: 0, address: 0 });
474
- const dpk = DoublePublicKey.deserialize(subAddress.serialize());
475
- const address = Address.encode(dpk, AddressEncoding.Bech32M);
476
- console.log('Address:', address);
595
+ // Get receiving address (bech32m encoded)
596
+ const address = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'testnet');
597
+ console.log('Address:', address); // tnav1...
477
598
  ```
478
599
 
479
600
  ### Restore Wallet from Seed
@@ -497,6 +618,27 @@ await client.sync({
497
618
  });
498
619
  ```
499
620
 
621
+ ### Restore Wallet from Mnemonic
622
+
623
+ ```typescript
624
+ const client = new NavioClient({
625
+ walletDbPath: './restored-wallet.db',
626
+ electrum: { host: 'localhost', port: 50005 },
627
+ restoreFromMnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art',
628
+ restoreFromHeight: 50000, // Block height when wallet was created
629
+ network: 'testnet',
630
+ });
631
+
632
+ await client.initialize();
633
+
634
+ // Full sync from restoration height
635
+ await client.sync({
636
+ onProgress: (height, tip) => {
637
+ console.log(`Syncing: ${height}/${tip}`);
638
+ },
639
+ });
640
+ ```
641
+
500
642
  ### Using P2P Backend
501
643
 
502
644
  ```typescript
@@ -607,6 +749,77 @@ for (const utxo of utxos) {
607
749
  }
608
750
  ```
609
751
 
752
+ ### Encrypt Wallet with Password
753
+
754
+ ```typescript
755
+ import { NavioClient } from 'navio-sdk';
756
+
757
+ const client = new NavioClient({
758
+ walletDbPath: './secure-wallet.db',
759
+ electrum: { host: 'localhost', port: 50005 },
760
+ createWalletIfNotExists: true,
761
+ network: 'testnet',
762
+ });
763
+
764
+ await client.initialize();
765
+
766
+ // Set a password to encrypt the wallet
767
+ const keyManager = client.getKeyManager();
768
+ await keyManager.setPassword('my-secure-password');
769
+ console.log('Wallet encrypted:', keyManager.isEncrypted()); // true
770
+
771
+ // The wallet is now encrypted but unlocked
772
+ console.log('Wallet unlocked:', keyManager.isUnlocked()); // true
773
+
774
+ // Lock the wallet (recommended when not in use)
775
+ keyManager.lock();
776
+ console.log('Wallet unlocked:', keyManager.isUnlocked()); // false
777
+
778
+ // Unlock to use
779
+ const success = await keyManager.unlock('my-secure-password');
780
+ console.log('Unlock successful:', success); // true
781
+ ```
782
+
783
+ ### Export Encrypted Backup
784
+
785
+ ```typescript
786
+ // Export encrypted database for backup
787
+ const walletDb = client.getWalletDB();
788
+ const encryptedBackup = await walletDb.exportEncrypted('backup-password');
789
+
790
+ // Save to file (Node.js)
791
+ const fs = require('fs');
792
+ fs.writeFileSync('wallet-backup.enc', encryptedBackup);
793
+
794
+ // Or download in browser
795
+ const blob = new Blob([encryptedBackup], { type: 'application/octet-stream' });
796
+ const url = URL.createObjectURL(blob);
797
+ const a = document.createElement('a');
798
+ a.href = url;
799
+ a.download = 'wallet-backup.enc';
800
+ a.click();
801
+ ```
802
+
803
+ ### Restore from Encrypted Backup
804
+
805
+ ```typescript
806
+ import { WalletDB } from 'navio-sdk';
807
+
808
+ // Load encrypted backup
809
+ const fs = require('fs');
810
+ const encryptedData = fs.readFileSync('wallet-backup.enc');
811
+
812
+ // Decrypt and load
813
+ const walletDb = await WalletDB.loadEncrypted(
814
+ new Uint8Array(encryptedData),
815
+ 'backup-password'
816
+ );
817
+
818
+ // Load the wallet
819
+ const keyManager = await walletDb.loadWallet();
820
+ console.log('Wallet restored');
821
+ ```
822
+
610
823
  ---
611
824
 
612
825
  ## Database Schema
@@ -617,12 +830,15 @@ The wallet database includes the following tables:
617
830
  |-------|-------------|
618
831
  | `keys` | Key pairs for transactions |
619
832
  | `out_keys` | Output-specific keys |
833
+ | `crypted_keys` | Encrypted key pairs (when password set) |
834
+ | `crypted_out_keys` | Encrypted output keys (when password set) |
620
835
  | `view_key` | View key for output detection |
621
836
  | `spend_key` | Spending public key |
622
837
  | `hd_chain` | HD chain information |
623
838
  | `sub_addresses` | Sub-address mappings |
624
839
  | `wallet_outputs` | Wallet UTXOs with amounts |
625
840
  | `wallet_metadata` | Wallet creation info |
841
+ | `encryption_metadata` | Encryption parameters (salt, verification hash) |
626
842
  | `tx_keys` | Transaction keys (optional) |
627
843
  | `block_hashes` | Block hashes for reorg detection |
628
844
  | `sync_state` | Synchronization state |
@@ -687,12 +903,14 @@ npm install
687
903
  npm run build
688
904
 
689
905
  # Run tests
906
+ npm run test # Run all unit tests (vitest)
690
907
  npm run test:keymanager # KeyManager tests
691
908
  npm run test:walletdb # WalletDB tests
692
909
  npm run test:electrum # Electrum client tests
693
910
  npm run test:client # Full client tests (Electrum)
694
911
  npm run test:p2p # P2P protocol tests
695
912
  npm run test:client:p2p # Full client tests (P2P)
913
+ npm run test:encryption # Encryption module tests
696
914
 
697
915
  # Generate documentation
698
916
  npm run docs # HTML docs in ./docs
@@ -711,6 +929,52 @@ npm run analyze:db
711
929
 
712
930
  ---
713
931
 
932
+ ### Encryption Module
933
+
934
+ The SDK includes a low-level encryption module for password-based encryption.
935
+
936
+ ```typescript
937
+ import {
938
+ encrypt,
939
+ decrypt,
940
+ deriveKey,
941
+ serializeEncryptedData,
942
+ deserializeEncryptedData,
943
+ createPasswordVerification,
944
+ verifyPassword,
945
+ randomBytes,
946
+ } from 'navio-sdk';
947
+
948
+ // Encrypt arbitrary data
949
+ const plaintext = new TextEncoder().encode('secret data');
950
+ const encrypted = await encrypt(plaintext, 'password');
951
+
952
+ // Decrypt data
953
+ const decrypted = await decrypt(encrypted, 'password');
954
+ console.log(new TextDecoder().decode(decrypted)); // 'secret data'
955
+
956
+ // Serialize for storage (converts to base64 strings)
957
+ const serialized = serializeEncryptedData(encrypted);
958
+ const json = JSON.stringify(serialized);
959
+
960
+ // Deserialize from storage
961
+ const parsed = JSON.parse(json);
962
+ const deserialized = deserializeEncryptedData(parsed);
963
+
964
+ // Password verification (for UI feedback)
965
+ const salt = randomBytes(16);
966
+ const hash = await createPasswordVerification('password', salt);
967
+ const isValid = await verifyPassword('password', salt, hash);
968
+ ```
969
+
970
+ **Security Properties:**
971
+ - **Key Derivation**: Argon2id with 64MB memory, 3 iterations, 4 parallelism
972
+ - **Encryption**: AES-256-GCM (authenticated encryption)
973
+ - **Random IV**: 12 bytes per encryption (never reused)
974
+ - **Random Salt**: 16 bytes per password derivation
975
+
976
+ ---
977
+
714
978
  ## Known Limitations
715
979
 
716
980
  1. **Amount Recovery**: The `navio-blsct` library v1.0.20 has a bug in `RangeProof.recoverAmounts()`. Outputs are detected and stored but amounts show as 0 until the library is updated.