ERC-2876 - Deposit contract and address standard

Created 2020-08-13
Status Stagnant
Category ERC
Type Standards Track
Authors

Simple Summary

This ERC defines a simple contract interface for managing deposits. It also defines a new address format that encodes the extra data passed into the interface's main deposit function.

Abstract

An ERC-2876 compatible deposit system can accept ETH payments from multiple depositors without the need for managing multiple keys or requiring use of a hot wallet.

An ERC-2876 compatible wallet application can send ETH to ERC-2876 compatible deposit systems in a way that the deposit system can differentiate their payment using the 8 byte id specified in this standard.

Adoption of ERC-2876 by all exchanges (as a deposit system and as a wallet for their withdrawal systems), merchants, and all wallet applications/libraries will likely decrease total network gas usage by these systems, since two value transactions cost 42000 gas while a simple ETH forwarding contract will cost closer to 30000 gas depending on the underlying implementation.

This also has the benefit for deposit system administrators of allowing for all deposits to be forwarded to a cold wallet directly without any manual operations to gather deposits from multiple external accounts.

Motivation

Centralized exchanges and merchants (Below: "apps") require an address format for accepting deposits. Currently the address format used refers to an account (external or contract), but this creates a problem. It requires that apps create a new account for every invoice / user. If the account is external, that means the app must have the deposit addresses be hot wallets, or have increased workload for cold wallet operators (as each deposit account will create 1 value tx to sweep). If the account is contract, generating an account costs at least 60k gas for a simple proxy, which is cost-prohibitive.

Therefore, merchant and centralized exchange apps are forced between taking on one of the following:

The timing of this proposal is within the context of increased network gas prices. During times like this, more and more services who enter the space are being forced into hot wallets for deposits, which is a large security risk.

The motivation for this proposal is to lower the cost of deploying and managing a system that accepts deposits from many users, and by standardizing the methodology for this, services across the world can easily use this interface to send value to and from each other without the need to create multiple accounts.

Specification

Definitions

Deposit Address Format

In order to add the 8 byte "id" data, we need to encode it along with the 20 byte account address. The 8 bytes are appended to the 20 byte address.

A 3 byte checksum is included in the id, which is the first 3 bytes of the keccak256 hash of the 20 byte address and first 5 byte nonce of the id concatenated (25 bytes).

The Deposit Address format can be generated with the following JavaScript code:

/**
 * Converts a 20 byte account address and a 5 byte nonce to a deposit address.
 * The format of the return value is 28 bytes as follows. The + operator is byte
 * concatenation.
 * (baseAddress + nonce + keccak256(baseAddress + nonce)[:3])
 *
 * @param {String} baseAddress the given HEX address (20 byte hex string with 0x prepended)
 * @param {String} nonce the given HEX nonce (5 byte hex string with 0x prepended)
 * @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
};

The checksum can be verified within the deposit contract itself using the following:

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

The Contract Interface

A contract that follows this ERC:

interface DepositEIP {
  function deposit(bytes8 id) external payable returns (bool);
}

Depositing Value to the Contract from a Wallet

Rationale

The contract interface and address format combination has one notable drawback, which was brought up in discussion. This ERC can only handle deposits for native value (ETH) and not other protocols such as ERC-20. However, this is not considered a problem, because it is best practice to logically AND key-wise separate wallets for separate currencies in any exchange/merchant application for accounting reasons and also for security reasons. Therefore, using this method for the native value currency (ETH) and another method for ERC-20 tokens etc. is acceptable. Any attempt at doing something similar for ERC-20 would require modifying the ERC itself (by adding the id data as a new input argument to the transfer method etc.) which would grow the scope of this ERC too large to manage. However, if this address format catches on, it would be trivial to add the bytes8 id to any updated protocols (though adoption might be tough due to network effects).

The 8 byte size of the id and the checksum 3 : nonce 5 ratio were decided with the following considerations:

Backwards Compatibility

An address generated with the deposit address format will not be considered a valid address for applications that don't support it. If the user is technical enough, they can get around lack of support by verifying the checksum themselves, creating the needed data field by hand, and manually input the data field. (assuming the wallet app allows for arbitrary data input on transactions) A tool could be hosted on github for users to get the needed 20 byte address and msg.data field from a deposit address.

Since a contract following this ERC will reject any plain value transactions, there is no risk of extracting the 20 byte address and sending to it without the calldata.

However, this is a simple format, and easy to implement, so the author of this ERC will first implement in web3.js and encourage adoption with the major wallet applications.

Test Cases

[
  {
    "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"
  }
]

Implementation

A sample implementation with an example contract and address generation (in the tests) is located here:

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

Security Considerations

In general, contracts that implement the contract interface should forward funds received to the deposit(bytes8) function to their cold wallet account. This address SHOULD be hard coded as a constant OR take advantage of the immutable keyword in solidity versions >=0.6.5.

To prevent problems with deposits being sent after the parent application is shut down, a contract SHOULD have a kill switch that will revert all calls to deposit(bytes8) rather than using selfdestruct(address) (since users who deposit will still succeed, since an external account will receive value regardless of the calldata, and essentially the self-destructed contract would become a black hole for any new deposits)

Copyright

Copyright and related rights waived via CC0.