Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposed initial abstraction changes for Metropolis #86

Closed
vbuterin opened this issue Apr 2, 2016 · 51 comments
Closed

Proposed initial abstraction changes for Metropolis #86

vbuterin opened this issue Apr 2, 2016 · 51 comments
Labels

Comments

@vbuterin
Copy link
Contributor

vbuterin commented Apr 2, 2016

This document is outdated; see #208 for current details.

Specification

If block.number >= METROPOLIS_FORK_BLKNUM, then:

  1. If the signature of a transaction is (0, 0, 0) (ie. v = r = s = 0), then treat it as valid and set the sender address to 2**160 - 1
  2. Set the address of any contract created through a creation transaction to equal sha3(0 + init code) % 2**160, where + represents concatenation, replacing the earlier address formula of sha3(rlp.encode([sender, nonce]))
  3. Create a new opcode at 0xfb, CREATE_P2SH, which sets the creation address to sha3(sender + init code) % 2**160. If a contract at that address already exists, fails and returns 0 as if the init code had run out of gas.

Rationale

The goal of these changes is to set the stage for abstraction of account security. Instead of having an in-protocol mechanism where ECDSA and the default nonce scheme are enshrined as the only "standard" way to secure an account, we take initial steps toward a model where in the long term all accounts are contracts, contracts can pay for gas, and users are free to define their own security model.

Under EIP 86, we can expect users to store their ether in contracts, whose code might look like the following (example in Serpent):

# Get signature from tx data
sig_v = ~calldataload(0)
sig_r = ~calldataload(32)
sig_s = ~calldataload(64)
# Get tx arguments
tx_nonce = ~calldataload(96)
tx_to = ~calldataload(128)
tx_value = ~calldataload(160)
tx_gasprice = ~calldataload(192)
tx_data = string(~calldatasize() - 224)
~calldataload(tx_data, 224, ~calldatasize())
# Get signing hash
signing_data = string(~calldatasize() - 64)
~mstore(signing_data, tx.startgas)
~calldataload(signing_data + 32, 96, ~calldatasize() - 96)
signing_hash = sha3(signing_data:str)
# Perform usual checks
prev_nonce = ~sload(-1)
assert tx_nonce == prev_nonce + 1
assert self.balance >= tx_value + tx_gasprice * tx.startgas
assert ~ecrecover(signing_hash, sig_v, sig_r, sig_s) == <pubkey hash here>
# Update nonce
~sstore(-1, prev_nonce + 1)
# Pay for gas
~send(MINER_CONTRACT, tx_gasprice * tx.startgas)
# Make the main call
~call(msg.gas - 50000, tx_to, tx_value, tx_data, len(tx_data), 0, 0)
# Get remaining gas payments back
~call(20000, MINER_CONTRACT, 0, [msg.gas], 32, 0, 0)

This can be thought of as a "forwarding contract". It accepts data from the "entry point" address 2**160 - 1 (an account that anyone can send transactions from), expecting that data to be in the format [sig, nonce, to, value, gasprice, data]. The forwarding contract verifies the signature, and if the signature is correct it sets up a payment to the miner and then sends a call to the desired address with the provided value and data.

The benefits that this provides lie in the most interesting cases:

  • Multisig wallets: currently, sending from a multisig wallet requires each operation to be ratified by the participants, and each ratification is a transaction. This could be simplified by having one ratification transaction include signatures from the other participants, but even still it introduces complexity because the participants' accounts all need to be stocked up with ETH. With this EIP, it will be possible to just have the contract store the ETH, send a transaction containing all signatures to the contract directly, and the contract can pay the fees.
  • Ring signature mixers: the way that ring signature mixers work is that N individuals send 1 coin into a contract, and then use a linkable ring signature to withdraw 1 coin later on. The linkable ring signature ensures that the withdrawal transaction cannot be linked to the deposit, but if someone attempts to withdraw twice then those two signatures can be linked and the second one prevented. However, currently there is a privacy risk: to withdraw, you need to have coins to pay for gas, and if these coins are not properly mixed then you risk compromising your privacy. With this EIP, you can pay for gas straight our of your withdrawn coins.
  • Custom cryptography: users can upgrade to ed25519 signatures, Lamport hash ladder signatures or whatever other scheme they want on their own terms; they do not need to stick with ECDSA.
  • Non-cryptographic modifications: users can require transactions to have expiry times (this being standard would allow old empty/dust accounts to be flushed from the state securely), use k-parallelizable nonces (a scheme that allows transactions to be confirmed slightly out-of-order, reducing inter-transaction dependence), or make other modifications.

