Files
mines/rin/wallet/cmd/PROPER_SELF_CUSTODY_SOLUTION.md
Dobromir Popov 79b319e4dc try file restore
2025-09-30 00:46:03 +03:00

13 KiB

Proper Self-Custody Wallet Solution for RinCoin

The Problem

You're absolutely right - the wallet file backup method is NOT user-friendly for self-custody:

  • Not a simple 12-word phrase
  • Large binary files
  • Can't easily write down or memorize
  • Not compatible with standard wallet UX

The REAL Solution: BIP39 Mnemonic with Key Derivation

We need to implement BIP39 support in the web wallet layer, NOT rely on RinCoin's RPC!

How It Works:

User's 12 Words (BIP39)
    ↓ (in web wallet)
Master Seed (BIP32)
    ↓ (derive keys)
Private Keys for Addresses
    ↓ (import to RinCoin)
RinCoin Wallet (via importprivkey)

Key Insight:

The web wallet handles BIP39/BIP32 derivation, then imports the derived keys into RinCoin!

🎯 Implementation Architecture

Layer 1: Web Wallet (BIP39 Support)

The browser extension/web wallet handles:

  • BIP39 mnemonic generation and validation
  • BIP32 key derivation
  • Address generation
  • Key management

Layer 2: RinCoin Node (Key Storage Only)

The RinCoin node just stores imported keys:

  • Receives derived private keys via importprivkey
  • Tracks balances and transactions
  • Signs transactions when needed

🔧 Technical Implementation

1. Backup Flow (Generate Mnemonic)

// In Web Wallet
import * as bip39 from 'bip39';
import { BIP32Factory } from 'bip32';
import * as bitcoin from 'bitcoinjs-lib';

// Generate new wallet with mnemonic
async function createNewWallet() {
  // 1. Generate BIP39 mnemonic (12 or 24 words)
  const mnemonic = bip39.generateMnemonic(128); // 12 words
  
  // 2. Convert to seed
  const seed = await bip39.mnemonicToSeed(mnemonic);
  
  // 3. Create master key
  const root = bip32.fromSeed(seed);
  
  // 4. Derive keys for RinCoin (using Litecoin derivation path)
  // m/44'/2'/0'/0/0 (Litecoin path, which RinCoin is based on)
  const masterPath = "m/44'/2'/0'/0";
  
  // 5. Generate first 20 addresses
  const addresses = [];
  for (let i = 0; i < 20; i++) {
    const child = root.derivePath(`${masterPath}/${i}`);
    const privateKey = child.toWIF();
    const address = deriveRinCoinAddress(child.publicKey);
    
    addresses.push({
      index: i,
      address: address,
      privateKey: privateKey
    });
  }
  
  // 6. Import keys to RinCoin node
  for (const addr of addresses) {
    await importPrivateKeyToNode(addr.privateKey, addr.address);
  }
  
  // 7. Return mnemonic to user (SHOW ONCE, USER MUST WRITE DOWN)
  return {
    mnemonic: mnemonic,
    addresses: addresses.map(a => a.address)
  };
}

// Import single key to RinCoin
async function importPrivateKeyToNode(privateKey, label) {
  return await rpcCall('importprivkey', [privateKey, label, false]);
}

2. Restore Flow (From Mnemonic)

// In Web Wallet
async function restoreWalletFromMnemonic(mnemonic) {
  // 1. Validate mnemonic
  if (!bip39.validateMnemonic(mnemonic)) {
    throw new Error('Invalid mnemonic phrase');
  }
  
  // 2. Convert to seed
  const seed = await bip39.mnemonicToSeed(mnemonic);
  
  // 3. Create master key
  const root = bip32.fromSeed(seed);
  
  // 4. Create new wallet on node
  await rpcCall('createwallet', [walletName, false, false]);
  
  // 5. Derive and import keys with gap limit
  const masterPath = "m/44'/2'/0'/0";
  let consecutiveUnused = 0;
  const GAP_LIMIT = 20;
  
  for (let i = 0; consecutiveUnused < GAP_LIMIT; i++) {
    const child = root.derivePath(`${masterPath}/${i}`);
    const privateKey = child.toWIF();
    const address = deriveRinCoinAddress(child.publicKey);
    
    // Import key
    await importPrivateKeyToNode(privateKey, `addr_${i}`);
    
    // Check if address has been used (has transactions)
    const hasTransactions = await checkAddressUsage(address);
    
    if (hasTransactions) {
      consecutiveUnused = 0; // Reset counter
    } else {
      consecutiveUnused++;
    }
  }
  
  // 6. Rescan blockchain to find all transactions
  await rpcCall('rescanblockchain', []);
  
  // 7. Get balance
  const balance = await rpcCall('getbalance', []);
  
  return {
    success: true,
    balance: balance,
    message: 'Wallet restored successfully!'
  };
}

3. Address Derivation for RinCoin

import * as bitcoin from 'bitcoinjs-lib';

// RinCoin uses Litecoin's address format
function deriveRinCoinAddress(publicKey) {
  // Use Litecoin network parameters (RinCoin is Litecoin-based)
  const rincoinNetwork = {
    messagePrefix: '\x19RinCoin Signed Message:\n',
    bech32: 'rin',  // For SegWit addresses (rin1q...)
    bip32: {
      public: 0x019da462,  // Litecoin's public key version
      private: 0x019d9cfe  // Litecoin's private key version
    },
    pubKeyHash: 0x30,  // Litecoin pubkey hash (for legacy addresses)
    scriptHash: 0x32,  // Litecoin script hash
    wif: 0xb0          // Litecoin WIF
  };
  
  // Generate SegWit address (rin1q...)
  const { address } = bitcoin.payments.p2wpkh({
    pubkey: publicKey,
    network: rincoinNetwork
  });
  
  return address;
}

📱 User Experience Design

Creating New Wallet:

┌─────────────────────────────────┐
│     Create RinCoin Wallet      │
├─────────────────────────────────┤
│                                 │
│  [Generate New Wallet]          │
│                                 │
│  ↓                              │
│                                 │
│  🔐 Your Recovery Phrase:       │
│  ┌───────────────────────────┐ │
│  │ witch collapse practice    │ │
│  │ feed shame open despair    │ │
│  │ creek road again ice least │ │
│  └───────────────────────────┘ │
│                                 │
│  ⚠️  WRITE THIS DOWN!           │
│  These 12 words are the ONLY    │
│  way to recover your wallet.    │
│                                 │
│  ☐ I have written down my      │
│     recovery phrase             │
│                                 │
│  [Continue] ─────────────────→  │
└─────────────────────────────────┘

Restoring Wallet:

┌─────────────────────────────────┐
│    Restore RinCoin Wallet      │
├─────────────────────────────────┤
│                                 │
│  Enter your 12-word recovery    │
│  phrase:                        │
│                                 │
│  ┌───────────────────────────┐ │
│  │ 1. witch     7. despair   │ │
│  │ 2. collapse  8. creek     │ │
│  │ 3. practice  9. road      │ │
│  │ 4. feed     10. again     │ │
│  │ 5. shame    11. ice       │ │
│  │ 6. open     12. least     │ │
│  └───────────────────────────┘ │
│                                 │
│  [Restore Wallet]               │
│                                 │
│  ↓                              │
│                                 │
│  🔄 Restoring wallet...         │
│  • Deriving keys...        ✓   │
│  • Importing to node...    ✓   │
│  • Scanning blockchain...  ⏳  │
│                                 │
│  ✅ Restored!                   │
│  Balance: 1059.00 RIN           │
└─────────────────────────────────┘

🔐 Security Features

1. Mnemonic Only (No Key Storage)

// NEVER store the mnemonic permanently!
// Only derive keys when needed

class SecureWallet {
  // User enters mnemonic each session
  async unlockWallet(mnemonic) {
    this.root = await deriveRootKey(mnemonic);
    // Keep in memory only
  }
  
  // Clear on logout
  lockWallet() {
    this.root = null;
    // Clear all key material from memory
  }
  
  // Derive key on-demand
  async getPrivateKey(index) {
    if (!this.root) throw new Error('Wallet locked');
    return this.root.derivePath(`m/44'/2'/0'/0/${index}`);
  }
}

2. Optional Encryption

// For convenience, allow encrypted storage
async function saveEncryptedMnemonic(mnemonic, password) {
  const encrypted = await encrypt(mnemonic, password);
  localStorage.setItem('encrypted_wallet', encrypted);
}

