ARK
Introduction
The ARK protocol is a bitcoin layer 2 proposal that focuses on making reducing liquidity locking requirements and channel setup requirements for end users. The trade off is to push the liquidity provision and management to "ARK Service Providers" - ASPs.
In ARK, a user gets access to a virtual output that the server and the user control. The user can unilaterally exit after paying the fees to confirm 1 or more transactions. Ideally, the user asks the server to pay another user off-chain. The server can’t steal any of the user’s funds and the user to user transfers are atomic.
The protocol has a few subtleties and the Bitcoin DSL can help clarify how ARK works. In the following we describe the ARK approach without using any covenants.
Alice Builds Funding Transaction
Alice, creates a funding transaction with it’s own input and an output that can be spent by Alice & ASP or only by ASP after a timeout period.
The funding transaction can be spent cooperatively by the two parties signing the funding transaction or by user after a timeout period (say 1 month)
Using Bitcoin DSL we capture the funding transaction output script as a miniscript policy
The Bitcoin DSL state transition for funding transaction is:
state_transition :create_funding_tx do (1)
@funding_tx = transaction inputs: [
{ tx: @alice_coinbase_tx, vout: 0, script_sig: 'sig:_empty' } (2)
],
outputs: [
{
policy: 'or(99@thresh(2,pk(@alice),pk(@asp)),'\
'and(older(@timeout_period),pk(@asp_timelock)))', (3)
amount: 49.999.sats
}
]
assert_not_mempool_accept @funding_tx (4)
end
1 | Create a state transition that will allow us to run things in various orders |
2 | Alice does not sign the transaction yet |
3 | Policy is used to define the locking condition |
4 | The transaction can’t be spent yet, because it is not fully signed |
ASP Builds Redeem Transaction for Alice
Alice sends the funding transaction without adding a signature to it and the ASP creates a redeem transaction for Alice, which is signed by ASP and sent back to Alice.
state_transition :create_redeem_tx do
# Redeem transaction for Alice is only signed by ASP at this point
@redeem_tx = transaction inputs: [
{ tx: @funding_tx, vout: 0, script_sig: 'sig:@asp sig:_empty' } (1)
],
outputs: [
{
policy: 'or(99@thresh(2,pk(@alice),pk(@asp)),'\
'and(older(@timeout_period),pk(@alice_timelock)))', (2)
amount: 49.998.sats
}
]
assert_not_mempool_accept @redeem_tx (3)
end
1 | ASP adds its signature |
2 | Policy states Alice can sweep after timeout, else Alice and ASP cooperate |
3 | The transaction can’t be spent yet, because it is not fully signed |
Confirm Funding Transaction
Once Alice has the redeem transaction she adds her signature to the input of the funding transaction and broadcasts it to the network.
state_transition :broadcast_funding_tx do
update_script_sig for_tx: @funding_tx,
at_index: 0,
with_script_sig: 'sig:wpkh(@alice)' (1)
broadcast @funding_tx
extend_chain
assert_confirmations @funding_tx, confirmations: 1
# Extend chain so that ASP can spend some coinbases
extend_chain num_blocks: 101, to: @asp
end
1 | Alice signs the input to the funding transaction |
Initiate Payment from Alice to Bob
Alice next asks ASP to send a payment to Bob on her behalf. This requires that
-
Alice’s redeem transaction is consumed and Bob gets a new redeem transaction - this exchange has to be atomic.
-
Neither party can steal funds from any other party, or lose funds in any other way.
ASP Creates a Pool Transaction and Redeem Tx for Bob
To execute such a payment, the ASP creates a pool transaction that is funded by an ASP UTXO. The pool transaction has two outputs - one that can be spent cooperatively by Alice and the ASP or by ASP after a timelock. The second output is the connector that Alice will later use.
# Alices sends a signed request to ASP for initiating a payment to Bob
# ASP creates a Redeem transaction for Bob
state_transition :initialise_payment_to_bob do
@pool_tx = transaction inputs: [
{ tx: @asp_payment_coinbase_tx, vout: 0, script_sig: 'sig:wpkh(@asp)' }
],
outputs: [
{
policy: 'or(99@thresh(2,pk(@bob),pk(@asp)),'\
'and(older(@timeout_period),pk(@asp_timelock)))',
amount: 49.997.sats
},
{
descriptor: 'wpkh(@asp)', # The connector output (1)
amount: 0.001.sats
}
]
assert_mempool_accept @pool_tx
@redeem_tx_for_bob = transaction inputs: [
{ tx: @pool_tx, vout: 0, script_sig: 'sig:@asp sig:_empty ""' } (2)
],
outputs: [
{
policy: 'or(99@thresh(2,pk(@bob),pk(@asp)),'\
'and(older(@timeout_period),pk(@bob_timelock)))', (3)
amount: 49.996.sats
}
]
# pool_tx is not confirmed yet, so Bob's redeem tx can't be accepted
assert_not_mempool_accept @redeem_tx_for_bob
end
1 | The connector output, that Alice will use in the next step |
2 | ASP adds its signature |
3 | Bob’s redeem transaction can be spent cooperatively or by Bob after timelock |
Alice Creates Forfeit Transaction
Alice now creates a forfeit transaction that has two inputs:
-
Alice’s redeem transaction that is being used to pay Bob
-
Pool’s connector output - this essentially makes the forfeit transaction spendable only if the pool transaction is confirmed on chain.
state_transition :build_alice_forfeit_tx do
@forfeit_tx = transaction inputs: [
# Only signed by Alice
{ tx: @redeem_tx, vout: 0, script_sig: 'sig:_empty sig:@alice ""' }, (1)
# connector output from pool_tx
{ tx: @pool_tx, vout: 1, script_sig: 'sig:wpkh(@asp)' } (2)
],
outputs: [
{ descriptor: 'wpkh(@asp)', amount: 49.997.sats }
]
# Can't broadcast forfeit tx until redeem tx is confirmed
assert_not_mempool_accept @forfeit_tx
end
1 | Alice uses the redeem transaction output, and signs the input. |
2 | The second input is the connector output from the pool transaction. |
Fork: We Cover Two Outcomes From Here
At this point there are a few possible outcomes but to demonstrate the Bitcoin DSL and at the same time capture how ARK makes the payments, we cover only two of the outcomes.
-
Bob redeems the coins and ASP plays fair.
-
ASP does nothing and Alice redeems the coins.
Bob Redeems the Coins
state_transition :bob_redeems_coins do
# Bob adds his signature to redeem tx
update_script_sig for_tx: @redeem_tx_for_bob,
at_index: 0,
with_script_sig: 'sig:@asp sig:@bob ""' (1)
# Bob can redeem his coins
assert_mempool_accept @redeem_tx_for_bob
# Alice can no longer redeem her coins
assert_not_mempool_accept @redeem_tx
end
1 | Bob adds his signature the redeem transaction before broadcasting it |
This outcome is execute by running the transitions as:
# Publish pool transaction, paying Alice to Bob
run_transitions :setup,
:create_funding_tx,
:create_redeem_tx,
:broadcast_funding_tx,
:initialise_payment_to_bob,
:build_alice_forfeit_tx,
:publish_pool_tx, (1)
:bob_redeems_coins
1 | Pool transaction is published |
Alice Redeems the Coins After Timeout
state_transition :alice_redeems_coins_after_timeout do
add_csv_to_transaction @redeem_tx, index: 0, csv: @timeout_period
update_script_sig for_tx: @redeem_tx,
at_index: 0,
with_script_sig: 'sig:@asp sig:@alice ""' (1)
broadcast @redeem_tx
confirm transaction: @redeem_tx
@spend_alice_coins = transaction inputs: [
{ tx: @redeem_tx, vout: 0,
script_sig: 'sig:@alice_timelock @alice_timelock 0x01', (2)
csv: @timeout_period }
],
outputs: [
{ descriptor: 'wpkh(@alice)', amount: 49.997.sats }
]
extend_chain num_blocks: @timeout_period (3)
# Alice can now redeem her coins
assert_mempool_accept @spend_alice_coins
end
1 | Alice adds her signature the redeem transaction before broadcasting it |
2 | Alice spends the redeem tx output after a timeout period |
3 | As usual, we move chain forward before we can spend a timeout transaction |
This outcome is execute by running the transitions as:
# Cancel the payment because ASP is not responsive
run_transitions :setup,
:create_funding_tx,
:create_redeem_tx,
:broadcast_funding_tx,
:initialise_payment_to_bob,
:build_alice_forfeit_tx,
:alice_redeems_coins_after_timeout (1)
1 | Pool transaction is not published and therefore redeem tx can be spent |