Dr. Mark B Lundeberg, 2018 Feb 15
bitcoincash:qqy9myvyt7qffgye5a2mn2vn8ry95qm6asy40ptgx2
This security advisory notes a vulnerability in the common construction of cross-chain smart contracts (contracts between distinct cryptocurrencies) through hash locking. I focus on the primary use case in atomic swap contracts, which allow two cryptocurrencies to changes hands simultaneously and safely. A simple fix is provided.
The canonical bitcoin atomic swap works as follows: Alice prepares a random
secret k
with a 20-byte hash H=HASH160(k)
and then funds the following
contract (a P2SH redeem script, which she shows to swap counterparty Bob), in
Bitcoin script:
IF
<BKey> CHECKSIGVERIFY
HASH160 <H> EQUAL
ELSE
<AKey> CHECKSIGVERIFY
<ATime> CHECKLOCKTIMEVERIFY
ENDIF
Meaning of this contract: A signature from Bob's public key BKey
in
combination with secret k
can spend the money. However as a fallback in case
of cancellation, a signature from Alice's public key AKey
lets her get a
refund, but only after ATime
.
Bob does not know k
, yet. He funds a similar contract on his blockchain using
Alice's H
value, but with a BTime
expiring significantly sooner than
ATime
:
IF
<AKey> CHECKSIGVERIFY
HASH160 <H> EQUAL
ELSE
<BKey> CHECKSIGVERIFY
<BTime> CHECKLOCKTIMEVERIFY
ENDIF
Now, Alice may redeem Bob's contract but in doing so, she must reveal k
. Bob
can now see k
which allows him to redeem Alice's contract. If the deal is
called off then Bob is allowed to get a refund at BTime
, and then Alice can
get her refund after ATime
. The above setup is generally perceived as
perfectly secure and is being proposed for safe on-chain cryptocurrency
exchanges that do not involve a third party.
...
But what if Alice could construct a k
that works in Bob's contract, and not
in her own contract?
Bitcoin scripts have a maximum allowed data element size of 520 bytes
ref
, which means that k
cannot exceed 520 bytes in a valid bitcoin
transaction. This arbitrary number, 520 bytes, is not identical in all
cryptocurrencies. Although atomic swaps conventionally use a 32 byte secret
k
, nothing in the above contracts prevents Alice from using a much much
larger k
.
Let SmallCoin be a cryptocurrency with maximum element size of 520 bytes, and BigCoin be a cryptocurrency with maximum element size of 1000 bytes (or unlimited). Alice owns some SmallCoin and wishes to scam owners of LargeCoin through cross-chain atomic swaps.
-
Alice and Bob agree to exchange her SmallCoins for his LargeCoins via atomic swap.
-
Alice prepares a 700 byte secret
k
, and proceeds as normal to fund her atomic swap SmallCoin contract with 20-byte hashH=HASH160(k)
. -
Bob waits for her contract to confirm, then funds his BigCoin contract (as above) with the same hash
H
. -
Alice redeems Bob's contract, revealing
k
in the process. -
Bob tries to use Alice's
k
to redeem Alice's contract, however the size ofk
is simply too large for SmallCoin and is rejected. -
Alice waits until her own contract expiry time
ATime
and is now able to redeem her own contract. She now owns both the SmallCoins and BigCoins, and Bob has nothing.
In step 5, Alice's contract is impossible for Bob to redeem, however he had no
way of knowing that since Alice only provided the 20-byte hash H
at the
start. Bob is screwed the moment he completes step 3; only in step 4 does he finds out the length of k
.
With Bitcoin in particular, there is no way for Bob to write a script that constructs a 900 byte string out of smaller pieces, due to scripting language limitations (string concatenation, multiplication, and bit shifting are all disabled opcodes).
Bob's contract must be modified to include a check on the size of Alice's secret, which forces her to honestly tell him the length beforehand. For example, if she claims her secret is 16 bytes (128 bit strength) then he should write:
IF
<AKey> CHECKSIGVERIFY
SIZE 16 EQUALVERIFY HASH160 <H> EQUAL
ELSE
<BKey> CHECKSIGVERIFY
<BTime> CHECKLOCKTIMEVERIFY
ENDIF
Alice cannot redeem this script if she lied about the size of k
. In Bitcoin
script, including SIZE 16 EQUALVERIFY
or SIZE 32 EQUALVERIFY
only adds 3 or
4 bytes respectively.
Bob must check Alice's contract first, to see whether any possible value of k
that satisfies his contract will also satisfy her contract (her contract may
have explicit/implicit size limitations).
The atomic swaps from decred's setup look to be accidentally immune since the supported cryptocurrencies appear to all be forked from bitcoin and thus have the same 520 byte limit. The contracts do however lack size checks, meaning they will be exploitable when expanding to other cryptocurrencies.
Upon expansion to alien cryptocurrencies like Ethereum, the attack described here will become a concern and can go two ways:
-
(Alice has BTC, Bob has ETH) If Bob's ETH contract uses a dynamically-sized
bytes
array fork
values, Alice can steal using a 700 bytek
as described above. -
(Alice has ETH, Bob has BTC) If Alice's ETH contract uses a
bytes32
fixed-length array fork
but Bob's BTC contract permits variable-lengthk
(up to 520 bytes), Alice can steal by using a 64 bytek
.
Great find. Your fix can be made radically simpler though w/o on chain checks.
SHA256 commits to the length of the string to prevent length extension attacks. By making the hashes a 64 byte nonce, one can just reveal the midstate and prove that any string that could satisfy the hash would be of length 64 bytes.
edit: This is the same thing that @jl2012 is recommending, my bad -- didn't really read the other comments (just scanned to see if anyone mentioned midstates). It does require the nonce to be at least 57 bytes long, but the extra data could be zeros (and hence more easily compressible)