-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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
Standard Functions for Preauthorized Actions #662
Comments
There are a lot more contracts than just ERC20 that will want to make use of this approach (multisig is an obvious example). Perhaps this should be made more general? |
Updated the EIP. I agree, it could be generalized beyond ERC20. Any stateful function could be outsourced with this standard. Also updated naming convention. I imagine a 2nd layer application could look up whether a stateful function is extended by searching for |
I'm not sure about the term "provable". I think "offline signed" or "off chain signed" transactions would be a better description? In the particular token transfer case (which I think it's a great way to start the debate), I'd argue for a refactor, because now we have three functions that transfer tokens: Also I'd suggest adding a Finally, what prevents proofs to be submitted multiple times? Maybe the contract should keep a record of submitted transactions to make sure they are not submitted over and over.
|
Cool stuff @alex-miller-0! It's very close to what we've been working on at uPort - basically a service that can pay gas on behalf of the user and execute any Ethereum transaction through a user-controlled Proxy contract. Main idea is to separate the paying of gas from access control: |
Agreed. I like your implementation.
This limits the spec to token transfers though, since you wouldn't be able to pay ether for outsourced transaction calls.
Yeah, forgot to include replay protection. What about something like the following:
It would limit the spec to only functions with <4 parameters - not sure how useful that is. |
Aragon has a function that does what this spec is proposing. Note the arbitrarily sized |
Aragon does it in an interesting way. The problem with making arbitrary data calls is that the msg.sender will always be the transaction sender, not the message signer.
That's true. But in any way you build it you'd need to have custom functions for it. We could have a generic token function that would send X tokens to msg.sender but then it would only make sense in this context. Other option is for the contract itself to calculate the fee on other factors (adjust fees to target a delay no longer than X minutes) I think this shows that many projects are implementing basically the same ideas in different ways, and while it's encouraging to see different approaches, it means that some standardization would be very useful here. So the suggested pattern would be:
|
You need to also include some kind of nonce-mechanism to prevent using the same signed transfer multiple times. I am not sure what the mechanism should be, but I guess this is where it becomes tricky |
AFAIK the Also something that could be interesting to think about is adding a |
Good point. Removed I'm still not sold on the fee parameter. That feels like a separate EIP to me. All I wanted to do here was write a standard for outsourcing transactions. I think the token transfer fee can be its own EIP frankly. What are everyone's thoughts? |
Yes, think so too. Mainly because as you put it before:
Which is way more generalist than what having a I feel |
@alex-miller-0 @alexvandesande last summer when I was putting together something like this for multisig we did replay protection just like Aragon does it, except instead of combining the data itself with a nonce we just used a hash. So the part actually signed is:
and that's what gets marked as spent on a per-address basis. Then the authed_hash is simply marked as having been authorised by the ecrecovered address, and after that point anyone is allowed to call a generic To me that approach feels very simple and general. We could formalise it in an EIP and then just have an index of specific functions for that EIP that ERC20 tokens must support. If you want things like paying a fee to the publisher of the transaction (or any other complicated feature) it is simple: add an action that does that to your merkle tree. @christianlundkvist I'm not surprised that uport is also doing something like this. Haven't had a chance to read through your approach but would the above make sense in your context as well? |
I would argue that fee is not specific to token transfers but could be a general purpose field for any incentivization. In token transfers it could be a token quantity, in other contracts it could sign how much the contract will pay back in ether (from the contract account, which would use its own accounting) or even a general purpose signal of priority (0-10)
Every contract would interpret it differently, but it makes sense to add it in a general signed execution like that
… On 4 Jul 2017, at 12:24, Jeff Coleman ***@***.***> wrote:
@alex-miller-0 @alexvandesande last summer when I was putting together something like this for multisig we did replay protection just like Aragon does it, except instead of combining the data itself with a nonce we just used a hash. So the part actually signed is:
hash(authed_hash + nonce)
and that's what gets marked as spent on a per-address basis. Then the authed_hash is simply marked as having been authorised by the ecrecovered address, and after that point anyone is allowed to call a generic doAction(function,args[],authorisingAccount) command which just hashes the first two fields and checks if the hash has been authorised by the third, performing it with that authority if so. Performed actions are of course marked as no longer authorised. This has the advantage that you can have a second function expandActions(hashes[],authorisingAccount) which just hashes a whole list of hashes and checks if the root hash is authorised, so you can merkle together an entire series of actions and then just sign it once to authorise all of them.
To me that approach feels very simple and general. We could formalise it in an EIP and then just have an index of specific functions for that EIP that ERC20 tokens must support. If you want things like paying a fee to the publisher of the transaction (or any other complicated feature) it is simple: add an action that does that to your merkle tree.
@christianlundkvist I'm not surprised that uport is also doing something like this. Haven't had a chance to read through your approach but would the above make sense in your context as well?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
Oops, left out a parameter. It's actually supposed to be:
because you want the actions to only be valid for one particular destination contract (in this example, one particular token contract). |
@emansipater I really like the idea of having several transactions hashed/merkled together and doing one sig over all of them, very elegant! 😃 The method that uPort uses involves Proxy contracts, as in #121. Here is a brief high-level overview of how uPort does this: The user has a Proxy contract through which all transactions are routed. Another contract (Controller, or IdentityManager) is authorized to tell the Proxy to forward an arbitrary transaction (defined by a tuple The IdentityManager has a list of addresses that are authorized to forward transactions. There is a function something like
(see also here for similar thoughts: #191) where nonce is a nonce for replay protection. If the signature is by one of the authorized addresses, then the "metatransaction" There are some subtleties here also in that if the function throws, then the @emansipater In general it seems there are two ways of handling this kind of delegation: either use a proxy contract and have the target smart contract do access control based on uPort went with the former (proxy contracts) because it requires less buy-in from smart contract developers - they can just keep using msg.sender as they are used to, and any complex logic can be taken care of by the Proxy/IdentityManager combo. We also have other logic here also like key recovery by using a recovery network etc. Having logic on the smart contract side works as well and I have done a little bit of thinking around that too, but it might take a while to reach a consensus on best practices around this. |
@emansipater I really like the approach you mention because it can really help scalability and makes transactions cheaper. I would support any approach that makes it more generic like that |
@alexvandesande yes, this was developed in the context of state channels where each bit of gas has a substantial effect on the final throughput multiplier so that was our aim. @christianlundkvist I see the optimal compromise as having this standard and then making the forwarding contracts compliant with it. That way either approach is possible, depending on whether the destination contract supports it directly or not. |
@emansipater Are you suggesting something like this?
|
@alex-miller-0 No, more like
if you'll forgive the hastily scribbled frankencode |
Yep this sounds like the best way :) |
What's the purpose of
Why not? That would make a lot more sense.
It's not necessary to include this, since you can calculate it from the inputs.
You could easily make v a third element of the array without increase in ABI encoding length - or make them all individual parameters. The choice to put two in an array and the third separately seems odd.
This requires storage proportional to the number of past executions, which isn't ideal. What about a simple nonce scheme, instead? |
@Arachnid Great comments - thanks.
Agreed on both. I will change these on the next update.
Can you show an example? I don't understand how this would work if arrays have to be typed. Unless you just mean a EDIT: I actually think you meant something like this:
Is that correct?
I agree with the statement, but can't visualize what you are suggesting - will you provide an example? |
Updated the proposal with some of @Arachnid 's suggestions. |
Yes, that's what I had in mind - though I think that three individual args would work just as well.
It looks like you already updated the contract to use nonces. As I mentioned earlier, though - you can remove the first element of the data, and simply calculate it. If it's incorrect it will return an invalid sender address, and so will be rejected. |
I tried to work this into the example function, but I can't figure out what you mean. Can you provide an example? |
There's actually a really really good reason not to use a nonce scheme, which is that it allows for revoking of previously signed but not yet published messages. In state channels this is a security exploit, not a feature. I'll try to think if there's a simple approach that still preserves nonrevocability but doesn't waste so much space on replay prevention--it seems solvable. A different, though somewhat related issue is the need for a) atomicity control and b) sequence/dependency control. In my opinion both of these are well worth including in this EIP. The first can be solved by just allowing arrays of actions in the doPreauthedAction() and then reverting if any one action fails (whole array is hashed to check whether authorised). Slightly complicates the parameter array portion but not too bad I think. It might not be obvious at first glance, but this also solves the second requirement, provided that we provide universal support for an action type which can make assertions about the state of PreauthedActions. That seems sensical anyways, so these two don't bloat the proposal much at all. |
Highly agree with both (though I would clarify that this should be for whitelisted local calls only, obviously not arbitrary local calls). Also @alex-miller-0 can we update the EIP name to "Standard Functions for Preauthorized Actions" or something similar? The current name really is not appropriate--that term provable made sense for your other EIP but not for this one. I might also add that once this proposal is complete that other EIP proposal can be radically simplified by removing the signature checking etc. that this one will already do and just specifying a standard "destroyTokens" function which preauthorisations under this EIP can call. |
He means that since you are passing the parameters anyways, you don't need to also pass the hash of them. You can just hash them to recover the hash. If the parameters are altered in any way and you get a different hash than the one which was actually signed, ecrecover will just return a different random sender. Since that sender will not have authorised anything, the operation will be rejected. So this is perfectly safe to do--it doesn't introduce any security issues. |
Yes. I will rename.
Thanks - that helps. I will update my tests and update the EIP in a bit. |
Proposal has been updated to include only |
@alex-miller-0 great! Now all we need to do is generalise this so that instead of a bunch of separate Regarding the incrementing vs non-replayable nonce approaches, Vitalik and I played around with this today but couldn't really come up with anything better than just having a bytes32 nonce and treating nonces below 10^10 as incrementing nonces while still storing a non-replay boolean for other possible values. People can then choose what they need according to their application. In theory most state channel actions won't go to chain, so the one-word-per-authorisation-event penalty probably won't be too bad, and applications that don't need nonrevocability can just use a regular nonce to save on storage costs. |
A thought: Why not make this caller-side? If the caller has a wallet contract that supports third parties submitting signed messages on the owner's behalf, then that wallet can be used to call any contract using this functionality, without the need for explicit support by the called contracts. Depending on the use-case, you can write a specific holding contract for just this purpose, such as the way my token drop idea works. |
@Arachnid That's a perfectly viable approach for many use cases (see the uport discussion above), but since both types of use case need this spec I think this EIP should just be kept as generic as possible, and not specify whether it is caller-side or contract-side. Either one will have function signatures being activated on behalf of some external EOA. For what it's worth, there are certain situations where the caller side approach fails or is at least unnecessarily complicated. One of the big ones is in the area of newly generated cold storage accounts, who then have to predict a contract address that hasn't been created yet (with the EOA itself being treated as the authentication reference they can securely calculate the EOA address before it exists). |
I like the incrementing-nonces below 10^10 idea. @emansipater i really like how you can expand preauths in your implementation, however i think there is value in being able to approve and execute in just one call. You could do it from another contract too but then you'd be adding the overhead of 2 extra 'CALL's . |
There are a lot of situations (especially in state channels) where one wants to approve-but-not-execute. It wouldn't be too terrible I suppose to add a |
I'm not sure this can be kept entirely generic; there needs to be some kind of replay protection, which is probably going to be contract-type specific if it's implemented at the contract end instead of the account end.
Can you provide an example?
I'm not sure I follow. Contract addresses can be calculated easily from creator and nonce, but I'm not sure how that's relevant; you can create a cold storage account that's able to sign transactions, and then deploy the wallet from an entirely different account.
Can you give an example? If the caller is offchain, can't they just do a local |
My existing suggestion works fine for that: the authorisation includes 1) the hash of the action (or action tree), 2) the address of the contract intended to process the authorisation and 3) a nonce. The combined hash of these three is what receives the replay protection. If it's contract-side the address in 2) is something like a token contract. If it's caller side then the address is now the address of the caller's wallet contract, so you end up signing a different hash. Different types of contracts implement different actions (the caller side one probably has a generic call capability like uport, while the token contracts have only token-specific actions enabled like transfer and burn). This means that the same spec with the same replay protection works simultaneously for both use cases even if used interchangeably (it also doesn't add any space since contracts know their own addresses). Or do you mean something else?
The immediately following sentence is the example.
Contract addresses can be calculated easily, but not securely. If the cold storage generated EOA acts as the recipient for an ERC-20 transfer, it is the only key that must be trusted to secure those funds. If there is also an additional account which must be used to deploy the wallet then that account has now become totally trusted by the cold storage key (because they have the ability to deploy a totally different contract at that same "recipient" address which does not enforce the requirement of getting a signature from cold storage before acting). Think of a hardware wallet on a cold storage machine trying to provide guarantees to a user--they can't because without actually accessing the chain they don't know whether or not there is actually a correctly functioning proxy at the destination to which authorisations are being provided. Things get even more complicated if you try to set up a cold storage multisig account (where you might not even know in advance which of the multisig keys will have to actually create the wallet contract). There are various workarounds of course, but they are all stateful, which means you have to coordinate the cold action with an on-chain action, which is just silly. The root problem is that you can't securely allow a 3rd party service to deploy a contract for you, including a wallet contract.
Really simple situation: activating only one tiny part of a large and complex state channel state. Suppose you have a large and complex channel open with another party that has lots of different subchannels in it. Suppose you lose your connection to them, and as a result can't get them to countersign a withdrawal you need to make. However, you do have a signed root from them agreeing to the huge list of subchannel updates that compose the latest state prior to the lost connection. If you can't reestablish a connection, you have to publish at least one signature of theirs anyways, so the most efficient thing is to just publish the root. However, you only need to expand and execute the subchannel holding the funds you actually need. If the others aren't time sensitive, it costs you nothing to keep trying to reestablish a connection but not execute all the other approved actions (because that would drag the whole rest of the subchannels out onto the chain and cost a ton in fees). After a short delay, they finally show up again (local internet outage as it turns out) and immediately start countersigning all of your requests. You'll be very glad at this point that you didn't pay all the fees to dump your entire subchannel list to chain (not to mention the major privacy advantage of having waited)! Obviously, in the above situation doing a local call doesn't actually start the timeout for the subchannel, so that wouldn't help. There are lots of non-state channel situations too, like granting a tree of "preauthorised debits" that may or may not be called upon by the authorised party, etc. |
Why does this need to be an EIP? Is this just a best-practice recommendation? If so, EIPs are not the right place for it. Is there great benefit derived from standardizing this? On the surface, it seems the answer is no since the |
@MicahZoltu the existing spec is still evolving in the discussion here, but the generic approach I've suggested would eliminate the provable_* scheme in favour of a standardised scheme based on function signatures (which would definitely warrant an EIP so that everyone can use a compatible standard and make life much easier for implementers). |
The user-mode provable methods being discussed here have drawbacks we've analyzed in depth in RSK. It's highly preferable to hard-fork the system to allow that any method call accepts off-line signatures. One example that solves the problem in a more generic way but still lacks signature segregation is creating an EXECUTE opcode. (we have an RSKIP for this). This opcode receives the address of a transaction tx in memory, a size, a gaslimit, and it executes the transaction. The tx gas limit and fee rate are ignored. The EXECUTE gas limit is used instead. All the methods described here (including this EIP) have one common drawback: having the off-line signed transaction T for an account A gives no guarantee to the holder of T that he will be able to execute T in the future, as the owner of A can always double-spend the nonce of T with any other transaction and render T invalid. |
A hard-fork to allow any function call to be made with an offline signature would be quite nice, especially if utilising both the merklable hash/expand format and the combined incrementing/onetime nonce scheme described in my comments above (to allow both revocable and non-revocable transactions). @SergioDemianLerner doesn't seem like you read through all the comments, but the idea is to support a 32 bit nonce, with values below 10^10 treated as normal nonces that must be one higher than the previous nonce, while values above that are simply ignored and the hash of the entire transaction (nonce included) is marked as "played" to guarantee non-revocability. The transaction authoriser can then choose between revocable and non-revocable transaction types. Expirations are nice touch to permit space reclamation--will have to think about how to efficiently implement that into the suggested scheme. btw @alex-miller-0 what do you think about the increasingly detailed suggestions I'm making? Do they still fit with the original intent of your EIP? Personally I think that a general solution is the best way to solve your problem, but I would understand if you wanted me to make a separate EIP instead. |
@emansipater I appreciate your comments - I'm reading all of them. I think your approach is likely more valuable long-term, but the complexity seems higher than what I intended for this EIP, which was to create a very simple function derivative to outsource logic. I want to implement my proposed scheme for a single function in a contract I'm deploying soon and complexity in Ethereum scares me. Anyway I get your rationale, but I'd prefer you make a separate EIP. I'll probably archive this one soon. |
@alex-miller-0 I actually see implementing each function separately as the overall more complex scheme, but I suppose a separate EIP is probably appropriate. |
Sorry for the late arrival, but I'm interested in the use-case of decentrilized exchange contracts. Right now, a contract can own tokens/control them, which can enable the "ask" side of an orderbook if the other token of the pairing is ETH. (i.e user sends eth to contract, "spits out tokens" by calling the token contract and transferring some of its tokens to the user). However, no "bids" are possible, and neither are any orderbooks where the user payment is not in ETH. Because a user sending tokens to the exchange contract does not touch the exchange contract at all. It only involves an update to a mapping elsewhere. The idea to have the user first call the exchange contract which should initiate the token swap is impossible without this EIP. The It's hard to process everything in this thread, but isn't multiple sigs hashed together overkill here? @alexvandesande How about "embeddedSigner". Its how I'm thinking of it because all sigs are made offline, but the difference here is another signature besides the overall Eth transaction signature. |
Has there been a consensus in implementation? I'd like to move forward with a token standard that has a function for sending transactions without the end user needing ether to pay gas |
@alexvandesande I ended up not implementing this, but I still think the original design is a reasonable specification for simplicity. If you want to link an implementation to this issue or open up a ERC let us know! |
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. |
Title
Specification
EIP661 is abstracted to include any stateful function.
A function is defined name
provable_X
, whereX
can be any state-updating function.Note: The underscore is used to retain casing of the original function name.
This function may be executed by any actor with the correct parameters on the owner's behalf. It returns
true
if the state was successfully updated andfalse
otherwise. The function may also be called locally to ensure a valid signature was passed or called.Using provable functions
A hashed message is formed according to the following:
Where
sha3
is the keccak-256 sha3 hash,...params
are the tightly packed parameters of the original function,word
is the ABI definition of the provable function, andcontract_address
is the address of the contract where both the original and provable functions reside.This message is signed by a user's private key and that signature can be passed by any Ethereum actor to the provable function as the first parameter.
Example
An example for ERC20's
transfer
function is as follows:The first parameter is an array where the following is true:
Replay protection is added by checking the proof against an archived mapping and using nonces, which increment automatically for each signer:
Rationale
These provable functions may be useful for applications that wish to call functions on the user's behalf without the user having to make a transaction. This would pass the gas cost on to the application.
The proposed methodology might be especially useful for 3rd-party token transfers, as this requires two transactions (the user must first
approve
some contract to move tokens and then that contract must be called to move the tokens).However, this can be further extended to many non-token use cases. The proposed
provable_X
may be included with any function that the application wishes to be outsourced.The text was updated successfully, but these errors were encountered: