Skip to content

Instantly share code, notes, and snippets.

@justmoon
Last active November 24, 2018 12:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justmoon/e63dff5c012e776a8329eb0c30a394f0 to your computer and use it in GitHub Desktop.
Save justmoon/e63dff5c012e776a8329eb0c30a394f0 to your computer and use it in GitHub Desktop.
ILP Compatibility for Bitcoin

Introduction

Bitcoin is a popular digital asset hosted by a public proof-of-work blockchain. As such, it would be nice to allow Bitcoin users to (securely) participate in Interledger Protocol (ILP) transactions.

There are three modes of operation described in this document, each with different security and performance trade-offs:

  1. Escrow Hashed Timelocked Contract (HTLC)

    This is the most secure, but also slowest way to integrate with Bitcoin. Each ILP transaction requires two consecutive Bitcoin transactions, so a delay over more than an hour is to be expected. This mode would be suitable for larger payments.

  2. Payment Channel

    This is the most efficient mode, but requires some trust between the sender and receiver. It also requires Bitcoin transactions to setup and claim the channel, but each channel can process a large number of smaller payments. The risk/trust between sender and receiver is limited to the amount of money in flight (ILP payments in prepared state) at any given point in time. This mode would be suitable for micropayments.

  3. Notarized Payment Channel

    This mode provides a balance of efficiency vs speed. It requires trust from sender and receiver that the notary will not collude with their counterparty, but the risk is limited to the amount in flight (as with regular payment channels) and the sender no longer has to trust the receiver and vice versa. This mode would be suitable for small to medium sized payments.

Mode 1: Escrow Hashed Timelocked Contract (HTLC)

In escrow mode, each ILP payment corresponds to two Bitcoin transactions. A prepare transaction, spending funds from a sender's wallet into a Hashed Timelocked Contract (HTLC). This is followed by either a execute transaction, spending funds from the HTLC to a wallet of the recipient's choice, a timeout transaction, which returns funds to the sender's wallet after a minimum block time has been reached or a reject transaction, which returns the funds to the sender's wallet, authorized using a rejection secret revealed by the receiver.

Bitcoin Script

The output of the prepare transaction uses a P2SH script as follows:

OP_IF
  OP_SIZE 32 OP_EQUALVERIFY
  OP_HASH256 <condition> OP_EQUALVERIFY
  <receiverKey> OP_CHECKSIGVERIFY
OP_ELSE
  OP_DUP OP_HASH160 <rejectionHash> OP_EQUAL
  OP_NOTIF
    <locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP
  OP_ELSE
    OP_DROP
  OP_ENDIF
  <senderKey> OP_CHECKSIGVERIFY
OP_ENDIF

The corresponding input of the execute transaction looks as follows:

<receiverSignature> <preimage> 1

The input of the timeout transaction looks as follows:

<senderSignature> 0

The input of the reject transaction looks as follows:

<senderSignature> <rejectionSecret> 0

The variables in the script are defined as:

  • <preimage> - An arbitrary 32-byte value matching the ILP payment's condition preimage.
  • <condition> - Equals the double hash of the preimage (SHA256(SHA256(<preimage>))), which means it equals the hash of the ILP payment's condition.
  • <senderKey> - The sender's secp256k1 public key.
  • <receiverKey> - The receiver's secp256k1 public key.
  • <senderSignature> - A transaction signature generated by the sender using their private key.
  • <receiverSignature> - A transaction signature generated by the receiver using their private key.
  • <rejectionSecret> - A secret value chosen by the recipient and revealed in the case of a rejection.
  • <rejectionHash> - The OP_HASH160 hash of the rejection secret (RIPEMD160(SHA256(<rejectionSecret>)))

How it works

The script will first decide whether to run in execute or cancel mode. This is determined by the top item on the stack which corresponds to the 0 or 1 at the end of each redemption script. 1 signifies execute mode and 0 signifies cancel mode. In execute mode, the transaction is signed by the recipient, which means the recipient gets to choose the transaction outputs and can direct the money wherever they please. In cancel mode, which is used for both timeout and reject transactions, the transaction is signed by the sender, meaning they can direct the money wherever they please.

execute transaction

In order to enable execute mode, the input ends with a 1 added to the top of the stack. Next, the recipient must present the <preimage>. The script verifies two things about the preimage:

  • That the <preimage> is 32 bytes long
  • That the double SHA256 hash of the <preimage> matches the <condition>

The length verification is performed by the OP_SIZE 32 OP_EQUALVERIFY portion. The hash integrity is checked by the OP_HASH256 <condition> OP_EQUALVERIFY. Finally, <receiverKey> OP_CHECKSIGVERIFY allows the receiver to spend the funds with their <receiverSignature>.

reject transaction

First, cancel mode is enabled by ending the input script with 0. Next, the input script provides the <rejectionSecret>, which will cause OP_DUP OP_HASH160 <rejectionHash> OP_EQUAL to evaluate to 1 (true). Next, the OP_ELSE branch is executed, which drops the <rejectionSecret> from the stack.

Finally, the <senderSignature> is verified against the <senderKey>.

timeout transaction

First, cancel mode is enabled by ending the input script with 0. Next, the output script will attempt to interpret the <senderSignature> as a <rejectionSecret> which will fail and push 0 (false) onto the stack. This sends the script into the OP_NOTIF branch, which uses OP_CHECKLOCKTIMEVERIFY to verify that the timeout has been reached. The locktime is then dropped from the stack using OP_DROP.

Finally, the <senderSignature> is verified against the <senderKey>.

Mode 2: Payment Channel

Sender and receiver set up a payment channel using OP_CHECKLOCKTIMEVERIFY (CLTV channel). If the sender wants to trust the receiver for in-flight payments, they will send money over the channel to the recipient during the ILP prepare phase. If the recipient wants to trust the sender, the sender will send money over the channel to the recipient after recieving a valid fulfillment in the ILP execute phase.

Mode 3: Notarized Payment Channel

TODO

@michielbdejong
Copy link

Let me play advocate of the devil here. :) I would expect people to pay for connector services, but not for notary services. Thinking out loud here:

  • If I repeatedly want to pay the same person, intra-bitcoin, I would set up a payment channel to them. That’s where I might want to use a notary service, in case disputes arise. Fair enough, but I don’t think this would be the most common use case. I probably want to pay many different people, and when I pay someone, I often have no plans yet for the next payment, or if I do, I don't want to put dedicated money on hold for that yet.

  • If I want to make a one-off intra-bitcoin payment, and keep the rest of my stash in cold, I don’t think anything is safer/faster than a classic bitcoin payment.

  • If I want to pay from bitcoin to elsewhere, I need a connector. If I expect to make more payments to non-bitcoin addresses in the future, it make sense to me to set up a payment channel to a connector I’m happy with. I don’t see a use case for a notary there, the connector is the service I (partially) trust.

  • If I want to easily receive payments into bitcoin, whether incoming from elsewhere or intra-bitcoin, I would pay a connector to set up a payment channel to me, and keep it funded.

  • Again, I’m trusting this connector to provide this service, involving a second service provider as a notary seems like overcomplicating things.

  • If I want to pay you, and I have an outgoing payment channel to connector A, and you have an incoming payment channel from connector C, we would hope connectors A and C peer with each other. This is where a notary B could be useful. But then again, why not just make B a connector instead of a notary?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment