- opening two channels with the victim. - routing a payment through them. - successfully replacement-cycling the victim's htlc-timeouts for Δ blocks. - without the victim discovering the htlc-preimage transaction.
"Imagine Bob is routing a lightning payment from Alice to Carol. While in flight, the payment is protected by HTLC outputs in his pre-signed channel commitments with each peer."
"A HTLC (Hash/Time Lock Contract) is a conditional payment from sender to receiver. It can be spent immediately by the receiver by revealing the preimage to a hash H, or reclaimed by the sender after some timeout. Check out a real HTLC spend here."
"By securing the HTLC on each hop with the same hashlock, payments can be routed atomically. Carol can't claim the outgoing HTLC without revealing the preimage, which Bob can then use to redeem the incoming HTLC from Alice. At least that's the theory..."
"To ensure Bob has time to react if something goes wrong, the timelock on the outgoing HTLC expires first at some block height T. Then the timelock on the incoming HTLC expires at some later height T+Δ, after which Alice can reclaim her money."
"OK, so here's the attack: Remember Bob has HTLCs pending in two channels. One outgoing HTLC to Carol, which expires at block T, and one incoming HTLC from Alice, which expires at block T+Δ."
"At block T, Carol still hasn't revealed the preimage to settle the payment, so Bob is forced to time it out on-chain. He broadcasts the commitment tx to close his channel with Carol, and once it confirms sends an "htlc-timeout" tx which spends the HTLC to reclaim his funds."
"Unbeknownst to Bob, Alice and Carol are colluding to steal his money. They have prepared for the attack by broadcasting a chain of two transactions with low fees, apparently unrelated to the lightning channel, which we'll call the "cycle parent" and "cycle child"."
"As soon as the attackers see Bob's htlc-timeout transaction hit the mempool, they broadcast an "htlc-preimage" transaction, which spends both the HTLC output (using Carol's hash preimage) and an output from the cycle parent."
"Since this htlc-preimage transaction pays a higher fee rate and spends the same inputs, it replaces both the cycle child and Bob's htlc-timeout transaction in the mempool."
"If Bob sees this, he can take the preimage and use it to immediately redeem the incoming HTLC from Alice. So the attackers broadcast a new transaction replacing the cycle parent. The htlc-preimage depends on that for one of its inputs, so is also evicted from the mempool."
"At the end of this cycle, the HTLC from Bob's channel with Carol ends up unspent, and no trace of the htlc-timeout and htlc-preimage transactions remain in the mempool."
"The attackers repeat the cycle to eject Bob's htlc-timeout transaction every time he rebroadcasts it. If they prevent it getting mined for another Δ blocks, Alice can timeout the HTLC on the other channel, and leave Bob out of pocket for the entire value of the payment."
"Increasing the timelock delta or rebroadcasting the htlc-timeout more aggressively make the attack more difficult and more expensive, but still not impossible."
"Bob can actively monitor his local mempool to spot the htlc-timeout before it gets replaced. But a smart attacker could selectively broadcast replacements so that miners receive them while Bob does not."
"Perhaps Bob could improve his chances by employing watchtowers connected to other parts of the network to look out for cycled htlc-timeouts and forward him any relevant preimages."
"A proper fix probably requires more fundamental changes."
"We could redesign the HTLC protocol to prevent adding extra inputs to htlc-preimages (so they can't be replaced)."
"Or change relay policy to propagate replaced transactions (so the preimage always reaches Bob)."
"Or have miners keep a cache of recently replaced transactions which may be able to re-enter the mempool later (so that Bob doesn't need to rebroadcast his htlc-timeout). This could be built into Bitcoin Core, or run as an external service."
"Or soft fork in a new opcode which does the opposite of check-locktime-verify (so we can make the htlc-preimage spend path invalid as soon as the timelock expires)," said @mononaut.