Skip to content

Change-address heuristic silently ignores multi-output txs with >1 fresh output #7

Description

@cognis-digital

Context

_is_likely_change() (in cryptotrace/core.py) folds a lone fresh output back into the spender's cluster only when a tx has exactly one previously-unseen, non-input output. This is deliberately conservative, but it means a very common real-world pattern is missed:

  • A payment tx with two fresh outputs (e.g. a batched payout that also creates change to a fresh address) is treated as fully ambiguous → the change output is never clustered.
  • CoinJoin-adjacent or wallet-batching txs that legitimately produce multiple fresh outputs get no change attribution at all.

Why it matters

Change detection is one of the two clustering heuristics that drive sanctions inheritance. Under-clustering here means a co-owned change wallet can escape a cluster's sanctions tag, which is a false negative in a compliance context.

Possible directions (non-breaking)

  • Add an optional value-based change heuristic: when tx value is available, the output whose value is not a "round" amount (or the residual) is the likely change even among multiple fresh outputs.
  • Expose a change_strategy parameter on cluster_addresses() defaulting to the current conservative behaviour, so existing output is unchanged.

Reproducer (current behaviour):

from cryptotrace.core import cluster_addresses, Transaction
# tx with two fresh outputs -> neither is folded as change
txs = [Transaction("t0", ["A"], ["FRESH1", "FRESH2"], "BTC", 1.0)]
print([c.addresses for c in cluster_addresses(txs)])  # -> [] (no cluster)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions