451 lines
13 KiB
Markdown
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. 🎉
|