It seems like we fundamentally disagree on the philosophy of Ethereum. It’s unlikely my position on the topic will change, so we can set aside that disagreement for now.
In option 2), if you require AUTH perform a call-like operation into a replay protection contract you’ll need to specify the interface, like you said. This complicates the consensus specification, because right now it does not need to be specified at the consensus-level.
The main edge case I have in mind is what happens if someone calls the replay protection precompile/contract independently of AUTH? How can you securely pass data to the replay protector from AUTH? Do you need to do a second ecrecover or do you bind the replay protection to CALLER or do you magically shuttle it to the precompile? Could this lead to a sponsor being griefed? Should that even be considered by the protocol?
These aren’t unanswerable questions, but it certainly doesn’t simplify things.
From security perspective it is different. EIP-3074 currently gives the invoker a blank check which can be easily abused. option 3 only enables replay. A malicious invoker combined with a malicious replay-verifier could, at worse, replay a transaction previously signed by the EOA for the same chainid. Whereas with the current EIP-3074, the invoker could make any transaction on behalf of any EOA that ever signed a message to it.
As you describe option 3) above, it is no different from EIP-3074 from security perspective. A malicious replay protector can say every nonce is valid. At that point, the malicious invoker again has a “blank check”. There is nothing forcing either the invoker or replay protector to check any part of the commit, where the actions are actually specified. So if a user can be forced to interact with a malicious invoker, they are at equal risk as with EIP-3074 as it’s written.
A malicious invoker under option 2) has the opportunity of 1 blank check, whereas option 3) has N blank checks. After that nonce is burned in option 2), the damage is done.
However, if you can trick a user to signing a tx to a bad invoker, it’s safe to assume you can trick them into signing an arbitrary transaction. I’ve stated on multiple occasions that the argument “but the worst that can happen is one bad tx” is a very weak counter argument to batching. Batching is coming. Whether it’s via meta-txs, ERCs, or EIP-3074. When that time comes, one bad signature will be able to empty an EOA.
Even if you disregard the fact that batching is coming, I don’t think “security by diversification” is a strong argument that 1 blank check is acceptable. I investigated this more a while back.
This is an unfair comparison. You’re framing it like “wallet developers will hold all the power privately and the community will be helpless”. EIP-3074 allows permissionless innovation. If the wallet developers block the adoption of a certain invoker, the community could fork the wallet and add it to the whitelist. Or they could build a new wallet. This is far easier than forking Ethereum to bypass the core developers.
And that’s what I love about this ecosystem Disagreeing on philosophy shouldn’t stop us from building an optimal solution together. It actually makes us less likely to fall into the trap of group-thinking.
Right. It means that the interface has to be well defined, just like the args of AUTH itself. I’d make the ABI accept a signed AUTH message because it contains everything this contract needs. It then checks that address(this) is specified as the replay protection contract, extract the EOA from the signature, and use the opaque nonce field in its own logic. Doesn’t seem difficult to define but perhaps I’m missing something.
Why is that a problem? If a user chose to sign an AUTH message and send it directly to the replay protection contract, then the user simply burned a nonce without using it. Just like sending a transaction to self.
One way is to make it a part of AUTH so it will call the contract directly. Another is to change the semantics of AUTH to authorize its caller rather than the current context, so AUTH will only be called inside these whitelisted replay verifiers, and then delegated to the invoker. Neither seem ideal but let’s try to find a better way.
No need for a second ecrecover. The invoker knows that it’s calling a trusted replay protection contract which already does its own ecrecover. If the replay protection call is successful, it means that ecrecover has already succeeded. The call should return the recovered address.
It should be considered, at least to the point of making it possible for sponsors to avoid being griefed. We don’t need to make it impossible to write a bad sponsor that can be griefed though.
Why does it open the sponsor to a griefing attack any more than an EIP 3074 does? It implements exactly the same logic that the invoker would, except that the replay protection becomes an external call. Wouldn’t the same griefing vectors work on both?
You’re right - it doesn’t simplify things. It’s not meant to. It is meant to remove the need for signing blank checks and improve security, by introducing an additional trust boundary. Adding trust boundaries to a system always complicates its internal interactions, but it’s still essential to the security of any complex system.
Consider hardware security mechanisms like SGX (or any HSM for that matter). The programming model of SGX is a pain to work with. It’s much easier to just keep all the secrets in your usermode process memory, perform all operations there, and not have to deal with context switches and stateless ops. But this model makes it possible to secure sensitive operations in a way that protects them from bugs in the rest of the code.
If a system has a small number of sensitive operations, it makes sense to separate them and make them less vulnerable to bugs in the rest of the system. My suggestion is to do just that. If the user signs everything except for the replay protection, then AUTH/AUTHCALL are no longer a major risk. The only remaining risk point is the replay protection, so I’m trying to separate that part.
That’s not what option 3 says. In all three options, I’m only referring to the replay protection, because I assume we already replaced commit with the actual content:
so regardless of the replay protection, AUTHCALL no longer has a blank check. At most, if replay protection is broken, it can AUTHCALL a previously used call. There’s no commit in this model.
I don’t think it is. Let’s assume the user is forced to interact with a malicious invoker. With EIP 3074 it means that the user loses control over the EOA forever. With option 3, there may be two cases:
The user interacts with a malicious invoker which uses a legit replay protector. In that case there is no risk. The invoker can decide whether to perform the AUTHCALL or not, but otherwise has no way to hurt the user.
The user interacts with a malicious invoker which uses a malicious replay protector. This is not possible in option 2 because replay protectors are whitelisted in an EIP. With option 3 it’s equivalent to not having a trusted replay protector, so the user’s transaction may be replayed any number of times in the future. But still, it cannot be modified by the malicious invoker. If the user has 100 DAI and 100 UNI in an account, and signs a transfer of 10 DAI, the malicious invoker can replay it 10 times and steal all 100 DAI, but it can’t touch the UNI. It’s pretty bad, but not nearly as bad as a malicious invoker in EIP 3074. And yet, that’s why I prefer option 2 which also mitigates that.
It’s not what the options say. In all three options, there are no blank checks. AUTH verifies the entire content that used to be covered by commit. The only thing that differs between the three options is how nonce is interpreted.
Batching is coming, but it doesn’t have to mean that a single bad signature will compromise the EOA forever. In the model I suggested above, the user signs a message containing one or more AUTHCALL data. If the user was tricked to misunderstand some of them, then yes, there will be damage. But it doesn’t mean the user loses control of the EOA. With EIP 3074 it does.
Batching, in itself, shouldn’t change the Ethereum security model. The user still needs to authorize every transaction in the batch, just like without batching. The only difference is that they end up being batched for efficiency. If by batching you mean that the user doesn’t need to authorize each transaction, then it completely changes the Ethereum security model.
I do think batching is coming (and should come). It just doesn’t require blank checks.
I didn’t suggest “security by diversification”. Do you see an blank checks in my suggestion?
It does. I never meant that it stops innovation. I’m more concerned about security. The issue is not whether the community can or cannot deploy new invokers (I know it can). It’s whether a malicious wallet maintainer who “plays the long game” (or just has a bug in the invoker) can cause.
A wallet maintainer could devise tricks like the ones I suggested in my first post. The wallet would look honest for months or even years, while collecting signatures. Then, when enough money is on the table, become malicious. For example, the wallet maintainer could use the delegation flow I suggested, allowing its maintainer to take over governance of other projects. The users who just use the wallet for trading, will never notice or complain.
Putting that kind of power in private hands creates incentives for certain people to exploit the system. At some point someone definitely will. By making it public, requiring replay protection to pass the EIP process, it becomes much harder to hide such schemes.
Admittedly, a wallet maintainer could achieve the same goal by stealing and sending private keys to a server and performing the attack from there. However it is far harder to hide that, than to hide a bug in an invoker or not even hide anything at all - just deploy the bad invoker to the other chain after a year (with the current EIP 3074 which excludes chainid).
Alright. I respect your point of view as well. Since we’re not collaborating effectively on a modified proposal, I guess EIP 3074 really is a take it or leave it proposition.
We’ll leave it for the community to decide whether to accept it as-is, despite the security issues I pointed out. If it doesn’t get included we can get back to it and propose something with a different risk profile.
I’ve been thinking about this thread a bit, and I’ve read up a bit on the types of vulnerabilities that @yoavw has highlighted, and how he’s compared it to setuid.
The thing is, at the heart of the matter I think we agree: Accounts should delegate the minimum possible authority outside of themselves.
The disagreement comes from “at what layer should we allow EOAs to delegate their authority?”
If the only use of delegation were batching and MetaTransactions, then I think yoav’s simpler proposal would be sufficient. But I believe that delegation is fundamental to secure composition, and my interest in 3074 goes far beyond those two use cases.
3074 provides the minimum foundation for a general-purpose delegation framework for EOAs. Yes, it’s dangerous because an initial delegation to a bad invoker can be catastrophic. But, if the invoker is very well audited, and is designed to allow finer-grained additional delegation, we could compose chains of delegations where each link can gain no additional authority. This is a pattern that I’ve been looking out for and have described as ethereum object capabilities.
By providing accounts an initial capability to delegate, we provide a fundamental tool that can be used to enable fine-grained delegations of any sort, and I think this can start to look much more like yoav’s ideal security environment, where additional processes more frequently are truly only getting the capabilities they require.
It may feel counter-productive, or ironic that in order to allow an EOA to delegate the minimum possible authority, they must first be able to delegate any authority, but I think this is basically a result of the EOA’s inability to delegate any authority at the protocol level today. Under the current simplified proposal, any time we want an extra delegation-related feature for accounts, we would need to go through the process of getting it accepted at the base layer of the blockchain. Alternatively, by providing the two very simple opcodes of 3074, we are able to provide any type of delegation in the future on top of the platform, without additional consensus changes. I think Micah said it very well,
Thanks, but I can’t take credit for that, although I fully agree with this analogy. I was just quoting @SamWilsn who correctly pointed out that EIP 3074 is setuid and not sudo.
Even if it is perfectly audited, there could be unforeseen risks. Consider the cross-chain attack I demonstrated. The invoker on the first chain was perfectly secure, but once the user moves assets across a bridge to the other chain, there’s a malicious invoker waiting to attack. The user never interacted with the invoker on the other chain. That invoker didn’t even exist at the time the user signed the message. This particular vector can be fixed easily by including chainid in the signed message, but the point I was trying to make is that excluding anything from the signed message opens up vectors we may be missing. There’s little downside in including everything, and a potentially unlimited downside in excluding anything.
I like this chain idea! It’s getting pretty close to capabilities in the Linux sense. The only problem is that each single-capability-invoker is given full access by the user, and we rely on its implementation to restrict the actual action.
My question is why can’t we implement exactly that chain, using the method I suggested. My suggestion is to require that the user (wallet) signs a list of AUTHCALLs rather than a single commit. If the chain involves 5 operations by different invokers, then with EIP 3074 the user would be signing 5 different AUTHs, each of them with full access to another invoker. My suggestion is to sign a specific AUTHCALL for each. So if one of these invokers is buggy, it is still limited to the operation signed by the wallet. We get exactly the same functionality, at the same cost, but with much lower risk.
The UX also remains identical because the wallet would be aware of these 5 invokers and the AUTHCALLs they’re going to make. So a wallet that supports these operations could represent them as a single action for the user to see, and then sign a transaction with these 5 AUTHCALLs rather than 5 AUTHs.
Am I missing something that would break the chained-invokers idea if we use my proposal?
Yes, I think this could be great. We see things similarly. The part that I’m missing is why we need the full access AUTH to achieve this, rather than signing each of these AUTHCALLs.
Right. I think we can achieve the same without having to give up so much control over the EOA. Just sign every operation instead of a single blank check.
You mean my proposal? I wasn’t proposing whitelisting the invokers. Anyone would be able to deploy an invoker and the risk is much lower because an invoker in my proposal can only perform actions signed by the user. Not through a commit to be interpreted by the invoker, but an actual AUTHCALL.
The part that we’ve been debating re whitelist is just the replay protection. If the user has to sign the entire AUTHCALL and the protocol verifies this, then we are stuck with the current nonce implementation which is less friendly to batching. We were debating whether replay protection should be implemented as separate contracts, and those would be whitelisted. In practice I don’t expect to see too many different replay protection schemes, but I do expect to see many different invokers. My proposal doesn’t hinder invoker innovation. It just slows down replay-protection implementations.
And even if we dropped the whitelist entirely, as I suggested in option 3, we still end up with better security than the current EIP. A buggy invoker might lead to a replay, but it would never lead to performing actions the user never signed.
What are the downsides of having the wallet sign things like to and calldata for each AUTHCALL?
I think cross-chain is worth considering, but is well accounted for by adding a chain_id parameter to the commitment within the invoker contract. Since it can be such a well documented best practice for an invoker, it seems like it can safely live in either place, but I actually don’t feel very strongly about this. I used the “multiple-chain delegation” as a hypothetical example (it might be nice to be able to delegate cross-chain permissions with a single signature), but it seems like a small enough change I really could go either way on the chain_id point.
My impression is that if this were implemented in this way, these delegations would need to be redeemed in the order they are issued, which tightly couples issuer to redeemer. By allowing an invoker to implement arbitrary redemption logic, we can have “counterfactual”, order-free capabilities being shared, that can be redeemed lazily (resulting in fewer on-chain transactions).
Some delegations might be good for multiple calls.
DAIv2 style permit(): It allows issuing a token allowance with a single signature. The redeemer can then redeem/use their allowance in any number of transactions, as long as they don’t exceed that limit.
vote-delegation: I might trust another account to vote on a particular topic or a particular DAO on my behalf, any number of times, until revocation.
I demonstrated why it isn’t, with the contracts I deployed and linked earlier in this thread. The commitment is only checked on the first chain. The invoker on the second chain doesn’t even look at the commitment, and just does whatever it wants with the user’s AUTH. The bad invoker on the second chain was deployed after the user already signed the auth on the first chain and was not aware of a future malicious invoker on another chain. Therefore chain_id must be checked by the protocol itself, not by the invoker.
But my chain_id example was just to demonstrate the point, that anything not covered by the protocol-level check may lead to unintended consequences. The current version of the EIP missed that vector. A fixed version that only adds chain_id might still miss other vectors that I haven’t noticed in my preliminary audit. But if we enforce all the fields at the protocol level, there are no cases to miss.
Therefore I think anything should be covered directly by the signature unless there’s a good reason against it.
Why do they need to be ordered? My proposal was to sign a list of call-hashes in AUTH, and then to have AUTHCALL refer to an index in that list. The invoker would be able to trigger them in any order.
But why wouldn’t the wallet arrange them in the expected order? It knows what’s going to happen in the transaction, and could sign a list in the right order.
The user can create an allowance that will last for any number of transactions, without having to repeat it every time. When the allowance runs out, the user needs to create a new allowance. It keeps the user in control. If the user wants to only sign one allowance message and have it last forever, then sign a max allowance. If the user signs a smaller allowance, it means that the user wishes to stay in control of spending, so the invoker should respect that anyway and let the user sign the next allowance. No need to sign an open check for the invoker to auto-renew the allowance.
I think this kind of functionality belongs in smart contracts, not invokers. Most voting contracts explicitly support delegation (e.g. Uniswap style governers). When the user signs such delegation, it’s explicit and the user is in control. By making it easy for invokers to vote directly without the user signing a delegation, we’re opening the network to attacks like the governance-hijacking I described in my first post. This scenario is exactly the one I’m trying to prevent with my proposal.
I think invokers shouldn’t replace smart contracts. They should add functionality that is hard to achieve with smart contracts due to EOA limitations. Vote-delegation and allowance-management don’t seem to fall in that category.
This requires a user to delegate to an invoker that can be published at the same address with arbitrarily different code. That is worth avoiding, but is not unavoidable. Invokers can be published in a way where the code is statically committed to, using CREATE2, and lacking a SELFDESTRUCT op code. Those could be enforced at the wallet level.
I think you’re talking about using the token’s own allowance function. I guess using a token allowance wasn’t an ideal example, since ERC-20 token contracts do provide this kind of delegation for themselves, but they do not provide it in a recursively chainable form.
On the other hand, if an allowance invoker were made, it could allow counterfactual allowances to not only be performed on tokens that never wrote a permit() method, but those allowances could themselves be delegated, allowing recursive allowance graphs, which could form webs of trust with credit, all without publishing a single contract.
The fact that some smart contracts implement delegation for some of their functions is nice, but with a standard delegation framework, contract authors could ignore delegation as a feature, get it for free for all functions, and users could benefit from having those benefits on all contracts. This simplifies secure contract development (since you aren’t asking each developer to re-implement a safe delegation contract).
It would be no different if delegation invokers were well vetted and integrated as an optional user action into wallets. The user would always have explicit control over anything they did. I’m not sure how you’re imagining invokers working, but it seems like you’re imagining some YOLO step that none of us are proposing.
Your initial example relies on a malicious dex performing a long-con where it deploys its own batching invoker, relies on wallets adding it without scrutiny, long-cons many users into adding it to their wallets, and performs malicious behavior that would’ve been obviously possible to any single person who’d reviewed it.
This seems to miss the repeated point that invokers should not be trusted by wallets carelessly. Your scenario requires wallets that are allowing installing invokers with basically no review or warning process for helping users identify dangers, even from an easily-identified malicious contract. Those dangers could be mostly eliminated with just some basic diligent assurances:
Most wallets only ever integrate invokers they’ve vetted as if it were their own internal code.
Any wallet that allows adding arbitrary invokers needs to at least warn the user that this contract could be stealing all of their funds (and yes, users should learn to respect such warnings). Ideally it would also require a verified contract source code, and would include an audit-warning system, for notifying users of known malicious contracts.
I think in a permissionless ecosystem we should embrace tools that allow us to safely innovate. When I see a tool for generalized delegation, I see enormous potential. Allowances and vote delegation are easy examples to describe, but the actual applications are basically all authority related functions. Anything with an onlyOwner would be made more dynamic with an option to delegate.
I feel a little weird that I’m about to provide more examples in defense of “delegation” as a useful tool, because to me it’s perhaps the most obviously useful tool, but I will list more examples that come to mind:
Ability to assign the target of an ENS entry could be shared with an arbitrary group, designated counterfactually. (This could be like sharing the ability to update a website)
Ability to freeze a contract (in case of a security threat being discovered)
Ability to propose an expense to a DAO (web of trust for proposals)
Ability to issue a NFT as part of a collection (artist collectives)
Ability to make a move in a game (crowd-sourced gaming)
Ability to accept a bounty (web of trust for certified contractors)
Ability to buy an exclusive item, like an event ticket (a referral fee could be added as part of the delegation, sometimes called incentive trees)
And yes, delegation can and should live at the smart contract account layer, but that is no reason to ban it from the EOA layer as well. It’s a generally useful tool for composition, and a permissionless ecosystem thrives when composition is cheap and safe.
To stop this particular attack because I already highlighted it. We don’t know how many others exist. The problem is, if an attack vector is discovered and fixed in a wallet at some point in the future, all EOAs that used the wallet and the invoker in the past are still compromised by it.
Suppose we didn’t know about the attack vector I described, and didn’t enforce the CREATE2-only rule, and users already used invokers on different chains. And then I publish about it and all the wallets add this check. Any user who previously used an invoker that was created by CREATE is potentially compromised and needs to move all assets immediately, on all chains. In some cases this won’t even be possible, e.g. with locked tokens.
One of the major issues with EIP 3074 is that it’s impossible to fix things in hindsight. Past vulnerabilities will continue to haunt users, long after they’re fixed. This could have been prevented by checking everything rather than just a commit.
The same could be achieved with the user signing the allowance, since the allowance itself persists so no need to also persist the capability to increase it from an invoker. An invoker (whether based on EIP 3074 or on my proposal) doesn’t need the token to support permit(). The user signs an allowance (once), entrusting the contract to withdraw from the desired token. From then on, things like gas abstraction can rely on that allowance. The invoker can check the cost of the user transaction and withdraw from the allowance, without needing to reestablish the allowance. If the allowance runs out because the user limited it, then the user needs to sign a new one as part of the next transaction. Still no need to give the invoker the power to renew the allowance without the user’s consent. And the allowance could also be delegated by the invoker as well, still without being able to increase it.
With the trade-off of adding a much riskier construct. I’d rather see each contract implement its own delegation, and if the contract is buggy it will have an adverse effect on that contract, than see a single buggy invoker that compromises all contracts at once. Privilege separation limits the potential damage of each bug. The current AUTH removes this separation and puts all the power in a single contract, hoping that it is completely safe. The proverbial putting all the eggs in one basket.
I’m not imagining a YOLO invoker. My concern is a buggy/malicious contract that slips through the audits. Things slip through audits all the time, but their consequences are usually limited. A buggy invoker would compromise everything at once. Any EOA that ever used it, every contract that gives any of these EOAs power (ownership, voting, etc).
So I guess in a way I would consider any transaction to an invoker an act of YOLO. It assumes that the invoker is perfect and trusts it with all assets, now and in the future.
Or with scrutiny that misses an intentional bug. It’s really hard to find intentional bugs and many of them can lurk in trusted code for years despite many audits. Never assume that any code is 100% bug free, especially if there’s an incentive for the developer to introduce a bug.
I do assume a review. I just assume that the reviewers will miss some bugs, as they often do. The consequences of missing an invoker bug are far worse than the consequences in any other contract. A bug in a widely used invoker would make TheDAO look like a minor incident.
All the use cases in the list are possible to do without giving the invoker infinite power, by implementing these capabilities in contracts. When we’re considering adding something so risky, we should only consider use-cases that couldn’t be supported by less risky means. If the same innovation could be supported without the risk, then adding a riskier way to support them seems like a bad trade-off.
What a thread to catch up on. I previously looked into EIP 3074 as it was comparable to what I’m implementing purely in smart contracts. It factors in the concerns being discussed here around invoker implementation/trust, and replay protection via chainid & nonce.
So although not at the protocol level this might be a less-risky implementation of AA, albeit less elegant in some ways.
Here the “invoker” contract (VerificationGateway.sol) is responsible for generating the smart contract wallets given a public key and corresponding signed data (BLS sig scheme).
A user’s BLSwallet contract can make a generic .call with arbitrary data only if called from its invoker (see action). The contract wallet is also responsible for incrementing its nonce.
A user must sign the chainId and the wallet contract’s nonce with the call data (amongst other things). This signed message can then be passed to the invoker to then action the corresponding signer’s BLSWallet.
Anyone (eg aggregator) can submit these signed messages to the invoker contract, and they are incentivised by a token reward. The amount to pay is part of the message signed for and is transferred by the invoker when making the wallet call.
There will also be a demo to construct, sign, and submit a dapp’s smart contract calls (individually or batched), and it might be some time before we see such things implemented in wallet plugins/apps/hardware.
My position on this as well. Though I do not have as much patience as you do to keep arguing I feel like there is a fundamental change in what it means to give a signature. But arguments usually involve lots of use cases that I need to get into, and I get the feeling that the “burden of proof” is the reverse of what it should be. I also suggested an alternative, which may be similar to yours (multi-signature transaction type), but I do not have time to prepare an EIP properly, so I effectively lose the arguments because of that
I am of the opinion that the 3074 authors have put forth a herculean effort to address all arguments. At this point, I do think the burden falls to others to construct a complete and sound counter-argument. Without that, then any proposal could be shot down by just someone saying “I’m not convinced” (perhaps without even reading through/understanding everything), and that would rapidly lead to stagnation.
I suspect the root cause of disagreement with you is similar to others, which is whether the EVM language should be a collection of powerful primitives that others can build things on (some of which will be safe, some of which will not be safe), or if the EVM consists of higher level more limited power primitives that are designed to protect users. 3074 introduces a new primitive that someone can use incorrectly, but it also is an incredibly flexible primitive that opens a lot of doors for people to leverage in safe ways.
Fundamentally, just like today you should not be signing arbitrary transactions, and you also MUST trust your wallet tooling with your private keys. It is the wallet’s job to protect users from signing things that are bad for the user, not the EVM’s job. The EVM’s job is to give people useful primitives to build interesting things.
I get the feeling that the “burden of proof” is the reverse of what it should be
We have had two security (1, 2) audits of the specification and have discussed the EIP at length with many developers at all different level of the stack. We have yet to find any security issues with EIP-3074 itself, only philosophical disagreements about how expressive the EVM should be and how the EIP fits into the longer term picture of the protocol.
I’m not aware of a succinct proof-of-safety for EIPs yet, so unfortunately there is some burden for those against the change to engage in the conversation. If there is something you feel like hasn’t been addressed, we would be happy to try addressing it
I do not really think this is the philosophical disagreement, but rather security assumption, that, once given up, cannot be taken back, without disallowing the introduced opcodes later. And I do appreciate all the work you guys did when engaging with concerns, kudos to you. I believe no amount of explanations can make this proposal more secure without fundamental change (removal of perpetual irrevocable delegation). And for both security audits, the issue that concerns me was taken out of scope, the auditors decided either intentionally or un-intentionally to avoid this issue, by saying: “EIP recommends users to do X to avoid problems”, or not looking into this aspect at all.
But anyway, I already gave up on this, to be honest. Cannot be involved into everything
Hard to blame you. I think everyone lost patience at some point.
Exactly. When adding something so risky, to achieve things that could be achieved with less risk, the burden of proof should be on the proposer. I don’t feel this has been met.
I believe I did provide such counter-argument and brought sufficient evidence. I don’t think you could reasonably blame me for just saying “I’m not convinced” without supporting my stance. I spent many hours on this discussion, on demonstrating the problems, deploying contracts, participating in side discussions about it, etc. Arguably, others in this discussion are pulling the “I’m not convinced” card by saying that it’s just a philosophical disagreement rather than a technical problem that should be resolved before proceeding. It’s basically “agree to disagree”, but we all “live” in the same Ethereum so we have to agree eventually.
I think you described this argument well in one of your earlier posts, when you compared it to low-level languages that don’t implement any protection. And you’re right - low level languages shouldn’t put restrictions, and should make anything possible as long as the hardware allows it.
However… nobody uses low languages like C to develop financial systems that hold billions of dollars. We write the kernel in C (and recently a bit of Rust), but when coding complex logic that handles things like finance, we don’t need capabilities such as executing code from heap/stack or implement self-modifying code, so we drop these capabilities in favor of a more restricted VM like JVM, with memory protection and strong separation between code and data. It doesn’t allow us to do certain cool things that we can do with C/C++ (and I love trampoline functions and lazy linkage as much as the next guy) but it helps us build secure systems when dealing with complex business logic. Low level languages definitely have their place (and it’s where I feel most at home, being a hacker and a kernel/hypervisor developer), but I don’t think EVM is the right place for such powerful low level primitives. Security should be top of mind here.
With EIP 3074 the wallet can’t really do that. You can only protect users from threats you currently know about, but the user’s signature can be abused at any later time, after the threat becomes known. For example, the cross-chain attack I mentioned would not be mitigated by a wallet maintainer who hasn’t thought of that, and there’s nothing in the EIP about requiring that invokers are created using CREATE2. Now that I demonstrated this attack, wallets will stop users from interacting with CREATEd invokers, but what if I had brought it up only a few months after the EIP was merged in? Wallets would upgrade and stop interacting with such invokers, but anyone who previously signed an AUTH to such invoker is screwed already. An attacker could exploit past signatures even after the wallets are upgraded to mitigate that specific attack. And this attack is just one example. The point is that any future attack could make everyone vulnerable and the wallet would be powerless to help.
I believe I did demonstrate attacks that the audits ignored, and that cannot be mitigated by EIP 3074 as-is.
I think the issues haven’t been addressed but we seem to disagree on that.
And as for the burden of proof, since we’re talking about adding an opcode that is riskier than anything added before, I think the burden of proof is on the suggester to show why the required use-cases cannot be achieved by less risky means.
Kudos, I couldn’t have put it better myself.
I was also wondering about that when I saw the audits. The EIP gives users plenty of rope to hang themselves, no way to undo the damage, and the audits just warn users to try not to hang themselves.
Users implicitly entrust us with protecting their assets. We should keep that in mind when developing the protocol. Ethereum is not about just the developers. Users matter too.
I understand you, but I hope you don’t give up. I also can’t find the time to be involved in most of what’s going on, but when something so risky is being considered, I feel we must voice our concern.
As I’ve explained before, in order for EIP-3074 to be used safely the following must be done:
Implement and deploy an invoker safely. This means following invoker best practices, audits, and formal verification. You found an edgecase not yet included in the best practices – invokers deployed by CREATE were malleable on other chains. The solution is to not trust invokers deployed with CREATE. It is not a security issue of EIP-3074, just an unsafe use of the primitives.
Wallets must verify for themselves that the invoker follows best practices and is safe. They may need to do their own auditing / formal verification if they aren’t convinced. If they do a good job here, the invoker can be considered safe. If they make a mistake, their users are at risk. But this threat model is the same as them shipping any update to their wallet. Actively malicious wallets are no more dangerous under EIP-3074 than now.
Users should only be allowed to sign EIP-3074 messages to invokers explicitly whitelisted by the wallet. This means they cannot sign authorizations to actively malicious invokers.
If you can show that under these assumptions that EIP-3074 is exploitable, then there is a safety issue. If you have to change the assumptions, then you disagree philosophically with the EIP.
The EIP explains in the rationale section. I also explained in ACD 116 why the your proposed mechanisms were less flexible than EIP-3074.
As a reminder, here are a few use cases:
social recovery - I want to delegate control of my EOA to a multisig of my friends. Every proposal that requires calldata be signed over up front is inadequate to solve this problem, because I don’t know what assets I’ll have when I lose my EOA.
cold wallet EOA - suppose I have a cold wallet, but want to interact with some of my cold wallet assets with my hot wallet (say with permission to change some ENS attributes). This requires malleability of the signature, because the cold wallet will sign one message saying “this hot wallet can do these things as me” and then the invoker will verify that messages coming from my hot wallet only do certain things.
specialized invokers - Some actions are very common, like approve-call. It would be more gas efficient allow a user to sign over just the approve-call invoker with their call and have that signature be used implicitly to authorize the approve.
The point is not that I found an edgecase not yet included in the best practices, but that someone else will find another one in a year or two, and EIP-3074 makes users vulnerable to any future discoveries. If the best practices two years from now differ in any way from the ones we have today, then the difference between them opens an attack on any user who signed an AUTH during these two years. In the example I demonstrated, users get robbed using a signature they gave a year earlier, using a wallet that implemented the best practices at the time they signed it. When the best practices are updated, the user may be powerless to mitigate the attack.
Users will follow currentbest practices but will get exploited when the best practices change if they used the wallet at any earlier time.
You implicitly assume currently known malicious invokers. What about whitelisted ones that will have well-hidden intentional bugs, to be discovered at some point in the future (e.g. when the author exploits the bug for the first time)? The invoker will be removed from the whitelist immediately but it doesn’t matter because the user already signed the AUTH. You seem to assume that wallet maintainers will be able to perform the perfect audit on every 3rd party invoker. That is a strong assumption.
It is exploitable under these assumptions, because the bestbest practices you can use are the ones currently known, whereas the vulnerability lies in the ones that will become known in the future. If the best practices change at any point in the future, then yes - AUTH messages signed before the best practices changed are exploitable and therefore EIP 3074 is exploitable.
Sounds like you’re trying to replace smart contracts with invokers. A user could have social recovery by using a wallet that supports that, such as Argent. Switching from an EOA to a contract wallet in-place is a nice hack, but comes with a security price-tag. Users could get the same functionality without the security price, by explicitly moving their assets to a smart contract wallet. We don’t need to change the consensus for that.
If you own an ENS name and want someone else to be able to change certain ENS attributes, transfer the ENS name to a contract that accepts any operation from your EOA, and specific attribute changes from another EOA. No need for a new opcode. Contracts can already do that. The price of moving the ENS name to another contract is much smaller than the price we’ll be paying in security by adding this opcode.
Why is that not possible with my proposal or a similar one, where the user signs a list of calls? The wallet will know that the invoker is performing both the approve and the call, so it will put them in a list, sign that list, and send the signature to the invoker. No need for a blank check because everything is known in advance.
This is not a security issue of EIP-3074, but rather a philosophical disagreement. I trust that we can develop safe contracts. We have many contracts that are considered safe such as the Uniswap contracts, Gnosis safe, Argent wallets, etc. If you don’t trust those, don’t use them. If you don’t trust EIP-3074, don’t use it.
What happens if Gnosis Safe hides a bug deep down in their smart contract only to call in at some far future date? The point of smart contracts is for them to be trustless and publicly auditable. We can write safe contracts. What criteria wallets require before using an invoker is an important, but different matter than the EIP itself. I have described a framework in which this can be done safely.
It’s not about the specifics, it’s about the class of things EIP-3074 allows. Transaction malleability allows EOA to make delegations off-chain. This is extremely powerful. Your example requires another contract be deployed. My proposal costs the user nothing.