Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-2876: 存款合约和地址标准

Authors Jonathan Underwood (@junderw)
Created 2020-08-13
Discussion Link https://github.com/junderw/deposit-contract-poc/issues/1

简单总结

此 ERC 定义了一个简单的合约接口,用于管理存款。它还定义了一种新的地址格式,该格式编码了传递到接口主存款函数中的额外数据。

摘要

一个兼容 ERC-2876 的存款系统可以接受来自多个存款人的 ETH 付款,而无需管理多个密钥或要求使用热钱包。

一个兼容 ERC-2876 的钱包应用程序可以向兼容 ERC-2876 的存款系统发送 ETH,存款系统可以使用此标准中指定的 8 字节 ID 来区分他们的付款。

所有交易所(作为存款系统和作为其提款系统的钱包)、商家和所有钱包应用程序/库采用 ERC-2876 可能会减少这些系统的总网络 gas 使用量,因为两次价值交易花费 42000 gas,而简单的 ETH 转发合约根据底层实现,花费接近 30000 gas。

对于存款系统管理员来说,这样做的好处是可以将所有存款直接转发到冷钱包,而无需任何手动操作来从多个外部帐户收集存款。

动机

中心化交易所和商家(以下简称“应用程序”)需要一种地址格式来接受存款。目前,使用的地址格式是指一个帐户(外部或合约),但这会产生一个问题。它要求应用程序为每个发票/用户创建一个新帐户。如果帐户是外部的,则意味着应用程序必须将存款地址作为热钱包,或者增加冷钱包操作员的工作量(因为每个存款帐户都会创建一个价值交易来进行清扫)。如果帐户是合约,那么生成一个帐户至少需要 60k gas 用于简单的代理,这在成本上是令人望而却步的。

因此,商家和中心化交易所应用程序被迫在以下选项中做出选择:

  • 巨大的安全风险(存款帐户是热钱包)
  • 巨大的人工成本(冷帐户管理器花费时间清扫数千个冷帐户)
  • 巨大的服务成本(部署每个存款地址的合约模型)。

本提案的时机是在网络 gas 价格上涨的背景下。在这种时期,越来越多进入该领域的服务都被迫使用热钱包进行存款,这是一个很大的安全风险。

本提案的动机是降低部署和管理接受来自许多用户的存款的系统的成本,并且通过标准化这种方法,世界各地的服务可以轻松地使用此接口来相互发送价值,而无需创建多个帐户。

规范

定义

  • 本文档中使用的关键词“必须(MUST)”、“禁止(MUST NOT)”、“必需(REQUIRED)”、“应该(SHALL)”、“不应该(SHALL NOT)”、“推荐(SHOULD)”、“不推荐(SHOULD NOT)”、“建议(RECOMMENDED)”、“可以(MAY)”和“可选(OPTIONAL)”应按照 RFC 2119 中的描述进行解释。
  • 合约接口是此 ERC 的合约组件。
  • 存款地址格式是“存款地址格式”中描述的用于编码 20 字节帐户地址和 8 字节 ID 的新格式。
  • 合约是指实现此 ERC 的合约接口的合约。
  • 8 字节“id”是用作合约接口的输入参数的 8 字节 ID。
  • 5 字节“nonce”“id”的前 5 个最高有效字节。
  • 3 字节“checksum”“id”的最后 3 个最低有效字节。
  • deposit(bytes8)是指该签名的函数,该函数在合约接口中定义。
  • 父应用程序是指将使用在deposit(bytes8)函数中获得的信息的应用程序。(例如,交易所后端或非托管商家应用程序)
  • 存款人是指将通过deposit(bytes8)调用向合约发送价值的人。
  • 钱包是指在存款人的请求下发送价值交易的任何应用程序或库。(例如,MyEtherWallet、Ledger、blockchain.com、各种库)

存款地址格式

为了添加 8 字节的“id”数据,我们需要将其与 20 字节的帐户地址一起编码。这 8 个字节附加到 20 字节的地址。

ID 中包含一个 3 字节的校验和,它是连接的 20 字节地址和 ID 的前 5 字节 nonce 的 keccak256 哈希的前 3 个字节(25 字节)。

可以使用以下 JavaScript 代码生成存款地址格式:

/**
 * 将 20 字节的帐户地址和 5 字节的 nonce 转换为存款地址。
 * 返回值的格式为 28 字节,如下所示。+ 运算符是字节
 * 串联。
 * (baseAddress + nonce + keccak256(baseAddress + nonce)[:3])
 *
 * @param {String} baseAddress 给定的 HEX 地址(带 0x 前缀的 20 字节十六进制字符串)
 * @param {String} nonce 给定的 HEX nonce(带 0x 前缀的 5 字节十六进制字符串)
 * @return {String}
 */
function generateAddress (baseAddress, nonce) {
  if (
    !baseAddress.match(/^0x[0-9a-fA-F]{40}$/) ||
    !nonce.match(/^0x[0-9a-fA-F]{10}$/)
  ) {
    throw new Error('Base Address and nonce must be 0x hex strings');
  }
  const ret =
    baseAddress.toLowerCase() + nonce.toLowerCase().replace(/^0x/, '');
  const myHash = web3.utils.keccak256(ret);
  return ret + myHash.slice(2, 8); // first 3 bytes from the 0x hex string
};

可以使用以下方法在存款合约本身中验证校验和:

function checksumMatch(bytes8 id) internal view returns (bool) {
    bytes32 chkhash = keccak256(
        abi.encodePacked(address(this), bytes5(id))
    );
    bytes3 chkh = bytes3(chkhash);
    bytes3 chki = bytes3(bytes8(uint64(id) << 40));
    return chkh == chki;
}

合约接口

遵循此 ERC 的合约:

  • 如果发送的交易 msg.data 为空(纯价值交易),合约必须回退。
  • 合约必须具有以下存款函数:
interface DepositEIP {
  function deposit(bytes8 id) external payable returns (bool);
}
  • 当合约需要保留该值时,deposit(bytes8)必须返回false,但要向存款人发出信号,表明存款(就父应用程序而言)本身尚未成功。(这可用于部分付款,例如,发票为 5 ETH,发送 3 ETH 返回 false,但发送第二个 tx 与 2 ETH 将返回 true。)
  • 如果存款以某种方式失败并且合约不需要保留发送的值,则deposit(bytes8)必须回退。
  • 如果该值将被保留并且父应用程序(交易所/商家)从逻辑上认为付款已完成,则deposit(bytes8)必须返回true
  • deposit(bytes8)应该检查包含在 8 字节 ID 中的校验和。(有关示例,请参见“存款地址格式”)
  • 如果存款 ID 是一次性使用的发票,该发票具有设定的价值并且收到的价值高于设定的价值,则父应用程序应该返回收到的多余价值。但是,这不应该通过直接发送回msg.sender来完成,而应该在父应用程序中注明,并且应尽最大努力通过带外方式联系存款人。