async function loadEncryptedMnemonic(password) {
  const encrypted = localStorage.getItem('encrypted_wallet');
  return await decrypt(encrypted, password);
}

🎯 Complete Implementation Example

// rin-web-wallet.js

import * as bip39 from 'bip39';
import { BIP32Factory } from 'bip32';
import * as ecc from 'tiny-secp256k1';
import * as bitcoin from 'bitcoinjs-lib';

const bip32 = BIP32Factory(ecc);

class RinWebWallet {
  constructor(rpcUrl, rpcUser, rpcPassword) {
    this.rpcUrl = rpcUrl;
    this.rpcUser = rpcUser;
    this.rpcPassword = rpcPassword;
  }
  
  // Create new wallet with mnemonic
  async createWallet(walletName) {
    // Generate mnemonic
    const mnemonic = bip39.generateMnemonic(128); // 12 words
    
    // Create wallet on node
    await this.rpcCall('createwallet', [walletName, false, false]);
    
    // Import initial addresses
    await this.importAddressesFromMnemonic(mnemonic, walletName, 20);
    
    return {
      mnemonic: mnemonic,
      message: 'WRITE DOWN THESE 12 WORDS!'
    };
  }
  
  // Restore wallet from mnemonic
  async restoreWallet(mnemonic, walletName) {
    // Validate
    if (!bip39.validateMnemonic(mnemonic)) {
      throw new Error('Invalid mnemonic');
    }
    
    // Create wallet on node
    await this.rpcCall('createwallet', [walletName, false, false]);
    
    // Import addresses with gap limit
    await this.importAddressesFromMnemonic(mnemonic, walletName, 100);
    
    // Rescan blockchain
    await this.rpcCall('rescanblockchain', [], walletName);
    
    const balance = await this.rpcCall('getbalance', [], walletName);
    
    return {
      success: true,
      balance: balance
    };
  }
  
  // Internal: Import addresses from mnemonic
  async importAddressesFromMnemonic(mnemonic, walletName, count) {
    const seed = await bip39.mnemonicToSeed(mnemonic);
    const root = bip32.fromSeed(seed);
    
    for (let i = 0; i < count; i++) {
      const child = root.derivePath(`m/44'/2'/0'/0/${i}`);
      const privateKey = child.toWIF();
      
      // Import to node
      await this.rpcCall('importprivkey', [
        privateKey,
        `address_${i}`,
        false  // Don't rescan yet
      ], walletName);
    }
  }
  
  // RPC call helper
  async rpcCall(method, params = [], wallet = null) {
    const url = wallet ? `${this.rpcUrl}/wallet/${wallet}` : this.rpcUrl;
    
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Basic ' + btoa(`${this.rpcUser}:${this.rpcPassword}`)
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: method,
        method: method,
        params: params
      })
    });
    
    const data = await response.json();
    if (data.error) throw new Error(data.error.message);
    return data.result;
  }
}

// Usage
const wallet = new RinWebWallet(
  'http://localhost:9556',
  'rinrpc',
  '745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90'
);

// Create new
const { mnemonic } = await wallet.createWallet('my_wallet');
console.log('SAVE THESE WORDS:', mnemonic);

// Restore
await wallet.restoreWallet('witch collapse practice feed shame open despair creek road again ice least', 'restored_wallet');

Benefits of This Approach

  1. Standard BIP39 - Compatible with hardware wallets, other software
  2. 12-word backup - Easy to write down, memorize, store
  3. Self-custody - User controls the mnemonic
  4. Cross-platform - Restore on any device
  5. Secure - Industry-standard cryptography
  6. User-friendly - Matches MetaMask/Trust Wallet UX
  7. Works with RinCoin - Uses importprivkey for each address

🚀 Next Steps

  1. Implement BIP39/BIP32 support in web wallet
  2. Test key derivation with RinCoin addresses
  3. Create beautiful UI for mnemonic generation/restore
  4. Add encrypted local storage option
  5. Implement proper gap limit scanning
  6. Add address book and transaction history

📚 Required Libraries

npm install bip39 bip32 tiny-secp256k1 bitcoinjs-lib

Or for Python backend:

pip install mnemonic bip32utils bitcoinlib

YES! You CAN create a secure, self-custody RinCoin wallet with easy mnemonic restore!

The key is implementing BIP39 support in your web wallet layer, not relying on RinCoin's limited RPC support. 🎉