stellar / slingshot

A new blockchain architecture under active development, with a strong focus on scalability, privacy and safety
Apache License 2.0
414 stars 61 forks source link

zkvm: payment channels and HTLC #496

Open oleganza opened 3 years ago

oleganza commented 3 years ago

1. Payment channel

Simple bilateral payment channel.

Alice and Bob bring amounts of some asset into a channel and periodically update the distribution of assets. Distribution is a signed predicate that re-locks the funds under a relative timeout. Within the timeout, a newer signed distribution could be applied to replace the stale one.

This could be one-way channel (Alice->Bob, only Alice brings funds), or it could be a multi-asset channel (Alice brings USD, Bob brings EUR), or a set of multiple currencies that FX traders move around between each other.

The system trivially extends to N-party channel with N-of-N signatures updating the balance distribution.

Setup

Alice and Bob exchange pubkeys A and B used in the operation of the channel and establish a joint MuSig key AB = A & B.

Alice and Bob sign a balance update with seq=1 reflecting initial balances.

Now, it's safe to lock funds. They compute the initial predicate P_init that transiently re-formats the contract in a format compatible with P_exit predicate used to manage exits:

P_init = AB + program {
     // transient contract:
     contract(payload {
        assets, 
        seq=0, 
        timeout=MAX_INT, 
        "" (empty prog),
        tag
      ) -> P_exit
}
P_exit = AB + program {
   verify(tx.mintime > self.timeout)
   eval(self.redistribution_program)
}

Balance update

Alice and Bob agree to a new distribution of funds and sign a new predicate that can be used to override any prior state of the contract. The program is signed with P_exit multikey.

program($seq, $tx, $new_distribution) = {
    verify($seq > self.seq)
    self.redistribution_program = $new_distribution
    self.timeout = $tx.maxtime+T
    lock(self, P_exit)
}

Example of a re-distribution program:

$new_distribution = program {
    output($90 -> Alice)
    output($10 -> Bob)
}

Settlement

Both parties sign a tx that re-distributes funds bypassing the contract logic. The resulting tx looks like a simple transfer w/o any details leaking.

Initiate forced settlement

Alice wants to close the channel because Bob does not cooperate.

Alice forms a transaction Tx1 that opens P_init into programmatic branch that re-locks assets under a transient contract with necessary parameters under P_exit.

Within the same Tx1 Alice spends P_exit with a signed program performing the latest balance update. Tx1 leaves assets locked in the last-agreed proportion under the same predicate P_exit, but withdrawable after timeout.

After timeout, Alice signs Tx2 that pays up-to-date fee and opens up P_exit's programmatic branch that releases the assets to pre-arranged destinations to Alice and Bob.

Contest forced settlement

If Bob observes that Alice initiate channel close with a stale signed predicate (seq number less than the latest they agreed upon).

Privacy considerations

Financial values (asset types and quantities) remain encrypted at all times, in cooperative and non-cooperative cases.

In case of cooperation, there are two transactions: funding and settlement. Both look like regular single-key spends.

In case of non-cooperation, channel contract is published. But the sequence numbers and timeout durations are kept encrypted, so the channel software or participants can't be fingerprinted or estimate their transaction volume.

Simplicity of the scheme allows outsourcing monitoring of the blockchain to semi-trusted servers that will bump the state if they notice a stale exit. Confidentiality of the transfers protects against extorsion and permits using flat-fee to support such watch servers.

2. Multi-hop channels (lightning)

If a simple channel has unconditional balance updates (extra checks are only for replacing stale versions), multi-hop channel needs to update the balances conditionally: "Bob sends $5 to Carol only if he gets $5 from Alice".

The conditional updates use HTLCs (hash-timelocked contracts) to provide reversal after an absolute timeout (relative won't do). To keep channels open indenfinitely, HTLC-locked balances must be replaced with non-HTLC updates as in simple payment channels when the payment is guaranteed (preimage for HTLC is provided).

This means that HTLC condition is wrapped into a payment channel condition:

  1. first, we establish which version of agreement is the latest (payment channel protocol),
  2. then we deal with its extra conditions (if HTLC is on), or just stay put (if it's unconditional update after HTLC is opened).

This works same as above, but intermediate HTLC-locked conditions looks like this:

$new_distribution = program {
    self.timeout = tx.maxtime
    lock with taproot {
       branch1(): {
           verify(tx.mintime >= (self.timeout + delta));
           output($90 -> Alice)
           output($10 -> Bob)
       },
       branch2($preimage): {
           verify(sha256($preimage) == self.htlc);
           output($85 -> Alice)
           output($15 -> Bob) // Alice is sending $5 through Bob
       },      
    }
}