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:
-
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.
-
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.
-
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.
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.
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>))
)
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.
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>
.
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>
.
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>
.
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.
TODO
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?