机密资产(CA)
机密资产(Confidential Asset,CA) 标准是 Aptos 上面向同质化资产(FA)的隐私协议。它允许用户隐藏链上的转账金额和持有余额,同时保持发送方与接收方地址公开可见。
任何非 dispatchable FA 均可封装为机密余额。协议支持最大 2⁶⁴ − 1 的转账金额和最大 2¹²⁸ − 1 的余额。
每个 (用户, 代币) 对会生成一套独立的密钥对:
- 解密密钥(DK):用户持有的私钥标量,永远不离开客户端。
- 加密密钥(EK):由 DK 派生并存储在链上,供他人为该用户加密金额。
关系为 EK = DK⁻¹ · H,其中 H 为 Ristretto255 哈希基点。
这套密钥与用户的 Aptos 账户签名密钥完全独立。
待处理余额与可用余额
Section titled “待处理余额与可用余额”每个机密存储包含两个加密余额:
pending_balance(待处理余额):接收所有入账存款和转账。available_balance(可用余额):可花费余额,用于出账转账和提款。
资金首先进入 pending_balance,用户必须调用 rollover_pending_balance 将其移入 available_balance 后才能使用。
Twisted ElGamal 加密
Section titled “Twisted ElGamal 加密”每个余额被拆分为 16 位块,每个块用 Twisted ElGamal 在 Ristretto255 上加密:
P_i = v_i · G + r_i · H (承诺)R_i = r_i · EK (密钥密文)G为 Ristretto255 基点,H为哈希基点。- 持有者通过计算
v_i · G = P_i − DK · R_i并求解离散对数完成解密。 - 由于加密具有加法同态性,新存款可直接加到密文上,无需解密。
| 余额类型 | 块数 | 总位数 | 最大值 |
|---|---|---|---|
pending_balance | 4 × 16 位 | 64 | 2⁶⁴ − 1 |
available_balance | 8 × 16 位 | 128 | 2¹²⁸ − 1 |
归一化(Normalization)
Section titled “归一化(Normalization)”同态加法可能导致块的值超过 16 位。归一化会将 available_balance 的每个块重新压缩到 [0, 2¹⁶) 区间。归一化在 rollover 之前是必需的,且需要提交链上可验证的 ZKP。
enum ConfidentialStore has key { V1 { pending_balance: CompressedBalance<Pending>, available_balance: CompressedBalance<Available>, ek: CompressedRistretto, // 加密密钥(公开) pending_balance_count: u32, // 达到 MAX_TRANSFERS_BEFORE_ROLLOVER 时必须 rollover pause_incoming: bool, // 暂停接收转账(密钥轮换前需启用) }}MAX_TRANSFERS_BEFORE_ROLLOVER = 65536:若 pending_balance_count 达到上限,下一笔入账转账前用户必须先调用 rollover_pending_balance。
除 deposit 外,所有修改状态的操作都需要 ZKP,分两类:
Sigma 协议证明(Schnorr 风格)
Section titled “Sigma 协议证明(Schnorr 风格)”四种 NP 关系,各用 Schnorr 风格的 Σ-协议证明:
| 操作 | NP 关系 | 证明内容 |
|---|---|---|
register_balance | R_reg | EK = DK⁻¹ · H(用户知道 DK) |
withdraw_to | R_withdraw | 提款金额与密文匹配;余额保持非负 |
confidential_transfer | R_transfer | 发送金额与接收方密文匹配;余额守恒 |
rotate_encryption_key | R_keyrot | 新 EK = δ · 旧 EK;R 分量已用新密钥重新加密 |
Bulletproof 范围证明
Section titled “Bulletproof 范围证明”每次花费/归一化操作都包含一个 Bulletproof 范围证明,证明每个余额块 ∈ [0, 2¹⁶)。
- DST:
b"AptosConfidentialAsset/BulletproofRangeProof" - 在链上通过
ristretto255_bulletproofs原生模块批量验证。
审计员系统允许指定方读取机密金额。每笔转账都包含为相应审计员加密的金额密文,链上验证其正确性。
审计员优先级
Section titled “审计员优先级”| 优先级 | 类型 | 说明 |
|---|---|---|
| 最高 | 资产审计员 | 由资产创建者按代币设置,覆盖全局审计员 |
| 默认 | 全局审计员 | 由 Aptos 治理设置,适用于所有未配置资产审计员的代币 |
| 可选 | 自愿审计员 | 由发送方按笔添加,数量不限 |
当配置了有效审计员(全局或资产级)时,其参与是强制性的。链上证明验证器会确保对应审计员的密文正确构造。
审计员 Epoch 追踪
Section titled “审计员 Epoch 追踪”available_balance 存储了在上次 rollover 时用当时审计员 EK 加密的 R_aud 分量。当审计员密钥变更(epoch 递增)时,用户必须 rollover 以刷新该分量;链上验证器会强制 epoch 一致性。
| 设置 | 控制方 | 效果 |
|---|---|---|
| 白名单 | 治理 | 仅白名单内的 FA 类型可使用 CA;白名单为空时不限制 |
| 紧急暂停 | 治理 | 暂停除存款和提款外的所有操作 |
| 全局审计员 | 治理 | 设置/更换强制全局审计员 EK |
| 资产审计员 | FA 创建者 | 设置/更换强制按代币审计员 EK |
所有入口函数均在 aptos_framework::confidential_asset 中。_raw 后缀表示接受原始字节向量(用于证明数据)而非类型化结构体,这是当前稳定 API。
register_balance_raw
Section titled “register_balance_raw”为 (signer, token) 创建 ConfidentialStore 并将 EK 存储到链上。
public entry fun register_balance_raw( sender: &signer, token: Object<Metadata>, ek: vector<u8>, // 32 字节压缩 Ristretto255 点 proof_bytes: vector<u8>, // R_reg 的序列化 sigma 证明)deposit
Section titled “deposit”将代币从 signer 的公开 FA 存储移入 pending_balance。不需要 ZKP。
public entry fun deposit( sender: &signer, token: Object<Metadata>, to: address, amount: u64,)rollover_pending_balance
Section titled “rollover_pending_balance”同态地将 pending_balance 加入 available_balance,并将 pending_balance 重置为零。可用余额必须先完成归一化。
public entry fun rollover_pending_balance( sender: &signer, token: Object<Metadata>, new_balance: ConfidentialAvailableBalance, proof: SigmaProofWithdraw, zkrp: RangeProof, pause_incoming: bool,)withdraw_to_raw
Section titled “withdraw_to_raw”从 available_balance 解密一笔金额并发送到公开 FA 存储。
public entry fun withdraw_to_raw( sender: &signer, token: Object<Metadata>, to: address, amount: u64, new_balance: vector<vector<u8>>, // 剩余可用余额字节 proof_bytes: vector<u8>, // R_withdraw sigma 证明 zkrp_bytes: vector<u8>, // Bulletproof 范围证明)normalize_raw
Section titled “normalize_raw”通过 ZKP 将 available_balance 的各块重新压缩到 16 位范围。
public entry fun normalize_raw( sender: &signer, token: Object<Metadata>, new_balance: vector<vector<u8>>, proof_bytes: vector<u8>, zkrp_bytes: vector<u8>,)confidential_transfer_raw
Section titled “confidential_transfer_raw”将隐私金额从发送方的 available_balance 转移到接收方的 pending_balance。金额永远不会在链上公开。
public entry fun confidential_transfer_raw( sender: &signer, token: Object<Metadata>, recipient: address, new_balance: vector<vector<u8>>, transfer_amount: vector<vector<u8>>, // 用接收方 EK 加密的金额密文 proof_bytes: vector<u8>, // R_transfer sigma 证明 zkrp_bytes: vector<u8>, // Bulletproof 范围证明 memo: vector<u8>, // 可选;最大 256 字节;未加密)rotate_encryption_key_raw
Section titled “rotate_encryption_key_raw”用新 EK 替换链上存储的 EK,并用新密钥重新加密 available_balance 的 R 分量。需要 pause_incoming = true 且 pending_balance 为零。
public entry fun rotate_encryption_key_raw( sender: &signer, token: Object<Metadata>, new_ek: vector<u8>, new_balance: vector<vector<u8>>, proof_bytes: vector<u8>, zkrp_bytes: vector<u8>,)| 函数 | 返回值 | 说明 |
|---|---|---|
get_encryption_key | Option<vector<u8>> | 用户当前的代币 EK |
has_user_registered | bool | (user, token) 的机密存储是否存在 |
get_pending_balance | 原始字节 | 加密的待处理余额 |
get_available_balance | 原始字节 | 加密的可用余额 |
get_pending_balance_count | u32 | 上次 rollover 后的待处理入账次数 |
is_balance_normalized | bool | 可用余额是否处于 16 位正常形式 |
is_incoming_paused | bool | 是否已暂停接收转账 |
get_global_auditor | Option<vector<u8>> | 全局审计员 EK(治理设置后有值) |
get_asset_auditor | Option<vector<u8>> | 资产级审计员 EK(创建者设置后有值) |
is_emergency_paused | bool | 治理是否已触发紧急暂停 |
get_total_confidential_supply | u128 | 当前所有机密存储中锁定的代币总量 |
- 不支持 dispatchable FA。 只有非 dispatchable 的同质化资产才能使用 CA。带有自定义转账分发函数的 dispatchable FA 在注册时会被
is_safe_for_confidentiality拒绝。 - 地址公开。 每笔交易的发送方和接收方地址以明文形式出现在链上。
- 总供应量公开。
get_total_confidential_supply会揭示当前处于机密模式的代币数量。