mehrdadbgh/ethereum-aml-detection
GitHub: mehrdadbgh/ethereum-aml-detection
基于 KNIME、Neo4j 和 Streamlit 构建的以太坊洗钱检测 pipeline,通过图谱分析追踪 DeFi 攻击中被盗资金的跨跳流向并识别系统性洗钱模式。
Stars: 0 | Forks: 0
# Ethereum 洗钱检测系统
一个区块链取证 pipeline,可追踪四起真实 DeFi 攻击事件中被盗资金的流向,检测系统性的洗钱模式,并生成交互式风险评分仪表板 —— 使用 KNIME、Neo4j 和 Streamlit 构建。
**追踪金额达 1.99 亿美元 · 检测到 1,243 笔 Tornado Cash 存款 · 对跨 3 跳的 559 个钱包进行了分析**
## 结果概览
| 案例 | 日期 | 被盗金额 | Tornado Cash 洗钱金额 | 模式 | 检出率 |
|------|------|--------|------------------------|---------|-----------------|
| **Beanstalk Farms** | 2022年4月 | 1.81 亿美元 | 7580 万美元 (271 笔存款) | 直接存款,3 小时窗口期 | 100% |
| **Nomad Bridge** | 2022年8月 | 1.9 亿美元 | 2790 万美元* (181 笔存款) | 191 个钱包汇聚 → 兑换 → 存款 | 100% |
| **Euler Finance** | 2023年3月 | 1.97 亿美元 | 170 万美元 (11 笔存款) | 协商归还 + 部分洗钱 | 100% |
| **Harmony Bridge** | 2022年6月 | 1 亿美元 | 9360 万美元 (780 笔存款) | 多层均等拆分 → 存款 | 91% |
*Nomad 的数据反映了 2022 年 8 月黑客攻击当天的定价;2024 年 8 月的实际存款价值约为 3970 万美元 —— 这是下文中讨论的已知局限性。
## 本系统检测的内容
- **Tornado Cash 洗钱特征** —— 存款笔数、分块大小指纹识别(100/10/1/0.1 ETH)以及洗钱速度分类
- **汇聚模式** —— 多个钱包向一个地址发送在统计上完全相同的金额(Nomad:191 个钱包,标准差 = 0.0)
- **拆分模式** —— 一个钱包向多个钱包分发资金,包括对指示脚本化、自动化行为的*机械均等*拆分(`equal_split` 标志)的检测
- **资金归还与洗钱** —— 区分归还给协议的资金与真正被洗白的资金(Euler:98.6% 归还,0.86% 洗钱)
- **跨案件实体关联** —— 挖掘出 Euler 和 Ronin Bridge 攻击之间与朝鲜 / Lazarus Group 的联系,如果孤立分析其中任何一个案件,这种联系都是不可见的
- **CEX 风险敞口** —— 标记流入具有 KYC 要求的中心化交易所的资金(为执法部门提供传票机会)
- **综合风险评分** —— 结合上述所有信号的 0–110 分钱包级评分
## 架构
```
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌───────────────┐
│ Etherscan │ --> │ KNIME │ --> │ Neo4j │ --> │ Streamlit │
│ V2 API │ │ ETL + Loop │ │ Graph Store │ │ Dashboard │
└─────────────┘ └──────────────┘ └─────────────┘ └───────────────┘
```
**数据提取 (Extraction)** —— KNIME 递归循环通过三个 Etherscan 端点拉取每个钱包的交易历史(原生 ETH、ERC-20、内部调用),顺着资金流向向外逐跳扩展,当没有新资金超过 10,000 美元的重要性阈值时停止。
**图构建 (Graph construction)** —— 每个钱包和转账都被写入 Neo4j,并应用了按比例计算的历史定价、Tornado Cash 交互标记以及已知实体标注(黑客、攻击合约、协议地址、混币器、交易所),这些均通过基于类别的过滤来应用 —— 而不是在检测查询本身中使用硬编码的地址列表 —— 因此边界扩展逻辑和检测器可以无需修改代码即可推广到新案件。
**检测 (Detection)** —— 六个基于 Cypher 的检测器在图上运行:Tornado Cash 汇总、每个钱包的存款、分块模式指纹识别、拆分 + 转发速度、汇聚(附带变异系数测试)以及综合风险评分。
**展示 (Presentation)** —— 一个 5 页的 Streamlit 仪表板(概述、Tornado Cash、资金流向、风险评分、案件叙事)将检测器输出作为静态 JSON 读取,无需实时数据库连接即可查看。
## 为什么选择这四个案件
每个案件都展示了一种结构上不同的洗钱模式,这是经过深思熟虑后选择的,旨在对检测逻辑的不同部分进行压力测试:
- **Beanstalk** —— 最简单的案件:一个钱包,没有中间人,直接转向 Tornado Cash。验证了基线存款检测和分块模式指纹识别。
- **Nomad** —— 一起混乱的多参与者攻击(300 多名独立参与者),其中主要路径显示了数据集中最强的汇聚信号,随后是长达两年的洗钱延迟 —— 测试系统是否仍能在噪声和时间间隔下找到信号。
- **Euler** —— 唯一一起*大部分*资金被归还的案件。测试系统能否正确区分协商归还与主动洗钱,而不是将每个接触过被盗资金的钱包都标记为有罪。
- **Harmony** —— 最深层的案件:在洗钱前进行了三层拆分,有四个钱包转发了机械一致的金额。测试多跳遍历和 `equal_split` 自动化检测逻辑。
## 截图
### 仪表板





