Always up-to-date draft is located here.
This construction may not be final and I’ll continue to work on it for better UX and guarantees. Everyone is welcome to find the weak spots and errors in this construction so at the end of the day it can become correct and final!
Special thanks to josojo for an inspiration!
zkSNARKs governed Plasma by Matter Inc, informal spec
Draft 0.3, with Q&A and clarifications
This Plasma is not an UTXO one, but one with the balances, like the Ethereum itself!
Rationale
I’ll skip the huge explainer about zkSNARKs here and give mainly two reasons why zkSNARKs may be a useful for Plasma constructions:
- prove the validity of state transition:
State(block N) -> State(block N+1) - hide most of the witness (like Merkle proofs) as private inputs
These options combined allow to reduce the proof to basically two G1 and one G2 elements (Groth16 proof system) plus O(1) Fq field elements.
The biggest challenge
Although one (here I mean the Plasma operator) can prove the validity of state transition, so the block is correctly formed, applied, etc, any Plasma with the global state (for example, with some form of tree for balances storage) is plagued by enormously difficult data availability problem. This problem for a trivial exits (using the Merkle proof of the balance for some block) can be illustrated as the following:
- There is a correct state at block
N -
Mallorystarts an exit from blockNclaiming a right to exit somebalancefrom the particularaccount - An operator publishes a provably valid(!) block
N+1and withholds it - In this block there is a transaction from
Malloryto her accomplice that spendsMallory'sbalance - Such transaction is a pure double-spend that can not be challenged due to data for challenge not being available
- Such chain of transactions can continue
Proposed construction
To even try to solve such a problem one can try to make a smart-contract that governs Plasma on the main chain much more complicated and strict for an operator. Unfortunately any challenge-response game with time limits is a potential attack on the operator in case of either miners’ censorship or cryptokitties.
In parallel I’ll calculate some rough estimates for number of constraints per transaction in R1CS of state transition proving zkSNARKs. These estimates are based on:
- use of an embedded Edwards curve for signature verifications (roughly
4.2constraints per bit of the field, multiplied by two for two scalar multiplications per signature check ~2000constraints) - use of the most constraint-wise effective hash functions:
- MiMC
Longshift 2n/n, ~800constraints per hash - Pedderrsen hashes based on the embedded Edwards, ~
3000constraints per hashing2n -> n
- MiMC
For detailed information check the following sources 1, 2, 3.
State format
For purposes of this construction all the balances, public keys and additional information are stored in leafs of the sparse Merkle tree (SMT) of the depth D. To form the final state the root hash of SMT is additionally hashed with the block number that last updated this state. Each leaf stores the following parameters, that are later concatenated for hashing:
-
public key- public key of the onwer of this leaf balance-
nonce- for replay protection -
last sent at height- the chain height when there was a last transfer from this account
Transaction format
The transaction is an intent from user to send part of his balance to another participant of the system. Account numbers are limited to the range from 0 to 2**D - 1, where D is a depth of a sparse Merkle tree mentioned above, and numerate leafs in the SMT.
The transaction format is the following:
-
from- enumerates an account of the sender -
to- enumerates an account of the recipient -
nonce- should match the nonce of thefromaccount in SMT if this transaction is to be included in a block -
last sent at height- indicates a corresponding field of the state. Logic is described below -
good up to height- indicates that this transaction should only be included in a block of this height or less -
max fee- the maximum fee amount that the sender wishes to pay for a transaction -
allow multiple in block- a permission from user to include multiple transactions into block
max fee field is optional in constructions that don’t use per-transaction fee.
Combination of separate nonce and good up to height is required as the Plasma is potentially censorable and user should have some guarantees that the Plasma operator will not have a power of second opinion and include an outdated transaction in a block.
Multiple transactions per block
User can grant a permission to an operator to include more than one transaction in block. While the largest portion of state transition proving zkSNARK is described in the next section, here is a rationale why last sent at height is required as a part of the transaction and how multiple transaction per block work:
- By providing an explicit
last sent at heightuser gives a consent that he has observed a fullblockwhere his last transaction was included. - Of course there exist an options about partial or full block withholding where only an operator knows it and can continue to sent new transactions, but it will implicitly prevent normal users from doing so!
- If
allow multiple in blockis NOT set an operator should check the following:-
noncematches the data in SMT -
last sent at heightmatches the data in SMT
-
- IF
allow multiple in blockis set an operator should check the following:-
noncematches the data in SMT for every transaction in sequence in this block -
last sent at heighteither matches the data in SMT OR is equal to thenew state height
-
good up to height field logic is described in the next session and is valid for both set and unset flag allow multiple in block.
Transactions block
Transaction block is based on a zkSNARK that proves proper update of the sparse Merkle tree. It has the following public inputs:
- Old
state. - New
state. - Merkle tree root of the transactions tree of the applied block.
- Total collected fee
Internally the following is proved per application of one transaction:
- Block ordering is checked (
old state height + 1 = new state height) - Using the Merkle proof internal states of the two leafs updated by the transaction are proved for the old
state - Content of the
transactionin block is a private input - Ownership is properly checked,
balancesare properly updated,nonceis increased,last sent at heightis updated for sender - Rules about
last sent at heightfor transaction with permission for multiple inclusion per block are honored -
good up to heightfield of the transaction should be greater than or equal to thenew state height - new Merkle tree root is recalculated
-
total feeis updated
Procesure is repeated for the set of transactions, followed by calculation of the block Merkle tree root and the new state from the balances Merkle tree root and the applied block number.
In terms of number of constraints to R1CS price of such update is clearly dominated by the term 4*ConstraintsPerHash*D, where D is a balances tree depth. For state tree of depth 32 it’s roughly 1000 * 128 ~ 150000 constraints per one transaction application. One can limit a R1CS size to 1 billion (DIZK FTW) that implies ~8000 transactions per block. Of course there are technological roadblocks - balances tree can not be updated concurrently, so transaction speed may be quite low.
The block is considered applied after an operator publishes a full content of transactions from this block for public inspection, so everyone can update their states and compare with one published by an operator as a part of the proof.
Deposits block
Deposit block updates the balances tree by inclusion of the new leaf instead of empty one with the proper associated public key, balance, nonce = 0 and properly set last sent at height and last updated at height. Size of such block is expected to be small, for example it should only serve up to 128 deposits. In such zkSNARK public inputs are the following:
- Old
state - New
state - All deposit balances and associated public keys
Although the word “all” here can be scary, all this information is already listed in the smart-contract upon deposit requests from users. One should also make a similar kind of topup block to topup existing accounts, although those are skipped entirely in this document.
One important aspect for limitation of number of deposits to the Plasma: an operator should keep some security deposit that should cover the cost publishing an exit blocks in the amount required to exit every balance, so with growing number of deposits into the new leafs an operator MUST increase his security deposit.
Exits block
Exit block is quite simillar and it updates the balances tree by zeroing the balances of the leafs with the proper request owner.
Upon the exit request the user provider a zkSNARK proof of the full data availability for the last state his transaction was included in the chain along with some bond. This is effectively a Merkle proof for some chain height. As public inputs such zkSNARK discloses the nonce, last sent at height and last updated at height for the exiting account. The last sent at height field is checked agains timestamping and if more than 3 days has passes the request is accepted. Those requests form a queue based on the last sent at height - the smaller is better.
Priority capping is allowed here, where an exit from the very old state with very small last sent at height will have it’s priority capped to the chain height of - 1 day from now, and this “capped priority” will be used as a counter for the data availability rollback described in the corresponding section. If priority capping is not introduced than there is a potential for infinite state rollback if a malicious party starts valid exits with smaller and smaller last sent at height that will always get into the exit queue before every other exit request.
This exit request can be challenged over the 3 days period by providing a proof of full data availability that discloses a state for the same account with higher nonce and last sent at height. This is a griefing vector, although the user should only be griefed at maximum once if he properly checks the data availability and does not sent transactions if the latest committed block is not published in full.
If the exit request is not challenged it has to be included into some exit block no later than 1 day after the end of the challenge period. This requirement is enforced by the smart-contract that manages the exit queue.
Public inputs for the exit block zkSNARK are the following:
- Old
state - New
state - All
accountsandlast sent at heightand from requests for exits from the users - All
balancesfor the corresponding exit requests. Thesebalancesare provided by an operator based on the old state.
Internally the following is checked:
- With a Merkle proof the latest internal state of the
accountis revealed baseon on oldstate -
last sent at heightis checked againts the request data
Such requirements provide the following guarantee:
- If user tries to exit not from the latest state and data is publically available - he is challenged by other users
- If user tries to exit not from the latest state and data is NOT publically available - he is challenged by the operator (griefed), but this actions reveals a newer state
- If user is not challenged or exits from the latest state, an operator is enforced to process his exit or the chain is halted
Extra requirements for submission of different block kinds
One can further improve the user experience by adding more limitations:
- Total number of pending deposits should be limited
- An operator MUST produce a next block as a
deposit blockif pending deposits queue is capped - An operator MUST produce a
deposit blockonce everyXhours if there are anydepositrequests pending, otherwise the contract at first does not allow to send any more block, and later after some extra delay prevents any blocks from being published (effective stop) - If the user’s deposit was not included in a
depositblock after some delayTa revocation procedure is allowed - Total number of pending exits should NOT be limited
- Same way an operator MUST either produce an
exit blockonce everyXhours, or if exits limit is aboveMitems, or the block submission is stopped. In case of more thanMitems in the queue an operator must produce the number ofexit blocksto pop thoseMitems from the queue - Any kind block should be published at least once every
Xhours and in case of the huge pending exits queue the queue above has to be popped over the perios ofYhours, otherwise a block submission is prohibited - Any violations (enforced stops, effectively) are punishable by operator’s deposit slashing in half
A stopped block submission is equal to either an operator going offline or a block withholding attack where the smart-contract tries to protect user rights limiting the operator in what kind of blocks he can send, and an operator failing to do so.
Actions in case of block withholding
If user sees a block data being unavailable he submits a request for an exit. At some point the block submission will be prohibited and it implies, that the first (unchallenged) item in the exit queue has the last sent at height that corresponds to roughly - 4 days from now. Such height is the deemed the latest available.
Prohibited block submission
There should exit a mechanism to still allow users to exit block submission is stopped by the smart-contract’s rules. As described above one can determine the last fully available block by inspecting the exit queue. In case of the stopped submission (may be after some extra time delay) the following procedure starts:
- Any party can send a proof for a new exit blocks that start to pop requests from the exit queue over some time slot of
1 hour - Those items are ranked by size - on the setup phase an operator MUST make zkSNARKs for
exit blocksof various sizes - At the end of the time slot of if the
exit blockof the largest size is published - such block is accepted and becomes a new tip - The party who’s proof is used to update the chain tip does NOT need to publish any extra data - everything important is already in the
public inputsof the proof and other participats can update their state accordingly and continue in the next round - Procedure repeats until the queue is empty
- A block publisher gets some funds from the remaining operator’s security deposit
- One can make a procedure more interactive with commit-reveal scheme to reduce number of proofs checked on-chain, but this will require a deposit from the party that commits to provide some proof for an
exit block
Q&A
- What is the block is not available in full?
- In this case no one can prove that some leaf in the SMT because the
new statedepends on the latest root hash of the SMT and can not be calculated without every transaction in block. The exception is if the block is padded by empty transactions, but it’s trivial.
- In this case no one can prove that some leaf in the SMT because the
- Why
last sent at heightshould match at the exit block?- Imaging the following if
last sent at heightis not checked:- User
Ahas a state withlast sent at height = Nand sends a transactionA => B - This transaction is included in block
N+1 - User colludes with an operator
- User publishes an exit request with the
last sent at height = N - No one can challenge this request
- An operator proceeds with an
exit blockAFTER blockN+1and this block is accepted aslast sent at height = N+1 != N, but this field is not checked! - An operator publishes an exit from
B - This is a double spend!
- User
- If
last sent at heightis checked an operator can not proceed withexit blockfor userAthat has priorityNand that will become the first in a queue even if an operator published all possibleexit blocks, but still will not challenge an exit request with a newer state!. So,Nbecomes a last point of validity and data availability.
- Imaging the following if
- Are blocks published?
- Block headers (which are Merkle tree roots of the transactions tree) are published as one of the public inputs to the
transaction blockzkSNARK proof. - Those headers are NOT stored on-chain
- Users should download a block in full from the operator and check off-chain that the header from downloaded block matches the published one.
- Block headers (which are Merkle tree roots of the transactions tree) are published as one of the public inputs to the
- Is this the only way to revert the chain tip?
- No, there are others. For example, one can remove blocks one by one, but in this case an operator can make a lot of unavailable blocks before the reversion process starts and revert will be long and gas expensive.
- In principle one can make some protocol for users to vote (using proofs for availability for some random
accountin a state) what is the lateststateknown for evertone.
Authors
- Alex Vlasov