fix: batch payment penaliza correctamente + textos coherentes; docs: fixtures verificados

This commit is contained in:
Aitor
2026-06-03 19:18:47 +02:00
parent 889ffeb8dd
commit b77d21fd84
2 changed files with 123 additions and 7 deletions
+115
View File
@@ -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.
+8 -7
View File
@@ -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 @@
<div style={{fontSize:"0.7rem",color:C.t2,fontFamily:"monospace",marginBottom:4}}>NIVEL DE PRIVACIDAD</div>
<div style={{fontSize:"1rem",color:summaryColor,fontFamily:"monospace",fontWeight:700,marginBottom:4}}>{summary}</div>
<div style={{fontSize:"0.65rem",color:C.t2,lineHeight:1.5}}>
{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."}
</div>
{wallets&&wallets.length>0&&(
<div style={{marginTop:6,fontSize:"0.62rem",color:C.t2,fontFamily:"monospace"}}>