ethereum / EIPs

The Ethereum Improvement Proposal repository
https://eips.ethereum.org/
Creative Commons Zero v1.0 Universal
12.91k stars 5.29k forks source link

Trustless Signing UI Protocol #719

Closed MicahZoltu closed 2 years ago

MicahZoltu commented 7 years ago

A lot of debate has been happening around how we can make signing a more secure process for end users. One of the common arguments is that while all of the proposed solutions may resolve the problem for advanced users (developers, tech savvy users, professionals, etc.) it doesn't address the problem of naive users not understanding what they are signing. They can often be thought of as the legalese presented to people in contracts: it is better than signing a blank sheet of paper, but to the average person its mostly gibberish.

An idea that @Arachnid and I started batting around in an attempt to come up with a long-term solution to this problem is to provide a mechanism by which things can be signed (ideally transactions and arbitrary messages) such that signer can present the user with informed consent without having to trust the UI.

The general premise is that the actor wanting a signature presents the signing tool with the data they want signed as well as a DSL that describes how the data should be presented to the user. The signer would then ask the target contract if the DSL is valid, and only prompt the user to sign if the contract asserts that the DSL is in fact valid.

This is quite similar to #712, though it strives to take things a step further than just function name and parameters.

As far as the DSL itself goes, one option would be a text-only DSL that allows for replacement variables. An example DSL may be something like

I would like to create an order offering ${data[0,64] as number} ${(data[64,64] as contract).name()} tokens in exchange for ${data[128,64] as number} ${(data[192,64] as contract).name()} tokens.

An untrusted dApp would send that DSL (exactly) to the signer along with the transaction they want signed. The signer would then ask the transaction.to contract whether the hash of the DSL is an approved DSL. If it is, then the signer would extract data from the transaction.data and do an eth_call to fetch the name() of the two contracts (tokens in this example) and finally generate the string to present to the user. This solution is very simple and allows for devices with small screens that can only present text (e.g., Ledger) to be able to reasonably present the user with information that the contract author has deemed as enough for informed consent.

Another more feature rich solution (in the extreme) would be to allow the DSL to be some form of constrained layout engine markup (e.g., HTML). The idea here would be that the signer could verify the DSL was approved just like with the text DSL, but would be able to use a basic UI to present the data to the user like the 0x OTC dApp: image

For complex contracts, a full UI is much more understandable to an end user than a paragraph or two of madlibs text, and it gives the dApp developer the ability to create a generally better user experience.

The obvious disadvantage to the full GUI DSL is that it can't reasonably be rendered on a text-only display like a watch or hardware key. With the way this is proposed, a contract could support multiple DSLs so a well written contract may support both text only DSL (for small screens and screen readers) and also a GUI DSL for a better user experience for most users. This would allow dApp developers to provide high quality signing experiences to users on devices that support it with graceful degradation on devices that don't. It also allows signers to implement the presumably easier-to-implement madlibs spec fist, then expand towards the full GUI support implementation later.

Open Questions:

onbjerg commented 7 years ago

One thing I would like of the DSL is that it is not like natspec, in that natspec is parsed using JavaScript's eval, which is pretty unsafe for ÐApps.

The DSL should be a lot more constrained, almost down to only variable substitution, or at most with a specific set of built-in functions and arithmetic (e.g. to render units of ether or similar).

shrugs commented 7 years ago

I'm not sure any amount of the display should be configured by the requester. For example, now the requester needs to know the user's language and construct the DSL dependent on that information. There's also no way to validate that the english text accurately describes the data that's being signed.

I expect a structure-only approach to be a bit more flexible. Something along the lines of how Open Graph tags work.

type: 0xOrder
tokenA: 0xabcde
tokenB: 0xabcde
... etc ... (ala https://0xproject.com/docs/0xjs#Order )

Then the signing interface can represent that however they'd want.

MicahZoltu commented 7 years ago

@onbjerg Good thought on ensuring that whatever DSL we use is easily sandboxed by the implementing signer UI. Having something like freeform HTML+JS is probably too hard to sandbox effectively and therefore not worth the benefits it may provide.

@Shrugs You bring up a very good point about localization. I hadn't considered that, and it does introduce quite a few problems. The naive solution would be to make it so the contract can support a number of different DSL hashes (this is pretty easy) and then they can provide localized versions of the UI. When someone audits the UI, they will need to also audit all of the localized UIs though which means you need a multi-lingual group of auditers. Also, localization is often done after launch and any changes to the UI DSL (or introducing new ones) would require a new contract deployment, which is unfortunate.

I'm curious if anyone has thoughts on how to securely do localization (other than the above)?

@Shrugs The problem with just providing objects is that then the signers need to fully understand every dApp out there. This would be like expecting your browser to know how to render every page on the internet natively, and websites just provide the browser with an object graph of data. The 0x Order you linked is actually a really good example of the problem with this:

    exchangeContractAddress: string,
    expirationUnixTimestampSec: BigNumber,
    feeRecipient: string,
    maker: string,
    makerFee: BigNumber,
    makerTokenAddress: string,
    makerTokenAmount: BigNumber,
    salt: BigNumber,
    taker: string,
    takerFee: BigNumber,
    takerTokenAddress: string,
    takerTokenAmount: BigNumber,

Getting a brick of those options is way too hard for an end-user to use. They need someone to turn it into plain English (or whatever language) for them or layout the items in a way that makes it more clear what it all means.

danfinlay commented 7 years ago

I like the general thrust of this proposal.

Localization is an interesting problem, and yet it actually seems perfect that it would be impossible to change the displayed terms of a contract, so now a UI template proposal is a big deal, requiring careful thought of developers who hope to use it, although they can always add a new signature type they accept.

Rather than choosing between multiple render types, I liked an idea by Gavin Wood a few signing discussions ago where we follow email's lead, and allow a series of signing UIs to be defined, in an order where the signer falls back to the richest view it supports.

I think this could all be described as a new type for signTypedData, where the value links to a static hash (swarm or ipfs) of a config file that includes links to the different provided template types, and then their available languages. A simple text template could be a first type, then maybe markdown or something with limited styles + substitutions, and eventually maybe even richer things still.

MicahZoltu commented 7 years ago

In my head, the contract would provide a public method like, validateDslHash(bytes32) returns (bool). This would allow the signer to ask the contract, "is this a valid DSL?" before presenting it to the user. This would also allow for trustless signing of transactions (contract calls) with an enhanced UI, as long as the signer has access to a blockchain (sorry Ledger and offline MEW). For off-chain signatures the hash can be embedded in the signed data so the contract could verify it when it goes to execute, which would allow Ledger/Offline-MEW to sign these things without direct access to a chain (assuming the DSL doesn't require any lookups to render).

As far as how the signer receives the un-hashed DSL, I tentatively feel like that is a separate discussion. It could be that the JSON-RPC methods take in a DSL as a parameter, leaving it up to the UI that wants something signed to provide the DSL to the signer. Alternatively, it could be done via a specific method on the contract that returns a a number of hashes of the data for lookup in external systems (e.g., IPFS hash, Swarm hash, Storj hash, MaidSafe hash, S3 address, etc.).

At the moment, without compelling counterargument, I would like to leave the "how does the signer get the DSL" for a separate EIP that we can discuss after we have settled on what the DSL looks like and how it is used. I can forsee there being several different EIPs for DSL acquisition and I don't want one to hold up the others.

MicahZoltu commented 7 years ago

Forgot to actually respond to your direct comment. I think the concept of "ordering DSLs" would be up to whatever the mechanism for acquiring DSLs is. If the UI sends the DSL as part of an RPC call, then that RPC call would perhaps take an ordered list of DSLs. If the signer fetches the DSL from a URI that the contract returns, then the contract could return an ordered array of addresses.

Arachnid commented 7 years ago

I like the idea of using the hash of the DSL to allow the contract to verify the user was presented with the correct description; it neatly cuts the gordian knot of where to store the DSL data.

We really need validateDSLHash as a primitive, though, or else this is only usable for message signing, not transaction signing.

VictorTaelin commented 7 years ago

What you describe sounds exactly what Moon-Lang is, a very simple UI description DSL I'll be presenting at devcon. It allows you to specify interfaces as a tree of nested boxes. Each box can render pixels to screen, manage an internal state and talk to Ethereum. You can copy/paste someone else's component on your own DApp safely, making the whole web very forkable. Moon's AST has very few constructors, a compact binary format and you could write a full interpreter in any language in about 150 LOCs.

Feel free to try it:

https://gateway.ipfs.io/ipfs/QmSCvGcufJ38g7ELYoUHdGmjyUekgB85No4xBeDWMRMEis/

Click the fork button above and change something to see how forking works. Then click the moon button to see a simple wallet demo. Also click the links on the code to enter sub-components, which can also be safely forked by copying their hashes.

shrugs commented 7 years ago

@MicahZoltu sorry it took me so long to get back to this, I think I missed the notification.

In the "OpenGraph tags" scenario, my transaction request would first conform to a type like EthereumTransaction, supporting fields like contract, value, gasLimit, etc, which all signing interfaces would be expected to support at the minimum.

My transaction request would also conform to 0xOrder, supporting 0x-specific tags like mentioned above. There should be one format (the generic EthereumTransaction) that everyone knows how to render (basically the title, image, url of the web, and opting into more specific types (article, video, music, etc) is 1) optional for the transaction requester and 2) optional for the signing interface to implement.

Arbitrary tx types can be defined and optionally supported (ERC20Transfer, EthereumFoundationDonation, etc).


I see the need to validate the signing interface itself, though; if you delegate all display to the signing interface, there's absolutely no way for you to know for certain whether or not what you see is what's being signed (unless you look at the code or write it, blah blah). But going down the path of "validating UIs" seems like a real rabbit hole.

That said, the idea of signing both the message and "this is how I presented this transaction" is pretty clever, and definitely solves the main problem.


Perhaps the best move is a little of both worlds? Signing UIs present two displays, one above the other. One is a rich interface of their choosing, which we don't validate the presentation of. This could conform to the opengraph tag model.

Underneath this rich display is the pure-text DSL representing the same information. This DSL is either provided by the transaction requester or looked up in the store of DSLs (with all of the localizations provided). There can be different versions of this DSL beyond the EthereumTransaction type, just like the rich interface.

EthereumTransaction: "this is a transaction to 0xabcde with data `AA BB` and value 0.5ETH with a gasPrice of 4Gwei"
ERC20 Transfer: "this is a transfer of 5 TKN from 0xabcde to 0xfghij"
0xOrder: "This is an order trading 5 TKN for 10 WETH, expiring at {time}"

Just a note, I don't see a point in validating markdown; it'll be presented to the user as html, which defeats the purpose. Actually, now that I think about it, there's no guarantee that the signing ui will present the correct DSL to the user, regardless of what it is, but still sign the correct one. The only way to circumvent this would be to have the user copy/paste the text shown by the signing UI into the transaction requester's interface so that they can double check that that's what was shown. (unless I'm missing something).

MicahZoltu commented 7 years ago

@MaiaVictor Can you provide some detail as to why you decided to author a new DSL/renderer rather than using an existing one like XHTML? I looked at the moon-lang GitHub readme, it seems that you are trying to build a compile-to-javascript language (like Elm), or am I mistaken?


@Shrugs I'm not a fan of signing tools having baked in forms for things like ERC20 transfers or EthereumFoundationDonation because this doesn't scale at all. That would be like Chrome/Edge/Firefox having the built-in ability to render very specific pages on the internet rather than being able to render any page on the internet.

Regarding the trust issue, the user already has explicitly trusted the signing UI since the signing UI has full access to the user's private keys and password. Because of this, we can assume that the UI is going to do its best to present the user with the most accurate information possible, and in this case that means presenting the user with a signing UI that is verifiably associated with the contract being interacted with. The goal here is to make it so the user only needs to trust their signing UI of choice and the contract they are interacting with, they do not also need to fully trust the dapp UI. A great example of this is EtherDelta, where the contracts are immutable and have been vetted by a number of parties. The dapp UI on the other hand is a traditional centralized interface and should not be trusted. At the moment, when the user signs something on request from the dapp UI, the user doesn't have any good way of validating that what they are signing is what they intended. They must trust that the UI isn't asking them to sign something malicious, and this is not a great trust model.

Regarding Open Graph tags, without a mechanism for validation I don't think adding them really helps the situation at all. Either you trust the dapp UI, in which case we don't need anything more than "dapp has requested a signature" or you don't trust the dapp UI, in which case anything it provides without validation could be malicious. The goal of this proposal is specifically scoped to solving the untrusted dapp UI + trusted contract problem. A solution to this would allow someone to put a "donate ABC tokens" button on their blog (untrusted) and a user using an Ethereum enabled browser (Parity, MetaMask, Mist, etc.) would be able to click that button and send ABC token without having to trust the website author. They only need to trust their browser and the ABC token contract.

shrugs commented 7 years ago

I realized where my misunderstanding was: the proposal is to find a way for the contract itself to verify that the user was informed correctly, in the name of consumer protection.

