-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
Replay Attack Protection: Include Blocklimit and Blockhash in each Transaction #134
Comments
Ps thanks to @pipermerriam and @PeterBorah for informing this |
This would create a problem with offline transactions signed long before they are submitted to the network. |
@charlieknoll maybe you missed this part of the proposal.
I believe that addresses your concern. |
@pipermerriam yes I missed that, thanks. Looks like a good idea to me. |
Good proposal - would like to see it implemented! |
Only using a blockhash would probably be enough, as only during few transactions during a fork would be affected, the proposal is fine as is clients should however default to a 0 blocklimit. Otherwise users might run into problems when signing transactions offline or when there is a long delay between signing and publishing a transaction, ie multisig. |
We've been discussing something similar in the Go-ethereum channel. A couple of slight changes make it significantly more general:
A more significant change is to specify either that every nth (say, 10,000th) block hash gets added to the list, allowing distinctions based on forks after a short period, or to specify that the node should look up the hash in a contract at a given address. That contract would be implemented such that it allows anyone to submit a valid recent blockhash to be stored indefinitely, allowing anyone to verify that the current chain had that blockhash in the past, and therefore allowing anyone to create permanent 'fork points' at any time. |
Using the genesis hash is probably a good idea. For the "every n-th" block either a merkle tree root (or some subleaf) of these blocks can be used. Transactions should still be able to include a nonce to make sure that if an account is doing many transaction, they can't be replayed within the span of the n blocks of the merkle tree. For offline transactions, the tx could use a scheme based on |
This is interesting. However it wouldn't offer replay protection to private chains or forks where the contract wasn't deployed. Also there's no incentive to submit blocks.
Agreed. I think setting some sort of pool of blocks to check (last 256 + every 10k since genesis + genesis) is sufficient. @Arachnid is the go team working on replay attack protection? I'm wondering if I should turn this into a formal EIP or let those with better knowledge of the problem take the lead. |
Deploying that contract would be part of the process of deploying a new chain, just like the built-in contracts.
The cost is minimal, and once you've submitted a block, you can use it in future TXes for replay prevention. I think tscs37's point that it would allow someone to bypass the "expiry" feature by submitting its blockhash is a good one. My goal was to prevent the expansion of state required by fast and light clients beyond what's under the state hash and the 256 most recent block hashes, which checking in every nth block would do - clients would need to explicitly fetch those block headers when fast syncing in order to be able to validate TXes.
We've been discussing it, and I believe someone's working on an EIP; you might ask about it in the go-ethereum channel. |
👍. This has more uses than merely large scale forks a la the current situation. If you wish for transaction A to work and only work in a given microfork (for example, a transaction that only makes sense if another transaction was or wasn't sent) then this is quite useful. Or if you wish to embargo a 51% attacker's chain, then this is a very simple way to do it. |
I think the intention should less be to prevent replays across forks. This can be a neat side-effect. But it can be used to make light and offline clients much more easy to write and if done the right way, could allow single addresses to issue concurrent transactions. With the current nonce system it's not easily possible to distribute the generation of transaction, ie thinking of a large exchange or bank. |
I also like this idea. Mostly, I like the contract-route. So I would suggest the following.
The reason for using hashes only from odd-numbered blocks, is that we can ensure that I sketched up an example of how contract
As you can see, anyone can submit a hash, "savepoint", to the contract. If we are afraid that this will become a DoS-vector, where an attacker submit every (other) hash, which makes verification of tx:s more difficult (if this verification fails, there's no gas cost, remember) since the pool cache grows, we may want to add further restrictions, so that only every N:th block hash can be saved. Pros:
Cons:
|
I like your mockup, this could certainly work. Only concern is the savepoint-numbers. I'd prefer ever 512th block to be saved, maybe even every 1024th block. A savepoint every couple hours is with much safety margin to operate safely over a fork with a good majority hashpower. This could be included into the weak-subjectivity of Serenity, so that these savepoints also act as beacons for clients to safely join the network. To make this more useful I'd recommend the following configuration; instead of using a The save function should simply attempt to save the last block that could be considered a beacon. If none of the last 256 blocks are a beacon candidate it throws. (Which requires the beacon range to be above 256 or else you get double calls on the same beacon) It should largely operate automatic with no external input parameters. This could allow full nodes like geth to utilize this contract for an even faster sync to the network where they sync to the state of the last beacon and then sync backwards to the network while being able to operate within minutes. Since a forked chain will eventually within the beacon-interval end up with a different beacon merkle root, the fast sync will very quickly detect it and can consider other chain sources. On creation the contract precompile should include the genesis block and in a second operation the block number 1920010. |
It's worth noting that using odd/even numbers means that a reorg could change the meaning of your transaction; we need to define what happens to a transaction whose VAH is an uncle; does it get ignored? I don't see a problem with making the save-interval on the contract small, because any resource consumption attack requires a lot of resources from the attacker, for very low return (lookups are O(log n), and it would take a lot of ether to bloat the contract past what can be easily cached). @tscs37 Can you elaborate on why you'd store a merkle root against the hash, and how it would work? Also, it's unclear to me that having such a contract would improve sync; the contract wouldn't by nature be any more reliable than the information it can get from the nodes it's trying to sync with. |
Storing a merkle root allows clients to very quickly sync up with the correct network if multiple competing chains are present, allowing forks even against the GHOST block-weight-protocol if the client implements it. This means that if, for example, miners execute a softfork, even a majority hashrate has no meaning as users can opt to use the non-softforked chain. It also enables weak subjectivity; a single merkle root hash is enough for a client to instantly sync to the correct chain by inspecting the block data/state. I'd still try to keep the save interval somewhat big, a short interval is not that necessary and at 512 blocks, the wait maximum wait time is 58.9 minutes and also means the last savepoint is at maximum 58.9 minutes old. Re: Uncles; as uncles are valid parts of the chain, they should also be allowed as VAH values. |
My thoughts: Re reorgs and odd/even: a reorg never change the block number. |
The problem with using blocknumbers is that they are not replay safe. If the users fears that a reorg might drop the transaction, they can use a block that is farther in the past. Using a block hash from a couple beacon intervals is enough safety against reorgs. Uncles are not accessible to the EVM but to the validating nodes. This is equally solved by waiting for blocks to matures a bit as it is by using uncles. This might be an interesting extension to the EVM, to be able to access uncle data like their blocknumber and hash as part of future consensus algorithms. |
Nice discussion going! So a few thoughts... UnclesI have two objection to accepting uncles as Secondly, from a more theoretic perspective, uncles represent "roads not taken". By stating "This transaction is only valid after hash X", we want the transaction to only happen if block X has happened. So if X is an uncle, it means that the chain we aimed for with the Blocknumbers
@tscs37 Could you elaborate? Replay safe in what fashion? Using blocknumbers how? I touched on the problem with reorganisations above: "Regular wallets can use a recent odd-numbered blockhash (presumably from six blocks back or so, since chain can be reorganized).". I dont understand any other potential problems with chain reorganisations. Any mined block (both mainchain and uncles) have a hash and a blocknumber. A block can never be reorganized from being number More clever use of key->valueI don't understand the syncing problem that we're solving with merkle trees either, actually. And I'm not saying that as in "you're wrong", but genuinely I do not understand :)
If they sync up until the last hour, aren't they already synced ? Having said that, I agree that if we can put something more clever and usable instead of Beacon intervalI'm torn on the intervals. I definitely feel that 8hours is unnecessarily long. If, somehow, for some reason, an unplanned HF occurs, there could be a lot of replayed transactions occuring in that timespan. 1 hour sounds better to me.
Would it really? BTCRelay has 110742 entries, all bitcoin block headers since a while back. I'm sure it wasn't cheap, but doable. And it's pay once, live with it forever. |
If a fork were to occur at block 34534, then both chains now have differing blocks 34535 and 34536, so using blocknumbers will not be replay safe across those chains. I'm in favor of slightly longer beacon intervals because it allows forks to assert themselves. Until the winning chain is determined some time may pass and until then it might be beneficial to replay as much as possible on both chains. After one or a few hours the network should have mostly stabilized on one fork. |
Ah, I think there has been a misunderstanding. I never meant to use blocknumbers, but hashes from a pool. |
So, thinking about this more, submitting lots of hashes doesn't seem like a viable attack strategy. In order to force a client to keep an otherwise-irrelevant hash in memory, you have to submit transactions that reference it, which means they're valid, and thus cost gas. The only other attack is to ensure that the contract's list is too large to be entirely cached in memory - which clients will likely assume anyway for simplicity - and thus force the client to do one trie lookup for each transaction you submit with an invalid hash. We could specify that transactions from accounts with a balance but an invalid hash are valid transactions, but have no effect on the state besides charging them a modest amount of gas for the lookup, but I personally don't think the effort of doing a second trie lookup (the account balance being the first) is a big enough threat to warrant that. |
If the client operates correctly then invalid hashes should not appear, so invalid hashes are a sign the transaction is from another network or malfunctioning client. In either case causing the account a subtraction of gas could cause issues again, where transactions of one network are replayed to cause gascosts on the other. After some thinking I believe that a Beacons should expire after some time so clients, even light clients, can keep the entirety of the beacon list in memory. By default this can be placed at If a transaction is expected to hang around in the mempool for a while it can use the Genesis Hash, which is basically beacon #0 and virtually never expires. In short that means a client needs to hold the following memory contents:
The Beacon Contract should also store the block in which the beacon was stored, ie the Beacon 1920111 was stored at Block 1920122. |
Or it's an attack. I don't believe costing a little gas on an alternate fork is a significant concern, but as I said, I also don't think it's really necessary to do this to protect against DoS attacks.
This would remove one of the main points of having beacons, which is to support offline clients that can't fetch recent blocks from the network. I don't believe that fetching a trie entry to validate a transaction is an undue burden. |
Offline Wallets can utilize the Genesis Hash to construct a transaction even if no network connection is present, if that is not enough security, the user can simply transport a single and recent beacon hash to the offline wallet. I think for 99.99% of all offline wallets we can safely assume the user is capable of transporting a beacon hash to the signing software that is no older than 120 days, which is a low estimate on a 10 second blocktime. To make this simpler, a transaction may be allowed to only use 8 hexadecimal digits of a blockhash, similar to a git commit hash. |
The genesis hash won't protect against forks, and "simply transport a single and recent beacon hash" is papering over a lot of complexity. I honestly don't see the point in expiring beacons. It adds complexity both to the contract and to the implementations, for something that I think is a nonissue. |
An unplanned HF is almost certainly due to a bug in one or more client implementations - and I'd expect the bug to be fixed and the network to be merged again as soon as possible; in that situation replays across the forks are desirable, since they reduce the odds of a rollback when that happens. |
Isn't that more or less what we're doing with the So it sounds like the proposal above, but without genesis and without most-recent hashes. And it sounds like that would miss a couple of things:
|
Yes, but without the contract it'd have fewer moving parts.
From the client; it needs to know the block number of planned & past forks in order to implement them anyway.
I think that would be the most sensible way to treat it.
We could extend it to "most recent fork block hash or 256 most recent block hashes" to regain that property (this may have been what Vlad suggested, but I can't recall for certain). It's worth noting that someone deliberately seeking to flood the transaction pool can do it with either scheme by choosing the effectively-infinite option instead. If we really wanted to mitigate this for good, I'd suggest adding an expiry timestamp field to the TX, with a max allowable expiry period. |
I think this would be great to have on transactions, expiring transactions would be a sane default for some clients.
It requires the maintainer of any client to forever include all hashes of forks, possibly hardcoded or as a configuration file that is delivered with the client. Putting it on the chain means the developers only need to know the address of that contract and then they can validate it on any network, they don't have to care about any past forks, about the hashes of those blocks, etc., the EVM handles validation of transactions, the beacons are maintained by the community as a whole. |
No, just the block numbers - and they have to be included in the client anyway, so it knows when to change rulesets. |
Personally, I think it would be in general a good move to put some or all of transaction validation onto the EVM, so this could be a first step to that. Less Code on the Client Side and a globally shared "client". |
@Arachnid I have thought a bit about the scheme with only a set of blocknumbers, delivered with the client (possibly hardcoded). Let's say there's another planned (controversial) HF. Before HF, all clients run software S. Now, let's say there's some controversy. And some people prefer not to apply the HF. And some just don't update. Then we have
So, unless I'm mistaken in my reasoning above, I believe an on-chain fork-block-oracle which both updating and non-updating clients can agree about, is better. On a side-note, should we branch this EIP off into a separate EIP, since what we're discussing here has deviated quite a lot from the original suggestion? |
If we specify that a transaction is only valid if it specifies the hash of the latest fork block, then this is solved straightforwardly: upgraded clients that decline the fork should continue to treat the previous hash as the only acceptable one, as will non-upgraded clients. Only upgraded clients choosing the fork will accept the new fork block hash, ensuring that there are exactly two networks, and transactions can't be replayed between them. I'd very much like to hear @vladzamfir chime in on this, though, since it was originally his suggestion.
I don't think so; the goal is to find a solution to replay protection, and if we created a new EIP, at most one of the two would be implemented anyway. |
The problem the on-chain verification solves is that of manipulating a client to relay transactions anyway. Some software If the verification of transactions moved on chain, then that means that if one wanted to create this one way bridge, it would most likely incur slightly higher transactions costs (due to the additional logic in the contract) and also require to rewrite a contract on chain, so not as simple as simply changing the network ID or apply a simple consensus breaking patch (ie reduce difficulty) |
Ah, I see, I thought the idea was a list of fork-blocks, but it's just the one. |
But why would they want to do that in the first place? And why should we try and stop them if they do? This seems like an odd reason to prefer one technical implementation over another.
Right. A list is required in order to permit syncing the blockchain from the genesis block, but at any one time there is only one valid hash. |
Note, I had a similar proposal/RFC in #127. I closed it, since I liked the more far reaching applications of this proposal initially. My assumption about generalized replay protection is this:
Now that this issue has somewhat moved from the initial proposal, I have a couple of comments & questions:
I'm not 100% if I support the idea of somewhat fixed BeaconBlocks (every N blocks, every user interaction with contract, ...). It does solve the issues of planned hardforks just fine. However a more general "base block" approach allows for more flexible applications. Consider
Why were BeaconBlocks instead of arbitrary binding blocks considered?
For replay protection |
Why the |
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. |
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. |
Update the EIP to the working version from https://github.com/eth-infinitism/account-abstraction/blob/develop/eip/EIPS/eip-4337.md Changes: AA-94 update keccak rules. AA-93 Adding debug RPC APIs for the Bundler to use (ethereum#153) AA 92 simulate execution (ethereum#152) AA 73 unify reputation (ethereum#144) AA-68 rpc calls (ethereum#132) AA-61 rename wallet to account (ethereum#134) AA-69 wallet support for simulation without signing (ethereum#133) AA-70 rename requestId to userOpHash (ethereum#138) AA-67 relax storage rules in opcode banning (ethereum#121) AA-63 remove paymaster stake value from EntryPoint (ethereum#119) AA-51 simpler simulation api, including aggregation AA-60 validate timestamp (ethereum#117) Clarify wallet factory behavior when the wallet already exists (ethereum#118)
Update the EIP to the working version from https://github.com/eth-infinitism/account-abstraction/blob/develop/eip/EIPS/eip-4337.md Changes: AA-94 update keccak rules. AA-93 Adding debug RPC APIs for the Bundler to use (ethereum#153) AA 92 simulate execution (ethereum#152) AA 73 unify reputation (ethereum#144) AA-68 rpc calls (ethereum#132) AA-61 rename wallet to account (ethereum#134) AA-69 wallet support for simulation without signing (ethereum#133) AA-70 rename requestId to userOpHash (ethereum#138) AA-67 relax storage rules in opcode banning (ethereum#121) AA-63 remove paymaster stake value from EntryPoint (ethereum#119) AA-51 simpler simulation api, including aggregation AA-60 validate timestamp (ethereum#117) Clarify wallet factory behavior when the wallet already exists (ethereum#118)
Update the EIP to the working version from https://github.com/eth-infinitism/account-abstraction/blob/develop/eip/EIPS/eip-4337.md Changes: AA-94 update keccak rules. AA-93 Adding debug RPC APIs for the Bundler to use (ethereum#153) AA 92 simulate execution (ethereum#152) AA 73 unify reputation (ethereum#144) AA-68 rpc calls (ethereum#132) AA-61 rename wallet to account (ethereum#134) AA-69 wallet support for simulation without signing (ethereum#133) AA-70 rename requestId to userOpHash (ethereum#138) AA-67 relax storage rules in opcode banning (ethereum#121) AA-63 remove paymaster stake value from EntryPoint (ethereum#119) AA-51 simpler simulation api, including aggregation AA-60 validate timestamp (ethereum#117) Clarify wallet factory behavior when the wallet already exists (ethereum#118)
* Update to latest working version Update the EIP to the working version from https://github.com/eth-infinitism/account-abstraction/blob/develop/eip/EIPS/eip-4337.md Changes: AA-94 update keccak rules. AA-93 Adding debug RPC APIs for the Bundler to use (#153) AA 92 simulate execution (#152) AA 73 unify reputation (#144) AA-68 rpc calls (#132) AA-61 rename wallet to account (#134) AA-69 wallet support for simulation without signing (#133) AA-70 rename requestId to userOpHash (#138) AA-67 relax storage rules in opcode banning (#121) AA-63 remove paymaster stake value from EntryPoint (#119) AA-51 simpler simulation api, including aggregation AA-60 validate timestamp (#117) Clarify wallet factory behavior when the wallet already exists (#118) * lint fixes
Specification
In each transaction (prior to signing), include the following
blockhash
of a recent blockblocklimit
In order for a transaction to be valid, it must be included in the blockchain within
blocklimit
blocks of a block with hashblockhash
. Transactions with ablocklimit
andblockhash
of 0 are always valid, regardless of the chain history.Reasoning
This would offer a number of improvements
blocklimit
blocks a transaction would not be broadcast, therefore you could safely ignore it).The text was updated successfully, but these errors were encountered: