跳转到内容

Confidential Asset (CA)

Aptos 机密资产标准(也称为”机密资产”或”CA”)是一种用于管理同质资产(FA)的隐私协议. 它允许用户在隐藏 FA 金额的同时进行交易,同时保持发件人和收件人地址公开可见.

此标准允许任何 FA 包装成相应的机密资产,确保与现有代币的兼容性. 它支持 64 位传输,余额最高可达 128 位.

对机密资产余额(机密余额)的操作需要零知识证明(ZKP)来验证交易的正确性 而不透露隐藏金额和其他敏感数据.

对于每个注册的机密资产,用户会生成一个唯一的密钥对:

  • 存储在链上的加密密钥(EK).
  • 用户安全保管的解密密钥(DK).

这些密钥是独立的,不应与用户的 Aptos 账户密钥混淆.

每个机密余额分为两部分:

  • pending_balance - 累积所有传入交易.
  • actual_balance - 专用于传出交易.

两个余额都使用相同的用户 EK 加密,确保底层金额保持私密.

机密余额及其相关的加密密钥存储在 ConfidentialAssetStore 资源中. ConfidentialAssetStore 为用户拥有的每个机密资产实例化,并由 confidential_asset 模块管理:

struct ConfidentialAssetStore has key {
pending_balance: confidential_balance::CompressedConfidentialBalance,
actual_balance: confidential_balance::CompressedConfidentialBalance,
ek: twisted_elgamal::CompressedPubkey,
// ...
}

机密余额通过将代币金额分割成称为块的小单位来处理. 每个块代表总金额的一部分,并使用用户的 EK 单独加密. 这种设计确保了余额的高效管理.

待处理余额由四个块组成,持有所有传入转账. 它可以在需要滚动到实际余额之前处理多达 2^16 个 64 位转账. 在此累积过程中,待处理余额块可以增长到每个 32 位.

实际余额由八个块组成,支持 128 位值. 在任何操作之后,实际余额应归一化回 16 位块以保持高效解密.

confidential_balance 模块中的 ConfidentialBalance 结构用于表示待处理和实际余额:

struct ConfidentialBalance has drop {
chunks: vector<twisted_elgamal::Ciphertext>,
}

加密包括:

  • 将总金额分割成 16 位块.
  • 应用用户的 EK 单独加密每个块.

解密包括:

  • 应用用户的 DK 解密每个块.
  • 为每个块解决离散对数(DL)问题以恢复原始值.
  • 组合恢复的值以重建总金额.

归一化确保块始终减少到可管理的大小(16 位). 如果没有归一化,块可能会增长过大,使解密过程(解决 DL)显著变慢甚至不切实际. 此机制在每次操作后自动应用于实际余额, 确保用户始终可以解密其余额,即使余额通过多次交易增长. 只有在滚动后,用户才需要手动归一化实际余额.

该协议利用同态加密,允许对机密余额进行算术运算而无需解密. 此功能对于在转账期间更新接收者的待处理余额和滚动时至关重要, 用户的待处理余额被添加到实际余额中.

下图显示了机密资产模块之间的关系:

CA Modules Relationship CA Modules Relationship

用户与 confidential_asset 模块交互以对其机密余额执行操作. confidential_asset 模块调用 confidential_balance 模块来管理机密余额,并调用 confidential_proof 模块来验证 ZKP. 在底层,confidential_balance 模块使用 twisted_elgamal 模块对块进行操作.

public entry fun register(sender: &signer, token: Object<Metadata>, ek: vector<u8>)
#[view]
public fun has_confidential_asset_store(user: address, token: Object<Metadata>): bool

用户必须为他们打算交易的每个代币注册一个 ConfidentialAssetStore. 在此过程中,用户需要在其端生成一个密钥对(EK 和 DK).

ConfidentialAssetStore 首次注册时,机密余额设置为零, 表示为 pending_balanceactual_balance 的零密文.

您还可以使用 has_confidential_asset_store 函数检查用户是否拥有特定代币的 ConfidentialAssetStore.

#[test_only]
module confidential_asset_addr::register_example {
/// ...
fun register(bob: &signer, token: Object<Metadata>) {
let bob_addr = signer::address_of(bob);
// 这是一个仅用于测试的函数,因此我们不需要担心密钥对的安全性。
let (_bob_dk, bob_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let bob_ek = twisted_elgamal::pubkey_to_bytes(&bob_ek);
confidential_asset::register(bob, token, bob_ek);
print(&utf8(b"Bob's pending balance is a zero ciphertext:"));
print(&confidential_asset::pending_balance(bob_addr, token));
print(&utf8(b"Bob's actual balance is a zero ciphertext:"));
print(&confidential_asset::actual_balance(bob_addr, token));
print(&utf8(b"Bob's encryption key is set:"));
print(&confidential_asset::encryption_key(bob_addr, token));
}
}
public entry fun deposit(sender: &signer, token: Object<Metadata>, amount: u64)
public entry fun deposit_to(sender: &signer, token: Object<Metadata>, to: address, amount: u64)

depositdeposit_to 函数将代币带入协议,从发送者的主要 FA 存储转移传递的金额 到接收者的待处理余额.

此函数中的金额是公开可见的,因为将新代币添加到协议中需要正常转账. 然而,协议中的代币通过机密转账变得模糊,确保后续交易的隐私.

#[test_only]
module confidential_asset_addr::deposit_example {
/// ...
fun deposit(bob: &signer, alice: &signer, token: Object<Metadata>) {
let bob_addr = signer::address_of(bob);
let alice_addr = signer::address_of(alice);
// 这是一个仅用于测试的函数,因此我们不需要担心密钥对的安全性。
let (bob_dk, bob_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let (alice_dk, alice_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let bob_ek = twisted_elgamal::pubkey_to_bytes(&bob_ek);
let alice_ek = twisted_elgamal::pubkey_to_bytes(&alice_ek);
confidential_asset::register(bob, token, bob_ek);
confidential_asset::register(alice, token, alice_ek);
print(&utf8(b"Bob's FA balance before the deposit is 500:"));
print(&primary_fungible_store::balance(bob_addr, token));
assert!(primary_fungible_store::balance(bob_addr, token) == 500);
let bob_amount = 100;
let alice_amount = 200;
// 余额尚未隐藏,因为我们明确将金额传递给函数。
confidential_asset::deposit(bob, token, bob_amount);
confidential_asset::deposit_to(bob, token, alice_addr, alice_amount);
print(&utf8(b"Bob's FA balance after the deposit is 200:"));
print(&primary_fungible_store::balance(bob_addr, token));
assert!(primary_fungible_store::balance(bob_addr, token) == 200);
print(&utf8(b"Bob's pending balance is not zero:"));
print(&confidential_asset::pending_balance(bob_addr, token));
// 在现实世界中,我们无法看到其他人的余额,因为这需要
// 解密密钥的知识。
// 余额解密需要解决离散对数问题,
// 因此我们只是简单地检查传递的金额是否正确。
assert!(confidential_asset::verify_pending_balance(bob_addr, token, &bob_dk, bob_amount));
print(&utf8(b"Alice's pending balance is not zero:"));
print(&confidential_asset::pending_balance(alice_addr, token));
assert!(confidential_asset::verify_pending_balance(alice_addr, token, &alice_dk, alice_amount));
}
}
public entry fun rollover_pending_balance(sender: &signer, token: Object<Metadata>)

rollover_pending_balance 函数将待处理余额添加到实际余额中,将待处理余额重置为零. 它无需额外证明即可工作,因为此函数利用了协议中使用的同态加密的属性.

#[test_only]
module confidential_asset_addr::rollover_example {
/// ...
fun rollover(bob: &signer, token: Object<Metadata>) {
let bob_addr = signer::address_of(bob);
// 这是一个仅用于测试的函数,因此我们不需要担心密钥对的安全性。
let (bob_dk, bob_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let bob_ek = twisted_elgamal::pubkey_to_bytes(&bob_ek);
let bob_amount = 100;
confidential_asset::register(bob, token, bob_ek);
confidential_asset::deposit(bob, token, bob_amount);
print(&utf8(b"Bob's pending balance is NOT zero:"));
print(&confidential_asset::pending_balance(bob_addr, token));
print(&utf8(b"Bob's actual balance is zero:"));
print(&confidential_asset::actual_balance(bob_addr, token));
assert!(confidential_asset::verify_pending_balance(bob_addr, token, &bob_dk, bob_amount));
assert!(confidential_asset::verify_actual_balance(bob_addr, token, &bob_dk, 0));
// 不需要显式归一化,因为实际余额已经归一化。
assert!(confidential_asset::is_normalized(bob_addr, token));
confidential_asset::rollover_pending_balance(bob, token);
print(&utf8(b"Bob's pending balance is zero:"));
print(&confidential_asset::pending_balance(bob_addr, token));
print(&utf8(b"Bob's actual balance is NOT zero:"));
print(&confidential_asset::actual_balance(bob_addr, token));
assert!(confidential_asset::verify_pending_balance(bob_addr, token, &bob_dk, 0));
assert!(confidential_asset::verify_actual_balance(bob_addr, token, &bob_dk, (bob_amount as u128)));
}
}
public entry fun confidential_transfer(
sender: &signer,
token: Object<Metadata>,
to: address,
new_balance: vector<u8>,
transfer_amount: vector<u8>,
auditor_eks: vector<u8>,
auditor_amounts: vector<u8>,
zkrp_new_balance: vector<u8>,
zkrp_transfer_amount: vector<u8>,
sigma_proof: vector<u8>)

confidential_transfer 函数将代币从发送者的实际余额转移到接收者的 待处理余额.发送者使用接收者的加密密钥加密转移的金额,使接收者的 机密余额能够同态更新.

为了确保透明性,发送者还可以使用审计员的 EK 加密转移的金额, 允许审计员在其端解密转移的金额.

#[test_only]
module confidential_asset_addr::transfer_example {
/// ...
fun transfer(bob: &signer, alice: &signer, token: Object<Metadata>) {
let bob_addr = signer::address_of(bob);
let alice_addr = signer::address_of(alice);
// 这是一个仅用于测试的函数,因此我们不需要担心密钥对的安全性。
let (bob_dk, bob_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let (alice_dk, alice_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
// 注意:如果设置了资产特定的审计员,我们需要将其包含在 `auditor_eks` 向量中作为第一个元素。
//
// let asset_auditor_ek = confidential_asset::get_auditor(token);
// let auditor_eks = vector[];
// if (asset_auditor_ek.is_some()) {
// auditor_eks.push_back(asset_auditor_ek.extract());
// };
let (_, auditor_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let auditor_eks = vector[auditor_ek];
let bob_ek_bytes = twisted_elgamal::pubkey_to_bytes(&bob_ek);
let alice_ek_bytes = twisted_elgamal::pubkey_to_bytes(&alice_ek);
confidential_asset::register(bob, token, bob_ek_bytes);
confidential_asset::register(alice, token, alice_ek_bytes);
// Bob 的当前余额为 300,他想转账 50 给 Alice,Alice 的余额为零。
let bob_current_amount = 300;
let bob_new_amount = 250;
let transfer_amount = 50;
let alice_current_amount = 0;
let alice_new_amount = 50;
confidential_asset::deposit(bob, token, bob_current_amount);
confidential_asset::rollover_pending_balance(bob, token);
print(&utf8(b"Bob's actual balance is 300"));
assert!(confidential_asset::verify_actual_balance(bob_addr, token, &bob_dk, (bob_current_amount as u128)));
print(&utf8(b"Alice's pending balance is zero"));
assert!(confidential_asset::verify_pending_balance(alice_addr, token, &alice_dk, alice_current_amount));
let current_balance = confidential_balance::decompress_balance(&confidential_asset::actual_balance(bob_addr, token));
let (
proof,
// 新余额是转账后使用发送者的加密密钥加密的余额。
// 它将被设置为发送者的新实际余额。
new_balance,
// 使用接收者的加密密钥加密的转账金额。
// 它将同态地添加到接收者的待处理余额中。
transfer_amount,
// 使用审计员的加密密钥加密的转账金额。
// 它不会存储在链上,但审计员可以使用其 dk 解密转账金额。
auditor_amounts
) = confidential_proof::prove_transfer(
&bob_dk,
&bob_ek,
&alice_ek,
transfer_amount,
bob_new_amount,
&current_balance,
&auditor_eks,
);
let (
sigma_proof,
zkrp_new_balance,
zkrp_transfer_amount
) = confidential_proof::serialize_transfer_proof(&proof);
confidential_asset::confidential_transfer(
bob,
token,
alice_addr,
confidential_balance::balance_to_bytes(&new_balance),
confidential_balance::balance_to_bytes(&transfer_amount),
confidential_asset::serialize_auditor_eks(&auditor_eks),
confidential_asset::serialize_auditor_amounts(&auditor_amounts),
zkrp_new_balance,
zkrp_transfer_amount,
sigma_proof
);
print(&utf8(b"Bob's actual balance is 250"));
assert!(confidential_asset::verify_actual_balance(bob_addr, token, &bob_dk, bob_new_amount));
print(&utf8(b"Alice's pending balance is 50"));
assert!(confidential_asset::verify_pending_balance(alice_addr, token, &alice_dk, alice_new_amount));
}
}
public entry fun withdraw(
sender: &signer,
token: Object<Metadata>,
amount: u64,
new_balance: vector<u8>,
zkrp_new_balance: vector<u8>,
sigma_proof: vector<u8>)
public entry fun withdraw_to(
sender: &signer,
token: Object<Metadata>,
to: address,
amount: u64,
new_balance: vector<u8>,
zkrp_new_balance: vector<u8>,
sigma_proof: vector<u8>)

withdrawwithdraw_to 允许用户从协议中提取代币, 将传递的金额从发送者的实际余额转移到接收者的主要 FA 存储. 此函数使用户能够释放代币,同时不透露其剩余余额.

#[test_only]
module confidential_asset_addr::withdraw_example {
/// ...
fun withdraw(bob: &signer, alice: &signer, token: Object<Metadata>) {
let bob_addr = signer::address_of(bob);
let alice_addr = signer::address_of(alice);
// 这是一个仅用于测试的函数,因此我们不需要担心密钥对的安全性。
let (bob_dk, bob_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let (_alice_dk, alice_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let bob_ek_bytes = twisted_elgamal::pubkey_to_bytes(&bob_ek);
let alice_ek_bytes = twisted_elgamal::pubkey_to_bytes(&alice_ek);
confidential_asset::register(bob, token, bob_ek_bytes);
confidential_asset::register(alice, token, alice_ek_bytes);
let bob_current_amount = 500;
let bob_new_amount = 450;
let transfer_amount = 50;
// Bob 提取所有可用代币
confidential_asset::deposit(bob, token, (bob_current_amount as u64));
confidential_asset::rollover_pending_balance(bob, token);
print(&utf8(b"Alice's FA balance before the withdrawal is zero:"));
print(&primary_fungible_store::balance(alice_addr, token));
assert!(primary_fungible_store::balance(alice_addr, token) == 0);
print(&utf8(b"Bob's actual balance before the withdrawal is 500"));
assert!(confidential_asset::verify_actual_balance(bob_addr, token, &bob_dk, bob_current_amount));
let current_balance = confidential_balance::decompress_balance(&confidential_asset::actual_balance(bob_addr, token));
let (proof, new_balance) = confidential_proof::prove_withdrawal(
&bob_dk,
&bob_ek,
transfer_amount,
bob_new_amount,
&current_balance
);
let new_balance = confidential_balance::balance_to_bytes(&new_balance);
let (sigma_proof, zkrp_new_balance) = confidential_proof::serialize_withdrawal_proof(&proof);
confidential_asset::withdraw_to(bob, token, alice_addr, transfer_amount, new_balance, zkrp_new_balance, sigma_proof);
print(&utf8(b"Alice's FA balance after the withdrawal is 50:"));
print(&primary_fungible_store::balance(alice_addr, token));
assert!(primary_fungible_store::balance(alice_addr, token) == 50);
print(&utf8(b"Bob's actual balance after the withdrawal is 450"));
assert!(confidential_asset::verify_actual_balance(bob_addr, token, &bob_dk, bob_new_amount));
}
}
public entry fun rotate_encryption_key(
sender: &signer,
token: Object<Metadata>,
new_ek: vector<u8>,
new_balance: vector<u8>,
zkrp_new_balance: vector<u8>,
sigma_proof: vector<u8>)
public entry fun rotate_encryption_key_and_unfreeze(
sender: &signer,
token: Object<Metadata>,
new_ek: vector<u8>,
new_confidential_balance: vector<u8>,
zkrp_new_balance: vector<u8>,
rotate_proof: vector<u8>)
public entry fun rollover_pending_balance_and_freeze(sender: &signer, token: Object<Metadata>)

rotate_encryption_key 函数修改用户的 EK,并使用新 EK 重新加密实际余额. 此函数在继续之前检查待处理余额是否为零,确保用户在旋转过程中不会丢失资金.

为了简化旋转过程:

  • 必须首先通过调用 rollover_pending_balance_and_freeze 滚动并冻结待处理余额. 这可以防止在密钥旋转期间处理新交易.
  • 然后可以使用 rotate_encryption_key_and_unfreeze 旋转 EK 并解冻.
#[test_only]
module confidential_asset_addr::rotate_example {
/// ...
fun rotate(bob: &signer, token: Object<Metadata>) {
let bob_addr = signer::address_of(bob);
// 这是一个仅用于测试的函数,因此我们不需要担心密钥对的安全性。
let (bob_current_dk, bob_current_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let (bob_new_dk, bob_new_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let bob_current_ek_bytes = twisted_elgamal::pubkey_to_bytes(&bob_current_ek);
let bob_new_ek_bytes = twisted_elgamal::pubkey_to_bytes(&bob_new_ek);
let bob_amount = 100;
confidential_asset::register(bob, token, bob_current_ek_bytes);
confidential_asset::deposit(bob, token, (bob_amount as u64));
// 我们需要滚动待处理余额并冻结代币以防止任何新存款进入。
confidential_asset::rollover_pending_balance_and_freeze(bob, token);
print(&utf8(b"Bob's encryption key before the rotation:"));
print(&confidential_asset::encryption_key(bob_addr, token));
assert!(confidential_asset::verify_actual_balance(bob_addr, token, &bob_current_dk, bob_amount));
let current_balance = confidential_balance::decompress_balance(&confidential_asset::actual_balance(bob_addr, token));
let (proof, new_balance) = confidential_proof::prove_rotation(
&bob_current_dk,
&bob_new_dk,
&bob_current_ek,
&bob_new_ek,
bob_amount,
&current_balance
);
let (
sigma_proof,
zkrp_new_balance
) = confidential_proof::serialize_rotation_proof(&proof);
// 旋转加密密钥后,我们解冻代币以允许新存款。
confidential_asset::rotate_encryption_key_and_unfreeze(
bob,
token,
bob_new_ek_bytes,
confidential_balance::balance_to_bytes(&new_balance),
zkrp_new_balance,
sigma_proof
);
print(&utf8(b"Bob's encryption key after the rotation:"));
print(&confidential_asset::encryption_key(bob_addr, token));
// 请注意,这里我们使用新的解密密钥来验证实际余额。
assert!(confidential_asset::verify_actual_balance(bob_addr, token, &bob_new_dk, bob_amount));
}
}
public entry fun normalize(
sender: &signer,
token: Object<Metadata>,
new_balance: vector<u8>,
zkrp_new_balance: vector<u8>,
sigma_proof: vector<u8>)
public fun is_normalized(user: address, token: Object<Metadata>): bool

normalize 函数确保实际余额减少到 16 位块以实现高效解密. 这仅在 rollover_pending_balance 操作之前是必要的,后者要求实际余额事先归一化.

所有其他函数,例如 withdrawconfidential_transfer,都隐式处理归一化,因此在这些情况下不需要手动归一化.

#[test_only]
module confidential_asset_addr::normalize_example {
/// ...
fun normalize(bob: &signer, token: Object<Metadata>) {
let bob_addr = signer::address_of(bob);
// 这是一个仅用于测试的函数,因此我们不需要担心密钥对的安全性。
let (bob_dk, bob_ek) = twisted_elgamal::generate_twisted_elgamal_keypair();
let bob_ek_bytes = twisted_elgamal::pubkey_to_bytes(&bob_ek);
let bob_amount = 500;
confidential_asset::register(bob, token, bob_ek_bytes);
confidential_asset::deposit(bob, token, (bob_amount as u64));
// 滚动函数是唯一需要事先归一化实际余额的函数,
// 并且在执行后无论待处理余额如何都会使其未归一化。
confidential_asset::rollover_pending_balance(bob, token);
assert!(!confidential_asset::is_normalized(bob_addr, token));
confidential_asset::deposit(bob, token, (bob_amount as u64));
// 在执行第二次滚动之前,必须归一化实际余额。
// 如果您尝试滚动未归一化的余额,将会出现错误:
// confidential_asset::rollover_pending_balance(bob, token);
let current_balance = confidential_balance::decompress_balance(&confidential_asset::actual_balance(bob_addr, token));
let (
proof,
new_balance
) = confidential_proof::prove_normalization(
&bob_dk,
&bob_ek,
bob_amount,
&current_balance
);
let (sigma_proof, zkrp_new_balance) = confidential_proof::serialize_normalization_proof(&proof);
confidential_asset::normalize(
bob,
token,
confidential_balance::balance_to_bytes(&new_balance),
zkrp_new_balance,
sigma_proof
);
assert!(confidential_asset::is_normalized(bob_addr, token));
assert!(confidential_asset::verify_actual_balance(bob_addr, token, &bob_dk, bob_amount));
// 一旦余额归一化,就可以执行滚动。
// 请注意,像 `withdraw` 和 `confidential_transfer` 这样的函数不需要事先归一化实际余额,
// 因为 zk-证明保证实际余额在其执行后是归一化的。
confidential_asset::rollover_pending_balance(bob, token);
}
}