From 9ee860a78ac4d905945d1d3898277be8d413e558 Mon Sep 17 00:00:00 2001 From: Aitor Date: Sat, 30 May 2026 19:12:24 +0200 Subject: [PATCH] =?UTF-8?q?inicio:=20dashboard=20de=20privacidad=20Bitcoin?= =?UTF-8?q?=20con=20an=C3=A1lisis=20on-chain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 23 + LICENSE | 21 + README.md | 171 +++ SETUP.md | 158 +++ dashboard.html | 2850 +++++++++++++++++++++++++++++++++++++++++ system-metrics.js | 157 +++ txoko-metrics.service | 19 + 7 files changed, 3399 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SETUP.md create mode 100644 dashboard.html create mode 100644 system-metrics.js create mode 100644 txoko-metrics.service diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0da63d --- /dev/null +++ b/.gitignore @@ -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* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..27c7e94 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..847a4e3 --- /dev/null +++ b/README.md @@ -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* diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..62edcec --- /dev/null +++ b/SETUP.md @@ -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. diff --git a/dashboard.html b/dashboard.html new file mode 100644 index 0000000..aeab05e --- /dev/null +++ b/dashboard.html @@ -0,0 +1,2850 @@ + + + + + + Txoko Node Dashboard + + + + + + + + +
+ + + diff --git a/system-metrics.js b/system-metrics.js new file mode 100644 index 0000000..1733f11 --- /dev/null +++ b/system-metrics.js @@ -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}`); +}); diff --git a/txoko-metrics.service b/txoko-metrics.service new file mode 100644 index 0000000..836ada7 --- /dev/null +++ b/txoko-metrics.service @@ -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