Serenity Currency and Crypto Abstraction
Vitalik Buterin <firstname.lastname@example.org>
- Accounts now have only two fields in their RLP encoding: code and storage.
- Ether is no longer stored in account objects directly; instead, at address
0, we premine a contract which contains all ether holdings. The
eth.getBalancecommand in web3 is remapped appropriately.
msg.valueno longer exists as an opcode.
- A transaction now only has four fields: to, startgas, data and code.
- Aside from an RLP validity check, and checking that the to field is twenty bytes long, the startgas is an integer, and code is either empty or hashes to the to address, there are no other validity constraints; anything goes. However, the block gas limit remains, so miners are disincentivized from including junk.
- Gas is charged for bytes in code at the same rate as data.
- When a transaction is sent, if the receiving account does not yet exist, the account is created, and its code is set to the code provided in the transaction; otherwise the code is ignored.
tx.gasopcode is added alongside the existing
0x5c; this new opcode allows the transaction to access the original amount of gas allotted for the transaction
ECRECOVER, sequence number/nonce incrementing and ether are now nowhere in the bottom-level spec (NOTE: ether is going to continue to have a privileged role in Casper PoS). To replicate existing functionality under the new model, we do the following.
Simple user accounts can have the following default standardized code:
# We assume that data takes the following schema: # bytes 0-31: v (ECDSA sig) # bytes 32-63: r (ECDSA sig) # bytes 64-95: s (ECDSA sig) # bytes 96-127: sequence number (formerly called "nonce") # bytes 128-159: gasprice # bytes 172-191: to # bytes 192+: data # Get the hash for transaction signing ~mstore(0, msg.gas) ~calldatacopy(32, 96, ~calldatasize() - 96) h = sha3(96, ~calldatasize() - 96) # Call ECRECOVER contract to get the sender ~call(5000, 3, [h, ~calldataload(0), ~calldataload(32), ~calldataload(64)], 128, ref(addr), 32) # Check sender correctness assert addr == 0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1 # Check sequence number correctness assert ~calldataload(96) == self.storage[-1] # Increment sequence number self.storage[-1] += 1 # Make the sub-call and discard output ~call(msg.gas - 50000, ~calldataload(160), 192, ~calldatasize() - 192, 0, 0) # Pay for gas ~call(40000, 0, [SEND, block.coinbase, ~calldataload(128) * (tx.gas - msg.gas + 50000)], 96, 0, 0)
This essentially implements signature and nonce checking, and if both checks pass then it uses all remaining gas minus 50000 to send the actual desired call, and then finally pays for gas.
Miners can follow the following algorithm upon receiving transactions:
- Run the code for a maximum of 50000 gas, stopping if they see an operation or call that threatens to go over this limit
- Upon seeing that operation, make sure that it leaves at last 50000 gas to spare (either by checking that the static gas consumption is small enough or by checking that it is a call with
msg.gas - 50000as its gas limit parameter)
- Pattern-match to make sure that gas payment code at the end is exactly the same as in the code above.
This process ensures that miners waste at most 50000 gas before knowing whether or not it will be worth their while to include the transaction, and is also highly general so users can experiment with new cryptography (eg. ed25519, Lamport), ring signatures, quasi-native multisig, etc. Theoretically, one can even create an account for which the valid signature type is a valid Merkle branch of a receipt, creating a quasi-native alarm clock.
If someone wants to send a transaction with nonzero value, instead of the current
msg.sender approach, we compile into a three step process:
- In the outer scope just before calling, call the ether contract to create a cheque for the desired amount
- In the inner scope, if a contract uses the
msg.valueopcode anywhere in the function that is being called, then we have the contract cash out the cheque at the start of the function call and store the amount cashed out in a standardized address in memory
- In the outer scope just after calling, send a message to the ether contract to disable the cheque if it has not yet been cashed
This allows for a large increase in generality, particularly in a few areas:
- Cryptographic algorithms used to secure accounts (we could reasonably say that Ethereum is quantum-safe, as one is perfectly free to secure one's account with Lamport signatures). The nonce-incrementing approach is now also open to revision on the part of account holders, allowing for experimentation in k-parallelizable nonce techniques, UTXO schemes, etc.
- Moving ether up a level of abstraction, with the particular benefit of allowing ether and sub-tokens to be treated similarly by contracts
- Reducing the level of indirection required for custom-policy accounts such as multisigs
It also substantially simplifies and purifies the underlying Ethereum protocol, reducing the minimal consensus implementation complexity.