I personally don't think that that's the domain of a contract, but I could see situations where it would be beneficial. At the same time, though, I expect the majority of "uninformed user" issues could be solved by a comprehensive signing UI that "upgrades" raw ethereum txs into a richer representation. The dapp gives the signing ui the rawTx (is this not what's currently happening?)

 {
  nonce: '0x00',
  gasPrice: '0x09184e72a000', 
  gasLimit: '0x2710',
  to: '0x0000000000000000000000000000000000000000', 
  value: '0x00', 
  data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057'
}

and then the UI displays that information, upgrading the experience if it is able to. The dapp doesn't need to do any of the opengraph stuff because the signing ui can check to see if the to contract is a token contract and it can parse the data to determine function calls and arguments. It could even local test the transaction to estimate the state changes that would occur, to really inform the user about what will happen when they submit the transaction.

In this case the opengraph stuff isn't necessary, apologies for that tangent.


If having the contract verify that the user was informed correctly is necessary, the signed raw-text DSL approach (with multiple localizations) is probably a good option?

Although at this point the contract is still trusting the signing ui to actually display this DSL, and if we trust the signing UI we should also just trust the transaction that it signs, defeating this whole purpose. I suppose this approach would limit users to using signing uis that know how to request and sign the dsl, which is basically a quality gateway for "signing ui that doesn't suck".


I guess my opinion boils down to "we need better signing uis" rather than "we need a trust relationship between signing uis and contracts to avoid trusting the dapp middlemen".

MicahZoltu commented 7 years ago

the signing ui can check to see if the to contract is a token contract and it can parse the data to determine function calls and arguments

@Shrugs How would it detect the target contract is a token? How would it get the function name and argument types from the call data? How would it deal with non-token contracts (Ethereum smart contracts are far more than just "a bunch of tokens")?


having the contract verify that the user was informed correctly

This proposal isn't about having the smart contract verify the user was informed correctly, it is about allowing the signing UI to present an informed consent signing UI to the user in a way that does not rely on trusting the dapp UI. Dapp UIs should not be trusted in most cases, dapp trust ends at the contract, so we need a mechanism that allows for the dapp to provide the signing UI to the signing tool without the dapp UI being trusted.


I guess my opinion boils down to "we need better signing uis"

That is the point of this proposal. 😄 I suspect you are trying to solve the problem in the same way I was originally, which is to have the signing tool automatically present the user with transaction details, much like Parity does already. The problem is that while this may be useful to developers, it isn't useful to non-tech savvy end users because function names and parameters are not meaningful to them. By allowing the dapp to specify how the information that needs to be signed is presented to the user, the dapp author can build a signing UI that makes sense for their transactions and is user friendly. However, we need a way for the dapp to get the custom signing UI to the signing tool without having to trust the dapp UI, which is why the contract will have the ability to validate whether any particular UI DSL should be trusted. This way, the dapp UI can give the signing tool the signing UI, and then the signing tool can validate that the provided signing UI can be trusted (by validating its hash against the target contract).

shrugs commented 7 years ago

Ah, I've got a better understanding of what we're trying to do now, thank you. I don't have much more to add, then 😄

danfinlay commented 7 years ago

I’m concerned that trusting a smart contract for its own UI template could be dangerous, since a smart contract could be deployed to resemble some other smart contract, or to mis-represent what it’s really doing.

While this might be a nice way to represent signing operations in the meanwhile, I think the longer-term solution to good tx review will be something more like signers that know how to actually represent the likely effects of processing a transaction.

Do people really want to trust arbitrary smart contracts to provide their own approval UIs? How is that much better than trusting malicious UIs in the first place? It may not be sustainable to make custom approval UIs for each kind of operation, but it seems more trustless to me than showing whatever a contract wants.

Arachnid commented 7 years ago

I’m concerned that trusting a smart contract for its own UI template could be dangerous, since a smart contract could be deployed to resemble some other smart contract, or to mis-represent what it’s really doing.

Is that a viable threat model, though? If you audit the code, you can determine what it does and how it presents it. If you don't, there are far easier ways that already exist for the contract to do nasty stuff - although in either case they're limited unless you're sending ether to it or authorising it to act on other contracts.

Do people really want to trust arbitrary smart contracts to provide their own approval UIs?

Absent a threat model that demonstrates why it's a bad idea, absolutely.

How is that much better than trusting malicious UIs in the first place?

The contract has a vested incentive to represent actions on itself clearly. The UI interacting with it may not, because it may be owned by another party.

MicahZoltu commented 7 years ago

@danfinlay Aside from the comments from @Arachnid, keep in mind that a contract can only show its own signing UI. A contract cannot define what you see when signing on behalf of a different contract. This means that if my dapp needs approval for a token, I'll get the token's approval UI and not whatever the dapp contracts would have wanted to show me. As @Arachnid indicated, the risk is with sending ETH and there is an argument for having ETH values always be part of the meta-UI so a dapp that chooses not to present the ETH being transferred can't transfer ETH on the sly. For everything else, you are interacting with the contract so there isn't anything malicious a dapp could present to you to "trick" you into signing something you otherwise wouldn't.

danfinlay commented 6 years ago

It took me a while, because it's a lot safer to be cautious than confident that there are no dangers, but I think I've come around. I can't see any serious issues with this anymore, I'm on board.

After seeing MoonLang at DevCon, I can see why @MaiaVictor would suggest it here, it seems like a solution to the very "untrusted DSL" question that this opening issue was asking for, and so I'm interested in exploring it a bit more for this solution.

axic commented 6 years ago

Wouldn’t it make sense creating a format as a successor to natspec to incorporate all this? Natspec’s aim was basically the same with the difference it operates on individial functions and cannot describe a process which involves mutliple calls.

MicahZoltu commented 6 years ago

I think that whatever DSL we come up with can (and maybe should) be used both in NatSpec and in this. This simplifies the number of DSLs the contract author needs to learn. I am a little hesitant to assert an integration with NatSpec because that will change the set of stake holders involved in defining the spec, which will increase the chance of disagreement (different goals) as well as increase the time it takes to actually get things moving forward. NatSpec is a compiler+signer feature, whereas this is a contract+signer feature. Combining the two would mean compiler+contract+signer all need to agree on something reasonable.

I'm thinking that maybe what we should do with this spec is first define the mechanisms by which we can store/transmit/validate/present DSLs, but don't actually define a DSL. We can then have a very simple DSL that just lets you have a template string with to/from/value/gas/gasprice replacement variables in it and nothing else as the v1 DSL. We can then have separate discussions for future DSLs and as long as the system supports multiple DSLs then we can iterate on them at whatever speed we want, and iterate on them separately.

MicahZoltu commented 6 years ago

Just had a discussion in Gitter that had a result that I think is valuable to record as an example for posterity, and share with others. The following is a concrete example of the whole process of a token transfer using a fairly simple example DSL that can do constant contract calls and reference transaction variables:

The token's transfer method would look something like:

transfer(uint256 amount, address destination, bytes32 presentationHash) {
    require(acceptablePresentationHashes[presentationHash]);
    balances[msg.sender] -= amount;
    balances[destination] += amount;
}

The UI would send the following data to the signer via a new eth_signTransaction RPC endpoint:

signature: transfer(uint256 amount, address destination, bytes32 presentationHash); to: gas: 0x30D40 gasPrice: 0x value: 0x0 param.amount: 0xE param.destination: 0x1234abcd presentation: You are transferring ${amount / 10 ** to.decimals()} ABC tokens to ${0xENSADDRESS.resolve(destination)}.

The signer would present the user with the following transaction to sign:

You are transferring 15 ABC tokens to MicahZoltu.eth.

The signer would include the following as the presentationHash parameter to the underlying contract, as part of the transaction it signs:

keccak256('You are transferring ${amount / to.decimals()} ABC tokens to ${0xENSADDRESS.resolve(destination)}.')

carver commented 6 years ago

presentation looks like it's missing a 10^decimals:

keccak256('You are transferring ${amount / 10 ** to.decimals()} ABC tokens to ${0xENSADDRESS.resolve(destination)}.')

Am I reading this right that the UI would send the hex address of the destination, and the signer would do a reverse lookup to display the ENS name? Since anyone can set their address to reverse-resolve to any name they choose, this spec should require forward resolution after reverse resolution, to confirm validity.

MicahZoltu commented 6 years ago

Fixed the 10^, thanks! As for the ENS thing, this was meant to just be an example and assumes that .resolve will do all of the magic necessary to validate the name is the one the address owner actually wants displayed. Lots of hand waving around that part, I really just wanted to show how calling some well-known-contract might be done as part of a signing UI. 😄

jstoxrocky commented 6 years ago

@MicahZoltu Do you have any thoughts on what you expect a concrete example to look like for signing arbitrary data? Something like this for a state-channel-like example?

The contract's function would look something like this:

verify(uint8 v, bytes32 r, bytes32 s, uint256 amount, address recipient, bytes32 presentationHash) {
  bytes32 messageHash = keccak256(amount, destination, presentationHash); 
  address signer = ecrecover(messageHash, v, r, s);
  ...
}

The UI would send the following data to the signer via a new eth_signArbitraryMessage RPC endpoint(?):

amount: 0x38d7ea4c68000 recipient: 0x1234abcd presentation: You are signing an IOU allowing ${0xENSADDRESS.resolve(recipient)} to withdraw ${web3.utils.fromWei(amount)} ETH from the MyDapp state-channel.

The signer would present the user with the following transaction to sign:

You are signing an IOU allowing jstoxrocky.eth to withdraw 0.001 ETH from the MyDapp state-channel.

The signer would include the following as the presentationHash parameter to the underlying contract as part:

keccak256(‘You are signing an IOU allowing ${0xENSADDRESS.resolve(recipient)} to withdraw ${web3.utils.fromWei(amount)} ETH from the MyDapp state-channel.’)

MicahZoltu commented 6 years ago

I think for state channel updates I would do something like,

You are updating the state channel to: You have 0.997 ETH; jstoxrocky.eth has 0.003 ETH

Payment channels are tricky because the off-chain nature of them means that they we can't actually do a diff compared to the previous state channel update, so we can't say "You are sending 0.001 ETH to jstoxrocky.eth" because that requires knowing the previous state channel update.

This wouldn't depend on web3, so you would probably do something like ${amount / 10^18} ETH instead of web3.utils.fromWei(amount) ETH.

Focusing on the actual validation, after reading your comment I have realized that this thread actually has two different proposals. Under one proposal, (1) the signer would ask the contract "Is this a valid presentationHash?" and under the other proposal (2) the signer would include the presentationHash in the contract call as a final parameter (like in your example).

In (1), the signer must have access to the blockchain. This means offline MEW and hardware wallets can't utilize this system. In (2), integration is much more complicated as it involves the contract having some magic parameter that a UI can't provide itself (it is always provided by the signer) yet is part of the contract definition. While (2) is a more complete solution, I worry that without adding it to the protocol (as part of the signature validation system) it is going to be more headache than it is worth.

If someone wants to champion getting presentationHash added to Ethereum transactions I welcome that, but without I propose we move forward with the "connected signers only supported".

So, back to your example @jstoxrocky, if we assume we are going with (1) then the contract would look something like:

contract MyContract {
    private mapping(bytes32 => bool) validDslHashes;
    MyContract() {
        bytes32 hash = keccak25(‘You are signing an IOU allowing ${0xENSADDRESS.resolve(recipient)} to withdraw ${amount / 10^18} ETH from the MyDapp state-channel.’);
        validDslHashes[hash] = true;
        // TODO: add other localizations of presentation hash
    }
    validateDslHash(bytes32 hash) returns (bool) {
        return validDslHashes[hash];
    }
}

Note that the payment channel update that the signer signs would be a regular payment channel update, only including whatever is required for the payment channel, not anything about the presentation stuff (that ends at the signer). So in your example, this would just include recipient and amount.

jstoxrocky commented 6 years ago

Thanks for that explanation @MicahZoltu

So If I understand correctly, the dapp provides the DSL + parameters to sign. The signing UI plugs the parameters into the DSL and presents it to the user. If the user signs, the signing UI then checks the DSL against the DSL verification function in the contract. If that returns true, then it will send the transaction. Does this imply that contracts will need to implement a specific interface so that the signer knows where to look up and check the DSL hash? Also I'm a bit confused. Your example from Dec. 3rd contains this line: require(acceptablePresentationHashes[presentationHash]); making it seem like the presentationHash is validated when the transaction is sent. Not before.

Could you elaborate:

If someone wants to champion getting presentationHash added to Ethereum transactions I welcome that, but without I propose we move forward with the "connected signers only supported". Would this be for the purpose of having access to something like:

function () {
  require(validDslHashes[msg.presentationHash]);
  ...
}

Can you elaborate on:

In (2), integration is much more complicated as it involves the contract having some magic parameter that a UI can't provide itself (it is always provided by the signer) yet is part of the contract definition. While (2) is a more complete solution, I worry that without adding it to the protocol (as part of the signature validation system) it is going to be more headache than it is worth.

I think I'm confused. What is the magic parameter? In my head, my example was kind of just a reiteration of #712 but instead of having the signing UI present the raw data structure to the user, the formatted DSL is presented to the user instead - taking the place of the schema-hash when sent to the contract.

If I'm not mistaken, your example (maybe the EIP itself?) is about presenting something understandable to a user when they are signing an Ethereum transaction that interacts with a contract. What I think I am mostly concerned about is asking users to sign arbitrary data offchain in a comprehensible and safe way. Cases in which this signature is then sent to the contract later by a separate user. I like the DSL approach since it is human-readable to the user that signs, but it seems not to fit in with this EIP?

What are your thoughts?

MicahZoltu commented 6 years ago

@jstoxrocky A slight correction to what you described. The signer would check the presentationHash before prompting the user to sign anything. If it is OK then the signer will present it to the user for signing. This way, the user can sign arbitrary off-chain data that is submitted to the chain sometime later via some other means. Basically, the signer will not prompt you to ever sign anything where it can't verify the presentation hash.

As I mentioned in https://github.com/ethereum/EIPs/issues/719#issuecomment-371024303, there are currently two proposals kind of mixed in here. One is to have presentationHash be part of Ethereum transactions. This is a pretty big/major change to Ethereum, but would be great if someone wants to champion it as it would allow offline signers to utilize presentation hashes. The following code is an example of how that would be used by a contract to validate the presentation hash:

contract MyContract {
    function myMethod() {
        require(validDslHashes[msg.presentationHash]);
        // todo the rest of myMethod business logic
    }
}

The other is to just have the signer ask the contract if the presentation hash is valid before signing. This does require that contracts supporting presentation hashes implement some well known interface (which is why this needs to be an EIP so clients can standardize on the interface they look for). However, it is much simpler than trying to do a protocol change like adding presentationHash to the transaction object.

jstoxrocky commented 6 years ago

@MicahZoltu I like this idea a lot as it seems to kill several birds with one stone. I agree that having presentationHash as part of Ethereum transactions would be super useful. Seems like that might the real solution, if someone can get that into a scheduled protocol change.

I know this is kind of a rehash of several older arguments in #712 but I am curious to hear your thoughts on the severity of this attack vector:

Since validation of the presentation is performed by the signing UI and not by the contract itself when the transaction is sent, it seems like a malicious dapp could do something like this:

  1. A malicious dapp creates a benign presentation that validates against it's own contract.
  2. The malicious dapp sends the signing UI the presentation along with some arbitrary data to sign.
  3. The user agrees to sign the data since the presentation seems reasonable and it has been validated against the malicious dapps contract.
  4. The dapp receives this signature + data, but submits it to some other contract instead, extracting some value from the user or something.

Seems like this could be solved by having the validation check for the presentation occur in the function being called (a dapp wishing to signal its trustworthiness would create a unique presentation for itself forcing all other malicious dapps to also show this presentation to users).

Just curious as to how serious you think this is, is it scoped within this EIP, can this EIP solve it as is?

MicahZoltu commented 6 years ago

Contract authors will need to make sure the acceptable presentation strings include enough details such that the user can provide informed consent. For on-chain transactions, like a token transfer, this is pretty easy to do and should come naturally. For something like a state channel, you will want to make sure that the presentation string makes it clear to the user what exactly they are signing. Contract authors should assume that the presentation string is the only information that the user has for deciding whether to sign or not and they should assume that prior to viewing the presentation string the user was duped or tricked in some way.

A couple of examples: Bad: Transfer 5 tokens from 0xABCD to 0x1234 Good: Transfer 5 ABC tokens from 0xABCD to 0x1234 Bad: Update state channel to 5 ABC tokens for 0xABCD and 3 ABC tokens for 0x1234 Good: Update <product name> state channel to 5 ABC tokens for 0xABCD and 3 ABC tokens for 0x1234

jstoxrocky commented 6 years ago

@MicahZoltu Sorry if I sound like a broken record but I'm just trying to get a full grasp of your idea. Even in cases where the presentation is as explicit as possible, it seems like there is a chance of fraud unless part of the presentation data is verified within the contract.

Assume Alice opens two separate payment channels with Bob. One settled in ETH and one settled in ABC tokens. Alice wants to update the ABC token state-channel but not the ETH state-channel. Alice is shown a presentation of the form Update MyDappABCToken state channel to 5 ABC tokens for alice.eth and 3 ABC tokens for bob.eth. The signing UI gives the OK that the DSL hash is fine and Alice signs and sends the signature to Bob. Alice has really only signed the following values: senderAddress, newBalanceSender, recipientAddress, newBalanceRecipient (maybe hashed and signed with a schema signature like #712). Bob could use this same signature in both payment channels.

Is the solution to include some contextual strings like MyDappABCToken and/or ABC in the signature data?

MicahZoltu commented 6 years ago

@jstoxrocky I don't mind back and forth with you to help make things clear. 😄 If you prefer a faster round trip time, feel free to ping me in Gitter.

The problem you described is a problem with the state channel implementation, not with the presentation system. Any state channel authored the way you described and susceptible to the attack you described is a state channel with a critical security vulnerability. Such a state channel implementation should never make it into the wild and if it does it should be fixed immediately.

The presentation stuff is meant to solve a very specific problem, which is informed consent from users. It is not intended to solve all possible malicious dapp attack vectors, which would include the one you have described.

jstoxrocky commented 6 years ago

Thanks, @MicahZoltu, I appreciate you taking the time to discuss this. That makes sense that this EIP is about informed consent and not the specific attack I mentioned.

Do you believe that there should be a constraint imposed on presentations DSLs so that they include all signed data within them somehow? For example, in the simplest case of a text-only DSL, a constraint could be imposed so that every piece of data to be signed must be inserted into the text.

The signing UI would allow (1) but reject (2).

  1. dataToSign = [
    {'type': 'uint', 'name': 'x', 'value': 1}, 
    {'type': 'uint', 'name': 'y', 'value': 2}
    ]
    dsl = 'You are signing uint x = ${x}, and uint y = ${y}'`
  2. dataToSign = [
    {'type': 'uint', 'name': 'x', 'value': 1}, 
    {'type': 'uint', 'name': 'y', 'value': 2},
    ]
    dsl = 'You are signing uint x = ${x}'`

It seems like the user is not properly informed in case 2. This might be a difficult rabbit hole to go down. I guess alternatively we could just have the signing UI present both the value-substituted DSL along with the raw data structure to the user so that they can also see the details of what they are signing.

MicahZoltu commented 6 years ago

If a contract author wants to create a DSL that is malicious, there are a million ways they can do so. While we could perhaps plug one or two obvious holes, it won't stop a malicious contract author. Thus the question becomes, what are you trying to protect against? I'm generally hesitant to add complexity to a system (creating requirements on what DSLs are allowed would add complexity) without a strong enough need and in this case it is unclear to me what the strong need for this feature (DSL constraints) is.

Perhaps you have something in mind as to how we would benefit from constraining what the DSL can contain?

dpyro commented 6 years ago

The goal of this proposal is specifically scoped to solving the untrusted dapp UI + trusted contract problem.

If I understand correctly, this EIP is so the signer can use the contract to verify the UI presented to the user is whitelisted. So far I only see these methods for a contract verify this using a rich UI:

I don't think verifying the authenticity and integrity of a rich UI can be done in any single proposal. A tractable base for transactions is a standardized method for signers to query a smart contract for a human readable string of the main call. For example, a contract that contains:

transfer(uint256 amount, address destination)

may also have (if the plaintext of the method is available)

transferAsString(uint256 amount, address destination, string lang) public view returns (string)

or (if the plaintext of the method is not available)

asString(uint256 funcSelector, bytes parameters) public view returns (string)

(I'm almost certain there's a better way to do this!)

Which can return

You are transferring 15 ABC tokens to MicahZoltu.eth.

This also permits someone who is examining verified contract source code to easily verify the generated string is not malicious.

Arachnid commented 6 years ago

Verify the UI against hardcoded hashes deployed with the smart contract. The obvious drawback is that new UI revisions cannot be used until a new smart contract is deployed. It's probably not useable for a rich UI which needs handle additional issues such as localization and an ever-growing list of legalese disclaimers and phishing warnings that current dapps need to maintain.

Verify the UI against an adminstrator-only updated list maintained by the smart contract. This allows the UI to be revised and more UIs to be added without deploying a new smart contract. However, the administrator of the smart contract needs to also be both a trusted and continuously active party. This also puts the administrator in a targeted position since they are now the weakest link in the trust chain.

Delegate UI verification to another smart contract (such as a DAO). I honestly don't know the implications of this one.

All three of these can be accomplished with the same interface - returning a content hash. In the latter case, the administrator has a way to update the hash, which is out of scope for this standard.

For example, a contract that contains:

transfer(uint256 amount, address destination)

may also have (if the plaintext of the method is available)

transferAsString(uint256 amount, address destination, string lang) public view returns (string)

This seems like it wouldn't be an improvement to the concerns you outlined above?

My main objection to doing it this way (although it's simpler and doesn't require adding a new language to the mix) is that it bloats contract size onchain, and string manipulation in the EVM is particularly difficult and resource intensive.

ecp4224 commented 6 years ago

Could something like this work?

The URI could use any secured protocol (https, ipfs, etc..) that could add another layer of security over the presentation, and worse case it would fallback to the minimalPresentation

danfinlay commented 6 years ago

We could also simply point to a hash of a file that includes multiple presentation types, the way email mime types have a series of increasingly rich representations of the message.

MicahZoltu commented 6 years ago

This protocol is really made up of 3 parts:

  1. How does the UI provide the signer with the signing UI.
  2. What does the signing UI DSL look like.
  3. How does the signing UI verify that the DSL is legitimate.

For (1), if we have a duplex channel between the UI and the signer then the UI can ask the signer what variations of the DSL are supported (e.g. text only or something more complex) and then only send that. If we assume that we only have a simplex channel between the UI and the signer then something like @danfinlay suggested is probably a good idea, for the same reason it is good for email.

For (3), the user just calls the contract and asks it, "Is this a valid DSL hash". The contract can return true for any number of DSL hashes, so it can support both text-only and complex DSLs, or DSLs in different languages, etc.

MicahZoltu commented 6 years ago

I have edited the original description to more accurately capture what this EIP is targeting after much of this discussion. I just realized while reading it today that it doesn't accurately represent the current iteration.

MicahZoltu commented 5 years ago

One problem with this proposal is that the signer may not have access to the Ethereum network. For example, an offline signer or a hardware wallet signer.

Consider: What if there exists a standardized way to pass the hash of the DSL into the function being called so that the signer can be confident that "someone downstream from me will validate the hash". As an example, one can imagine saying that "an EIP719 compatible function signature will have the hash of the used DSL as the last parameter". Then when a transaction is submitted to a signer along with a DSL, the signer will append the DSL as the last parameter in the data being signed and then sign that. When the transaction is then executed on-chain, the contract would look at the signature (last parameter) and validate it against known-good DSLs. If it fails this check, the transaction will fail.

In order to reduce the work of the signer (useful for limited compute constrained devices like Ledgers), a well known placeholder could be provided as the function parameter so that the signer merely needs to hash the DSL and replace those well known bytes with the DSL bytes. For example, maybe the well known bytes are:

0x7217217217217217217217217217217217217217217217217217217217217217
sadfool1 commented 5 years ago

Project Hydro is a layer 2 of the Ethereum platform and has this exact protocol called 'ICE' which leverages on using digital identity aggregator #1495 or ERC 1484. Instead of sending across wallet services, you're sending to an Ethereum Identity Number (unique to you and other users). ICE whitepaper (draft): https://github.com/hydrogen-dev/hydro-docs/tree/master/Ice

However, i don't it can work when you have no access to the ethereum network as it requires proof from nodes.

github-actions[bot] commented 2 years ago

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

github-actions[bot] commented 2 years ago

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.

maceip commented 1 year ago

Opening this as I'm seeing webauthn-eip-4337 wallets become more commonplace and they currently don't show what is actually being signed.

The trend is to use the challenge field, loaded with the userop struct to sign. They initiate a signature by calling webauthn.credentials.get(). This opens a browser mediated modal, and doesn't show what you're actually signing.

Even for good ol simpleaccount.sol wallets: it's not clear how web developers should be securely showing a sign interface.

MicahZoltu commented 1 year ago

You need trusted access to the Ethereum network's EVM in order to trustlessly present signing data in a human readable way using this protocol. In the case of WebAuthN, the thing providing the signing UI would need to be trusted, and usually for hardware device signing (which I believe is what WabAuthN is commonly used for) there isn't a trusted EVM they can access.

Presenting trustless signing UIs on hardware devices is quite hard, though it may be solvable if the hardware device is powerful enough to do EVM execution on its own. You could provide witness data to the hardware device along with the rendering string and then the hardware device can validate that. Then the hardware device only needs a trusted blockhash, which in theory is easier to get.