Skip to content

Commit

Permalink
Add ERC for deposit address and contract interface (ethereum#2876)
Browse files Browse the repository at this point in the history
  • Loading branch information
junderw authored and tkstanczak committed Nov 7, 2020
1 parent 783e28e commit 551f883
Showing 1 changed file with 188 additions and 0 deletions.
188 changes: 188 additions & 0 deletions EIPS/eip-2876.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
---
eip: 2876
title: Deposit contract and address standard
author: Jonathan Underwood (@junderw)
discussions-to: https://github.com/junderw/deposit-contract-poc/issues/1
status: Draft
type: Standards Track
category: ERC
created: 2020-08-13
---

<!--You can leave these HTML comments in your merged EIP and delete the visible duplicate text guides, they will not appear and may be helpful to refer to if you edit it again. This is the suggested template for new EIPs. Note that an EIP number will be assigned by an editor. When opening a pull request to submit your EIP, please use an abbreviated title in the filename, `eip-draft_title_abbrev.md`. The title should be 44 characters or less.-->
## Simple Summary
<!--"If you can't explain it simply, you don't understand it well enough." Provide a simplified and layman-accessible explanation of the EIP.-->
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
<!--A short (~200 word) description of the technical issue being addressed.-->
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.

This ERC seeks to create a standard for deposit systems (and how wallets interface with those systems) that will alleviate these problems.

## Motivation
<!--The motivation is critical for EIPs that want to change the Ethereum protocol. It should clearly explain why the existing protocol specification is inadequate to address the problem that the EIP solves. EIP submissions without sufficient motivation may be rejected outright.-->
As stated in the abstract, merchant and centralized exchange apps are forced between taking on one of the following:

- Large security risk (deposit accounts are hot wallets)
- Large manual labor cost (cold account manager spends time sweeping thousands of cold accounts)
- Large service cost (deploying a contract-per-deposit-address model).

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
<!--The technical specification should describe the syntax and semantics of any new feature. The specification should be detailed enough to allow competing, interoperable implementations for any of the current Ethereum platforms (go-ethereum, parity, cpp-ethereum, ethereumj, ethereumjs, and [others](https://github.com/ethereum/wiki/wiki/Clients)).-->

### Prefix: Definitions
- The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
- `The contract interface` is the contract component of this ERC.
- `The deposit address format` is the newly made format described in part 1 for encoding the 20 byte account address and the 8 byte id.
- `The contract` refers to the contract that implements `the contract interface` of this ERC.
- `The 8 byte "id"` is an 8 byte id used as the input parameter for the contract interface.
- `The 5 byte "nonce"` is the first 5 most significant bytes of the `"id"`.
- `The 3 byte "checksum"` is the last 3 least significant bytes of the `"id"`
- `deposit(bytes8)` refers to the function of that signature, which is defined in `the contract interface`.
- `The parent application` refers to the application that will use the information gained within the `deposit(bytes8)` function. (ie. an exchange backend or a non-custodial merchant application)
- `The depositor` refers to the person that will send value to `the contract` via the `deposit(bytes8)` call.
- `The wallet` refers to any application or library that sends value transactions upon the request of `the depositor`. (ie. MyEtherWallet, Ledger, blockchain.com, various libraries)

### Part 1: 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:

```js
/**
* 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:

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

### Part 2: The Contract Interface

A contract that follows this ERC:

- `The contract` MUST revert if sent a transaction where `msg.data` is null (A pure value transaction).
- `The contract` MUST have a function a deposit function as follows:

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

- `deposit(bytes8)` MUST return `false` when the contract needs to keep the value, but signal to the depositor that the deposit (in terms of the parent application) itself has not yet succeeded. (This can be used for partial payment, ie. the invoice is for 5 ETH, sending 3 ETH returns false, but sending a second tx with 2 ETH will return true.)
- `deposit(bytes8)` MUST revert if the deposit somehow failed and the contract does not need to keep the value sent.
- `deposit(bytes8)` MUST return `true` if the value will be kept and the payment is logically considered complete by the parent application (exchange/merchant).
- `deposit(bytes8)` SHOULD check the checksum contained within the 8 byte id. (See Part 1 for an example)
- `The parent application` SHOULD return any excess value received if the deposit id is a one-time-use invoice that has a set value and the value received is higher than the set value. However, this SHOULD NOT be done by sending back to `msg.sender` directly, but rather should be noted in the parent application and the depositor should be contacted out-of-band to the best of the application manager's ability.

### Part 3: Depositing Value to the Contract from a Wallet

- `The wallet` MUST accept `the deposit address format` anywhere the 20-byte address format is accepted for transaction destination.
- `The wallet` MUST verify the 3 byte checksum and fail if the checksum doesn't match.
- `The wallet` MUST fail if the destination address is `the deposit address format` and the `data` field is set to anything besides null.
- `The wallet` MUST set the `to` field of the underlying transaction to the first 20 bytes of the deposit address format, and set the `data` field to `0x3ef8e69aNNNNNNNNNNNNNNNN000000000000000000000000000000000000000000000000` where `NNNNNNNNNNNNNNNN` is the last 8 bytes of the deposit address format. (ie. if the deposit address format is set to `0x433e064c42e87325fb6ffa9575a34862e0052f26913fd924f056cd15` then the `to` field is `0x433e064c42e87325fb6ffa9575a34862e0052f26` and the `data` field is `0x3ef8e69a913fd924f056cd15000000000000000000000000000000000000000000000000`)

## Rationale
<!--The rationale fleshes out the specification by describing what motivated the design and why particular design decisions were made. It should describe alternate designs that were considered and related work, e.g. how the feature is supported in other languages. The rationale may also provide evidence of consensus within the community, and should discuss important objections or concerns raised during discussion.-->
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:

- 24 bit checksum is better than the average 15 bit checksum of an EIP-55 address.
- 40 bit nonce allows for over 1 trillion nonces.
- 64 bit length of the id was chosen as to be long enough to support a decent checksum and plenty of nonces, but not be too long. (Staying under 256 bits makes hashing cheaper in gas costs as well.)

## Backwards Compatibility
<!--All EIPs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The EIP must explain how the author proposes to deal with these incompatibilities. EIP submissions without a sufficient backwards compatibility treatise may be rejected outright.-->
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
<!--Test cases for an implementation are mandatory for EIPs that are affecting consensus changes. Other EIPs can choose to include links to test cases if applicable.-->
```
[
{
"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
<!--The implementations must be completed before any EIP is given status "Final", but it need not be completed before the EIP is accepted. While there is merit to the approach of reaching consensus on the specification and rationale before writing code, the principle of "rough consensus and running code" is still useful when it comes to resolving many discussions of API details.-->
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
<!--All EIPs must contain a section that discusses the security implications/considerations relevant to the proposed change. Include information that might be important for security discussions, surfaces risks and can be used throughout the life cycle of the proposal. E.g. include security-relevant design decisions, concerns, important discussions, implementation-specific guidance and pitfalls, an outline of threats and risks and how they are being addressed. EIP submissions missing the "Security Considerations" section will be rejected. An EIP cannot proceed to status "Final" without a Security Considerations discussion deemed sufficient by the reviewers.-->
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](https://creativecommons.org/publicdomain/zero/1.0/).

0 comments on commit 551f883

Please sign in to comment.