Escrow
This example is part of the smart contracts presented in [AB+18POST].
Assume Alice wants to buy an item from Bob. Since they do not trust each other, they would like to use a contract to ensure that Bob will get paid if and only if Alice gets her item.
Assume that the needed amount to pay Bob is stored in an actual
transaction redeemable by Alice. We model that transaction in the
editor using a fake coinbase transaction A_funds
.
// Alice's public key
const kApub = pubkey:03ff41f23b70b1c83b01914eb223d7a97a6c2b24e9a9ef2762bf25ed1c1b83c9c3
// tx with Alice's funds, redeemable with Alice's private key
transaction A_funds {
input = _
output = 1 BTC: fun(x). versig(kApub; x)
}
kApub
is the public key of Alice and versig(kApub; x)
checks
that x
is a valid signature for kApub
.
Assuming that only Alice owns the corresponding private part
of kApub
, she is the only one able to spend A_funds
.
Simple contract
In a naive attempt to realise a secure contract, Alice generates the following transaction T
:
// Alice's private key
const kA = key:cSthBXr8YQAexpKeh22LB9PdextVE1UJeahmyns5LzcmMDSy59L4
// Alice's public key
const kApub = pubkey:03ff41f23b70b1c83b01914eb223d7a97a6c2b24e9a9ef2762bf25ed1c1b83c9c3
// Bob's public key
const kBpub = pubkey:03a5aded4cfa04cb4b49d4b19fe8fac0b58802983018cdd895a28b643e7510c1fb
transaction T {
input = A_funds: sig(kA)
output = 1 BTC: fun(x, y). versig(kApub, kBpub; x, y)
}
Transaction T
redeems transaction A_funds
using the
signature sig(kA)
, and locks the money so that it can be redeemed
only with the joint signatures of both Alice and Bob.
In order to spend T
, either Alice will send her signature to Bob or viceversa.
Consider the following parametric transaction:
transaction T1(sigA:signature, sigB:signature, pubK:pubkey) {
input = T: sigA sigB
output = 1 BTC: fun(x). versig(pubK; x)
}
It takes the signatures sigA
and sigB
of both Alice and Bob, and
a public key pubK
used in the output script.
Rationally, Alice will use pubK == kApub
while Bob pubK == kBpub
.
The protocol proceeds as follows: if Alice received her item, she sends her signature to Bob; if the item has not arrived or has not been shipped, Bob can send his signature to Alice and she gets back her money.
Alice compute the signature as follows:
// signature of T1 that send the money to Bob
const sigA = sig(kA) of T1(_,_,kBpub)
Similarly Bob does the same:
// signature of T1 that send the money to Alice
const sigB = sig(kB) of T1(_,_,kApub)
Once only one of the participant receives the signature,
he creates a transaction that spends T
.
Suppose Alice received Bob’s signature. She completes T1
as follows:
const sigB = sig:<hex string made by Bob>
const sigA = sig(kA) of T1(_,_,kApub)
eval T1(sigA, sigB, kApub)
Otherwise, if Bob received Alice’s signature:
const sigA = sig:<hex string made by Alice>
const sigB = sig(kB) of T1(_,_,kBpub)
eval T1(sigA, sigB, kBpub)
This approach assume that the two participant are honest and they
will send their signature to the other party.
However this is unrealistic: consider the case in which Alice has created
the transaction T
but Bob decided both to not sell the item and to not
refund her. Alice has freezed her bitcoins forever.
Arbitrated contract
The protocol seen so far has a dangerous vulnerability: it is secure
only if both participants are honest. Indeed, either Alice might refuse
to send her signature after receiving the item, hence causing Bob to lose
money; or Bob might refuse to send his one while not sending the item,
so causing Alice to lose the money. In both cases, the bitcoins stored
within transaction T
are lost.
A possible solution to this problem is to entitle a third participant the
role of arbiter, trusted by both Alice and Bob, to decide in case of problems.
Indeed, transaction T
is modified into a 2-of-3 multi signature schema:
// Carl's public key
const kCpub = pubkey:02ede655785dacac6d6985588f6558be2d318012ee36067d3227871d350678c132
transaction T {
input = A_funds: sig(kA)
output = 1 BTC: fun(x, y). versig(kApub, kBpub, kCpub; x, y)
}
Transaction T
can be redeemed either with the signatures of Alice and
Bob, or with the ones of Alice and the arbiter, or with the ones of
Bob and the arbiter.
In case of dispute, the arbiter (Carl) will send his signature either to Alice or Bob.
For example, assume he decided to refund Alice.
In this case, she can instantiate Carl’s signature and create the transaction T_A
to get her bitcoins back, as follows:
const sigC = sig:<hex string made by Carl>
transaction T_A {
input = T: sig(kA) sigC
output = 1 BTC: fun(x). versig(kApub; x)
}