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

451 lines
13 KiB
Markdown

# 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)
```javascript
// 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)
```javascript
// 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
```javascript
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)
```javascript
// 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
```javascript
// 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
```javascript
// 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
```bash
npm install bip39 bip32 tiny-secp256k1 bitcoinjs-lib
```
Or for Python backend:
```bash
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. 🎉