Root Cause
Wanchain's Cardano-BNB Chain bridge authorises cross-chain withdrawals via a TreasuryCheck validator that verifies a signed message committing to (token, amount, recipient). The critical flaw: the message was encoded with abi.encodePacked, which is non-injective when fields share overlapping byte boundaries. This allowed different (token, amount) tuples to produce the same 32-byte hash, meaning a valid signature for a small withdrawal could be reused as authorisation for a large one.
BlockSec's Phalcon system quantified the specific collision exploited: a signature originally covering ~3,110 NIGHT tokens was successfully reused to withdraw over 203,000,000 NIGHT — a 65,000x inflation.
// Non-injective encoding — VULNERABLE (simplified)
function buildWithdrawalHash(
address token,
uint256 amount,
address recipient
) internal pure returns (bytes32) {
// abi.encodePacked can collide when variable-length or adjacent-field
// byte boundaries overlap. Use abi.encode (padded) or EIP-712 instead.
return keccak256(abi.encodePacked(token, amount, recipient));
}
// Secure alternative:
// return keccak256(abi.encode(token, amount, recipient));
// Or: use EIP-712 typed structured data signing
Attack Steps
| Step | Action | Detail |
|---|---|---|
| 1 | Obtain signature | Attacker acquired a valid bridge signature authorising 3,110 NIGHT withdrawal |
| 2 | Identify collision | Found that encoding 203 M+ NIGHT produces the same keccak256 hash (non-injective encodePacked) |
| 3 | Submit large request | Called TreasuryCheck with 203 M NIGHT and the small-amount signature |
| 4 | Validator accepts | Hash collision causes signature check to pass; bridge releases 203 M NIGHT |
| 5 | Repeat | Multiple transactions totalled 515.2 M NIGHT drained |
| 6 | Liquidate | Converted NIGHT to stablecoins; funds dispersed off-chain |
Impact
- Loss: 515,200,000 NIGHT tokens = ~$10,000,000
- Protocol: Wanchain — Cardano-BNB Chain bridge
- Chains: Cardano / BNB Chain (cross-chain)
- Detection: BlockSec Phalcon monitoring flagged on July 21, 2026
- Response: Wanchain paused the bridge, offered a 10% white-hat bounty with an August 6, 2026 deadline
Lessons for Auditors
- Never use
abi.encodePackedfor signing multi-field messages. Without fixed-width padding, adjacent fields can concatenate in ways that produce hash collisions. Always useabi.encode(which pads every field to 32 bytes) or EIP-712 structured signing. - Formally verify that message encoding is injective. Each distinct (token, amount, recipient) combination must map to a unique byte sequence. Injectivity is not guaranteed by
keccak256alone — the pre-image construction must be injective. - Include per-withdrawal nonces. A monotonically incrementing nonce bound into the signed message prevents signature reuse even if a collision is discovered post-deployment.
- Fuzz signature schemes with boundary values. Generate signatures across the full uint256 range and verify that no two (amount, adjacent-field) pairs produce the same packed encoding.