ERC-7722: 不透明代币
一种旨在通过隐藏余额信息来增强隐私的代币规范。
Authors | Ivica Aračić (@ivica7), Ante Bešlić (@ethSplit), Mirko Katanić (@mkatanic), SWIAT |
---|---|
Created | 2024-06-09 |
Discussion Link | https://ethereum-magicians.org/t/erc-7722-opaque-token/20249 |
Table of Contents
摘要
本 ERC 提出了一种不透明代币的规范,该代币通过隐藏余额信息来增强隐私。隐私的实现方式是将余额表示为封装在哈希值中的链下数据,这些哈希值被称为“篮子(baskets)”。这些篮子可以通过链上的代币功能进行重组、转移和管理。
动机
智能合约账户可以作为定义明确的身份,可以附加可重用的声明和认证,使其在各种应用中非常有用。然而,当这些身份用于持有代币时,这种优势也带来了一个重大的隐私挑战。具体来说,对于与 ERC-20 兼容的代币,余额直接以明文形式存储在链上,这些余额的透明性可能会损害账户持有者的隐私。这就产生了一个两难境地:虽然与智能合约账户相关的声明和认证的重用可能是有利的,但它也增加了暴露敏感财务信息的风险,特别是当这些定义明确的身份与公开可见的代币持有量相关联时。
本提案旨在隐藏链上的余额,允许使用智能合约账户持有代币,而不会损害隐私或完整性。
规范
本文档中的关键词“必须(MUST)”、“禁止(MUST NOT)”、“需要(REQUIRED)”、“应(SHALL)”、“不应(SHALL NOT)”、“应该(SHOULD)”、“不应该(SHOULD NOT)”、“推荐(RECOMMENDED)”、“不推荐(NOT RECOMMENDED)”、“可以(MAY)”和“可选(OPTIONAL)”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
该概念围绕着将代币余额在链上表示为哈希值(称为篮子),这些哈希值模糊了实际的余额信息。这些篮子结合了一个随机盐、一个唯一的代币 ID 和代币的值,使得无法直接从区块链中推导出代币的值。
代币接口允许创建、转移、发行和重组(拆分和加入)这些篮子。为了防止未经授权的更改并保持完整性,预言机服务会验证在重组期间篮子的总价值是否保持一致。此外,差分隐私技术(如覆盖噪声和空转移)通过使追踪代币动向和确定实际交易细节变得困难,进一步保护隐私。
篮子
余额在链上表示为以下形式的哈希值:
keccak256(abi.encode(salt, tokenId, value))
// 其中 salt (bytes32) - 随机 32 字节,用于增加熵
// 使暴力破解哈希变得不可能
// tokenId (bytes32) - 代币智能合约实例中的唯一 tokenId
// value (uint256) - 仓位的值
在本文档的其余部分,我们将这些哈希值称为“篮子”,因为它们以不透明的方式隐藏了余额信息,类似于有盖的篮子隐藏其内容的方式。
代币接口
不透明代币必须实现以下接口。
interface OpaqueToken {
//
// 类型
//
struct SIGNATURE {
uint8 v; bytes32 r; bytes32 s;
}
struct ORACLECONFIG {
uint8 minNumberOfOracles; // 重组所需的最小预言机签名数
address[] oracles; // 有效预言机
}
//
// 事件
//
/**
* @dev 创建新代币时必须发出
* @param initiatedBy 创建并控制代币的地址
* @param tokenId 代币的标识符
* @param totalSupplyBasket 初始供应篮子,包含代币的总供应量
* @param ref 发起人使用的自定义引用
*/
event CreateToken(address initiatedBy, bytes32 tokenId, bytes32 totalSupplyBasket, bytes32 ref);
/**
* @dev 发行时必须发出
* @param initiatedBy 发起发行的地址
* @param baskets 发行给接收者的篮子
* @param receiver 接收篮子的地址
* @param ref 发起人使用的自定义引用
*/
event Issue(address initiatedBy, bytes32[] baskets, address receiver, bytes32 ref);
/**
* @dev 持有者篮子重组时必须发出
* @param initiatedBy 发起重组的地址和所有篮子的所有者
* @param basketsIn 重组且不再存在的篮子
* @param basketsOut 新创建的篮子
* @param ref 发起人使用的自定义引用
*/
event ReorgHolderBaskets(address initiatedBy, bytes32[] basketsIn, bytes32[] basketsOut, bytes32 ref);
/**
* @dev 供应篮子重组时必须发出
* @param initiatedBy 发起重组的地址
* @param basketsIn 重组且不再存在的供应篮子
* @param basketsOut 新创建的篮子
* @param ref 发起人使用的自定义引用
*/
event ReorgSupplyBaskets(address initiatedBy, bytes32[] basketsIn, bytes32[] basketsOut, bytes32 ref);
/**
* @dev 篮子从一个地址转移到另一个地址时必须发出
* @param initiatedBy 发起转移的地址
* @param receiver 作为篮子新所有者的地址
* @param baskets 转移的篮子
* @param ref 发起人使用的自定义引用
*/
event Transfer(address initiatedBy, address receiver, bytes32[] baskets, bytes32 ref);
/**
* @dev 赎回时必须发出
* @param initiatedBy 发起赎回的地址
* @param baskets 赎回的篮子
* @param ref 发起人使用的自定义引用
*/
event Redeem(address initiatedBy, bytes32[] baskets, bytes32 ref);
//
// 函数
//
/**
* @dev 返回此代币的配置
*/
function oracleConfig() external view returns (ORACLECONFIG memory);
/**
* @dev 返回篮子所有者的地址
*/
function owner(bytes32 basket) external view returns (address);
/**
* @dev 返回 `tokenId` 的总供应量
* 允许所有代币投资者从代币运营商的链下存储中获取此值。
*/
function totalSupply(bytes32 tokenId) external view returns (bytes32);
/**
* @dev 返回此代币的运营商,该运营商也负责提供主要的链下存储源。
*/
function operator() external view returns (address);
/**
* @dev 允许代币运营商创建具有指定 `tokenId` 和初始 `totalSupplyBasket` 的新代币。
* 当调用 {issue} 时,可以根据需要使用 {reorgSupplyBaskets} 对 `totalSupplyBasket` 进行分区。
* `ref` 参数可以由调用者自由使用,用于任何引用目的。
*/
function createToken(
bytes32 tokenId,
bytes32 totalSupplyBasket,
bytes32 ref
) external;
/**
* @dev 允许代币运营商通过将 `supplyBaskets` 分配给 `receiver` 来发行代币,
* `receiver` 成为这些篮子的所有者。
*/
function issue(
bytes32[] calldata supplyBaskets,
address receiver,
bytes32 ref
) external;
/**
* @dev 将 `baskets` 转移到 `receiver`,`receiver` 成为这些篮子的新所有者。
*/
function transfer(
bytes32[] calldata baskets,
address receiver,
bytes32 ref
) external;
/**
* @dev 将一组持有者篮子 (`basketsIn`) 重组为一组新的篮子 (`basketsOut`),
* 具有相同的值,即,输入篮子中所有值的总和等于输出篮子中值的总和。
* 为了确保完整性,需要外部预言机服务,该服务将签署篮子所有者请求的重组提案,
* 该提案作为 `reorgOracleSignatures` 传递。
* 预言机签名的最小数量在预言机配置中定义。
*/
function reorgHolderBaskets(
SIGNATURE[] calldata reorgOracleSignatures,
bytes32[] calldata basketsIn,
bytes32[] calldata basketsOut,
bytes32 ref
) external;
/**
* @dev 与 {reorgHolderBaskets} 相同,但适用于可用的供应篮子。
*/
function reorgSupplyBaskets(
SIGNATURE[] calldata reorgOracleSignatures,
bytes32[] calldata basketsIn,
bytes32[] calldata basketsOut,
bytes32 ref
) external;
/**
* @dev 赎回持有者的 `baskets` 并将它们返回到可用供应中
*/
function redeem(
bytes32[] calldata baskets,
bytes32 ref
) external;
}
用户角色
不透明代币中有两个角色:
- 代币运营商:创建代币并发行其中的仓位,并控制其非流通供应(保留在供应篮子中)的人。将使用 createToken、reorgSupplyBasket 和 issue 函数。还能够通过 forceTransfer 和 forceReorg 函数强制执行操作。
- 代币用户:持有流通代币(保留在拥有的篮子中)的地址。将使用 reorgSupplyBaskets、transfer 和 redeem 函数。
链下数据端点
- 代币的运营商(例如,发行人或注册机构)必须提供链下存储,该存储实现本节中描述的
GET basket
和PUT basket
REST 端点。 - 运营商必须确basket数据的可用性,并将根据需要与所有符合条件的持有者共享,即与过去持有basket或当前是basket持有者的所有地址共享。
- 为了确保数据仅与符合条件的持有者共享并可由其写入,运营商必须为两个端点实施身份验证。这里没有指定具体的身份验证模式,它可能取决于代币运营商的环境。
- 运营商必须允许现有代币持有者
PUT basket
- 运营商必须允许当前或历史 basket 持有者
GET basket
- 代币持有者应该在自己的链下存储中存储关于自己basket数据的副本,以防运营商的服务不可用。
用于创建和查询 basket 的 REST API 端点:
Endpoint: PUT baskets
Description: will store baskets if the `basket` hash is matching `data`.
PostData:
[
{
篮子: 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:
[
{
篮子: keccak256(abi.encode(salt, tokenId, value)),
data: {
salt: <bytes32>,
tokenId: <bytes32>,
value: <uint256>
}
},
...
]
reorg 端点
为了确保重组的完整性并避免意外或欺诈性的发行或赎回,需要预言机服务。
- 预言机必须提供本节中描述的
POST reorg
REST 端点 - 预言机必须签署任何重组提案请求,其中
- 按 tokenId 分组的输入篮子中值的总和等于按 tokenId 分组的输出篮子中值的总和。
item.basket
哈希与keccak256(abi.encode(data.salt, data.tokenId, data.value))
匹配
- reorg 端点必须是无状态的
- 预言机不得保留请求中的数据以供以后分析。
- reorg 端点不应要求身份验证,并且可以由任何人无限制地使用。
Endpoint: POST reorg
PostData:
{
in: [
{
篮子: keccak256(abi.encode(salt, tokenId, value)),
data: {
salt: <bytes32>,
tokenId: <bytes32>,
value: <uint256>
}
},
...
],
out: [
{
篮子: 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[]
签名: sign(keccak256(abi.encode(basketsIn, basketsOut)))
}
有效重组请求的示例(为了更好地可读性,省略了盐和哈希):
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)
覆盖噪声(差分隐私)
为了进一步增强隐私并模糊交易细节,需要通过重组和空转移引入额外的噪声层。例如,可以将收到的篮子重组为新的篮子,以防止信息泄漏给以前的所有者。此外,可以将空值篮子发送给随机接收者(空转移),使观察者难以确定谁在转移给谁。
带有重组和空值篮子转移的示例:
A 拥有 basket-a1{..., value:10}
B 拥有 basket-b1{..., value:5}, basket-b2{..., value:15}, ...
A: 将 basket-a1 转移到 B
B: 将 [basket-a1, basket-b1, basket-b2] 重组
到 [basket-b3{..., value:10}, basket-b4{..., value:10}, basket-b5:{..., value:10},
basket-b6:{..., value:0}, basket-b7:{..., value:0}]
其中输入的总和等于输出的总和
B: 将 basket-b5{value:10} 转移到 C
B: 将 basket-b6{value:0} 转移到 D
B: 将 basket-b7{value:0} 转移到 E
如果 B 直接将 basket-a1 发送到 C,A 就会知道 C 正在接收什么,但是,现在 B 已经重组了 basket,A 就无法再知道发送给 C 的是什么了。
此外,观察者仍然可以看到谁在与谁通信,但由于引入了噪声,他们无法分辨这些转移中哪些实际上是在转移真实值。
理由
打破 ERC-20 兼容性
ERC-20 代币固有的透明性为可重用的区块链身份提出了一个重大问题。为了解决这个问题,我们优先考虑隐私而不是 ERC-20 兼容性,以确保代币余额的机密性。
重组预言机
可以配置受信任的预言机和所需的最小签名数量,以达到所需的去中心化水平。
篮子持有者提出重组的输入和输出篮子,而预言机负责验证双方(输入和输出)的值的总和是否相等。该系统允许相互控制,确保任何一方都不能操纵该过程。
可以在链上追踪到欺诈性预言机,即,该系统至少确保了弱完整性。
为了进一步加强完整性,还可以应用零知识证明 (ZKP) 来提供重组证明,但是,出于效率和简单性的原因,我们选择使用预言机。
链下数据存储
我们选择了代币运营商(在大多数情况下是发行人或注册机构)作为链下数据的初始和主要来源。这是可以接受的,因为无论如何他们都必须知道哪个投资者持有哪个仓位,以便管理代币的生命周期事件。虽然这种方法可能不适用于更广泛的 Ethereum 生态系统中的每个用例,但它非常适合金融行业的受监管环境中的金融工具,这些工具依赖于严格的 KYC 和代币操作程序。
向后兼容性
- 不透明代币与 ERC-20 不兼容,原因已在“理由”一节中解释。
安全考虑
欺诈性预言机
预言机收集机密数据
机密数据丢失
版权
在 CC0 下放弃版权及相关权利。
Citation
Please cite this document as:
Ivica Aračić (@ivica7), Ante Bešlić (@ethSplit), Mirko Katanić (@mkatanic), SWIAT, "ERC-7722: 不透明代币 [DRAFT]," Ethereum Improvement Proposals, no. 7722, June 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7722.