What can go wrong when transferring coins into a sidechain with OP_WITHDRAWPROOFVERIFY

Last week I wrote a blog post about what OP_WITHDRAWPROOFVERIFY does. This week I want to write about what can go wrong when transferring coins into a sidechain using OP_WPV.

Here is an outline of the steps you need to take to deposit coins into a sidechain.

  1. Get a deposit address controlled by the federation on the Bitcoin blockchain
  2. Generate an address that your coins will be deposited into on the sidechain.
  3. Deposit money into the Bitcoin address controlled by the federation (this transaction is called the ‘locking transaction’)
  4. Wait for confirmations on the ‘locking transaction’
  5. Now collect the pieces of data specified (tx output index, locking tx, merkle block proof, contract) in my last blog post.
  6. Create a transaction on the sidechain that spends coins to your sidechain address.

A little complex, fortunately when there is enough liquidity in a sidechain you won’t have to go through this process — you will be able to use atomic swaps.

So what are the various things that can go wrong when using OP_WPV?

  1. You can provide a malformed/non minimal stack argument. This isn’t really that interesting but should be noted.
  2. Check that the provided merkle block has the proof of work that it claims to have. This is done by taking all of the information in the block header of the merkle block and checking that it hashes to a number below the the threshold (nBits) that it claims to hash below.
  3. Next we check that the merkle block’s contents hash to the same merkle root in the block header are provided. This means the contents of the merkle block have not been tampered with. We also check to make sure that only one transaction is matched inside of the merkle block. That transaction is the locking transaction.
  4. Now we check that the merkle block is not a spend from the genesis block on the blockchain we are pegged to. For instance, Satoshi’s genesis output could not be used as a pegin to our sidechain. I don’t know why this is here — for instance if you were pegged to litecoin there doesn’t seem to be a reason to prevent Charlie from spending from the genesis output.

If any of the items I listed above fail, you will not be credited with funds on the sidechain.

Now we have checked all block related information given to us inside of the OP_WITHDRAWPROOFVERIFY script signature. Next we need to move on to checking information related to our locking transaction — remember this is the transaction that sends funds to the federation’s address on the bitcoin blockchain.

  1. Locking transaction needs to be well formed, the output index which locks funds in the federation’s address needs to be minimally encoded.
  2. The output index given must be in range of the locking transaction’s output vector. For instance, we can’t have -1 as an output index. We also cannot have a number larger than the number outputs we have in the locking transaction.
  3. Check that the locking transaction’s hash is contained inside our merkle block. This gives us a cryptographic proof that the user has created a transaction and that transaction has been included inside of a block.

If any of the items above fail, you will not be credited with funds on the sidechain.

The next thing we check is the contract we created. This contract data structure is pretty interesting. The purpose of the contract is to trustlessly transfer funds into a user’s sidechain address by paying into a specific bitcoin address. Another way to say this is exactly one sidechain address can receive the credit for the bitcoin deposited in this bitcoin address. Here are the invariants of the contract:

  1. It must be 40 bytes in length.
  2. The first 4 bytes of the contract must be ‘P2PH’ or ‘P2SH’. This is needed to indicate if the user wants to create a P2SH or P2PKH scriptPubKey. I don’t see a reason to create a P2PKH scriptPubKey, as that type can be nested inside of a P2SH scriptPubKey. The reason to support P2SH is to allow for more expressive redeem scripts rather than standard ones such as P2PKH or multisig.
  3. The next 16 bytes serve as a nonce. This is used to inject randomness into the scriptPubKey to make it harder to track hashes cross chain.
  4. The last 20 bytes is a user’s hash where they want the sidechain funds to be sent to. This is your sidechain address.

If the contract is not formed correctly, you will not be able to spend funds on the sidechain. If you lose this contract, the federation controlling the bitcoin address will not be able to spend from it. This means the coins will be locked on the bitcoin blockchain forever. This is similar to what happens when you lose a redeem script for a p2sh scriptPubKey.

The last thing that we check is the outputs for the locking transaction on the bitcoin blockchain, and our spending transaction outputs on our sidechain.

  1. We must check that the address inside of the locking transaction on the bitcoin blockchain is one that the federation controls. We do this by using some elliptic curve math to generate unique public keys (with the contract we provided above) for the federation’s multisig script. We need to check that the address the user pays to on the bitcoin blockchain is the same. This is a little complex, but the way it is done inside of elements is more private than doing it the dumb way — which is just having a static address that everyone sends funds to to transfer funds into the sidechain.
  2. The first output of the spending transaction on the sidechain must send the same amount of money as the locking transaction’s output sends.
  3. The first output of the spending transaction on the sidechain must send funds to the sidechain address specified in the contract.
  4. If there is change from the input’s used to send money to the user’s sidechain address, that change must be sent back to an OP_WITHDRAWPROOVERIFY output to be used later.

In simple terms, this section means that we can only claim the same amount of funds on the sidechain as we sent to the federations address on the bitcoin blockchain. These funds should only be sent to the address the user specified in their contract. If any of the above invariants fail, you will not be credited with funds on the sidechain.

That is all that can go wrong when transferring funds into the sidechain. Fortunately this can all be done with a good wallet — no need for user interaction unless the wallet software is buggy.

We should consider the case for when a user’s software has bugs though. The only way to be able to redeem your Bitcoin is actually contacting the operators of the federation. You will need to show them that you cannot claim your sidechain funds because of one of the reasons above and the show them the contract. Again, if you lose the contract you will not be able to redeem your funds on the Bitcoin blockchain.

We are currently working on an implementation of OP_WITHDRAWPROOFVERIFY at SuredBits. We hope that our work will help move the industry towards interoperable open blockchains — which will allow us to avoid contentious debates in the future over parameters for a blockchain (such as block size). If this stuff interests you, please follow my company @SuredBits or my personal twitter account @Chris_Stewart_5.

Special thanks to gwillen, andytoshi, and instagibbs for clarifications on the implementation of OP_WITHDRAWPROOFVERIFY in elements.