发布于6小时前6小时 背景 在钱包风控和链上追踪里,很多异常资金不会直接从受害地址流向交易所,而是经过一批中转地址做拆分、合并、跳转,再统一归集。单看某一笔交易很难判断风险,但把地址之间的资金流关系拼起来后,很多模式会变得很明显。 这类地址通常不一定是合约,也不一定有复杂交互,更多是普通 EOA 地址。常见特征包括:短时间内创建或首次活跃、入账来源集中、出账目的集中、余额长期很低、交易间隔规律、资金停留时间短。 技术分析 识别“中转归集”地址,不建议只依赖单一黑名单或单笔金额阈值。更稳妥的方式是结合链上图谱特征和时间特征做评分。 1. 图谱结构特征 入度高、出度低:多个地址向它转入,最终只转给少数地址,常见于归集节点。 入度低、出度低但路径连续:单一来源转入后迅速转出,常见于中转跳板。 资金扇入:多个同类地址在相近时间窗口内向同一地址转账。 资金扇出:一个地址向多个新地址拆分,后续再回流到同一归集点。 2. 时间行为特征 资金停留时间短:入账后几分钟到几小时内转出。 首次活跃时间接近:一批地址在同一时间段首次出现交易。 批量操作节奏:多笔交易 gas price、nonce 增长、时间间隔呈现脚本化特征。 3. 金额特征 转出金额接近入账金额:扣除 gas 后几乎全部转走,余额接近 0。 固定比例拆分:如 0.5、1、2 ETH 或稳定币整数金额频繁出现。 多地址相同金额:同一来源向多个地址发送相近金额,后续流向一致。 操作步骤 实际排查时可以按下面流程做: 确定起点地址,例如被盗钱包、钓鱼收款地址或已知高危地址。 抓取一定深度的转账关系,通常先看 2 到 4 跳,不建议一开始无限扩展。 过滤无关交易,例如小额 dust、明显的空投、无价值代币转账。 计算每个地址的入度、出度、总入账、总出账、余额变化、资金停留时间。 对地址打分,优先关注高扇入、快速转出、余额归零的地址。 把高分地址继续向下游追踪,观察是否进入交易所、跨链桥、混币器或场外归集地址。 代码示例 下面是一个简化示例,用 Python 对已经导出的 ERC-20 转账记录做基础风控评分。这里假设数据来自节点日志、区块浏览器 API 或自建索引服务,字段包括 from、to、value、timestamp、tx_hash。 from collections import defaultdict from statistics import median # 示例数据结构 # transfers = [ # {"from": "0xaaa", "to": "0xbbb", "value": 1000, "timestamp": 1710000000, "tx_hash": "0x..."}, # ] def build_address_features(transfers): incoming = defaultdict(list) outgoing = defaultdict(list) for t in transfers: incoming[t["to"].lower()].append(t) outgoing[t["from"].lower()].append(t) addresses = set(incoming.keys()) | set(outgoing.keys()) features = {} for addr in addresses: ins = incoming.get(addr, []) outs = outgoing.get(addr, []) in_addrs = set(t["from"].lower() for t in ins) out_addrs = set(t["to"].lower() for t in outs) total_in = sum(t["value"] for t in ins) total_out = sum(t["value"] for t in outs) balance_delta = total_in - total_out # 计算资金停留时间:取每笔入账后最近一笔出账的时间差 dwell_times = [] out_times = sorted(t["timestamp"] for t in outs) for i in ins: later_outs = [ot for ot in out_times if ot >= i["timestamp"]] if later_outs: dwell_times.append(later_outs[0] - i["timestamp"]) median_dwell = median(dwell_times) if dwell_times else None features[addr] = { "in_degree": len(in_addrs), "out_degree": len(out_addrs), "tx_in_count": len(ins), "tx_out_count": len(outs), "total_in": total_in, "total_out": total_out, "balance_delta": balance_delta, "median_dwell_seconds": median_dwell, } return features def risk_score(f): score = 0 # 多来源流入,少数目的流出:疑似归集 if f["in_degree"] >= 5 and f["out_degree"] <= 2: score += 30 # 单跳中转:入账和出账都不多,但很快转走 if f["in_degree"] <= 2 and f["out_degree"] <= 2: if f["median_dwell_seconds"] is not None and f["median_dwell_seconds"] < 3600: score += 20 # 余额接近清空 if f["total_in"] > 0: out_ratio = f["total_out"] / f["total_in"] if 0.95 <= out_ratio <= 1.02: score += 25 # 高频收发 if f["tx_in_count"] + f["tx_out_count"] >= 10: score += 15 # 快速转出 if f["median_dwell_seconds"] is not None and f["median_dwell_seconds"] < 600: score += 10 return score features = build_address_features(transfers) ranked = [] for addr, f in features.items(): s = risk_score(f) if s >= 40: ranked.append((addr, s, f)) ranked.sort(key=lambda x: x[1], reverse=True) for addr, score, f in ranked[:20]: print(addr, score, f) 这个脚本不是完整风控系统,但适合作为第一层筛选。真正落地时,建议把 ETH 原生转账、ERC-20 Transfer、内部交易、跨链桥事件分别建表,再统一到地址关系图里。 注意事项 不要只看一条链:很多资金会通过跨链桥转移,单链图谱会断掉。至少要记录桥合约、目标链交易哈希和接收地址。 注意交易所热钱包误报:交易所热钱包天然具备高入度、高出度特征,需要用标签库排除。 区分机器人和攻击者:套利机器人、NFT 批量交易地址也会表现出高频交易,需结合资金来源和下游去向判断。 金额单位要统一:ERC-20 需要根据 decimals 归一化,不同代币不能直接相加。 时间窗口很关键:建议按 10 分钟、1 小时、24 小时分别统计,不同攻击场景节奏差异很大。 保留原始证据:追踪结果最好记录区块高度、交易哈希、日志索引,方便复核和导出报告。 实战判断思路 如果一个地址满足以下组合,优先级可以提高: 上游包含钓鱼、被盗、授权盗转相关地址; 首次收到资金后 10 分钟内转出; 转出金额接近入账金额; 下游继续流向多个新地址,最后进入同一归集地址; 多笔交易的 gas 参数和时间间隔明显接近。 单个特征一般不足以下结论,但多个特征叠加后,基本可以把排查范围缩小很多。 总结 钱包风控和链上追踪的关键不是“看到可疑交易”,而是能把交易放进上下文里判断。中转归集地址的识别,核心在于资金流图谱、时间窗口和余额变化三类特征。 实际工作中,建议先用规则模型做可解释筛选,再结合标签库、交易所地址、跨链桥事件和人工复核。这样既能控制误报,也能在事件发生后快速定位资金路径,为冻结、报案和后续追踪争取时间。 网络请求、日志与边界流量分析示意
5小时前5小时 可以补一个比较实用的判断点:不要只看“多入一出”,中转归集地址更稳定的特征通常是“资金停留时间短 + 输出模式固定 + 与上游地址批次相关”。 实际落规则时可以先做几个可解释的指标: 入账到出账时间差:比如同一地址收到资金后,80% 以上在 5~30 分钟内转出,且长期稳定。 入账来源分散度:来源地址数量多,但每个来源与该地址交互次数少,常见于归集入口。 出账目标集中度:出账大多流向少数几个下游地址,尤其是固定热钱包、兑换入口或下一层中转。 余额残留很低:每次转出后余额接近 dust 或固定保留手续费。 批次同步性:多个上游地址在相近时间窗口向同一地址打款,随后该地址统一转出。 可以按滑动窗口做一个简单评分,不建议单条规则直接封禁。例如: score = 0 if in_degree_24h >= 20: score += 2 if out_degree_24h <= 3 and total_out_value_24h / total_in_value_24h > 0.9: score += 3 if median_hold_time < 1800: score += 3 if balance_after_transfer_ratio < 0.05: score += 2 if source_entropy > 3.0 and target_entropy < 1.0: score += 2 if batch_in_ratio > 0.6: score += 2 if score >= 8: mark_as_possible_aggregation_address() 这里的 source_entropy 和 target_entropy 比单纯统计地址数量更好用,能区分“正常商户收款”和“中转归集”。正常商户可能也有大量入账,但它的出账时间、余额策略和下游结构通常不会这么机械。 如果是 UTXO 链,还可以额外看: 是否频繁出现多 input 合并交易; 找零地址是否规律变化; 归集交易的手续费率是否跟随批处理脚本固定; 同一批 input 的历史来源是否高度相似。 SQL 侧可以先粗筛一批候选: SELECT address, COUNT(DISTINCT from_address) AS src_cnt, COUNT(DISTINCT to_address) AS dst_cnt, SUM(in_value) AS total_in, SUM(out_value) AS total_out, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY hold_seconds) AS median_hold FROM address_flow_1d WHERE day >= CURRENT_DATE - INTERVAL '7 days' GROUP BY address HAVING COUNT(DISTINCT from_address) >= 20 AND COUNT(DISTINCT to_address) <= 5 AND SUM(out_value) / NULLIF(SUM(in_value), 0) > 0.9 AND PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY hold_seconds) < 1800; 最后建议把识别结果分成“疑似中转”“高置信归集”“已知服务地址”三类。交易所热钱包、支付网关、矿池分发地址很容易误伤,最好接入标签库做白名单/降权,不然风控策略上线后误报会比较多。
5小时前5小时 这个评分框架已经比较实用了,我建议再补一个容易误判的点:先区分“地址行为”和“实体行为”。在 UTXO 链上,交易找零地址、批量支付的归集地址经常会表现为多入一出;在账户模型链上,交易所热钱包、桥接合约也可能满足这些条件,但它们的业务周期和交互对象完全不同。 实际落地时,可以把“固定输出目标”细化成目标地址的稳定性,而不是只看数量。例如统计最近 30 天出账目标的 Top1/Top3 占比、目标地址是否发生过标签切换,以及出账是否经过合约调用。普通归集通常是直接转账,桥和 DEX 入口则会出现固定的合约方法、事件日志或 calldata 特征。 def concentration(values, top_n=3): total = sum(values) if total == 0: return 0.0 return sum(sorted(values, reverse=True)[:top_n]) / total def score_node(m): score = 0 # 来源多且单一来源交互次数低 if m.in_degree_24h >= 20 and m.avg_in_count_per_sender <= 2: score += 2 # 快速转出且资金基本不留存 if m.p80_hold_minutes <= 30 and m.out_in_value_ratio >= 0.90: score += 3 # 出账目标集中,但需要排除已知业务热钱包 if m.top3_out_value_ratio >= 0.85 and not m.is_known_service_wallet: score += 2 # 多个上游在短窗口内汇入 if m.max_batch_in_degree_10m >= 8: score += 2 # 同一模式至少持续多个窗口,避免一次性批量活动误报 if m.active_days_30d >= 7: score += 1 return score 这里的 p80_hold_minutes 最好按单笔资金或可追踪金额计算,不要简单用地址余额时间差,否则会被历史残留余额干扰。对于账户模型,可以按“入账金额桶”做近似匹配;对于 UTXO,则优先按输入输出金额和找零启发式匹配,并把手续费单独扣除。 另外建议增加一个“模式稳定性”维度:同一地址连续几个滑动窗口都达到阈值才进入人工复核,例如 6 个窗口中至少 4 个命中。最终动作也不要直接封禁,可以先标记为疑似中转、提高交易审核级别,并保留上游批次、出账目标和时间差作为解释字段。这样既能降低交易所批处理、商户收款和桥接业务带来的误报,也方便后续复盘规则效果。
创建帐户或登录后发表意见