443 lines
18 KiB
Markdown
443 lines
18 KiB
Markdown
|
---
|
||
|
eip: 3224
|
||
|
title: Described Data
|
||
|
description: Contract method to compute human-readable descriptions for signable data.
|
||
|
author: Richard Moore (@ricmoo), Nick Johnson (@arachnid)
|
||
|
discussions-to: https://github.com/ethereum/EIPs/issues/3225
|
||
|
status: Stagnant
|
||
|
type: Standards Track
|
||
|
category: ERC
|
||
|
created: 2021-01-23
|
||
|
requires: 191
|
||
|
---
|
||
|
|
||
|
|
||
|
## Abstract
|
||
|
|
||
|
Human-readable descriptions for machine executable operations,
|
||
|
described in higher level machine readable data, so that wallets
|
||
|
can provide meaningful feedback to the user describing the
|
||
|
action the user is about to perform.
|
||
|
|
||
|
|
||
|
## Motivation
|
||
|
|
||
|
When using an Ethereum Wallet (e.g. MetaMask, Clef, Hardware
|
||
|
Wallets) users must accept and authorize signing messages or
|
||
|
sending transactions.
|
||
|
|
||
|
Due to the complexity of Ethereum transactions, wallets are very
|
||
|
limitd in their ability to provide insight into the contents of
|
||
|
transactions user are approving; outside special-cased support
|
||
|
for common transactions such as ERC20 transfers, this often amounts
|
||
|
to asking the user to sign an opaque blob of binary data.
|
||
|
|
||
|
This EIP presents a method for dapp developers to enable a more
|
||
|
comfortable user experience by providing wallets with a means
|
||
|
to generate a better description about what the contract claims
|
||
|
will happen.
|
||
|
|
||
|
It does not address malicious contracts which wish to lie, it
|
||
|
only addresses honest contracts that want to make their user's
|
||
|
life better. We believe that this is a reasonable security model,
|
||
|
as transaction descriptions can be audited at the same time as
|
||
|
contract code, allowing auditors and code reviewers to check that
|
||
|
transaction descriptions are accurate as part of their review.
|
||
|
|
||
|
|
||
|
## Specification
|
||
|
|
||
|
The **description string** and **described data** are generated
|
||
|
simultaneously by evaluating the contract
|
||
|
(i.e. the **describer**), passing the **describer inputs** to the
|
||
|
method:
|
||
|
|
||
|
```solidity
|
||
|
function eipXXXDescribe(bytes describer_inputs) view returns (string description_string, bytes described_data);
|
||
|
```
|
||
|
|
||
|
The method must be executable in a static context, (i.e. any
|
||
|
side effects, such as logX, sstore, etc.), including through
|
||
|
indirect calls may be ignored.
|
||
|
|
||
|
During evaluation, the `ADDRESS` (i.e. `to`), `CALLER`
|
||
|
(i.e. `from`), `VALUE`, and `GASPRICE` must be the same as the
|
||
|
values for the transaction being described, so that the
|
||
|
code generating the description can rely on them. For signing
|
||
|
**described messages**, `VALUE` should always be 0.
|
||
|
|
||
|
When executing the bytecode, best efforts should be made to
|
||
|
ensure `BLOCKHASH`, `NUMBER`, `TIMESTAMP` and `DIFFICULTY`
|
||
|
match the `"latest"` block. The `COINBASE` should be the zero
|
||
|
address.
|
||
|
|
||
|
The method may revert, in which case the signing must be aborted.
|
||
|
|
||
|
|
||
|
### New JSON-RPC Methods
|
||
|
|
||
|
Clients which manage private keys should expose additional
|
||
|
methods for interacting with the related accounts.
|
||
|
|
||
|
If an user interface is not present or expected for any other
|
||
|
account-based operations, the description strings should be
|
||
|
ignored and the described data used directly.
|
||
|
|
||
|
These JSON-RPC methods will also be implemented in standard
|
||
|
Ethereum libraries, so the JSON-RPC description is meant more
|
||
|
of a canonical way to describe them.
|
||
|
|
||
|
|
||
|
### Signing Described Messages
|
||
|
|
||
|
```solidity
|
||
|
eth_signDescribedMessage(address, describer, describerInput)
|
||
|
// Result: {
|
||
|
// description: "text/plain;Hello World",
|
||
|
// data: "0x...", // described data
|
||
|
// signature: "0x..."
|
||
|
// }
|
||
|
```
|
||
|
|
||
|
Compute the **description string** and **described data** by
|
||
|
evaluating the call to **describer**, with the
|
||
|
**describerInput** passed to the ABI encoded call to
|
||
|
`eipXXXDescription(bytes)`. The `VALUE` during execution must
|
||
|
be 0.
|
||
|
|
||
|
If the wallet contains a user interface for accepting or
|
||
|
denying signing a message, it should present the description
|
||
|
string to the user. Optionally, a wallet may wish to
|
||
|
additionally provide a way to examine the described data.
|
||
|
|
||
|
If accepted, the computed **described data** is signed
|
||
|
according to [EIP-191](./eip-191.md), with the *version
|
||
|
byte* of `0x00` and the *version specific data* of describer
|
||
|
address.
|
||
|
|
||
|
That is:
|
||
|
|
||
|
```
|
||
|
0x19 0x00 DESCRIBER_ADDRESS 0xDESCRIBED_DATA
|
||
|
```
|
||
|
|
||
|
The returned result includes the **described data**, allowing
|
||
|
dapps that use parameters computed in the contract to be
|
||
|
available.
|
||
|
|
||
|
### Sending Described Transactions
|
||
|
|
||
|
```solidity
|
||
|
eth_sendDescribedTransaction(address, {
|
||
|
to: "0x...",
|
||
|
value: 1234,
|
||
|
nonce: 42,
|
||
|
gas: 42000,
|
||
|
gasPrice: 9000000000,
|
||
|
describerInput: "0x1234...",
|
||
|
})
|
||
|
// Result: {
|
||
|
// description: "text/plain;Hello World",
|
||
|
// transaction: "0x...", // serialized signed transaction
|
||
|
// }
|
||
|
```
|
||
|
|
||
|
Compute the **description string** and **described data** by
|
||
|
evaluating the call to the **describer** `to`, with the
|
||
|
**describerInput** passed to the ABI encoded call to
|
||
|
`eipXXXDescription(bytes)`.
|
||
|
|
||
|
If the wallet contains a user interface for accepting or
|
||
|
denying a transaction, it should present the description string
|
||
|
along with fee and value information. Optionally, a wallet may
|
||
|
wish to additionally provide a way to further examine the
|
||
|
transaction.
|
||
|
|
||
|
If accepted, the transaction data is set to the computed
|
||
|
**described data**, the derived transaction is signed and sent,
|
||
|
and the **description string** and serialized signed
|
||
|
transaction is returned.
|
||
|
|
||
|
|
||
|
### Signing Described Transaction
|
||
|
|
||
|
```solidity
|
||
|
eth_signDescribedTransaction(address, {
|
||
|
to: "0x...",
|
||
|
value: 1234,
|
||
|
nonce: 42,
|
||
|
gas: 42000,
|
||
|
gasPrice: 9000000000,
|
||
|
describerInput: "0x1234...",
|
||
|
})
|
||
|
// Result: {
|
||
|
// description: "text/plain;Hello World",
|
||
|
// transaction: "0x...", // serialized signed transaction
|
||
|
// }
|
||
|
```
|
||
|
|
||
|
Compute the **description string** and **described data** by
|
||
|
evaluating the call to the **describer** `to`, with the
|
||
|
**describerInput** passed to the ABI encoded call to
|
||
|
`eipXXXDescription(bytes)`.
|
||
|
|
||
|
If the wallet contains a user interface for accepting or
|
||
|
denying a transaction, it should present the description string
|
||
|
along with fee and value information. Optionally, a wallet may
|
||
|
wish to additionally provide a way to further examine the
|
||
|
transaction.
|
||
|
|
||
|
If accepted, the transaction data is set to the computed
|
||
|
**described data**, the derived transaction is signed (and not
|
||
|
sent) and the **description string** and serialized signed
|
||
|
transaction is returned.
|
||
|
|
||
|
### Description Strings
|
||
|
|
||
|
A **description string** must begin with a mime-type followed
|
||
|
by a semi-colon (`;`). This EIP specifies only the `text/plain`
|
||
|
mime-type, but future EIPs may specify additional types to
|
||
|
enable more rich processing, such as `text/markdown` so that
|
||
|
addresses can be linkable within clients or to enable
|
||
|
multi-locale options, similar to multipart/form-data.
|
||
|
|
||
|
|
||
|
## Rationale
|
||
|
|
||
|
### Meta Description
|
||
|
|
||
|
There have been many attempts to solve this problem, many of
|
||
|
which attempt to examine the encoded transaction data or
|
||
|
message data directly.
|
||
|
|
||
|
In many cases, the information that would be necessary for a
|
||
|
meaningful description is not present in the final encoded
|
||
|
transaction data or message data.
|
||
|
|
||
|
Instead this EIP uses an indirect description of the data.
|
||
|
|
||
|
For example, the `commit(bytes32)` method of ENS places a
|
||
|
commitement **hash** on-chain. The hash contains the
|
||
|
**blinded** name and address; since the name is blinded, the
|
||
|
encoded data (i.e. the hash) no longer contains the original
|
||
|
values and is insufficient to access the necessary values to
|
||
|
be included in a description.
|
||
|
|
||
|
By instead describing the commitment indirectly (with the
|
||
|
original information intact: NAME, ADDRESS and SECRET) a
|
||
|
meaningful description can be computed (e.g. "commit to NAME for ADDRESS (with SECRET)")
|
||
|
and the matching data can be computed (i.e. `commit(hash(name, owner, secret))`).
|
||
|
|
||
|
### Entangling the Contract Address
|
||
|
|
||
|
To prevent data being signed from one contract being used
|
||
|
against another, the contract address is entanlged into
|
||
|
both the transaction (implicitly via the `to` field) and
|
||
|
in messages by the EIP-191 versions specific data.
|
||
|
|
||
|
The use of the zero address is reserved.
|
||
|
|
||
|
### Alternatives
|
||
|
|
||
|
- NatSpec and company are a class of more complex languages that attempt to describe the encoded data directly. Because of the language complexity they often end up being quite large requiring entire runtime environments with ample processing power and memory, as well as additional sandboxing to reduce security concerns. One goal of this is to reduce the complexity to something that could execute on hardware wallets and other simple wallets. These also describe the data directly, which in many cases (such as blinded data), cannot adequately describe the data at all
|
||
|
|
||
|
- Custom Languages; due to the complexity of Ethereum transactions, any language used would require a lot of expressiveness and re-inventing the wheel. The EVM already exists (it may not be ideal), but it is there and can handle everything necessary.
|
||
|
|
||
|
- Format Strings (e.g. Trustless Signing UI Protocol; format strings can only operate on the class of regular languages, which in many cases is insufficient to describe an Ethereum transaction. This was an issue quite often during early attempts at solving this problem.
|
||
|
|
||
|
- The signTypedData [EIP-712](./eip-712.md) has many parallels to what this EIP aims to solve
|
||
|
|
||
|
- @TODO: More
|
||
|
|
||
|
|
||
|
## Backwards Compatibility
|
||
|
|
||
|
All signatures for messages are generated using [EIP-191](./eip-191.md)
|
||
|
which had a previously compatible version byte of `0x00`, so
|
||
|
there should be no concerns with backwards compatibility.
|
||
|
|
||
|
|
||
|
## Test Cases
|
||
|
|
||
|
All test cases operate against the published and verified contracts:
|
||
|
|
||
|
- Formatter: Ropsten @ 0x7a89c0521604008c93c97aa76950198bca73d933
|
||
|
- TestFormatter: Ropsten @ 0xab3045aa85cbcabb06ed3f3fe968fa5457727270
|
||
|
|
||
|
The private key used for signing messages and transactions is:
|
||
|
|
||
|
```
|
||
|
privateKey = "0x6283185307179586476925286766559005768394338798750211641949889184"
|
||
|
```
|
||
|
|
||
|
|
||
|
### Messages
|
||
|
|
||
|
**Example: login with signed message**
|
||
|
|
||
|
- sends selector login()
|
||
|
- received data with selector doLogin(bytes32 timestamp)
|
||
|
|
||
|
```
|
||
|
Input:
|
||
|
Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270
|
||
|
Describer Input: 0xb34e97e800000000000000000000000000000000000000000000000000000000
|
||
|
i.e. encode(
|
||
|
[ "bytes4" ],
|
||
|
[ SEL("login()") ]
|
||
|
)
|
||
|
|
||
|
Output:
|
||
|
Description: text/plain;Log into ethereum.org?
|
||
|
Data: 0x14629d78000000000000000000000000000000000000000000000000000000006010d607
|
||
|
i.e. encodeWithSelector("doLogin(bytes32)", "0x000000000000000000000000000000000000000000000000000000006010d607" ]
|
||
|
|
||
|
Signing:
|
||
|
Preimage: 0x1900ab3045aa85cbcabb06ed3f3fe968fa545772727014629d78000000000000000000000000000000000000000000000000000000006010d607
|
||
|
Signature: 0x8b9def29343c85797a580c5cd3607c06e78a53351219f9ba706b9985c1a3c91e702bf678e07f5daf5ef48b3e3cc581202de233904b72cf2c4f7d714ce92075b21c
|
||
|
```
|
||
|
|
||
|
### Transactions
|
||
|
|
||
|
All transaction test cases use the ropsten network (chainId: 3)
|
||
|
and for all unspecified properties use 0.
|
||
|
|
||
|
**Example: ERC-20 transfer**
|
||
|
|
||
|
```
|
||
|
Input:
|
||
|
Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270
|
||
|
Describer Input: 0xa9059cbb000000000000000000000000000000000000000000000000000000000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000002b992b75cbeb6000
|
||
|
i.e. encode(
|
||
|
[ "bytes4", "address", "uint"],
|
||
|
[ SEL("transfer(address,uint256)"), "0x8ba1f109551bD432803012645Ac136ddd64DBA72", 3.14159e18 ]
|
||
|
)
|
||
|
Output:
|
||
|
Description: text/plain;Send 3.14159 TOKN to "ricmoose.eth" (0x8ba1f109551bD432803012645Ac136ddd64DBA72)?
|
||
|
Described Data: 0xa9059cbb0000000000000000000000000000000000000000000000002b992b75cbeb60000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72
|
||
|
i.e. encodeWithSelector("transfer(address,uint256)", "0x8ba1f109551bD432803012645Ac136ddd64DBA72", 3.14159e18)
|
||
|
|
||
|
Signing:
|
||
|
Signed Transaction: 0xf8a280808094ab3045aa85cbcabb06ed3f3fe968fa545772727080b844a9059cbb0000000000000000000000000000000000000000000000002b992b75cbeb60000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba7229a0f33ea492d326ac32d9b7ae203c61bf7cf0ac576fb0cf8be8e4c63dc89c90de12a06c8efb28aaf3b70c032b3bd1edfc664578c49f040cf749bb19b000da56507fb2
|
||
|
```
|
||
|
|
||
|
**Example: ERC-20 approve**
|
||
|
|
||
|
```
|
||
|
Input:
|
||
|
Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270
|
||
|
Describer Input: 0x095ea7b3000000000000000000000000000000000000000000000000000000000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000002b992b75cbeb6000
|
||
|
i.e. encode(
|
||
|
[ "bytes4", "address", "uint"],
|
||
|
[ SEL("approve(address,uint256)"), "0x8ba1f109551bD432803012645Ac136ddd64DBA72", 3.14159e18 ]
|
||
|
)
|
||
|
|
||
|
Output:
|
||
|
Description: text/plain;Approve "ricmoose.eth" (0x8ba1f109551bD432803012645Ac136ddd64DBA72) to manage 3.14159 TOKN tokens?
|
||
|
Described Data: 0xa9059cbb0000000000000000000000000000000000000000000000002b992b75cbeb60000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72
|
||
|
i.e. encodeWithSelector("approve(address,uint256)", "0x8ba1f109551bD432803012645Ac136ddd64DBA72", 3.14159e18)
|
||
|
|
||
|
Signing:
|
||
|
Signed Transaction: 0xf8a280808094ab3045aa85cbcabb06ed3f3fe968fa545772727080b844a9059cbb0000000000000000000000000000000000000000000000002b992b75cbeb60000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba7229a0f33ea492d326ac32d9b7ae203c61bf7cf0ac576fb0cf8be8e4c63dc89c90de12a06c8efb28aaf3b70c032b3bd1edfc664578c49f040cf749bb19b000da56507fb2
|
||
|
```
|
||
|
|
||
|
**Example: ENS commit**
|
||
|
|
||
|
```
|
||
|
Input:
|
||
|
Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270
|
||
|
Describer Input: 0x0f0e373f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000e31f43c1d823afaa67a8c5fbb8348176d225a79e65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b00000000000000000000000000000000000000000000000000000000000000087269636d6f6f7365000000000000000000000000000000000000000000000000
|
||
|
i.e. encode(
|
||
|
[ "bytes4", "string", "address", "bytes32"],
|
||
|
[ SEL("commit(string,address,bytes32)"), "ricmoose", "0xE31f43C1d823AfAA67A8C5fbB8348176d225A79e", "0x65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b" ]
|
||
|
)
|
||
|
|
||
|
Output:
|
||
|
Description: text/plain;Commit to the ENS name "ricmoose.eth" for 0xE31f43C1d823AfAA67A8C5fbB8348176d225A79e?
|
||
|
Described Data: 0xf14fcbc8e4a4f2bb818545497be34c7ab30e6e87e0001df4ba82e7c8b3f224fbaf255b91
|
||
|
i.e. encodeWithSelector("commit(bytes32)", makeCommitment("ricmoose", "0xE31f43C1d823AfAA67A8C5fbB8348176d225A79e", "0x65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b"))
|
||
|
|
||
|
Signing:
|
||
|
Signed Transaction: 0xf88180808094ab3045aa85cbcabb06ed3f3fe968fa545772727080a4f14fcbc8e4a4f2bb818545497be34c7ab30e6e87e0001df4ba82e7c8b3f224fbaf255b912aa0a62b41d1ebda584fe84cf8a05f61b429fe4ec361e13c17f30a23281106b38a8da00bcdd896fe758d8f0cfac46445a48f76f5e9fe27790d67c51412cb98a12a0844
|
||
|
```
|
||
|
|
||
|
**Example: WETH mint()**
|
||
|
|
||
|
```
|
||
|
Input:
|
||
|
Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270
|
||
|
Describer Input: 0x1249c58b00000000000000000000000000000000000000000000000000000000
|
||
|
i.e. encode(
|
||
|
[ "bytes4" ],
|
||
|
[ SEL("mint()") ]
|
||
|
)
|
||
|
Value: 1.23 ether
|
||
|
|
||
|
Output:
|
||
|
Description: text/plain;Mint 1.23 WETH (spending 1.23 ether)?
|
||
|
Described Data: 0x1249c58b
|
||
|
i.e. encodeWithSelector("mint()")
|
||
|
|
||
|
Signing:
|
||
|
Signed Transaction: 0xf86980808094ab3045aa85cbcabb06ed3f3fe968fa5457727270881111d67bb1bb0000841249c58b29a012df802e1394a97caab23c15c3a8c931668df4b2d6d604ca23f3f6b836d0aafca0071a2aebef6a9848616b4d618912f2003fb4babde3dba451b5246f866281a654
|
||
|
```
|
||
|
|
||
|
## Reference Implementation
|
||
|
|
||
|
@TODO (consider adding it as one or more files in `../assets/eip-####/`)
|
||
|
|
||
|
I will add examples in Solidity and JavaScript.
|
||
|
|
||
|
|
||
|
## Security Considerations
|
||
|
|
||
|
### Escaping Text
|
||
|
|
||
|
Wallets must be careful when displaying text provided by
|
||
|
contracts and proper efforts must be taken to sanitize
|
||
|
it, for example, be sure to consider:
|
||
|
|
||
|
- HTML could be embedded to attempt to trick web-based wallets into executing code using the script tag (possibly uploading any private keys to a server)
|
||
|
- In general, extreme care must be used when rendering HTML; consider the ENS names `<span style="display:none">not-</span>ricmoo.eth` or ` ricmoo.eth`, which if rendered without care would appear as `ricmoo.eth`, which it is not
|
||
|
- Other marks which require escaping could be included, such as quotes (`"`), formatting (`\n` (new line), `\f` (form feed), `\t` (tab), any of many non-standard whitespaces), back-slassh (`\`)
|
||
|
- UTF-8 has had bugs in the past which could allow arbitrary code execution and crashing renderers; consider using the UTF-8 replacement character (or *something*) for code-points outside common planes or common sub-sets within planes
|
||
|
- Homoglyphs attacks
|
||
|
- Right-to-left marks may affect rendering
|
||
|
- Many other things, deplnding on your environment
|
||
|
|
||
|
### Distinguished Signed Data
|
||
|
|
||
|
Applications implementing this EIP to sign message data should
|
||
|
ensure there are no collisions within the data which could
|
||
|
result in ambiguously signed data.
|
||
|
|
||
|
@TODO: Expand on this; compare packed data to ABI encoded data?
|
||
|
|
||
|
### Enumeration
|
||
|
|
||
|
If an abort occurs during signing, the response from this call
|
||
|
should match the response from a declined signing request;
|
||
|
otherwise this could be used for enumeration attacks, etc. A
|
||
|
random interactive-scale delay should also be added, otherwise
|
||
|
a < 10ms response could be interpreted as an error.
|
||
|
|
||
|
### Replayablility
|
||
|
|
||
|
Transactions contain an explicit nonce, but signed messages do
|
||
|
not.
|
||
|
|
||
|
For many purposes, such as signing in, a nonce could be
|
||
|
injected (using block.timestamp) into the data. The log in
|
||
|
service can verify this is a recent timestamp. The timestamp
|
||
|
may or may not be omitted from the description string in this
|
||
|
case, as it it largely useful internally only.
|
||
|
|
||
|
In general, when signing messages a nonce often makes sense to
|
||
|
include to prevent the same signed data from being used in the
|
||
|
future.
|
||
|
|
||
|
|
||
|
## Copyright
|
||
|
|
||
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|