Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ERC: Opaque Token #468

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
328 changes: 328 additions & 0 deletions ERCS/erc-7722.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
---
eip: 7722
title: Opaque Token
description: A token specification designed to enhance privacy by concealing balance information.
author: Ivica Aračić (@ivica7), SWIAT
discussions-to: https://ethereum-magicians.org/t/erc-7722-opaque-token/20249
status: Draft
type: Standards Track
category: ERC
created: 2024-06-09
---

## Abstract

This ERC proposes a specification for an opaque token that enhances privacy by concealing balance information. Privacy is achieved by representing balances as off-chain data encapsulated in hashes, referred to as "baskets". These baskets can be reorganized, transferred, and managed through token functions on-chain.

## Motivation

In [ERC-20](./eip-20.html), token balances are transparent on-chain. While some privacy can be achieved by using pseudonyms for the sender and receiver, the risk of revealing the entire portfolio to the public increases when using smart contract accounts like Gnosis Safe or [ERC-725](./eip-725.html). This is particularly problematic in regulated environments where KYC (Know Your Customer) and KYBP (Know Your Business Partner) procedures are required.

This proposal aims to conceal balances on-chain, allowing the use of smart contract accounts to hold tokens without compromising privacy or integrity.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

### Baskets

Balances are represented on-chain as hashes of the form:

```
keccak256(abi.encode(salt, tokenId, value))

// where salt (bytes32) - random 32bytes to increase the entropy and
// make brute-forcing the hash impossible
// tokenId (bytes32) - a unique tokenId within token's smart contract instance
// value (uint256) - the value of the position
```

For the remainder of this document, we refer to these hashes as "baskets" because they conceal the balance information in an opaque manner, similar to how a covered basket hides its contents.

### Token Interface

An opaque token MUST implement the following interface.

```
interface OpaqueToken {
//
// TYPES
//

struct SIGNATURE {
uint8 v; bytes32 r; bytes32 s;
}

struct ORACLECONFIG {
uint8 minNumberOfOracles; // min. number of oracle signatures required for reorg
address[] oracles; // valid oracles
}

//
// EVENTS
//

event CreateToken(address initiatedBy, bytes32 tokenId, bytes32 totalSupplyBasket, bytes32 ref);
event Mint(address initiatedBy, bytes32[] baskets, bytes32 ref);
event ReorgHolderBaskets(address initiatedBy, bytes32[] basketsIn, bytes32[] basketsOut, bytes32 ref);
event ReorgSupplyBaskets(address initiatedBy, bytes32[] basketsIn, bytes32[] basketsOut, bytes32 ref);
event Transfer(address initiatedBy, address receiver, bytes32[] baskets, bytes32 ref);
event Burn(address initiatedBy, bytes32[] baskets, bytes32 ref);

//
// FUNCTIONS
//

/**
* @dev returns the configuration for this token
*/
function oracleConfig() external view returns (ORACLECONFIG memory);

/**
* @dev returns the address of the basket owner
*/
function owner(bytes32 basket) external view returns (address);

/**
* @dev returns the total supply for a `tokenId``
* All token investors are allowed to fetch this value from the token operator's off-chain storage.
*/
function totalSupply(bytes32 tokenId) external view returns (bytes32);

/**
* @dev returns the operator of this token, who is also responsible for providing the main
* off-chain storage source.
*/
function operator() external view returns (address);

/**
* @dev Allows the token operator to create a new token with the specified `tokenId` and an initial
* `totalSupplyBasket`. The `totalSupplyBasket` can be partitioned using {reorgSupplyBaskets} as needed
* when calling {mint}. The `ref` parameter can be used freely by the caller for any reference purpose.
*/
function createToken(
bytes32 tokenId,
bytes32 totalSupplyBasket,
bytes32 ref
) external;

/**
* @dev Allows the token operator to mint tokens by assigning `supplyBaskets` to a `receiver` which
* becomes the owner of these baskets.
*/
function mint(
bytes32[] calldata supplyBaskets,
address receiver,
bytes32 ref
) external;

/**
* @dev transfers `baskets` to a `receiver` who becomes the new owner of these baskets.
*/
function transfer(
bytes32[] calldata baskets,
address receiver,
bytes32 ref
) external;

/**
* @dev reorganizes a set of holder baskets (`basketsIn`) to a new set (`basketsOut`) having
* the same value, i.e., the sum of all values from input baskets equals the sum of values
* in output baskets. In order to ensure the integrity, external oracle service is required that
* will sign the reorg proposal requested by the basket owner, which is passed as `reorgOracleSignatures`.
* The minimum number of oracle signatures is defined in the oracle configuration.
*/
function reorgHolderBaskets(
SIGNATURE[] calldata reorgOracleSignatures,
bytes32[] calldata basketsIn,
bytes32[] calldata basketsOut,
bytes32 ref
) external;

/**
* @dev same as {reorgHolderBaskets}, but for the available supply baskets.
*/
function reorgSupplyBaskets(
SIGNATURE[] calldata reorgOracleSignatures,
bytes32[] calldata basketsIn,
bytes32[] calldata basketsOut,
bytes32 ref
) external;

