0xSoftBoi/lock-mint-bridge-lab
GitHub: 0xSoftBoi/lock-mint-bridge-lab
一个带有注释的 lock-and-mint 跨链桥安全审计教学项目,包含会计恒等性不变量、attestation gate 修复方案及历史跨链桥攻击复现。
Stars: 0 | Forks: 0
# lock-mint-bridge-lab
一个专为**审计人员**构建的、带有注释的精简版 lock-and-mint 跨链桥 —— 包含唯一的会计恒等性、维持该恒等性的 attestation gate 修复方案,以及由于缺乏该机制而导致的历史跨链桥黑客攻击的可运行复现。
它是两篇文章的公开配套资料:
- [审计我自己的跨链桥:从“无中生有地铸造代币”到所有严重漏洞被修复](https://0xsoftboi.github.io/blog/auditing-my-own-bridge/)
- [无论如何都会被 Shor 算法破解的后量子证明](https://0xsoftboi.github.io/blog/post-quantum-proof-shor-breaks-anyway/)
这是一个净室教学项目 —— 极简、独立,除了 `forge-std` 外没有其他外部依赖。它**未经过**审计,**绝不适用于**生产环境。
## 一句话概述
一个 lock-and-mint 跨链桥遵循以下会计恒等性:
```
function invariant_supply_le_collateral() public view {
assertLe(wrapped.totalSupply(), vault.totalLocked());
}
```
打破它,跨链桥就会凭空印钞。打破它的经典方式是:信任一个 relayer 去宣称“发生了一笔锁定”,但链上却没有可靠的证明。这就是 Ronin / Wormhole 系列事件 —— 该领域代价最惨重的漏洞类别。
## 修复方案:attestation gate
每一个涉及价值转移的调用(`mint`、`unlock`、`refund`)都会经过一个可插拔的 verifier,对包含**所有**参数以及 chain id 和合约地址的摘要进行验证:
```
bytes32 digest = keccak256(abi.encode(
DOMAIN, block.chainid, address(this),
commitId, recipient, amount, sourceChainId
));
require(verifier.verify(digest, attestation), "unauthorized");
```
- 绑定 `address(this)` 可防止签名在不同部署之间被重放;`block.chainid` 可防止其跨链重放;`commitId` 可防止二次铸造;domain tag 可防止 unlock 签名被当作 refund 重用。
- verifier 是**可插拔的**([`IAttestationVerifier`](src/IAttestationVerifier.sol)):在 EVM 目标链上使用 ECDSA([`EcdsaAttestationVerifier`](src/EcdsaAttestationVerifier.sol)),或者在你控制的链上使用后量子格密码 verifier —— 而无需触及 vault/adapter 逻辑。(为什么使用格签名,以及为什么*不*将其封装在 SNARK 中,是第二篇文章的主题。)
- 一个 `commitId` 最多只能到达一种最终状态 `{UNLOCKED, REFUNDED}`,这在链上得到了强制执行 —— 这就堵死了跨域的“已铸造**且**已退款”双重支付漏洞。
## 目录内容
| 合约 | 作用 |
|---|---|
| [`SourceVault`](src/SourceVault.sol) | 锁定抵押品;作为 `totalLocked` 的授权源。unlock/refund 受到门控且互斥。 |
| [`MintAdapter`](src/MintAdapter.sol) | 根据 attestation 铸造 wrapped token;通过 `commitId` 防止重放;销毁以开启返回流程。 |
| [`WrappedToken`](src/WrappedToken.sol) | 目标链 ERC-20。Admin ≠ minter(admin 密钥绝不能成为隐蔽的并行 minter)。 |
| [`EcdsaAttestationVerifier`](src/EcdsaAttestationVerifier.sol) | 操作员白名单 ECDSA gate(遵循 EIP-2 low-s,无零签名者)。 |
## 测试 (`forge test`)
- **`test/invariant/Supply.invariant.t.sol`** —— 使用 handler 驱动跨链桥,该 handler 模拟了诚实操作员**和**对抗性 relayer,运行参数为 **512 次运行 × 深度 100**:
- `invariant_supply_le_collateral` —— 供应量绝不会超过锁定的抵押品。
- `invariant_adversary_minted_nothing` —— 仅持有伪造 attestation 的攻击者铸造的代币数量精确为零。
- **`test/Bridge.t.sol`** —— 针对 gate 的单元测试(拒绝伪造 / 垃圾数据 / 篡改金额 / 重放),unlock-XOR-refund 结果,以及 admin≠minter 规则。此外还有 **`test_mutation_gateOff_breaksSupplyInvariant`**:禁用 gate(回退修复方案),同样的伪造 mint 就会成功,并在一次调用中打破恒等性 —— 如果移除修复方案后测试仍然无法失败,那它就没有真正测试这个修复方案。
- **`test/historical/Historical.t.sol`** —— 精简复现展示了 gate 如何拒绝每一种攻击手段:**Ronin**(已轮换 / 非操作员密钥),**Wormhole**(不存在未经验证的 mint 路径),**Nomad**(空/默认 proof 无法授权任何操作)。
```
forge install # pulls forge-std
forge test # 17 tests; invariants at 512 × 100
```
## 范围 / 诚实声明
supply≤collateral(供应量≤抵押品)的保证,端到端来看,是一种由 gate 使其**在链上可强制执行**的*操作员协调*属性 —— 恶意的 relayer 无法在没有操作员签名的情况下进行 unlock 或 refund —— 这并不是合约自身能够证明另一条链上事件的属性。历史复现是刻意简化后的真实漏洞利用替身,并非忠实的 fork。请参阅 [SECURITY.md](SECURITY.md)。
## 许可证
[MIT](LICENSE)。
标签:Foundry, 区块链, 智能合约, 漏洞复现, 跨链桥