fix: no atribuir al actor direcciones del otro lado de un CoinJoin
El informe decía 'el rastro se rompe en un CoinJoin, no se puede atribuir con honestidad más allá de este punto' y acto seguido listaba 10 direcciones atribuidas al actor, cinco de ellas salidas de esa misma mezcla — es decir, de otros participantes, señalados en un documento para una denuncia. CIOH no aplica dentro de un CoinJoin (es su excepción clásica: la mezcla existe para romper la suposición de dueño común) y la huella de software sale estable por construcción, porque todos usan el mismo programa. finalizeForensicGraph excluye las tx marcadas como mixer del conjunto del actor y del union-find; la atribución por huella salta esos nodos. Verificado contra el nodo con un Whirlpool real: de 10 atribuciones a 0.
This commit is contained in:
@@ -42,6 +42,24 @@ y el versionado sigue [Versionado Semántico](https://semver.org/lang/es/):
|
|||||||
`system-metrics.js` en el servidor.
|
`system-metrics.js` en el servidor.
|
||||||
- Medido: repetir un rastreo forense ya explorado cuesta **cero peticiones**.
|
- Medido: repetir un rastreo forense ya explorado cuesta **cero peticiones**.
|
||||||
|
|
||||||
|
### Corregido
|
||||||
|
- **El informe ya no atribuye al actor direcciones del otro lado de un
|
||||||
|
CoinJoin.** Detectado probando un rastro que atraviesa un Whirlpool real: el
|
||||||
|
informe declaraba *"el rastro se rompe en un CoinJoin — no se puede atribuir
|
||||||
|
con honestidad más allá de este punto"* y, tres líneas más abajo, listaba
|
||||||
|
**10 direcciones atribuidas al actor**, cinco de ellas salidas de esa misma
|
||||||
|
mezcla. Eran direcciones de otros participantes, señaladas en un documento
|
||||||
|
pensado para acompañar una denuncia.
|
||||||
|
- Los dos fundamentos que las colaban fallan precisamente ahí: **CIOH** asume
|
||||||
|
que quien gasta varios inputs juntos los controla, y un CoinJoin existe para
|
||||||
|
romper esa suposición (es la excepción clásica a la heurística); y la
|
||||||
|
**huella de software** sale estable por construcción, porque todos los
|
||||||
|
participantes usan el mismo programa.
|
||||||
|
- `finalizeForensicGraph` excluye ahora las transacciones marcadas como
|
||||||
|
`mixer` del conjunto de direcciones del actor y del union-find, y la
|
||||||
|
atribución por huella salta esos nodos.
|
||||||
|
- Verificado con el mismo caso: de 10 direcciones atribuidas a 0.
|
||||||
|
|
||||||
---
|
---
|
||||||
## [1.10.1] — 2026-07-27
|
## [1.10.1] — 2026-07-27
|
||||||
### Añadido
|
### Añadido
|
||||||
|
|||||||
+19
-1
@@ -4201,12 +4201,24 @@
|
|||||||
// pertenecen al servicio, no al mismo actor rastreado). Exclusión por
|
// pertenecen al servicio, no al mismo actor rastreado). Exclusión por
|
||||||
// dirección, no por nodo entero — un pago normal junto a un depósito
|
// dirección, no por nodo entero — un pago normal junto a un depósito
|
||||||
// de exchange en la misma tx sí cuenta como del actor.
|
// de exchange en la misma tx sí cuenta como del actor.
|
||||||
|
// Los CoinJoin quedan FUERA por completo, entradas y salidas. CIOH
|
||||||
|
// asume que quien gasta varios inputs juntos los controla, y un CoinJoin
|
||||||
|
// existe precisamente para romper esa suposición: es la excepción
|
||||||
|
// clásica a la heurística. Sus entradas y salidas son de participantes
|
||||||
|
// distintos, así que meterlas aquí produce un falso positivo garantizado
|
||||||
|
// y contradice la propia conclusión del informe ("no se puede atribuir
|
||||||
|
// con honestidad más allá de este punto").
|
||||||
const actorAddrSet = new Set([originOut.scriptpubkey_address].filter(Boolean));
|
const actorAddrSet = new Set([originOut.scriptpubkey_address].filter(Boolean));
|
||||||
|
const mixerTxids = new Set();
|
||||||
for (const n of nodes.values()) {
|
for (const n of nodes.values()) {
|
||||||
|
if (n.stopReason === "mixer") { mixerTxids.add(n.txid); continue; }
|
||||||
for (const a of n.addresses.in) actorAddrSet.add(a);
|
for (const a of n.addresses.in) actorAddrSet.add(a);
|
||||||
for (const a of n.addresses.out) { if (!n.custodyAddrs.includes(a)) actorAddrSet.add(a); }
|
for (const a of n.addresses.out) { if (!n.custodyAddrs.includes(a)) actorAddrSet.add(a); }
|
||||||
}
|
}
|
||||||
const { clusters, linkReasons } = unionFindCluster([...txCache.values()], actorAddrSet);
|
// El union-find tampoco debe ver las transacciones de mezcla: agruparía
|
||||||
|
// por inputs comunes a gente que no tiene nada que ver entre sí.
|
||||||
|
const txsForCluster = [...txCache.values()].filter(t => !mixerTxids.has(t.txid));
|
||||||
|
const { clusters, linkReasons } = unionFindCluster(txsForCluster, actorAddrSet);
|
||||||
|
|
||||||
// marcasDeTx necesita vin/vout crudos (coinbase, OFAC, minería,
|
// marcasDeTx necesita vin/vout crudos (coinbase, OFAC, minería,
|
||||||
// exchange, CoinJoin) — se calcula aquí, sobre originTx de verdad, y se
|
// exchange, CoinJoin) — se calcula aquí, sobre originTx de verdad, y se
|
||||||
@@ -4294,8 +4306,14 @@
|
|||||||
agreement: n.changeGuess?.combined?.agreement || null,
|
agreement: n.changeGuess?.combined?.agreement || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Igual que con CIOH, la huella de software no vale nada dentro de una
|
||||||
|
// mezcla: en un CoinJoin todos los participantes usan el mismo programa,
|
||||||
|
// así que la huella sale "estable" por construcción, no porque sea el
|
||||||
|
// mismo actor. Atribuir salidas de un CoinJoin por esa señal sería
|
||||||
|
// señalar a terceros que solo coincidieron en la misma transacción.
|
||||||
const fingerprintStableAddrs = new Set();
|
const fingerprintStableAddrs = new Set();
|
||||||
for (const n of allNodes) {
|
for (const n of allNodes) {
|
||||||
|
if (n.stopReason === "mixer") continue;
|
||||||
if (n.fingerprintComparison?.changed === false) n.addresses.out.forEach(a=>fingerprintStableAddrs.add(a));
|
if (n.fingerprintComparison?.changed === false) n.addresses.out.forEach(a=>fingerprintStableAddrs.add(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user