/**
* @dev burns holder's `baskets` and returns them to available supply
*/
function burn(
bytes32[] calldata baskets,
bytes32 ref
) external;
}
```

### Off-chain Data Endpoints

* The operator of the token (e.g., issuer or registrar) MUST provide the off-chain storage that implements the `GET basket` and `PUT basket` REST endpoints as described in this section.
* The operator MUST ensure the availability of the basket data and will share it on need-to-know basis with all eligible holders, i.e., with all address that either were holding the basket in the past or are currently the holder of the basket.
* To ensure data is only shared with and can be written by eligible holders, the operator MUST implement authentication for both endpoints. The concrete authentication schema is not specified here and my depend on the environment of the token operator.
* The operator MUST allow an existing token holder to `PUT basket`
* The operator MUST allow the current or historical basket holder to `GET basket`
* Token holders SHOULD store a copy of the data about their own baskets in their own off-chain storage for the case that operator's service is unavailable.

REST API Endpoints for creating and querying baskets:

```
Endpoint: PUT baskets
Description: will store baskets if the `basket` hash is matching `data`.
PostData:
[
{
basket: keccak256(abi.encode(salt, tokenId, value)),
data: {
salt: <bytes32>,
tokenId: <bytes32>,
value: <uint256>
}
},
...
]

Endpoint: GET baskets?basket-hash=<bytes32>
Description: will return the list of baskets depending on the query parameters.
Query Parameters:
- basket-hash (optional): returns one basket matching the requested hash
- if no query parameter is set, then the endpoint will return all baskets of the requestor
Response:
[
{
basket: keccak256(abi.encode(salt, tokenId, value)),
data: {
salt: <bytes32>,
tokenId: <bytes32>,
value: <uint256>
}
},
...
]
```

### reorg Endpoint

To ensure the integrity of a reorg and avoid accidental or fraudulent mints or burns, an oracle services is required.

* Oracles MUST provide a `POST reorg` REST Endpoint as described in this section
* Oracles MUST sign any reorg proposal request where
* the sum of values in input baskets grouped by tokenId is equal the sum of values of the output baskets grouped by tokenId.
* `item.basket` hash matches `keccak256(abi.encode(data.salt, data.tokenId, data.value))`
* The reorg endpoint MUST be stateless
* Oracle MUST NOT persist data from the request for later analysis.
* The reorg endpoint SHOULD NOT require authentication and can be used by anyone without restrictions.

```
Endpoint: POST reorg
PostData:
{
in: [
{
basket: keccak256(abi.encode(salt, tokenId, value)),
data: {
salt: <bytes32>,
tokenId: <bytes32>,
value: <uint256>
}
},
...
],
out: [
{
basket: keccak256(abi.encode(salt, tokenId, value)),
data: {
salt: <bytes32>,
tokenId: <bytes32>,
value: <uint256>
}
},
...
]
}

Response: {
// hash is signed with oracles private key
// basketsIn and basketsIn are bytes32[]
signature: sign(keccak256(abi.encode(basketsIn, basketsOut)))
}
```

Example for valid reorg requests (salt and hashes are omitted for better readability):

```
in : (..., token1, 10), (..., token1, 30), (..., token2, 5), (..., token2, 95)
out: (..., token1, 40), (..., token2, 100)

in : (..., token1, 40), (..., token2, 100)
out: (..., token1, 10), (..., token1, 30), (..., token2, 5), (..., token2, 95)
```

### Overlaying Noise (Differential Privacy)

To further enhance privacy and obscure transaction details, an additional layer of noise need to be introduced through reorgs and empty transfers. For example, received baskets can be reorganized into new baskets to prevent information leakage to the previous owner. Additionally, null-value baskets can be sent to random receivers (empty transfers), making it difficult for observers to determine who is transferring to whom.

Example with reorg and null-value basket transfers:
```
A owns basket-a1{..., value:10}
B owns basket-b1{..., value:5}, basket-b2{..., value:15}, ...
A: transfer basket-a1 to B
B: reorg [basket-a1, basket-b1, basket-b2]
to [basket-b3{..., value:10}, basket-b4{..., value:10}, basket-b5:{..., value:10},
basket-b6:{..., value:0}, basket-b7:{..., value:0}]
where sum of inputs is the sum of outputs
B: transfer basket-b5{value:10} to C
B: transfer basket-b6{value:0} to D
B: transfer basket-b7{value:0} to E
```

If B would directly send basket-a1 to C, A would know what C is receiving, however, now that B has reorg'ed the baskets, A can not know anymore what has been sent to C.

Moreover, observers still see who is communicating with whom, but since there is noise introduced, they can not tell which of these transfers are actually transferring real values.

## Rationale

### Breaking the ERC-20 Compatibility

The transparency inherent in ERC-20 tokens presents a significant issue for reusable blockchain identities, such as defined by ERC-725. To address this, we prioritize privacy over ERC-20 compatibility, ensuring the confidentiality of token balances.

### Reorg Oracles

The trusted oracles and the minimum number of required signatures can be configured to achieve the desired level of decentralization.

The basket holder proposes the input and output baskets for the reorg, while the oracles are responsible for verifying that the sums of the values on both sides (input and output) are equal. This system allows for mutual control, ensuring that no single party can manipulate the process.

Fraudulent oracles can be tracked back on-chain, i.e., the system ensures weak-integrity at minimum.

To further strengthen the integrity, it would also be possible to apply Zero-Knowledge Proofs (ZKP) to provide reorg proofs, however, we have chosen to use oracles for efficiency and simplicity reasons.

### Off-chain Data Storage

We have chosen the token operator, which in most cases will be the issuer or registrar, as the initial and main source for off-chain data. This is acceptable, since they must know anyway which investor holds which positions to manage lifecycle events on the token. While this approach may not be suitable for every use case within the broader Ethereum ecosystem, it fits well the financial instruments in the regulated environment of the financial industry, which rely on strict KYC and token operation procedures.

## Backwards Compatibility

* Opaque Token is not compatible with ERC-20 for reasons explained in the Rationale section.

## Reference Implementation

To be included as inline code or in `../assets/eip-####/`.

## Security Considerations

### Fraudulent Oracles
To be discussed...

### Oracles Collecting Confidential Data
To be discussed...

### Confidential Data Loss
To be discussed...

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
Loading