The problem with TierNolan’s atomic protocols is that they utilise non-standard transactions. This makes them unusable under real conditions. I would like to propose a variation of his first protocol that uses purely standard transactions (at least in Bitcoin).

The main trick is that since Bitcoin Core 0.10.0rc1 it is possible to use previously non-standard transactions if they are encoded in P2SH.

Differences from the original protocol

I want to emphasise that the original logic is remained untouched. But now all scriptPubKeys contain P2SH scripts instead of non-standard scripts and their follow-up scriptSigs in addition contain the original scripts from respective scriptPubKeys.

I also thought it would be nice to make the protocol behave as efficient as possible. Therefore the operators in scripts are in such order that the most expensive operations are performed as last. It’s just a minor detail, but it still might come in handy when it comes to DoS attacks.

Scripts

tx1 (commit)

redeemScript
OP_IF
    2 <pubkey A> <pubkey B> 2 OP_CHECKMULTISIG
OP_ELSE
    OP_HASH160 <H(x)> OP_EQUALVERIFY <pubkey B> OP_CHECKSIG
OP_ENDIF
scriptPubKey
OP_HASH160 <H(tx1 redeemScript)> OP_EQUAL

tx2 (refund)

scriptSig
0 <sig A> <sig B> OP_TRUE <tx1 redeemScript>
scriptPubKey

Anything Alice wants, e.g., a pay-to-pubkey-hash script.

tx3 (commit)

redeemScript
OP_IF
    2 <pubkey B> <pubkey A> 2 OP_CHECKMULTISIG
OP_ELSE
    OP_HASH160 <H(x)> OP_EQUALVERIFY <pubkey A> OP_CHECKSIG
OP_ENDIF
scriptPubKey
OP_HASH160 <H(tx3 redeemScript)> OP_EQUAL

tx4 (refund)

scriptSig
0 <sig B> <sig A> OP_TRUE <tx3 redeemScript>
scriptPubKey

Anything Bob wants, e.g., a pay-to-pubkey-hash script.

tx5 (claim from tx3)

scriptSig
<sig A> <x> OP_FALSE <tx3 redeemScript>
scriptPubKey

Anything Alice wants, e.g., a pay-to-pubkey-hash script.

tx6 (claim from tx1)

scriptSig
<sig B> <x> OP_FALSE <tx1 redeemScript>
scriptPubKey

Anything Bob wants, e.g., a pay-to-pubkey-hash script.

Summary

This protocol is atomic, uses straightforward scripts, doesn’t bloat UTXO, and is formed by standard transactions. Therefore, this one is going to be used in Coincer.

Implementation

Again, I’m attaching a Ruby script that implements the protocol. Only tx1, tx2, and tx6 are shown as the other three transactions are technically identical.

#!/usr/bin/env ruby

require 'rubygems'
require 'bitcoin'
require 'digest'
require 'securerandom'

Bitcoin.network = :testnet3

# generate x, key of A and B
x = SecureRandom.random_bytes(16)
key_a = Bitcoin::generate_key
key_b = Bitcoin::generate_key

key_a = Bitcoin::Key.new key_a[0], key_a[1]
pubkey_a = key_a.pub_compressed
key_a = Bitcoin.open_key(key_a.priv)

key_b = Bitcoin::Key.new key_b[0], key_b[1]
pubkey_b = key_b.pub_compressed
key_b = Bitcoin.open_key(key_b.priv)

include Bitcoin::Builder

# fetch the tx from a file in binary form and parse it
prev_tx = Bitcoin::P::Tx.from_file('tx.bin')

# the number of the output we want to use
prev_out_index = 0

# the key needed to sign an input that spends the previous output
key = Bitcoin::Key.from_base58('cSQHwFgXS6D4w1b9FykfRt4NwXjoYuJfWa8HytzmizPZRse1ZNXV')
pubkey = key.pub
key = Bitcoin.open_key(key.priv)

## TX1
tx1 = Bitcoin::P::Tx.new

# add the input
tx1.add_in Bitcoin::P::TxIn.new(prev_tx.binary_hash, prev_out_index, 0)

# add a change output
tx1.add_out Bitcoin::P::TxOut.value_to_address(49_0000_0000, 'muxV42VtkHAEmgxVSzTXn9sBGprKZDk7fq')

# prepare the redeem script
redeem = Bitcoin::Script.binary_from_string("OP_IF 2 #{pubkey_a} #{pubkey_b} 2 OP_CHECKMULTISIG OP_ELSE OP_HASH160 #{Digest::RMD160.hexdigest(Digest::SHA256.digest(x))} OP_EQUALVERIFY #{pubkey_b} OP_CHECKSIG OP_ENDIF")

# add an output locking coins into a trade
tx1.add_out Bitcoin::P::TxOut.new(1_0000_0000, Bitcoin::Script.binary_from_string("OP_HASH160 #{Digest::RMD160.hexdigest(Digest::SHA256.digest(redeem))} OP_EQUAL"))

# sign
sig = Bitcoin.sign_data(key, tx1.signature_hash_for_input(0, prev_tx))
tx1.in[0].script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [pubkey].pack("H*"))

# ready to use
#puts tx1.to_payload.unpack('H*')[0]  # tx in hex
#puts tx1.to_json                     # parsed tx in json

## TX2
tx2 = Bitcoin::P::Tx.new

# add the input
tx2.add_in Bitcoin::P::TxIn.new(tx1.binary_hash, 1, 0)

# add an output
tx2.add_out Bitcoin::P::TxOut.value_to_address(1_0000_0000, 'muxV42VtkHAEmgxVSzTXn9sBGprKZDk7fq')

# lock the transaction for 24 hours into the future from now
tx2.lock_time = Time.now.to_i + 3600*24

# sign
sig_a = Bitcoin.sign_data(key_a, tx2.signature_hash_for_input(0, redeem))
sig_b = Bitcoin.sign_data(key_b, tx2.signature_hash_for_input(0, redeem))

buf = Bitcoin::Script.to_multisig_script_sig(sig_a, sig_b)
buf += [Bitcoin::Script::OP_TRUE].pack('C*')
buf += Bitcoin::Script::pack_pushdata(redeem)
tx2.in[0].script_sig = buf

# ready to use
#puts tx2.to_payload.unpack('H*')[0]  # tx in hex
#puts tx2.to_json                     # parsed tx in json

## TX6
tx6 = Bitcoin::P::Tx.new

# add the input
tx6.add_in Bitcoin::P::TxIn.new(tx1.binary_hash, 1, 0)

# add an output
tx6.add_out Bitcoin::P::TxOut.value_to_address(1_0000_0000, 'muxV42VtkHAEmgxVSzTXn9sBGprKZDk7fq')

# sign
sig = Bitcoin.sign_data(key_b, tx6.signature_hash_for_input(0, redeem))
buf = Bitcoin::Script::pack_pushdata(sig + [Bitcoin::Script::SIGHASH_TYPE[:all]].pack("C"))
buf += Bitcoin::Script::pack_pushdata(x.unpack('H*')[0].htb)
buf += [Bitcoin::Script::OP_FALSE].pack('C*')
buf += Bitcoin::Script::pack_pushdata(redeem)
tx6.in[0].script_sig = buf

# ready to use
#puts tx6.to_payload.unpack('H*')[0]  # tx in hex
#puts tx6.to_json                     # parsed tx in json

Script licence: Public Domain