(2) and (3) introduce a feature similar to bitcoin's P2SH, allowing users to send funds to addresses that provably map to only one particular piece of code. Something like this is crucial in the long term because, in a world where all accounts are contracts, we need to preserve the ability to send to an account before that account exists on-chain, as that's a basic functionality that exists in all blockchain protocols today.

Miner strategy

Note that miners would need to have a strategy for accepting these transactions. This strategy would need to be very discriminating, because otherwise they run the risk of accepting transactions that do not pay them any fees, and possibly even transactions that have no effect (eg. because the transaction was already included and so the nonce is no longer current). One simple approach is to have a whitelist for the codehash of accounts that they accept transactions being sent to; approved code would include logic that pays miners transaction fees. However, this is arguably too restrictive; a looser but still effective strategy would be to accept any code that fits the same general format as the above, consuming only a limited amount of gas to perform nonce and signature checks and having a guarantee that transaction fees will be paid to the miner. Another strategy is to, alongside other approaches, try to process any transaction that asks for less than 250,000 gas, and include it only if the miner's balance is appropriately higher after executing the transaction than before it.

See https://www.reddit.com/r/ethereum/comments/5ab69v/metropolis_protocol_change_proposal_highlight_for/ for reddit discussion.

@gavofyork
Copy link

mixhash and nonce are all substituted with the empty string

unlike for the powhash where they are not present at all :-/

@gavofyork
Copy link

1/2/3 mean state trie will grow indefinitely large (so far there would be an additional ~50MB on it, a substantial increase on the pruned, compressed state db)

@gavofyork
Copy link

key, mstart, msz -> key, mstart, msize or ky, mst, msz. don't mix naming conventions.

@eth1au
Copy link

eth1au commented Apr 2, 2016

Solid. V, can you show Gav the DK Gas email. JG

@gavofyork
Copy link

for 9, what happens when there's a collision?

@gavofyork
Copy link

it should also be noted that (12) and (13) are suggested miner alterations, but do not form part of the consensus protocol.

@eth1au
Copy link

eth1au commented Apr 2, 2016

Perhaps. Alas, essential weekend reading: http://www.mit.edu/~mrognlie/piketty_diminishing_returns.pdf

@vbuterin
Copy link
Contributor Author

vbuterin commented Apr 2, 2016

Fixed the naming conventions for msz.

1/2/3 mean state trie will grow indefinitely large (so far there would be an additional ~50MB on it, a substantial increase on the pruned, compressed state db)

However, note that this growth is constant in tx load; right now, it's 20% of the state size, but in a future environment where we see 5-10 tx/sec, it will be much smaller, and we should optimize for that case. Additionally, if we are still uncomfortable with storing ALL state roots, then there is a compromise solution where we store the state roots at key block.number % CYCLE_LENGTH (eg. CYCLE_LENGTH = 65536), and in the STATEROOT and BLOCKHASH opcodes keep the condition that we return 0 if block.number - target_height >= CYCLE_LENGTH)

for 9, what happens when there's a collision?

I would say failure (either do nothing and return zero, or exception). We could remove the issue entirely by adding a nonce back in to the formula, but I want to avoid this because one of my goals with EIP 101 is the desire to remove the notion of in-protocol sequence numbers entirely, moving all of that logic to the protocol level. So if you want to create multiple instances of the same contract, you can just append a nonce after the code, and maintain and increment the nonce yourself in storage; this can all be done with a few EVM operations, and simplifies the protocol further as we don't have to deal with the current logic around when nonces are and aren't incremented within consensus code.

