# Inconsistent Voting Index Leads to Double Spends in Future

ZetaChain's inbound-tx ballot index is a hash of mutable message fields, so any client/field change lets old cross-chain txs be re-voted and double-spent.

- Payout: Contest reward
- Severity: high
- Program: ZetaChain
- Credited: ChristiansWhoHack
- Disclosed: 2024-09-06
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-11-zetachain-findings/issues/327
- Library page: https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AH-12
- Tags: domain:web3 impact:funds-loss impact:integrity kind:contest platform:code4rena sev:high tech:go technique:regression technique:source-review vuln:business-logic vuln:signature-replay

**Target:** Cosmos SDK crosschain module MsgVoteOnObservedInboundTx ballot index
**Root cause:** The ballot index is a hash over the entire inbound message including fields like GasLimit, Asset and EventIndex that can change across zetaclient versions; the index is the sole duplicate-submission guard and ballots are never deleted.
**Steps:**
1. Submit `MsgVoteOnObservedInboundTx` with GasLimit=1000000000, InTxHash=0x7a90..., EventIndex=1; ballot finalizes.
2. Resubmit identical message but GasLimit=1000000001 (same InTxHash).
3. Observe msg.Digest() differs, a new ballot is created and finalized, and two CCTXs with the same InboundTxObservedHash are created.
4. Run `go test -v ./x/crosschain/keeper/ -run TestNoDoubleEventProtections` to reproduce.
**How found:** Code-reading of tx.proto and the voting flow: noticed the index is a hash of the whole message and that duplicate protection relies solely on that index, then spotted EventIndex was recently added, proving fields do change post-deployment.
**Exploitation:** Attacker transfers large BTC/ETH/ERC20/Zeta amounts across chains, waits for a zetaclient field change (e.g. GasLimit, Asset, EventIndex, encoding), then resubmits old inbound txs via TxInTracker with proof; observers vote on the new digest and the same event is relayed again, yielding duplicate mints/transfers.
**Escalation:** Repeating across many prior transfers and each future client update multiplies duplicate payouts; no TxInTracker dedup check exists to stop it.
**Insight:** When a dedup key is a hash over a whole message, enumerate which fields are client-controlled or version-dependent (gas limits, optional fields, encoding) and check whether any dedup store exists independent of that hash.
