inicio: dashboard de privacidad Bitcoin con análisis on-chain

This commit is contained in:
Aitor
2026-05-30 19:12:24 +02:00
commit 9ee860a78a
7 changed files with 3399 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# Credenciales y configuración local — NUNCA subir al repo
system-metrics.local.js
config.local.js
*.env
.env*
# Archivos de sistema
.DS_Store
Thumbs.db
desktop.ini
# Logs
*.log
logs/
# Archivos temporales
*.tmp
*.swp
*~
# Node
node_modules/
npm-debug.log*
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Txoko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+171
View File
@@ -0,0 +1,171 @@
# Txoko Node Dashboard
Suite de auditoría de privacidad Bitcoin para nodos propios.
**your node, your rules** — ninguna consulta sale de tu red.
---
## Qué es
Txoko es una herramienta de auditoría de privacidad Bitcoin que corre sobre tu propio nodo. Te permite analizar tus transacciones y entender qué puede deducir un observador externo sobre tu actividad on-chain.
No es un explorador de bloques genérico. Es una herramienta para que el propietario de un nodo audite su propia privacidad con total control sobre sus datos.
A diferencia de OXT, Blockchair o am-i.exposed, **ninguna consulta sale de tu red**. Todo el análisis ocurre en tu nodo, en tu navegador.
---
## Funcionalidades
- **Privacy Lab** — análisis completo de una transacción con 15 heurísticas ponderadas
- **Informe narrativo** — explicación en prosa de lo que puede deducir un analista, sin jerga
- **UTXO Map** — mapa de tus UTXOs con score de privacidad por moneda
- **Wallet fingerprinting** — detecta qué wallet generó la transacción
- **Detección de CoinJoin** — Whirlpool, WabiSabi, JoinMarket, genérico
- **Explorador de bloques** — bloques recientes y búsqueda on-chain
- **Mempool** — stats, fees, fee intelligence, bloques proyectados
- **Monitor del nodo** — estado de Bitcoin Core, sistema, logs en tiempo real
- **Herramientas** — conversor sat/BTC/EUR, validador de dirección, decodificadores
---
## Filosofía
- **Sin dependencias externas de datos** — todo viene de tu propio nodo
- **Transparencia sobre certeza vs inferencia** — siempre distingue lo que es hecho de lo que es probabilidad
- **Didáctica honesta** — explicar las limitaciones es tan importante como el análisis
- **Ligero por defecto** — no estresar el nodo
---
## Requisitos
- Bitcoin Core con `txindex=1`
- [Mempool.space self-hosted](https://github.com/mempool/mempool) (backend API)
- [Fulcrum](https://github.com/cculianu/Fulcrum) (índice Electrum, recomendado)
- nginx como proxy inverso
- Acceso via Tailscale, red local o similar
Probado sobre Ubuntu Server 24.04 con HP EliteDesk (i5, 32GB RAM, 2TB NVMe).
---
## Instalación
### 1. Clonar el repositorio
```bash
git clone https://gitea.tu-comunidad/txoko-dashboard.git
cd txoko-dashboard
```
### 2. Copiar el dashboard
```bash
cp dashboard.html /var/www/txoko/dashboard.html
# o donde lo sirvas con nginx
```
### 3. Copiar el backend de métricas
```bash
cp system-metrics.js /home/armg/txoko/system-metrics.js
```
Editar `system-metrics.js` y añadir tus credenciales RPC de Bitcoin Core
(el archivo del repo usa placeholders).
### 4. Configurar el servicio systemd
```bash
sudo cp txoko-metrics.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable txoko-metrics
sudo systemctl start txoko-metrics
```
### 5. Configurar nginx
Añadir a tu configuración de nginx:
```nginx
# Dashboard
location /dashboard {
alias /var/www/txoko/dashboard.html;
}
# API Mempool (ajusta el puerto según tu instalación)
location /api/ {
proxy_pass http://localhost:8999;
}
# Métricas del sistema
location /system/ {
proxy_pass http://127.0.0.1:4082;
}
```
### 6. Abrir en el navegador
```
http://TU-IP-TAILSCALE:4080/dashboard
```
Introduce la URL de tu Mempool en el modal de configuración y empieza a analizar.
---
## Estructura del repositorio
```
txoko-dashboard/
├── dashboard.html # Frontend completo (HTML + CSS + JS en un solo archivo)
├── system-metrics.js # Backend de métricas del nodo (Node.js)
├── txoko-metrics.service # Servicio systemd
├── README.md
└── LICENSE
```
El frontend es un único archivo HTML autocontenido. Sin bundler, sin npm, sin proceso de build. Babel transpila el JSX en el navegador. Puedes auditarlo todo abriendo el archivo.
---
## Limitaciones conocidas
- Análisis de transacción aislada — sin acceso al grafo completo, algunas heurísticas tienen techo
- Sin base de datos de entidades — no identifica exchanges o servicios KYC (en desarrollo)
- El score de privacidad muestra bandas (ALTA/MEDIA/BAJA), no un número exacto — la precisión numérica sería falsa
- El fingerprinting de wallet requiere mínimo varias señales coincidentes para aparecer
---
## Comparativa
| | Txoko | Mempool self-hosted | OXT | Blockchair | am-i.exposed |
|--|-------|-------------------|-----|------------|--------------|
| Self-hosted | ✓ | ✓ | ✗ | ✗ | Parcial |
| Sin exponer consultas | ✓✓ | ✓✓ | ✗ | ✗ | Parcial |
| Análisis de privacidad | ✓✓ | ✗ | ✓✓ | ✓ | ✓✓ |
| Informe narrativo | ✓✓ | ✗ | ✗ | ✗ | ✗ |
| Wallet fingerprinting | ✓ | ✗ | ✗ | ✗ | ✓ |
| UTXO Map con score | ✓ | ✗ | Parcial | ✗ | ✗ |
| Didáctica transparente | ✓✓ | ✗ | ✗ | ✗ | ✓ |
---
## Contribuir
Proyecto en desarrollo activo. Issues y PRs bienvenidos.
Si encuentras un false positive en alguna heurística o tienes un caso de prueba interesante, abre un issue con el txid y lo que esperabas ver.
---
## Licencia
MIT — ver [LICENSE](LICENSE)
---
*₿ txoko · your node, your rules*
+158
View File
@@ -0,0 +1,158 @@
# Cómo subir Txoko a Gitea — paso a paso
Instrucciones exactas. Copiar y pegar en la terminal del Mac.
No hace falta entender git para seguir esto.
---
## PASO 1 — Crear el repo en Gitea (una sola vez)
1. Abre tu instancia de Gitea en el navegador
2. Clic en el **+** (arriba a la derecha) → "New Repository"
3. Rellena:
- **Repository Name:** `txoko-dashboard`
- **Description:** `Suite de auditoría de privacidad Bitcoin para nodos propios`
- **Visibility:** Private (o Public si quieres compartirlo con la comunidad)
- **Initialize repository:** NO marcar (ya traemos nuestros archivos)
4. Clic en "Create Repository"
5. Gitea te muestra una página con instrucciones — copia la URL del repo,
será algo como: `https://gitea.tu-comunidad/tu-usuario/txoko-dashboard.git`
---
## PASO 2 — Configurar git en el Mac (una sola vez, si no lo tienes)
```bash
git config --global user.name "tu nombre"
git config --global user.email "tu@email.com"
```
Comprueba que git está instalado:
```bash
git --version
```
Si no está: `brew install git`
---
## PASO 3 — Crear el repo local y primer commit (una sola vez)
```bash
# Crear carpeta del proyecto en el Mac
mkdir ~/txoko-dashboard
cd ~/txoko-dashboard
# Copiar los archivos del repo que te he preparado
# (descarga los archivos de esta conversación y cópialos aquí)
# Inicializar git
git init
git branch -M main
# Añadir todos los archivos
git add .
# Primer commit — el historial empieza aquí
git commit -m "inicio: dashboard de privacidad Bitcoin con análisis on-chain"
```
---
## PASO 4 — Conectar con Gitea y subir (una sola vez)
```bash
# Sustituye la URL por la de tu repo de Gitea
git remote add origin https://gitea.tu-comunidad/tu-usuario/txoko-dashboard.git
# Subir
git push -u origin main
```
Gitea te pedirá usuario y contraseña la primera vez.
Si quieres evitar introducirlos cada vez, crea un token en
Gitea → Settings → Applications → "Generate Token" y úsalo como contraseña.
---
## PASO 5 — Flujo de trabajo normal (cada vez que yo te dé un archivo nuevo)
```bash
cd ~/txoko-dashboard
# Copiar el archivo actualizado descargado de esta conversación
cp ~/Downloads/txoko-dashboard.html ./dashboard.html
# Ver qué cambió (opcional pero útil)
git diff dashboard.html
# Registrar el cambio con un mensaje descriptivo
git add dashboard.html
git commit -m "fix: descripción breve de lo que se arregló"
# Subir a Gitea
git push
# Copiar al nodo (igual que antes)
scp dashboard.html armg@100.116.19.86:/home/armg/txoko/dashboard.html
```
---
## Mensajes de commit — ejemplos
El mensaje va después de `-m` y describe QUÉ cambiaste.
No tiene que ser perfecto, solo útil para ti en el futuro.
```
"fix: timeout en consultas al nodo"
"feat: score por bandas ALTA/MEDIA/BAJA"
"fix: responsive pestaña Mempool en móvil"
"fix: detección Whirlpool con tolerancia 2%"
"feat: fingerprinting wallet completo"
"fix: umbral dust por tipo de salida"
"docs: actualizar README con nuevas funcionalidades"
```
Convención (opcional pero ordenada):
- `fix:` — corrige algo que no funcionaba bien
- `feat:` — añade algo nuevo
- `docs:` — solo documentación
---
## Si algo sale mal
**Subiste algo que no querías:**
```bash
git revert HEAD
git push
```
**Quieres volver a una versión anterior:**
```bash
git log --oneline # ver el historial
git checkout HASH_DEL_COMMIT -- dashboard.html # recuperar ese archivo
```
**Ver el historial:**
```bash
git log --oneline
```
---
## Resumen del flujo
```
Claude te da dashboard.html
cp ~/Downloads/dashboard.html ./dashboard.html
git add . && git commit -m "descripción"
git push
scp dashboard.html armg@100.116.19.86:/home/armg/txoko/
```
Cuatro comandos después de la descarga. Siempre los mismos.
+2850
View File
File diff suppressed because it is too large Load Diff
+157
View File
@@ -0,0 +1,157 @@
/**
* txoko-metrics Backend de métricas del nodo Bitcoin
*
* Servidor HTTP Node.js que escucha en 127.0.0.1:4082.
* Sirve métricas del sistema y estado de Bitcoin Core al dashboard.
*
* CONFIGURACIÓN:
* Editar las variables de la sección "CONFIGURACIÓN LOCAL" con
* los datos de tu nodo antes de arrancar.
*
* ARRANCAR:
* node system-metrics.js
*
* O como servicio systemd:
* sudo systemctl start txoko-metrics
*/
// ── CONFIGURACIÓN LOCAL ──────────────────────────────────────────────
// Copia este archivo como system-metrics.local.js y edita estos valores.
// El archivo .local.js está en .gitignore — nunca se sube al repo.
const RPC_HOST = '127.0.0.1';
const RPC_PORT = 8332;
const RPC_USER = 'TU_RPC_USER'; // Ver bitcoin.conf → rpcuser
const RPC_PASSWORD = 'TU_RPC_PASSWORD'; // Ver bitcoin.conf → rpcpassword
// Alternativa: autenticación por cookie (más seguro)
// const RPC_COOKIE = '/data/bitcoin/.cookie';
const METRICS_PORT = 4082;
const METRICS_HOST = '127.0.0.1'; // Solo localhost — no exponer al exterior
// ── FIN CONFIGURACIÓN ────────────────────────────────────────────────
const http = require('http');
const { execSync } = require('child_process');
const fs = require('fs');
const os = require('os');
// Cliente RPC Bitcoin Core
async function rpcCall(method, params = []) {
const body = JSON.stringify({ jsonrpc: '1.0', id: 'txoko', method, params });
const auth = Buffer.from(`${RPC_USER}:${RPC_PASSWORD}`).toString('base64');
return new Promise((resolve, reject) => {
const req = http.request({
host: RPC_HOST, port: RPC_PORT, method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${auth}` }
}, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const parsed = JSON.parse(data);
if (parsed.error) reject(parsed.error);
else resolve(parsed.result);
} catch (e) { reject(e); }
});
});
req.on('error', reject);
req.write(body);
req.end();
});
}
// Endpoints
const routes = {
'/system/info': async () => {
const cpus = os.cpus();
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
// CPU usage (snapshot simple)
const cpuUsage = cpus.reduce((sum, cpu) => {
const total = Object.values(cpu.times).reduce((a, b) => a + b, 0);
const idle = cpu.times.idle;
return sum + ((total - idle) / total) * 100;
}, 0) / cpus.length;
return {
cpu: { usage: cpuUsage.toFixed(1), cores: cpus.length, model: cpus[0]?.model },
memory: {
total: totalMem, free: freeMem, used: usedMem,
usedPercent: ((usedMem / totalMem) * 100).toFixed(1)
},
uptime: os.uptime(),
platform: os.platform(),
hostname: os.hostname()
};
},
'/system/bitcoin': async () => {
const [info, netinfo, mempoolinfo] = await Promise.all([
rpcCall('getblockchaininfo'),
rpcCall('getnetworkinfo'),
rpcCall('getmempoolinfo')
]);
return { blockchain: info, network: netinfo, mempool: mempoolinfo };
},
'/system/logs/bitcoin': async () => {
try {
const lines = execSync('journalctl -u bitcoind -n 80 --no-pager -o short-iso 2>/dev/null || tail -80 /data/bitcoin/debug.log 2>/dev/null || echo ""')
.toString().trim().split('\n').filter(Boolean);
return { logs: lines.map(line => ({
line,
level: line.includes('error') || line.includes('ERROR') ? 'error'
: line.includes('warn') || line.includes('WARN') ? 'warn'
: 'info'
}))};
} catch { return { logs: [] }; }
},
'/system/logs/fulcrum': async () => {
try {
const lines = execSync('journalctl -u fulcrum -n 80 --no-pager -o short-iso 2>/dev/null || echo ""')
.toString().trim().split('\n').filter(Boolean);
return { logs: lines.map(line => ({
line,
level: line.includes('Error') || line.includes('error') ? 'error'
: line.includes('Warn') || line.includes('warn') ? 'warn'
: 'info'
}))};
} catch { return { logs: [] }; }
},
'/system/health': async () => ({ status: 'ok', ts: Date.now() })
};
// Servidor HTTP
const server = http.createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'application/json');
// Extraer ruta sin query string
const path = req.url.split('?')[0];
const handler = routes[path];
if (!handler) {
res.writeHead(404);
return res.end(JSON.stringify({ error: 'Not found' }));
}
try {
const data = await handler();
res.writeHead(200);
res.end(JSON.stringify(data));
} catch (err) {
res.writeHead(500);
res.end(JSON.stringify({ error: err.message || 'Internal error' }));
}
});
server.listen(METRICS_PORT, METRICS_HOST, () => {
console.log(`txoko-metrics escuchando en ${METRICS_HOST}:${METRICS_PORT}`);
});
+19
View File
@@ -0,0 +1,19 @@
[Unit]
Description=Txoko Node Metrics — backend de métricas para el dashboard
After=network.target bitcoind.service
[Service]
Type=simple
User=armg
ExecStart=/usr/bin/node /home/armg/txoko/system-metrics.js
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
# Seguridad básica
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target