### 图可视化(Neo4j 浏览器)
**Nomad 汇聚** —— 60 多个钱包向一个地址发送在统计上完全相同的金额:

**Harmony 拆分链** —— 黑客 → 拆分器 → 洗钱骡子 → Tornado Cash。展示了两种方式:一种是包含混币器节点,因此可以看到在 Tornado Cash 终止的完整洗钱链;另一种是不包含混币器,这样在没有混币器密集的存款边簇主导布局的情况下,拆分层本身更容易追踪:


**Euler 完整流程** —— 攻击合约 → 黑客 → 5 个攻击者钱包 → 归还 / 部分洗钱 / Ronin 跨案件关联:

**Beanstalk 直接存款** —— 一个钱包直接存入 Tornado Cash,没有中间分层:

*(完整的截图集请参见 `docs/screenshots/`,包括 KNIME 工作流本身每个阶段的截图。)*
## 配置 Pipeline
双击 **Initialization** 组件以打开其配置对话框,并提供:
- **Etherscan API key** (V2)
- **Neo4j 连接详情** —— URI、用户名、密码
### 获取 Etherscan V2 API Key
1. 在 [etherscan.io](https://etherscan.io/register) 创建一个免费账户
2. 进入 **My Profile → API Keys**
3. 点击 **Add**,为密钥命名并复制它
4. 将其粘贴到 Initialization 组件的配置对话框中
免费层级(5 次调用/秒,100,000 次调用/天)足以运行此 pipeline。
## Pipeline 阶段
工作流按顺序分三个阶段运行。
**Initialization**(组件) —— 设置数据库约束,写入种子钱包,并标注已知实体。
**ETL**(元节点) —— 从 Etherscan 提取链上数据,将其转换为图的 schema,并将其写入 Neo4j。
**Detection & Export**(元节点) —— 从 Neo4j 读取检测结果,并将其作为 JSON 文件导出供 Streamlit 仪表板使用。
每个阶段的详细信息见下文。

## 初始化
双击 Initialization 组件打开其配置对话框,在其中提供上文所述的 Etherscan API key 和 Neo4j 连接详情。配置并执行后,Initialization 将按顺序执行四个步骤:
1. 使用提供的凭据**连接到 Neo4j**。
2. 在 `Wallet.address` 和 `SENT.transfer_id` 上**创建唯一性约束** —— 这在任何数据写入之前运行一次,因此 pipeline 中随后的每个 `MERGE` 都保证匹配或仅创建一个节点或关系。如果没有这个,同一个钱包或转账可能会在不同的跳中被写为重复的节点。
3. 通过 Table Creator **写入四个种子钱包**(Beanstalk、Nomad、Euler 和 Harmony 的已知黑客地址),将每个钱包标记为 `is_seed = true` 和 `processed = true`。将种子标记为已处理,可以防止 pipeline 后续的边界扩展逻辑错误地尝试将种子钱包作为新发现的洗钱目的地进行重新扩展。
4. **标注已知实体** —— 一次性的 `MERGE`,为大约 50 个地址(攻击合约、DEX/桥/混币器基础设施、CEX 存款地址以及已识别的中间钱包)打上 `label` 和 `category` 属性。这是让后续每个检测器和边界读取器能够按 `category` 过滤(例如,排除 `DEX`、`Bridge`、`Mixer`)的基础,而不是依赖检测逻辑本身中硬编码的地址列表 —— 在这里添加一个新地址,每个下游查询都会自动受益。(已知地址列表本身仍需手动输入;请参见[已知局限性](#known-limitations-phase-2-roadmap)。)

## ETL
ETL 元节点将我们要调查的钱包作为输入,并将它们送入递归循环。
对于每次迭代,分块循环一次处理一个输入钱包:
提取:从 Etherscan 拉取每个钱包的交易历史
转换:将原始 API 响应转换为图所需的 schema(定价、Tornado Cash 标记、转账 ID 等)
加载:将转换后的交易写入 Neo4j
一旦分块循环处理完当前跳的所有钱包,边界读取器就会查看最近一跳的交易,并找出总接收金额超过 10,000 美元、不属于已知基础设施(DEX、桥、混币器、协议、CEX)、不是 Ronin Bridge Exploiter(一个被永久排除在扩展之外的已知跨案件行为者)且尚未被处理的钱包。
如果边界读取器返回至少一个钱包,这些钱包将成为递归循环下一次迭代的输入。如果什么都没返回,循环结束 —— 没有更多的资金可追踪了。

### 提取
对于当前正在处理的钱包,此元节点使用钱包地址和 API key 为三个 Etherscan 端点 —— 原生 ETH 交易、内部交易和 ERC-20 代币交易 —— 构建请求 URL。所有三个请求并行发送,之后有一秒的延迟,以保持在 Etherscan 的速率限制之内。
从 JSON 提取并解析数据后,将检查行数。如果任何端点准确返回 1,000 行(Etherscan 的每次请求上限),分页元节点将从该钱包的最后区块号开始继续获取,最多 10,000 行。分页逻辑本身的详细信息在该元节点内部有说明。

#### 分页
首先,检查上一个请求的行数。如果恰好是 1,000,递归循环会找出这 1,000 行中的最后一个区块号,将其加 1,并构建从该区块号开始的新请求。这个过程不断重复 —— 每次获取另一页并检查计数 —— 直到响应返回的行数少于 1,000 行(意味着已检索到该钱包的完整历史记录)或达到 10,000 行的上限。这意味着高活跃度钱包能被完整捕获,而不是在 Etherscan 的每次请求限制处被静默截断。
### 转换
首先,检查交易的完整性(无错误)并且值大于 0。添加了一个 `transfer_type` 列,以便来自三个端点中每一个的交易在合并后仍然可以区分。
涉及这四个案件的每种资产的代币合约地址和 USD 价格都是通过 Table Creator 手动输入的 —— 这既是为了过滤空投和垃圾代币,也是为了应用历史定价。这是经过深思熟虑的 MVP 简化,而不是缺失的功能;有关如何实现自动化的说明,请参见[已知局限性](#known-limitations-phase-2-roadmap)。
最后,通过交易哈希、发送者地址、接收者地址和合约地址构建唯一的转账 ID,并将来自所有三个端点的数据合并为一个统一的结构。

### 加载
此元节点使用两个写入节点将转换后的批次写入 Neo4j。
**写入钱包和交易**将每笔交易的发送方和接收方作为 `Wallet`节点进行合并(首次出现时创建它们,在随后出现时更新 `last_seen`),并将交易本身作为它们之间的 `SENT` 关系进行合并,以在转换期间构建的唯一转账 ID 为键。当前的递归循环迭代通过流变量传入,并作为 `hop_index` 存储在发送方钱包和交易中,因此每一条数据都可以准确追溯到它是被调查的哪一跳发现的。
**将钱包标记为已处理**随后在其交易写入后,立即将刚刚写入的每个钱包标记为 `processed = true`。这就是边界读取器在下一次循环迭代中检查的内容,以避免重新调查已经扩展过的钱包。

## 检测与导出
此元节点包含八个子元节点,每个检测器一个。每个都遵循相同的模式:从 Neo4j 读取、解析 JSON 响应、使用案件名称和任何派生列对其进行扩充、清理表、将其转换为 JSON,并通过 JSON Writer 节点将其写出。每个文件都保存到 `aml-dashboard/data`,这是相对于当前工作流自身数据目录的路径 —— 因此导出位置会随工作流移动,而不是硬编码到一台机器上。
这八个检测器是:
**1. 按钱包划分的 Tornado Cash** —— 每个存入 Tornado Cash 的钱包,以及存款计数、USD 总额、首次发现的跳数以及第一笔/最后一笔存款的时间戳。这是洗钱活动的钱包级别视图。
**2. 分块模式** —— 每个案件中每种 Tornado Cash 存款金额的大小和频率。Tornado Cash 只接受固定面额(0.1、1、10、100 ETH),因此这揭示了每个黑客使用的确切批量指纹。
**3. Tornado Cash 汇总** —— 每个案件一行,将检测器 1 聚合到案件级别:总存款数、USD 总额、不同存款钱包的数量、平均存款规模以及第一笔和最后一笔存款之间经过的分钟数。
**4. 汇聚** —— 查找接收来自超过 5 个不同发送方资金的钱包,按 token 分组,并标记这些发送方是否发送了*在统计上完全相同*的金额(标准差低于平均值的 1%)。这里的真实标记是自动化、脚本化资金收集的强烈信号,而不是自然发生的活动。
**5. 拆分 + 速度** —— 汇聚的反向操作:向超过 2 个不同接收方发送资金的钱包,对传出转账应用相同的等额测试(`equal_split`)。还计算了钱包首次接收资金到首次转发资金之间经过了多少分钟(`minutes_to_forward`),这记录了资金到达后钱包行动的速度。
**6. 风险评分** —— 每个钱包的综合 0–110 分评分,由五个加权组件构成:Tornado Cash 存款计数、Tornado Cash USD 价值、拆分行为、汇聚行为和接收到的总价值。每个组件都按其自身的分级量表进行评分并求和,从而生成所有案件中风险最高的钱包的单一排名列表。
**7. 图数据** —— 用于在 Streamlit 仪表板中渲染资金流向图的边列表:每笔超过 1,000 美元且非常规 DEX/桥/混币器活动的转账,并附带源和目标标签。
**8. CEX 预警** —— 任何直接将资金发送到已知中心化交易所存款地址的钱包。流入 CEX 的资金意义重大,因为交易所强制执行 KYC,使其成为具体的执法跟进机会。

## 自行运行
### 仅仪表板(无需数据库)
这仅使用代码仓库中已包含的预导出 JSON 数据运行 Streamlit 仪表板 —— 不需要 Neo4j、KNIME 或 API key。
```
cd dashboard
python -m venv venv
venv\Scripts\activate # on Mac/Linux: source venv/bin/activate
pip install -r requirements.txt
streamlit run app.py
```
逐步来说,这是:
1. 进入 `dashboard` 文件夹,其中包含 `app.py` 和 `requirements.txt`
2. 创建一个独立的 Python 环境(`venv`),以便下面的包不会与您机器上的任何其他内容混淆
3. 激活该环境
4. 将三个必需的包(Streamlit、Plotly、Pandas)安装到其中
5. 启动仪表板
运行后,它将自动在您的浏览器中打开,地址为 `http://localhost:8501`。要停止它,请返回终端并按 `Ctrl+C`。
### 完整图(还原真实提取的数据)
完整的提取图(559 个钱包,2,345+ 笔交易,所有标签)作为 Neo4j 转储文件包含在 `backup/ethereum-aml-graph-2026-06-28T07-20-18.dump` 中。
`neo4j-admin` 命令行工具在大多数 Neo4j Desktop 安装中默认不在您的系统 PATH 中,因此还原它的最简单方法是通过 Neo4j Desktop GUI:
1. 在 Neo4j Desktop 中创建一个新的空 DBMS 实例
2. 选择**从转储文件**创建实例的选项(具体措辞因 Neo4j Desktop 版本而略有不同)
3. 选择上面的 `.dump` 文件
4. 加载完成后启动实例
然后直接在 Neo4j 浏览器中针对还原的图探索 `cypher/detectors/` 中的 Cypher 查询。
### 完整 Pipeline(从头开始重新运行提取)
在 KNIME Analytics Platform 中打开 `knime/ethereum_pipeline.knwf`。双击 Initialization 组件打开其配置对话框并输入您的 Etherscan API key 和 Neo4j 凭据,然后执行。需要运行中的 Neo4j 实例。
## 已知局限性(第二阶段路线图)
这是一个 MVP。以下局限性是已知的,我们有意将其记录下来而不是隐藏起来:
1. **黑客攻击日定价** —— USD 值使用的是按案件固定的价格,而不是交易日期的定价。这对于在黑客攻击后几天内发生洗钱的案件(Beanstalk、Euler、Harmony)是准确的,但将 Nomad 在 2024 年的实际洗钱价值低估了约 30%。通过月度价格表应用交易日期定价是计划中的修复方案。
2. **DEX 兑换边显示为双向** —— 通过 DEX 进行的代币兑换显示为两笔独立的转账(进出),而不是一条合并的兑换边,从而夸大了原始交易计数。报告是按方向进行的,从不对两笔交易求和,但适当的兑换合并转换是未来的工作。
3. **Harmony 检出率为 91%**(780/857 记录在案的存款) —— 差距在于一个低于 10,000 美元边界阈值的钱包加上提取路径中 SushiSwap 池的误报。
4. **Tornado 之后的隐私工具**(Railgun 等)未进行追踪 —— 据报道,在 Tornado Cash 受到制裁后,大约一半的 Harmony 洗钱资金转移到了 Railgun,这超出了本系统目前的范围。
5. **手动代币/定价表** —— 涉及这四个案件的资产的代币合约地址和 USD 价格是通过 KNIME Table Creator 手动输入的,既是为了过滤垃圾/空投代币,也是为了应用历史定价。这是经过深思熟虑的 MVP 范围决定,而不是缺失的功能:生产版本将动态解析代币元数据(例如,通过代币注册 API),并从按交易日期索引的市场数据 API 中提取历史价格,从而在添加新案件时无需硬编码任何内容。
6. **手动维护的已知实体列表** —— 在 Initialization 期间标注的大约 50 个地址(攻击合约、DEX/桥/混币器基础设施、CEX 存款、已识别的中间钱包)是经过研究后手动输入的。这就是目前让每个检测器和边界读取器能够按 `category` 过滤,而不是在*检测*逻辑中使用硬编码地址列表的原因 —— 但底层的*标签来源*仍然是手动的。生产版本会将其维护为一个外部、持续更新的实体标记数据集(类似于商业区块链归因服务),而不是工作流中的静态列表,从而自动提取新的基础设施和已知行为者,而无需修改工作流。
## 技术栈
- **KNIME Analytics Platform** —— ETL 编排、递归提取循环、Etherscan API 集成
- **Neo4j** —— 用于资金流存储和基于 Cypher 的模式检测的图数据库
- **Etherscan V2 API** —— 链上交易数据(原生 ETH、ERC-20、内部调用)
- **Python / Streamlit** —— 交互式仪表板,作为静态 JSON 提供服务,无实时依赖项
- **Plotly** —— 仪表板图表和浏览器内资金流图可视化
## 作者
作为针对区块链分析 / AML 工程岗位的作品集项目构建。欢迎反馈。
标签:Kubernetes, 代码示例, 以太坊, 区块链, 反洗钱, 威胁情报, 开发者工具, 数据分析, 逆向工具