From 8d0c2620d3fe2254371371ec05ace11ba4390b55 Mon Sep 17 00:00:00 2001 From: Aitor Date: Wed, 3 Jun 2026 20:03:14 +0200 Subject: [PATCH] feat: deteccion de dusting de privacidad (POSIBLE) ademas del dust tecnico; fix: tono neutro en dust --- FIXTURES.md | 22 +++++++++++++++++++++- dashboard.html | 47 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/FIXTURES.md b/FIXTURES.md index 4abd3b1..8d5a467 100644 --- a/FIXTURES.md +++ b/FIXTURES.md @@ -24,7 +24,7 @@ nodo y corrige la fila). | 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 | ☐ | +| 5 | Dusting de privacidad (555 sats, 2018) | `655c533bf059721cec9d3d70b3171a07997991a02fedfa1c9b593abc645e1cc5` | Salida de 555 sats detectada como "posible dusting" (POSIBLE), banda MEDIA, tono neutro | ✅ 2026-06-03 | | 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 | ☐ | @@ -123,3 +123,23 @@ cada check ahora reflejan si el resultado es bueno o malo para la privacidad (verde = bueno), no solo el estado interno. Un CoinJoin detectado sale en verde. El badge de certeza se mantiene aparte, indicando solo el nivel de confianza. Esto resuelve en parte el punto anterior sobre la claridad del badge. + +### 2026-06-03 — Fixture #5 (dust attack) +El txid del roadmap resultó ser un dusting real, pero con un matiz importante: +la salida sospechosa era de **555 sats**, justo POR ENCIMA del umbral técnico de +dust (546). La app no lo detectaba, porque solo miraba el umbral técnico. Se +encontraron y corrigieron dos cosas: +1. Tono: el texto decía "trátalo como dust y no lo gastes" (consejo, contra la + regla "Txoko informa, no aconseja"). Reescrito para informar la consecuencia + y dejar decidir. +2. Detección: se añadió un segundo nivel. Ahora el motor distingue **dust + técnico** (< umbral, CERTEZA, es un hecho) de **posible dusting de privacidad** + (umbral a 1000 sats, POSIBLE, es una sospecha — cantidades pequeñas pero + gastables que un atacante usa a propósito para esquivar el umbral técnico). + El caso de 555 sats ahora se detecta como "posible dusting" con certeza + POSIBLE, sin afirmar de más. + +Nota de proceso: en una iteración, el cambio se quedó en el archivo de trabajo +pero no se copió bien al repo, y se presentó una versión sin el cambio. Lección: +verificar con grep que el archivo presentado contiene el cambio antes de +desplegar. diff --git a/dashboard.html b/dashboard.html index 3f60709..eb6d12d 100644 --- a/dashboard.html +++ b/dashboard.html @@ -1247,17 +1247,44 @@ return 546; // Legacy P2PKH / P2SH / desconocido }; const dustOutputs = tx.vout.filter(v => v.value > 0 && v.value < dustThreshold(v.scriptpubkey_type)); - const hasDust = dustOutputs.length > 0; - checks.push({ - id:"dust", label:"Outputs de dust detectados", certainty:"CERTEZA", pass:!hasDust, - actionability: "no_corregible", - detail: hasDust - ? `${dustOutputs.length} output(s) por debajo del umbral de dust (${dustOutputs.map(d=>d.value+"sat").join(", ")}). Hecho: hay salidas diminutas. Interpretación: podría ser un ataque de dusting, pero también un cambio diminuto de tu propio wallet o una salida de protocolo (Lightning, inscripciones). No se puede distinguir solo con esta transacción — por precaución, trátalo como dust y no lo gastes junto a otros UTXOs.` - : "Sin outputs de dust (según el umbral de cada tipo de salida).", - didactic: "Un ataque de dust envía cantidades mínimas a múltiples direcciones. Cuando el receptor gasta ese dust combinándolo con otros UTXOs, revela qué UTXOs pertenecen al mismo wallet. Es una técnica de surveillance — pero no toda salida diminuta es un ataque: también las hay legítimas (cambios pequeños, salidas de protocolo). Lo prudente es no gastar dust sin querer.", - penalty: weights.dust, + // Dusting de privacidad: salidas pequeñas pero por ENCIMA del umbral técnico + // (un atacante manda 555, 888... gastables a propósito para que no sean + // "dust técnico" y el receptor las tenga en el wallet). No es un hecho como + // el dust técnico, sino una sospecha -> certeza POSIBLE. + const DUSTING_CEIL = 1000; + const dustingOutputs = tx.vout.filter(v => { + const th = dustThreshold(v.scriptpubkey_type); + return v.value >= th && v.value < DUSTING_CEIL; }); - if (hasDust) deductions += weights.dust; + const hasDust = dustOutputs.length > 0; + const hasDusting = !hasDust && dustingOutputs.length > 0; + if (hasDust) { + checks.push({ + id:"dust", label:"Outputs de dust detectados", certainty:"CERTEZA", pass:false, + actionability: "no_corregible", + detail: `${dustOutputs.length} output(s) por debajo del umbral de dust (${dustOutputs.map(d=>d.value+"sat").join(", ")}). Hecho: hay salidas por debajo del mínimo económico de la red. Interpretación: podría ser un ataque de dusting, pero también un cambio diminuto de tu propio wallet o una salida de protocolo (Lightning, inscripciones). No se puede distinguir solo con esta transacción. Consecuencia: si esa salida se gasta junto a otros UTXOs, queda vinculada con ellos — que es justo lo que persigue un ataque de dusting.`, + didactic: "Un ataque de dust envía cantidades mínimas a múltiples direcciones. Cuando el receptor gasta ese dust combinándolo con otros UTXOs, revela qué UTXOs pertenecen al mismo wallet. Es una técnica de surveillance — pero no toda salida diminuta es un ataque: también las hay legítimas (cambios pequeños, salidas de protocolo). La marca se materializa solo si el dust se gasta junto a otras monedas.", + penalty: weights.dust, + }); + deductions += weights.dust; + } else if (hasDusting) { + checks.push({ + id:"dust", label:"Salida muy pequeña (posible dusting)", certainty:"POSIBLE", pass:false, + actionability: "no_corregible", + detail: `${dustingOutputs.length} output(s) de cantidad muy pequeña (${dustingOutputs.map(d=>d.value+"sat").join(", ")}), por encima del mínimo técnico pero inusualmente bajos. Hecho: son salidas diminutas pero gastables. Interpretación: compatible con un dusting de privacidad (cantidades pequeñas enviadas a propósito por encima del umbral de dust para que el receptor las conserve), aunque también con un pago o cambio pequeño legítimo. No se puede confirmar solo con esta transacción. Consecuencia: si esa salida se gasta junto a otros UTXOs, queda vinculada con ellos.`, + didactic: "El umbral de dust técnico (≈546 sats) marca lo que la red considera antieconómico de gastar. Pero un dusting de privacidad suele usar cantidades algo mayores —gastables a propósito— para que el receptor las mantenga en su wallet y, al gastarlas, revele qué UTXOs son suyos. Por eso una salida pequeña por encima del umbral técnico merece atención, aunque no sea concluyente.", + penalty: weights.dust, + }); + deductions += weights.dust; + } else { + checks.push({ + id:"dust", label:"Outputs de dust detectados", certainty:"CERTEZA", pass:true, + actionability: "no_corregible", + detail: "Sin outputs de dust ni salidas inusualmente pequeñas (según el umbral de cada tipo de salida).", + didactic: "Un ataque de dust envía cantidades mínimas a múltiples direcciones. Cuando el receptor gasta ese dust combinándolo con otros UTXOs, revela qué UTXOs pertenecen al mismo wallet. Es una técnica de surveillance.", + penalty: weights.dust, informational: true, + }); + } // ── 10. Change detection — señales combinadas ───────────────────── let changeSignals = 0;