@vbuterin
Copy link
Contributor Author

vbuterin commented Apr 3, 2016

Changed the hash with state root/mixhash/nonce as empty strings into the standard PoW hash, but with the state root replaced with the empty string. Added rationale to post; here it is copied for convenience:

The state root needs to be set to the empty string because (2) is itself a state change, so the actual final state root is not known (and neither are the mixhash and nonce).

@chriseth
Copy link
Contributor

chriseth commented Apr 5, 2016

What happens if a slot is SLOAD'ed from that has been SSTOREBYTES'ed before?

@vbuterin
Copy link
Contributor Author

vbuterin commented Apr 6, 2016

Last 32 bytes, left-zero-padded.

@chriseth
Copy link
Contributor

chriseth commented Apr 6, 2016

@vbuterin Still many open questions regarding the variable-size slots. Is the general idea that SSTORE behaves identical to SSTOREBYTES with 32 bytes and SLOAD identical to SLOADBYTES plus padding? (I would prefer padding at the less-significant end, by the way).

So using SSIZE for not yet used slots returns zero, but it returns 32 for slots where we wrote 0. This is still a bit inconsistent with how solidity uses storage, but it might work.

Furthermore, I am not sure that the memory exceptions for SLOADBYTES are a good idea (different case than for CALL) because we do know the size.

Also it could be considered whether SLOADBYTES should zero-pad to multiples of 32 - that would be more consistent with our ABI.

@vbuterin
Copy link
Contributor Author

vbuterin commented Apr 8, 2016

Is the general idea that SSTORE behaves identical to SSTOREBYTES with 32 bytes and SLOAD identical to SLOADBYTES plus padding?

Yes, with the exception that you can use SSTOREBYTES and not SSTORE to save 32 zero bytes, as you brought up.

Furthermore, I am not sure that the memory exceptions for SLOADBYTES are a good idea (different case than for CALL) because we do know the size.

True. I'm fine either way.

Also it could be considered whether SLOADBYTES should zero-pad to multiples of 32 - that would be more consistent with our ABI.

I would prefer no. It's not consistent with the ABI, but the ABI is a high-level construct. Return slices of CALL and the like are not zero-padded to multiples of 32, and it is consistent with that.

@chriseth
Copy link
Contributor

12: I think we should still perform some static analysis on the target contract, it is easy to spam miners with a lot of txes with small startgas.

@chriseth
Copy link
Contributor

General concern: If we move nonce checking into the contract, to we still enforce the requirement that the chain should never include two transactions with the same hash?

@chriseth
Copy link
Contributor

chriseth commented Apr 20, 2016

9: I would guess that none of the existing contracts relies on the actual way the new address is calculated in a create, so changing that is fine. What I'm worried about is that previously, two create calls with identical code from the same contract did not result in a collision but now they do, because existing code does not add a nonce to the init code.

@vbuterin
Copy link
Contributor Author

vbuterin commented Apr 21, 2016

to we still enforce the requirement that the chain should never include two transactions with the same hash?

I would say no. One of the key positive side effects of an eventual universal adoption of EIP 101 is that transaction validity becomes statically evaluable, which means that blocks can be validated independently of having the prior state.

What I'm worried about is that previously, two create calls with identical code from the same contract did not result in a collision but now they do, because existing code does not add a nonce to the init code.

True. If we decide that this is a problem, one simple solution would be that if the destination address contains code, we keep incrementing the address by 1 (wrapping around if needed) until it doesn't, and run the code at the first address we find that is empty.

@Smithgift
Copy link

👍 on incrementing address on collision. In addition (ha) to all current contract-creating contracts continuing (ha ha) to function, it permanently resolves the question of two contracts accidentally having the same address.

@tromer
Copy link

tromer commented Jul 29, 2016

