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.
If it’s a philosophical disagreement then it’s a pretty fundamental one. Should we make it easy for users to take risks where even if they follow all the current best practices at any given time, they may be vulnerable due to not being able to fix their past mistakes? In most other cases, users can remain safe by following best practices. With EIP 3074 they can’t, because the best practices aren’t necessarily known at the time they expose themselves to the risk.
You’re right, I don’t need to use 3074 and I might not. And despite not being a professional auditor, I’m probably a bit more qualified than the average user to assess the risk of using an invoker. But we’re not here to defend ourselves. We’re here for the users.
With Gnosis Safe, users only take a specific risk, associated with the contract itself. With EIP 3074 invokers, users take a risk that the best practices are incomplete and will change in the future. If we miss anything in the first iteration, then by the time we realize it, all past users are already at risk.
Some users may not even realize it, because they may no longer be users of that invoker and they don’t realize they’re essentially holding their assets in it. If a Gnosis Safe bug is discovered, some users will get hit, but many others will learn about it and move their funds asap. But if the best practices of selecting invokers is changed, how will users know that they need to move all their assets immediately due to some past AUTH they signed a couple of years ago?
There’s an important difference. Vitalik’s proposal is to replace all EOAs with a simple contract that emulates the behavior of the current EOA, and with an upgrade option. Users may explicitly upgrade their EOA to use a contract wallet, and since it’ll be done via a transaction rather than a signed AUTH message, it won’t be replayable on other chains so it will only affect the user’s account on the intended chain. If a user wants to start using Gnosis Safe, for example, it’ll be possible to upgrade an existing EOA to Gnosis Safe at the user’s choice. And if the user later decides to switch to Argent, then the user is no longer exposed to Gnosis Safe. There is no lasting effect and the user is free to choose at any given moment.
Will users perceive the act of signing an AUTH to an invoker as migrating their account to that invoker on all chains, now and in the future? And will they understand that they can only add contracts to their wallets but never remove or replace them?
There’s a reason why users use smart contracts rather than delegating all their power to someone else. It is also cheaper to give all your ETH to a centralized exchange and just trade there, but we like staying in control. When I send an asset to a contract, I implicitly assume that the potential damage is limited to the asset I’m currently sending to that contract. EIP 3074 makes it impossible to assume that. So you’re right, this is extremely powerful. Also too powerful, and we can get the same results with just the cost of deploying a contract when we need one.
Except that if the user then decides to switch from Gnosis Safe to Metamask, then the bug exposure disappears. With EIP 3074 the user may sign any number of invokers (whitelisted by the wallet if that’s how the wallet works), and has no way to “remove” them.
But with support for multiple invokers (roughly equivalent to adding modules to a Gnosis Safe wallet), and with no way to opt-out after opting in.
I read it, and I really like the idea of using EOF and adding a validate section. Once users have an ability to blacklist an invoker, it becomes much safer to use. And this section keeps things clean and easier to verify.
I like the validation focused wallets but EIP 3074 doesn’t mandate that, and there may be other contract wallets that don’t work the same way. It would be safer if the protocol ensures that the user has a way to opt-out of any invoker, not just with contracts that implement an opt-out method.
If we did it in the opposite order, implementing EOF and validate section first, then forking to convert all EOAs, and only then introduce EIP 3074, then it would at least allow any user to change the validate section to blacklist unwanted invokers. That would solve a lot of the risks associated with EIP 3074.
A couple of things that stood out to me when I read it:
When converting all EOAs, you suggested that the default validate will just check the signature. Don’t we lose replay protection then, without a nonce check? What stops someone from replaying all the user’s past transactions after this fork? Unless I misunderstood the way it works, I think the fork will have to also expose the account nonce to EVM. Even if the replay protection mechanism changes in the future, the contract will be able to prevent replays of pre-fork transactions.
Without including chainid in the AUTH signature, the validate section won’t save the user from cross-chain attacks. User may authorize a honest-looking invoker on one chain, not realizing the need to blacklist that invoker on all other chains.
Regardless of EIP 3074, it’s a good idea to add this infrastructure (EOF+validate). And then we may be able to add EIP 3074 safely after the EOAs are converted.
Have fun at ethcc. Too bad I can’t be there myself, I was hoping to meet in person.
Not sure how that is an equivalent comparison – you can switch from an EOA that signed an EIP-3074 message to an EOA that hasn’t, then you also remove the bug exposure. This is equally as costly as switching from a Gnosis Safe to an EOA.
You’re correct, there must be nonce check.
Yes, agreed. I think chainid will probably be added to the EIP in the near future, as there have been no reasonable use cases without it presented.
I’m less concerned about the cost than about awareness. Users know what wallet they use, so if a vulnerability is discovered they are likely to switch. And if they switched regardless, before a vulnerability has been discovered, they don’t need to care about the security of a wallet they no longer use.
With invokers it’s different. Users must keep track of any invoker they ever used, even just once. And if one of them is deemed unsafe at some point, move everything to a new EOA. Keeping track of the wallet you use seems much easier than keeping track of all past invokers.
Users won’t do it, so it becomes the responsibility of the wallet maintainer to keep track and warn the user to switch EOA immediately if a previously-used invoker is found to be buggy or malicious. If I were a wallet maintainer, I wouldn’t want to be responsible for tracking the security of any 3rd party invoker I whitelisted, and tracking which user has signed such AUTH. I don’t know how I’d be able to reach all these users because they may be inactive for months and are still immediately vulnerable due to this past AUTH.
It might lead to wallets refusing to whitelist any 3rd party invoker, and only using their own. That would diminish the value of EIP 3074 much more than restricting the invoker to signed transactions, and actually prevent may use cases.
Another concern with needing to switch EOA due to a bug in any of the invokers previously used, is that sometimes you just can’t switch because you hold a non-transferrable asset. For example, suppose I hold tokens I received from various projects with a vesting contract. I can’t change the recipient in the vesting contract so I need to wait a few years for full vesting.
I have the same concern with Gnosis Safe but at least my exposure is limited to one contract, and it’s a proxy so I can upgrade the implementation to fix the bug in-place. If instead I used multiple invokers in the past, and then a bug is discovered in just one of them, I lose all my unvested tokens.