Skip to content

[Security] bridge-out CLI submits LP claimData without binding --btc-address/--amount #436

Description

@asbestos22

Summary

The shipped bridge-out CLI (node/src/bin/send_bridge_out.rs) submits an LP payInvoice escrow without checking that claimData binds the user's --btc-address / --amount.

The only on-chain bind from “pegBTC locked” to “BTC the user receives” is escrow.claimData (outputAmount + keccak256(outputScript) + nonce + confirmations). Once the LP claims, EscrowStorage._finalize sets CLAIMED and refund is impossible.

A malicious / compromised LP can return escrow.amount = 0.01 pegBTC (looks right) while claimData commits outputAmount = 1 sat. The CLI copies claimData verbatim and broadcasts initialize. User locks 0.01; LP pays 1 sat and claims the full lock.

Not proven here: live payInvoice lie, live initialize, pegBTC moved. This is a complete stock path in the shipped binary, not a live steal. It is not BitVM3 Take1/Take2.

Affected component

GOATNetwork/bitvm-node gc-v2 — Bridge State Machine helper CLI ([[bin]] name = "bridge-out").

  • node/src/bin/send_bridge_out.rs call_pay_invoice — POSTs --btc-address / --amount to default https://152-32-185-32.nodes.atomiq.exchange:8443/tobtc/payInvoice?chain=GOAT
  • parse_escrow_from_pay_invoiceclaim_data: parse_bytes32(&escrow_raw.claim_data, "claimData")? (verbatim)
  • run_swap_initialize_from_pay_invoicesubmit_swap_initialize_tx — no recompute
  • node/src/utils.rs hash_claim_commitment already implements the exact on-chain commitment and is not called on this path

Checked on gc-v2. Not #435 (Swap watcher from_script DoS). Not #428. Not #429 / #430 / #431. Not #21.

Mechanism

// send_bridge_out.rs parse_escrow_from_pay_invoice
claim_data: parse_bytes32(&escrow_raw.claim_data, "claimData")?,

CLI sends the user's requested address/amount to the LP, then trusts the returned claimData bytes. It never:

  1. Decodes outputAmount / outputScript from the commitment
  2. Recomputes hash_claim_commitment from --btc-address → scriptPubKey + expected sats + nonce/confirmations
  3. Asserts equality with escrow.claimData
  4. Prints the decoded payout before signing

On-chain claim only checks keccak of whatever script was committed. Timelock refund does not cover under-payment after a successful claim.

Impact

Per-user pegBTC theft against anyone who uses this CLI against a lying LP. Requires a malicious counterparty. Not an unauthenticated protocol path. Not BitVM3 kickoff/Take1/Take2.

Availability of a check that already exists in-repo (hash_claim_commitment) is the defect.

Suggested fix

In parse_escrow_from_pay_invoice (or immediately after), recompute hash_claim_commitment from the user's --btc-address / --amount / nonce / confirmations / btcRelay and require == escrow.claimData. Also assert escrow.amount, escrow.token, escrow.claimHandler, and refund timelock match the request before swap_initialize. Print decoded outputAmount + outputScript and refuse to submit on mismatch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions