Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-3224: 描述数据

用于计算可签名数据的人类可读描述的合约方法。

Authors Richard Moore (@ricmoo), Nick Johnson (@arachnid)
Created 2021-01-23
Discussion Link https://github.com/ethereum/EIPs/issues/3225
Requires EIP-191

摘要

机器可执行操作的人类可读描述,以更高级别的机器可读数据进行描述,以便钱包可以向用户提供有意义的反馈,描述用户即将执行的操作。

动机

当使用以太坊钱包(例如,MetaMask、Clef、硬件钱包)时,用户必须接受并授权签名消息或发送交易。

由于以太坊交易的复杂性,钱包在提供对用户批准的交易内容洞察力方面非常有限;除了对常见交易(如 ERC20 转账)的特殊情况支持外,这通常相当于要求用户签署一个不透明的二进制数据块。

此 EIP 提出了一种方法,供 DApp 开发者通过为钱包提供一种生成关于合约声称将发生什么的更好描述的方式,从而实现更舒适的用户体验。

它不处理希望撒谎的恶意合约,它只处理希望改善用户生活的诚实合约。我们认为这是一个合理的安全模型,因为交易描述可以与合约代码同时进行审计,允许审计师和代码审查员检查交易描述是否准确,作为其审查的一部分。

规范

描述字符串描述数据 通过评估合约(即 描述器)同时生成,将 描述器输入 传递给方法:

function eipXXXDescribe(bytes describer_inputs) view returns (string description_string, bytes described_data);

该方法必须在静态上下文中可执行(即,任何副作用,如 logX、sstore 等),包括通过间接调用,都可以被忽略。

在评估期间,ADDRESS(即 to),CALLER(即 from),VALUEGASPRICE 必须与被描述的交易的值相同,以便生成描述的代码可以依赖它们。对于签名 描述消息VALUE 应始终为 0。

执行字节码时,应尽最大努力确保 BLOCKHASHNUMBERTIMESTAMPDIFFICULTY"latest" 区块匹配。COINBASE 应该是零地址。

该方法可能会 revert,在这种情况下,必须中止签名。

新的 JSON-RPC 方法

管理私钥的客户端应公开其他方法,用于与相关帐户交互。

如果不存在用户界面或不希望存在任何其他基于帐户的操作,则应忽略描述字符串,并直接使用描述数据。

这些 JSON-RPC 方法也将在标准以太坊库中实现,因此 JSON-RPC 描述更多的是一种描述它们的规范方式。

签名描述消息

eth_signDescribedMessage(address, describer, describerInput)
// Result: {
//   description: "text/plain;Hello World",
//   data: "0x...", // described data
//   signature: "0x..."
// }

通过评估对 描述器 的调用,计算 描述字符串描述数据,并将 describerInput 传递给 ABI 编码的对 eipXXXDescription(bytes) 的调用。执行期间的 VALUE 必须为 0。

如果钱包包含用于接受或拒绝签名消息的用户界面,则应向用户显示描述字符串。可选地,钱包可能希望额外提供一种检查描述数据的方法。

如果接受,则根据 EIP-191 签署计算出的 描述数据版本 byte0x00版本特定数据 为描述器地址。

即:

0x19   0x00   DESCRIBER_ADDRESS   0xDESCRIBED_DATA

返回的结果包括 描述数据,允许使用合约计算出的参数的 DApp 可用。

发送描述交易

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
// }

通过评估对 描述器 to 的调用来计算 描述字符串描述数据,并将 describerInput 传递给 ABI 编码的对 eipXXXDescription(bytes) 的调用。

如果钱包包含用于接受或拒绝交易的用户界面,则应显示描述字符串以及费用和价值信息。可选地,钱包可能希望额外提供一种进一步检查交易的方法。

如果接受,则将交易数据设置为计算出的 描述数据,签名并发送派生的交易,并返回 描述字符串 和序列化的签名交易。

签名描述交易

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
// }

通过评估对 描述器 to 的调用来计算 描述字符串描述数据,并将 describerInput 传递给 ABI 编码的对 eipXXXDescription(bytes) 的调用。

如果钱包包含用于接受或拒绝交易的用户界面,则应显示描述字符串以及费用和价值信息。可选地,钱包可能希望额外提供一种进一步检查交易的方法。

如果接受,则将交易数据设置为计算出的 描述数据,签名(但不发送)派生的交易,并返回 描述字符串 和序列化的签名交易。

描述字符串

描述字符串 必须以 mime-type 开头,后跟一个分号(;)。此 EIP 仅指定 text/plain mime-type,但未来的 EIP 可能会指定其他类型以实现更丰富的处理,例如 text/markdown,以便地址可以在客户端中链接或启用多语言选项,类似于 multipart/form-data。

理由

元描述

已经有很多尝试解决这个问题,其中许多尝试直接检查编码的交易数据或消息数据。

在许多情况下,有意义的描述所需的信息不存在于最终编码的交易数据或消息数据中。

相反,此 EIP 使用数据的间接描述。

例如,ENS 的 commit(bytes32) 方法将承诺 hash 放在链上。该 hash 包含 blinded 名称和地址;由于名称是 blinded 的,因此编码的数据(即 hash)不再包含原始值,并且不足以访问包含在描述中的必要值。

通过间接描述承诺(使用原始信息完整:NAME、ADDRESS 和 SECRET),可以计算出有意义的描述(例如,“commit to NAME for ADDRESS (with SECRET)”),并且可以计算出匹配的数据(即 commit(hash(name, owner, secret)))。

纠缠合约地址

为了防止从一个合约签名的数据用于对抗另一个合约,合约地址被 entanlged 到交易中(通过 to 字段隐式地)和消息中(通过 EIP-191 版本的特定数据)。

保留使用零地址。

替代方案

  • NatSpec 及其公司是一类更复杂的语言,试图直接描述编码的数据。由于语言的复杂性,它们通常最终变得非常大,需要整个运行时环境,具有充足的处理能力和内存,以及额外的沙箱来减少安全问题。其中一个目标是将复杂性降低到可以在硬件钱包和其他简单钱包上执行的东西。这些也直接描述了数据,这在许多情况下(例如 blinded 数据)根本无法充分描述数据

  • 自定义语言;由于以太坊交易的复杂性,任何使用的语言都需要大量的表达能力并重新发明轮子。EVM 已经存在(它可能不是理想的),但它在那里并且可以处理所有必要的事情。

  • 格式字符串(例如,Trustless Signing UI Protocol);格式字符串只能在正则语言类上操作,这在许多情况下不足以描述以太坊交易。这在早期尝试解决这个问题时经常出现。

  • signTypedData EIP-712 与此 EIP 旨在解决的问题有很多相似之处

  • @TODO: 更多

向后兼容性

所有消息签名都是使用 EIP-191 生成的,该 EIP 之前具有兼容的版本 byte 0x00,因此无需担心向后兼容性。

测试用例

所有测试用例都是针对已发布和验证的合约进行操作:

  • Formatter: Ropsten @ 0x7a89c0521604008c93c97aa76950198bca73d933
  • TestFormatter: Ropsten @ 0xab3045aa85cbcabb06ed3f3fe968fa5457727270

用于签名消息和交易的私钥是:

privateKey = "0x6283185307179586476925286766559005768394338798750211641949889184"

消息

示例:使用签名消息登录

  • 发送选择器 login()
  • 接收带有选择器 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

交易

所有交易测试用例都使用 ropsten 网络 (chainId: 3) 并且所有未指定的属性都使用 0。

示例:ERC-20 转账

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

示例:ERC-20 授权

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

示例: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

示例: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

参考实现

@TODO(考虑将其添加为 ../assets/eip-####/ 中的一个或多个文件)

我将添加 Solidity 和 JavaScript 中的示例。

安全考虑

转义文本

钱包在显示合约提供的文本时必须小心,并且必须采取适当的措施对其进行清理,例如,请务必考虑:

  • 可以嵌入 HTML 以尝试欺骗基于 Web 的钱包使用 script 标签执行代码(可能将任何私钥上传到服务器)
  • 一般来说,在渲染 HTML 时必须非常小心;考虑 ENS 名称 <span style="display:none">not-</span>ricmoo.eth&thinsp;ricmoo.eth,如果不小心渲染,它们将显示为 ricmoo.eth,但事实并非如此
  • 可以包含其他需要转义的标记,例如引号 (")、格式 (\n (换行符)、\f (换页符)、\t (制表符)、任何许多非标准空格),反斜杠 (\)
  • UTF-8 过去存在漏洞,可能允许任意代码执行和崩溃渲染器;考虑对常见平面或平面内常见子集之外的代码点使用 UTF-8 替换字符(或 其他内容
  • 同形字攻击
  • 从右到左的标记可能会影响渲染
  • 许多其他事情,取决于您的环境

区分签名数据

实现此 EIP 以对消息数据进行签名的应用程序应确保数据中没有冲突,这可能导致签名的数据含糊不清。

@TODO:扩展这一点;比较 packed data 和 ABI 编码数据?

枚举

如果在签名期间发生中止,则来自此调用的响应应与来自拒绝的签名请求的响应相匹配;否则,这可能用于枚举攻击等。还应添加随机的交互式延迟,否则 < 10ms 的响应可以解释为错误。

可重放性

交易包含显式 nonce,但签名消息不包含。

对于许多目的,例如登录,可以将 nonce(使用 block.timestamp)注入到数据中。登录服务可以验证这是一个最近的时间戳。在这种情况下,时间戳可能会或可能不会从描述字符串中省略,因为它主要仅在内部有用。

一般来说,在签名消息时,包含 nonce 通常是有意义的,以防止将来使用相同的签名数据。

版权

版权和相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Richard Moore (@ricmoo), Nick Johnson (@arachnid), "ERC-3224: 描述数据 [DRAFT]," Ethereum Improvement Proposals, no. 3224, January 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3224.