From b77d21fd8479da8098c5afbfa0dff021a927826b Mon Sep 17 00:00:00 2001 From: Aitor Date: Wed, 3 Jun 2026 19:18:47 +0200 Subject: [PATCH] fix: batch payment penaliza correctamente + textos coherentes; docs: fixtures verificados --- FIXTURES.md | 115 +++++++++++++++++++++++++++++++++++++++++++++++++ dashboard.html | 15 ++++--- 2 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 FIXTURES.md diff --git a/FIXTURES.md b/FIXTURES.md new file mode 100644 index 0000000..0c842d0 --- /dev/null +++ b/FIXTURES.md @@ -0,0 +1,115 @@ +# Fixtures de validación — Txoko Node Dashboard + +Conjunto de transacciones y direcciones reales conocidas para validar que el +motor de análisis dice lo correcto en cada tipo de caso. Es la red de seguridad +contra falsos positivos: antes de publicar un cambio en el motor, se pasan estos +casos y se confirma que el resultado sigue siendo el esperado. + +**Cómo usarlo:** analiza cada txid/dirección en la app (apuntando a tu propio +nodo) y compara el resultado con la columna "Resultado esperado". Si algo no +coincide, ahí hay un fallo (o el fixture estaba mal etiquetado: verifícalo en tu +nodo y corrige la fila). + +> Nota: estos txids vienen del documento de diseño y **conviene reverificarlos +> contra tu nodo la primera vez**. Marca en la columna "Verificado" cuándo +> confirmaste cada uno. Un fixture con un txid equivocado es peor que no tenerlo. + +--- + +## Transacciones + +| # | Tipo | txid | Resultado esperado | Verificado | +|---|------|------|--------------------|------------| +| 1 | Whirlpool CoinJoin | `323df21f0b0756f98336437aa3d2fb87e02b59f1946b714a7b09df04d429dec2` | CoinJoin detectado, banda ALTA, fingerprint "Samourai/Sparrow" inferido | ☐ | +| 2 | WabiSabi CoinJoin | `fb596c9f675471019c60e984b569f9020dac3b2822b16396042b50c890b45e5e` | CoinJoin grande, banda ALTA, "Wasabi" inferido | ☐ | +| 3 | JoinMarket CoinJoin | `4f112abd2eefe3484a7bbf7c1731f784cba19de677468835145e9c448fb18b7d` | CoinJoin con outputs iguales, verificar mínimo ~10k sats | ☐ | +| 4 | Batch payment 144 outputs (F2Pool, 2022-02-15) | `ef5d100a70eea9b7349a40fdfd51064fc5cebf5e3b4240f92591569836896949` | 1→144 outputs, **NO** CoinJoin, banda **MEDIA**, check "Pago por lotes" penaliza (−45) | ✅ 2026-06-03 | +| 5 | Dust attack 555 sats | `655c533bf059721cec9d3d70b3171a07997991a02fedfa1c9b593abc645e1cc5` | Dust detectado con tipo correcto, no falso positivo, banda BAJA | ☐ | +| 6 | Taproot + OP_RETURN | `0bf67b1f05326afbd613e11631a2b86466ac7e255499f6286e31b9d7d889cee7` | OP_RETURN detectado (presencia/tamaño), banda BAJA | ☐ | +| 7 | Legacy P2PKH simple | `0b6461de422c46a221db99608fcbe0326e4f2325ebf2a47c9faf660ed61ee6a4` | Tipo legacy, fingerprint conservador, banda MEDIA/BAJA | ☐ | +| 8 | Taproot script-path | `37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8` | Taproot reconocido | ☐ | +| 9 | Bare multisig | `60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1` | Multisig reconocido | ☐ | +| 10 | OP_RETURN con datos ASCII | `8bae12b5f4c088d940733dcd1455efc6a3a69cf9340e17a981286d3778615684` | OP_RETURN detectado (solo presencia/tamaño, **no** mostrar contenido) | ☐ | + +## Direcciones + +| # | Tipo | address | Resultado esperado | Verificado | +|---|------|---------|--------------------|------------| +| A | Máxima reutilización (Satoshi genesis) | `1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa` | Reutilización crítica, banda BAJA extrema | ☐ | + +--- + +## Orden de privacidad esperado (coherencia global) + +El motor debería ordenar la privacidad de forma coherente, de más a menos: + +``` +Whirlpool / WabiSabi > Taproot simple > Legacy simple > Batch > Dust / Satoshi +``` + +Si un batch sale con mejor banda que un Whirlpool, hay una incoherencia. + +--- + +## Matices de honestidad (no basta con que la banda sea correcta) + +Un motor honesto no es un animador. Estos matices distinguen una herramienta +seria de una que da palmaditas. Revisar que los textos los respetan: + +### Whirlpool / CoinJoin +- Premia la mezcla (banda ALTA) pero **sin vender humo**: es alta privacidad en + ESTA transacción, no anonimato permanente. Si luego se gastan mal las monedas + (juntándolas, mandándolas a exchange KYC), se tira la mezcla. +- El tamaño del anonymity set importa: no dar el mismo "ALTA" a una mezcla floja + (pocos participantes) que a una buena. +- Honestidad sobre lo que NO ve: en una sola tx no se sabe si eres quien entra o + sale, ni qué pasó antes/después. El motor juzga la foto, no la historia (eso lo + completa el rastro de procedencia). +- No penalizar lo normal de un CoinJoin (outputs iguales, wallet identificado): + es el objetivo, no un fallo. + +### Batch payment (de exchange) +- Detectar que es un BATCH y **no confundirlo con CoinJoin**. Un CoinJoin mezcla + para ocultar; un batch solo agrupa pagos. Si dice "CoinJoin" cuando es batch, + el motor miente. +- Banda MEDIA/BAJA. +- Texto neutro: informar sin juzgar ni consolar. + - Nada de "no es culpa tuya" (paternalista; un noderunner quiere el dato). + - Sí dejar claro que los pagos vienen de un exchange/servicio (implica KYC, + custodia de terceros). + - Explicar el riesgo seco: se comparte origen con los otros destinatarios del + lote, un observador puede agruparlos. + +### Dust attack +- Detectar que es polvo enviado a propósito (cantidad mínima inesperada), marcar + esa moneda, banda BAJA. +- Explicar qué es y su consecuencia: gastarlo junto a otras monedas vincularía + esa marca con el resto del wallet. +- Tono neutro: informar la consecuencia, **no** aconsejar qué hacer. (La trampa + del dust es que el usuario asustado lo gasta "para quitárselo" y se autovincula; + el tono neutro informa sin dirigir y confía en que el usuario decide.) + +### Regla general +**Txoko informa, no aconseja.** Da el dato y la consecuencia; el usuario decide. + +--- + +## Registro de hallazgos (lo que han cazado los fixtures) + +### 2026-06-03 — Fixture #4 (batch payment) +Al validar un batch real de 144 outputs (F2Pool) se encontraron tres fallos, +todos corregidos: +1. La banda daba ALTA a un batch (no penalizaba). Ahora el batch penaliza + (peso 45) y un batch claro cae a MEDIA. +2. El texto del check de batch decía "esto no es un fallo tuyo" (paternalista, + contra la regla de diseño). Reescrito en tono neutro, explicando el riesgo + real (compartir origen con los otros destinatarios del lote). +3. La descripción de la banda decía "buenas propiedades de privacidad" aun + cuando la banda era MEDIA (umbrales del texto descuadrados respecto a la + banda). Alineados a 75/45. + +También se observó (anotado, no bloqueante): el badge de certeza junto al +título de cada check (CERTEZA/PROBABLE/POSIBLE) puede confundir cuando el check +da resultado negativo —por ejemplo "Estructura CoinJoin / mezcla — PROBABLE" +cuando en realidad NO hay CoinJoin (el resultado real está en el detalle, no en +el badge). Pendiente de mejorar la claridad de la presentación. diff --git a/dashboard.html b/dashboard.html index c386ce3..29a51ff 100644 --- a/dashboard.html +++ b/dashboard.html @@ -1024,7 +1024,7 @@ input_reuse: 25, input_type_mixing: 14, output_type_mismatch: 8, round_numbers: 10, rbf: 5, peeling: 8, unnecessary_input: 12, dust: 8, change_detection: 10, - wallet_fingerprint: 6, batch_payment: 4, entity_ofac: 20, + wallet_fingerprint: 6, batch_payment: 45, entity_ofac: 20, }; let deductions = 0; @@ -1226,7 +1226,7 @@ // Por ahora: informativo, sin penalización, para no dar un falso positivo. const isPeeling = tx.vin.length === 1 && tx.vout.length === 2 && !likelyCJ; checks.push({ - id:"peeling", label:"Patrón de pago simple (1 input, 2 outputs)", certainty:"informativo", pass:!isPeeling, + id:"peeling", label:"Patrón de pago simple (peeling)", certainty:"informativo", pass:!isPeeling, actionability: "evitable", detail: isPeeling ? "1 input y 2 outputs: el patrón de pago más común (un pago + un cambio). Por sí solo no es un problema. Solo se convierte en peeling chain si se encadena con otras transacciones iguales — eso no puede confirmarse mirando una transacción aislada." @@ -1388,14 +1388,15 @@ const distinctRatio = tx.vout.length > 0 ? uniqueOutValues.size / tx.vout.length : 0; const isBatch = tx.vout.length >= 5 && distinctRatio >= 0.8 && uniqueOutValues.size >= 5 && !likelyCJ; checks.push({ - id:"batch_payment", label:"Pago por lotes (batch payment)", certainty:"informativo", pass:!isBatch, + id:"batch_payment", label:"Pago por lotes (batch payment)", certainty:"PROBABLE", pass:!isBatch, actionability: "no_corregible", detail: isBatch - ? `${tx.vout.length} outputs, casi todos con valores distintos. Compatible con un exchange o servicio enviando a múltiples destinatarios en una sola transacción. Si eres uno de los receptores, esto no es un fallo tuyo.` + ? `${tx.vout.length} outputs, casi todos con valores distintos: compatible con un exchange o servicio que paga a muchos destinatarios en una sola transacción. Compartes los mismos inputs —y por tanto el mismo origen— con los otros destinatarios del lote, lo que permite a un observador agruparos.` : "No se detecta patrón de batch payment.", - didactic: "Exchanges y custodios agrupan múltiples pagos en una transacción para ahorrar fees. Si recibes fondos de un batch, otros destinatarios podrían correlacionar sus pagos con los tuyos porque comparten los mismos inputs — y por tanto el mismo origen. No es algo que puedas controlar como receptor, pero conviene saberlo.", - penalty: 0, informational: true, + didactic: "Exchanges y custodios agrupan múltiples pagos en una transacción para ahorrar comisiones. Recibir de un batch reduce tu privacidad: los demás destinatarios comparten origen contigo, así que un observador puede correlacionar todos los pagos del lote. Y si el origen es un exchange con KYC, ese servicio conoce la identidad asociada. Por eso baja la valoración aunque tú no controles cómo te pagaron.", + penalty: isBatch ? weights.batch_payment : 0, }); + if (isBatch) deductions += weights.batch_payment; // ── 15. Timing analysis — informativo ──────────────────────────── let timingDetail = "No hay timestamp disponible (transacción pendiente o sin confirmar)."; @@ -1783,7 +1784,7 @@
NIVEL DE PRIVACIDAD
{summary}
- {score>=70?"La transacción presenta buenas propiedades de privacidad.":score>=40?"Se detectaron características que reducen la privacidad.":"Esta transacción tiene características que facilitan su rastreo."} + {score>=75?"La transacción presenta buenas propiedades de privacidad.":score>=45?"La transacción es funcional, pero tiene características que reducen su privacidad.":"Esta transacción tiene características que facilitan su rastreo."}
{wallets&&wallets.length>0&&(