从钱包向合约存入价值

  • 钱包必须在接受 20 字节地址格式的任何地方接受存款地址格式作为交易目的地。
  • 钱包必须验证 3 字节的校验和,如果校验和不匹配,则失败。
  • 如果目标地址是存款地址格式data字段设置为除 null 之外的任何内容,则钱包必须失败。
  • 钱包必须将基础交易的to字段设置为存款地址格式的前 20 个字节,并将data字段设置为0x3ef8e69aNNNNNNNNNNNNNNNN000000000000000000000000000000000000000000000000,其中NNNNNNNNNNNNNNNN是存款地址格式中的最后 8 个字节。(例如,如果存款地址格式设置为0x433e064c42e87325fb6ffa9575a34862e0052f26913fd924f056cd15,则to字段为0x433e064c42e87325fb6ffa9575a34862e0052f26data字段为0x3ef8e69a913fd924f056cd15000000000000000000000000000000000000000000000000

理由

合约接口和地址格式的组合有一个值得注意的缺点,这在讨论中已经提出。此 ERC 只能处理原生价值 (ETH) 的存款,而不能处理其他协议,例如 ERC-20。但是,这不被认为是一个问题,因为在任何交易所/商家应用程序中,出于会计原因以及安全原因,最好在逻辑上 AND 以密钥方式分离不同货币的钱包。因此,将此方法用于原生价值货币 (ETH),而将另一种方法用于 ERC-20 代币等是可以接受的。对 ERC-20 执行类似操作的任何尝试都需要修改 ERC 本身(通过将 ID 数据作为新的输入参数添加到 transfer 方法等),这将使此 ERC 的范围变得太大而无法管理。但是,如果此地址格式流行起来,则将 bytes8 ID 添加到任何更新的协议中将是微不足道的(尽管由于网络效应,采用可能很困难)。

ID 的 8 字节大小和校验和 3:nonce 5 比率是经过以下考虑而确定的:

  • 24 位校验和优于 EIP-55 地址的平均 15 位校验和。
  • 40 位 nonce 允许超过 1 万亿个 nonce。
  • 选择 64 位 ID 长度是为了足够长以支持体面的校验和和大量的 nonce,但又不会太长。(保持在 256 位以下可以降低 gas 成本的哈希成本。)

向后兼容性

使用存款地址格式生成的地址不会被不支持它的应用程序视为有效地址。如果用户足够精通技术,他们可以通过自己验证校验和、手动创建所需的数据字段并手动输入数据字段来绕过缺少支持的问题。(假设钱包应用程序允许对交易进行任意数据输入)可以在 github 上托管一个工具,供用户从存款地址获取所需的 20 字节地址和 msg.data 字段。

由于遵循此 ERC 的合约将拒绝任何纯价值交易,因此无需担心提取 20 字节地址并在没有 calldata 的情况下发送到该地址。

但是,这是一种简单的格式,易于实现,因此此 ERC 的作者将首先在 web3.js 中实现,并鼓励主要钱包应用程序采用。

测试用例

[
  {
    "address": "0x083d6b05729c58289eb2d6d7c1bb1228d1e3f795",
    "nonce": "0xbdd769c69b",
    "depositAddress": "0x083d6b05729c58289eb2d6d7c1bb1228d1e3f795bdd769c69b3b97b9"
  },
  {
    "address": "0x433e064c42e87325fb6ffa9575a34862e0052f26",
    "nonce": "0x913fd924f0",
    "depositAddress": "0x433e064c42e87325fb6ffa9575a34862e0052f26913fd924f056cd15"
  },
  {
    "address": "0xbbc6597a834ef72570bfe5bb07030877c130e4be",
    "nonce": "0x2c8f5b3348",
    "depositAddress": "0xbbc6597a834ef72570bfe5bb07030877c130e4be2c8f5b3348023045"
  },
  {
    "address": "0x17627b07889cd22e9fae4c6abebb9a9ad0a904ee",
    "nonce": "0xe619dbb618",
    "depositAddress": "0x17627b07889cd22e9fae4c6abebb9a9ad0a904eee619dbb618732ef0"
  },
  {
    "address": "0x492cdf7701d3ebeaab63b4c7c0e66947c3d20247",
    "nonce": "0x6808043984",
    "depositAddress": "0x492cdf7701d3ebeaab63b4c7c0e66947c3d202476808043984183dbe"
  }
]

实现

一个包含示例合约和地址生成(在测试中)的示例实现位于此处:

https://github.com/junderw/deposit-contract-poc

安全注意事项

一般来说,实现合约接口的合约应该将收到的资金转发到 deposit(bytes8) 函数到他们的冷钱包帐户。此地址应该硬编码为一个常量,或者利用 solidity 版本 >=0.6.5 中的 immutable 关键字。

为了防止在父应用程序关闭后发送存款的问题,合约应该有一个终止开关,该开关将恢复所有对 deposit(bytes8) 的调用,而不是使用 selfdestruct(address)(因为存款的用户仍然会成功,因为外部帐户将收到价值,无论 calldata 如何,并且基本上自毁的合约将成为任何新存款的黑洞)

版权

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

Citation

Please cite this document as:

Jonathan Underwood (@junderw), "ERC-2876: 存款合约和地址标准 [DRAFT]," Ethereum Improvement Proposals, no. 2876, August 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2876.