Letting contracts pay fees is also needed by Zerocash over Ethereum (zcash-hackworks/babyzoe#1).

@Dexaran
Copy link
Contributor

Dexaran commented May 1, 2017

There is a great difference between contract and EOA-with-bytecode for ERC #223 token transaction:

If the receiver is an EOA with the implementation of tokenFallback, then tokenFallback must be executed.

If receiver is an EOA without tokenFallback implementation, then token transaction must submit without a try to execute tokenFallback (a try to execute tokenFallback will fail a transaction in this case).

If the receiver is a contract, tokenFallback must be executed without distinctions whether tokenFallback is implemented or not. (if there is no tokenFallback implementation on this receiver contract then transaction must fail)

@MicahZoltu
Copy link
Contributor

Is this still slated for Metropolis? Is "mid-summer" still looking reasonably likely for Metropolis?

I ask because I have a need to create a proxy-contract with a non-trivial ownership pattern and I'm wondering if I should try to create it now, and deal with all of the UI complexities that come with trying to interact with dApps through a proxy contract (I believe at the moment this is basically impossible if I want to use their UI) or if I should wait for Metropolis, which it sounds like will allow for this without having to write a custom UI for every dApp out there.

I believe uPort also has this problem, where if you want to use a dApp with uPort the dApp needs to be built to support uPort or you need to use uPort as your transaction processor (instead of a local node) so they can handle all of the complexities server-side with wrapping up your transaction calls and piping them through their proxy.

@fulldecent
Copy link
Contributor

@Arachnid You nominated this as an "EIPs that should be merged". Can you please share your notes on that here?

1 similar comment
@fulldecent
Copy link
Contributor

@Arachnid You nominated this as an "EIPs that should be merged". Can you please share your notes on that here?

@github-actions
Copy link

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

@github-actions github-actions bot added the stale label Jan 16, 2022
@github-actions
Copy link

github-actions bot commented Feb 7, 2022

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.

@github-actions github-actions bot closed this as completed Feb 7, 2022
@Rachi26

This comment was marked as spam.

@Tharolzakariya

This comment was marked as spam.

@Pandapip1
Copy link
Member

Pandapip1 commented Nov 11, 2022

I was invoked by the above spam bot, but this seems like a really good idea. In fact, it's nearly identical to something I thought about only a few weeks ago: https://ethresear.ch/t/assumptionless-ethereum/14010/4. Should we try to resurrect this?

bumblefudge added a commit to bumblefudge/EIPs that referenced this issue Feb 16, 2024
@Sintayew4
Copy link

#86

@GIgako19929
Copy link

This document is outdated; see #208 for current details.

Specification

If block.number >= METROPOLIS_FORK_BLKNUM, then:

  1. If the signature of a transaction is (0, 0, 0) (ie. v = r = s = 0), then treat it as valid and set the sender address to 2**160 - 1
  2. Set the address of any contract created through a creation transaction to equal sha3(0 + init code) % 2**160, where + represents concatenation, replacing the earlier address formula of sha3(rlp.encode([sender, nonce]))
  3. Create a new opcode at 0xfb, CREATE_P2SH, which sets the creation address to sha3(sender + init code) % 2**160. If a contract at that address already exists, fails and returns 0 as if the init code had run out of gas.

Rationale

The goal of these changes is to set the stage for abstraction of account security. Instead of having an in-protocol mechanism where ECDSA and the default nonce scheme are enshrined as the only "standard" way to secure an account, we take initial steps toward a model where in the long term all accounts are contracts, contracts can pay for gas, and users are free to define their own security model.

Under EIP 86, we can expect users to store their ether in contracts, whose code might look like the following (example in Serpent):

# Get signature from tx data
sig_v = ~calldataload(0)
sig_r = ~calldataload(32)
sig_s = ~calldataload(64)
# Get tx arguments
tx_nonce = ~calldataload(96)
tx_to = ~calldataload(128)
tx_value = ~calldataload(160)
tx_gasprice = ~calldataload(192)
tx_data = string(~calldatasize() - 224)
~calldataload(tx_data, 224, ~calldatasize())
# Get signing hash
signing_data = string(~calldatasize() - 64)
~mstore(signing_data, tx.startgas)
~calldataload(signing_data + 32, 96, ~calldatasize() - 96)
signing_hash = sha3(signing_data:str)
# Perform usual checks
prev_nonce = ~sload(-1)
assert tx_nonce == prev_nonce + 1
assert self.balance >= tx_value + tx_gasprice * tx.startgas
assert ~ecrecover(signing_hash, sig_v, sig_r, sig_s) == <pubkey hash here>
# Update nonce
~sstore(-1, prev_nonce + 1)
# Pay for gas
~send(MINER_CONTRACT, tx_gasprice * tx.startgas)
# Make the main call
~call(msg.gas - 50000, tx_to, tx_value, tx_data, len(tx_data), 0, 0)
# Get remaining gas payments back
~call(20000, MINER_CONTRACT, 0, [msg.gas], 32, 0, 0)

This can be thought of as a "forwarding contract". It accepts data from the "entry point" address 2**160 - 1 (an account that anyone can send transactions from), expecting that data to be in the format [sig, nonce, to, value, gasprice, data]. The forwarding contract verifies the signature, and if the signature is correct it sets up a payment to the miner and then sends a call to the desired address with the provided value and data.

The benefits that this provides lie in the most interesting cases:

  • Multisig wallets: currently, sending from a multisig wallet requires each operation to be ratified by the participants, and each ratification is a transaction. This could be simplified by having one ratification transaction include signatures from the other participants, but even still it introduces complexity because the participants' accounts all need to be stocked up with ETH. With this EIP, it will be possible to just have the contract store the ETH, send a transaction containing all signatures to the contract directly, and the contract can pay the fees.
  • Ring signature mixers: the way that ring signature mixers work is that N individuals send 1 coin into a contract, and then use a linkable ring signature to withdraw 1 coin later on. The linkable ring signature ensures that the withdrawal transaction cannot be linked to the deposit, but if someone attempts to withdraw twice then those two signatures can be linked and the second one prevented. However, currently there is a privacy risk: to withdraw, you need to have coins to pay for gas, and if these coins are not properly mixed then you risk compromising your privacy. With this EIP, you can pay for gas straight our of your withdrawn coins.
  • Custom cryptography: users can upgrade to ed25519 signatures, Lamport hash ladder signatures or whatever other scheme they want on their own terms; they do not need to stick with ECDSA.
  • Non-cryptographic modifications: users can require transactions to have expiry times (this being standard would allow old empty/dust accounts to be flushed from the state securely), use k-parallelizable nonces (a scheme that allows transactions to be confirmed slightly out-of-order, reducing inter-transaction dependence), or make other modifications.

(2) and (3) introduce a feature similar to bitcoin's P2SH, allowing users to send funds to addresses that provably map to only one particular piece of code. Something like this is crucial in the long term because, in a world where all accounts are contracts, we need to preserve the ability to send to an account before that account exists on-chain, as that's a basic functionality that exists in all blockchain protocols today.

Miner strategy

Note that miners would need to have a strategy for accepting these transactions. This strategy would need to be very discriminating, because otherwise they run the risk of accepting transactions that do not pay them any fees, and possibly even transactions that have no effect (eg. because the transaction was already included and so the nonce is no longer current). One simple approach is to have a whitelist for the codehash of accounts that they accept transactions being sent to; approved code would include logic that pays miners transaction fees. However, this is arguably too restrictive; a looser but still effective strategy would be to accept any code that fits the same general format as the above, consuming only a limited amount of gas to perform nonce and signature checks and having a guarantee that transaction fees will be paid to the miner. Another strategy is to, alongside other approaches, try to process any transaction that asks for less than 250,000 gas, and include it only if the miner's balance is appropriately higher after executing the transaction than before it.

See https://www.reddit.com/r/ethereum/comments/5ab69v/metropolis_protocol_change_proposal_highlight_for/ for reddit discussion.

  • [ ]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests