diff --git a/docs/SolanaProgramLibrary/confidential-token/confidential-token.md b/docs/SolanaProgramLibrary/confidential-token/confidential-token.md new file mode 100644 index 0000000..8d3f495 --- /dev/null +++ b/docs/SolanaProgramLibrary/confidential-token/confidential-token.md @@ -0,0 +1 @@ +# 介绍 \ No newline at end of file diff --git a/docs/SolanaProgramLibrary/confidential-token/deep-dive/encryption.md b/docs/SolanaProgramLibrary/confidential-token/deep-dive/encryption.md new file mode 100644 index 0000000..1128707 --- /dev/null +++ b/docs/SolanaProgramLibrary/confidential-token/deep-dive/encryption.md @@ -0,0 +1,183 @@ +# 加密 + +加密扩展程序采用公钥加密方案和经过身份验证的对称加密方案。对于公钥加密,该程序使用[twisted ElGamal](https://eprint.iacr.org/2019/319)加密方案 ,对称加密则是使用 [AES-GCM-SIV](https://datatracker.ietf.org/doc/html/rfc8452)。 + + +## Twisted ElGamal 加密方案 + +twisted ElGamal 加密是标准 ElGamal 加密方案的一个简单变体,其中密文分为两个部分: + ++ 加密消息的Pedersen承诺。这个组件与公钥无关。 ++ 一个"解密核心",它将加密随机性与特定的ElGamal公钥绑定。这个组件与实际加密的消息无关。 + +twisted ElGamal 密文的结构简化了某些零知识证明系统的设计。此外,由于加密消息被编码为 Pedersen 承诺,许多专门为 Pedersen 承诺设计的现有零知识证明系统可以直接应用于twisted ElGamal密文。 + +我们在[注释](https://spl.solana.com/assets/files/twisted_elgamal-2115c6b1e6c62a2bb4516891b8ae9ee0.pdf)中提供了 twisted ElGamal 加密的正式描述。 + +### 密文解密 + +在协议中使用 ElGamal 加密的缺点是解密低效。ElGamal 密文的解密时间随着加密数字的大小呈指数级增长。使用现代的硬件解密 32 位消息,时间可能在数秒之内,但随着消息大小的增加,这很快就变得不可行。标准的 Token 账户存储的一般是单位为 `u64` 位的余额,但 ElGamal 密文无法解密大于 64 位的消息。因此,在账户状态和转账数据中,对余额和转账金额的加密和处理方式需要特别注意。 + +## 账户状态 + +如果 twisted ElGamal 加密方案的解密速度很快,可以将加密转账账户和加密指令数据结构设计为如下形式: + +```rust +struct ConfidentialTransferAccount { + /// `true` 表示该账户已被批准使用。在获得批准之前, + /// 该账户的所有机密转账操作都将失败。 + approved: PodBool, + + /// 与 ElGamal 加密相关联的公钥 + encryption_pubkey: ElGamalPubkey, + + /// 待处理余额(由 `encryption_pubkey` 加密) + pending_balance: ElGamalCiphertext, + + /// 可用余额(由 `encryption_pubkey` 加密) + available_balance: ElGamalCiphertext, +} +``` + +```rust +// 实际的加密组件被组织在 `VerifyTransfer` 指令数据中 +struct ConfidentialTransferInstructionData { + /// 使用发送方 ElGamal 公钥加密的转账金额 + encrypted_amount_sender: ElGamalCiphertext, + /// 使用接收方 ElGamal 公钥加密的转账金额 + encrypted_amount_receiver: ElGamalCiphertext, +} +``` + +Token 程序在接收到转账指令后将 `encrypted_amount_receiver` 结合到账户的 `pending_balance` 中。 + +这两个组件的实际结构更加复杂,由于 `TransferInstructionData` 需要零知识证明组件,我们将在下一小节详细讨论其数据结构,此处我们专注于 `ConfidentialTransferAccount`的介绍。我们从上面理想化的`ConfidentialTransferAccount` 结构开始,逐步修改它以生成最终结构。 + +### 可用余额 + +如果可用余额仅作为普通的 `u64` 值进行加密,那么客户端将无法解密并恢复账户中的确切余额。因此,在 Token 程序中,可用余额还使用一种经过认证的对称加密方案进行了额外加密。得到的密文被存储为账户的 `decryptable_balance`,相应的对称密钥应该作为独立的密钥存储在客户端,或者从所有者签名密钥动态派生。 + +```rust +struct ConfidentialTransferAccount { + /// `true` 表示该账户已被批准使用。在获得批准之前, + /// 该账户的所有机密转账操作都将失败。 + approved: PodBool, + + /// 与 ElGamal 加密相关联的公钥 + encryption_pubkey: ElGamalPubkey, + + /// 待处理余额(由 `encryption_pubkey` 加密) + pending_balance: ElGamalCiphertext, + + /// 可用余额(由 `encryption_pubkey` 加密) + available_balance: ElGamalCiphertext, + + /// 可解密的可用余额 + decryptable_available_balance: AeCiphertext, +} +``` + +`decryptable_available_balance`易于解密,客户通常应利用它来解密账户内的可用余额。而`available_balance`的ElGamal密文则主要用于在创建转账指令时生成零知识证明。 + +`available_balance`与`decryptable_available_balance`应当对同一笔与账户相关的可用余额进行加密处理。账户中的可用余额仅可能在执行了`ApplyPendingBalance`指令或发起了一笔出账`Transfer`指令后发生变化。这两类指令均需在其指令数据中包含`new_decryptable_available_balance`这一项。 + +### 待处理余额 + +与可用余额的情况类似,人们可以考虑为待处理余额添加一个 `decryptable_pending_balance`。然而,虽然可用余额总是由账户所有者掌控(通过`ApplyPendingBalance`和`Transfer`指令),但账户的待处理余额可能会随着传入的转账而不断变化。由于可解密余额密文的相应解密密钥只有账户所有者知道,因此`Transfer`指令的发起者无法更新收款方账户的可解密余额。 + +因此,针对待处理余额,Token程序会保存两组独立的ElGamal密文,其中一组加密64位待处理余额的低位数,另一组则加密高位数。 + +```rust +struct ConfidentialTransferAccount { + /// `true` 表示该账户已被批准使用。在获得批准之前, + /// 该账户的所有机密转账操作都将失败。 + approved: PodBool, + + /// 与 ElGamal 加密相关联的公钥 + encryption_pubkey: ElGamalPubkey, + + /// 待处理余额的低位(由 `encryption_pubkey` 加密) + pending_balance_lo: ElGamalCiphertext, + + /// 待处理余额的高位(由 `encryption_pubkey` 加密) + pending_balance_hi: ElGamalCiphertext, + + /// 可用余额(由 `encryption_pubkey` 加密) + available_balance: ElGamalCiphertext, + + /// 可解密的可用余额 + decryptable_available_balance: AeCiphertext, +} +``` + +我们将转账指令数据中的转账金额密文划分为低位加密和高位加密两部分。 + +```rust +// 实际的加密组件被组织在 `VerifyTransfer` 指令数据中 +struct ConfidentialTransferInstructionData { + /// 使用发送方 ElGamal 公钥加密的转账金额 + encrypted_amount_sender: ElGamalCiphertext, + /// 使用接收方 ElGamal 公钥加密的转账金额低位 + encrypted_amount_lo_receiver: ElGamalCiphertext, + /// 使用接收方 ElGamal 公钥加密的转账金额高位 + encrypted_amount_hi_receiver: ElGamalCiphertext, +} +``` + +在接受到转账指令时,Token程序将指令数据中的`encrypted_amount_lo_receiver`累加到账户的`pending_balance_lo`,并将`encrypted_amount_hi_receiver`累加到`pending_balance_hi`。 + +将上述结构中的64位待处理余额及转账金额划分的一种方式是将其均匀分割为低位和高位的两个32位数字。由于每个密文中加密的数额均为32位数字,可以高效完成解密操作。 + +但这种方法的问题在于,加密32位数字的`pending_balance_lo`很容易溢出,变得大于32位数字。例如,向某账户进行两次金额为`2^32-1`的转账会使该账户中的`pending_balance_lo`密文达到`2^32`,即一个33位的数字。随着加密金额的溢出,解密密文的难度会逐渐增加。 + +为了应对溢出问题,在账户状态中增加以下两个组件。 + ++ 账户状态会追踪自上次`ApplyPendingBalance`指令以来收到的入账转账数量。 ++ 账户状态中保存了一个`maximum_pending_balance_credit_counter`,它限制了在应用 `ApplyPendingBalance` 指令之前可以接收的传入转账数量。上限可以通过`ConfigureAccount`进行配置,通常应设置为`2^16`。 + +```rust +struct ConfidentialTransferAccount { + ... // 省略 `approved`、`encryption_pubkey` 和可用余额字段 + + /// 待处理余额的低位(由 `encryption_pubkey` 加密) + pending_balance_lo: ElGamalCiphertext, + + /// 待处理余额的高位(由 `encryption_pubkey` 加密) + pending_balance_hi: ElGamalCiphertext, + + /// 在执行 `ApplyPendingBalance` 指令之前,可以对 `pending_balance` + /// 进行信用的 `Deposit` 和 `Transfer` 指令的最大数量 + pub maximum_pending_balance_credit_counter: u64, + + /// 自上次执行 `ApplyPendingBalance` 指令以来的传入转账数量 + pub pending_balance_credit_counter: u64, +} +``` + +我们对转账指令数据做出如下修改: + ++ 转账金额限制为 48 位数字。 ++ 转账金额被分为 16 位和 32 位数字,并作为两个密文 `encrypted_amount_lo_receiver `和 `encrypted_amount_hi_receiver `进行加密。 + +```rust +// 实际的加密组件被组织在 `VerifyTransfer` 指令数据中 +struct ConfidentialTransferInstructionData { + /// 使用发送方 ElGamal 公钥加密的转账金额 + encrypted_amount_sender: ElGamalCiphertext, + /// 使用接收方 ElGamal 公钥加密的转账金额的低 *16 位* + encrypted_amount_lo_receiver: ElGamalCiphertext, + /// 使用接收方 ElGamal 公钥加密的转账金额的高 *32 位* + encrypted_amount_hi_receiver: ElGamalCiphertext, +} +``` + +为了在ElGamal解密的效率与隐私转账的实用性之间取得平衡,对转账金额进行限制。使用`pending_balance_credit_counter`和`maximum_pending_balance_credit_counter`两个字段限制待处理余额中加密的`pending_balance_lo`和`pending_balance_hi`金额。 + +设想一下当`maximum_pending_balance_credit_counter`被设定为`2^16`的情况。 + + ++ `encrypted_amount_lo_receiver`最大加密一个16位数字,即使在经历了`2^16`次入账转账之后,账户中的`pending_balance_lo`密文所加密的余额最多仍是一个32位数。这部分待处理余额能够被高效解密。 + ++ `encrypted_amount_hi_receiver`最大加密一个32位数字,在经历`2^16`次入账转账后,`pending_balance_hi`密文加密的余额最多是一个48位数。 + + 解密一个大的 48 位数字是十分缓慢的。然而对于大多数应用来说,非常大金额的转账相对较少。要使一个账户持有大的 48 位数字的待处理余额,它必须接收大量的高交易金额。维护高代币余额的客户端可以频繁提交` ApplyPendingBalance` 指令,将待处理余额转入可用余额,以防止 `pending_balance_hi `加密过大的数字。。 diff --git a/docs/SolanaProgramLibrary/confidential-token/deep-dive/overview.md b/docs/SolanaProgramLibrary/confidential-token/deep-dive/overview.md new file mode 100644 index 0000000..53161b2 --- /dev/null +++ b/docs/SolanaProgramLibrary/confidential-token/deep-dive/overview.md @@ -0,0 +1,326 @@ +# 协议概览 + +本节概述了加密代币扩展的底层加密协议。实际使用加密扩展并不需要理解以下小节中讨论的细节,使用可查看前一章节参阅快速入门指南。 + +需要注意的是,本节主要介绍保密代币扩展的底层密码学设计的出发点,对协议的某些部分描述可能与实际实现有所差异。关于底层密码学的详细介绍,可参考后续的小节、[源代码](https://github.com/solana-labs/solana-program-library)以及其中的文档。 + +## 代币的加密和证明 + +`Mint` 和 `Account` 是 Token程序中使用的主要状态数据结构。其中,`Mint` 数据结构用于存储代币的全局信息。 + +```rust +/// Mint数据。 +struct Mint { + mint_authority: Option, + supply: u64, + ... // 其他字段省略 +} +``` + +`Account` 数据结构用于存储用户的代币余额信息。 + +```rust +/// Account数据。 +struct Account { + mint: Pubkey, + owner: Pubkey, + amount: u64, + ... // 其他字段省略 +} +``` + +用户可以使用`InitializeMint`和`InitializeAccount`指令初始化这两个数据结构。还有一些额外的指令可以用来修改这些状态。在本概述中,我们主要关注`Transfer`指令。为简单起见,在本节中我们将`Transfer`指令模型化为以下结构。 + + +```rust +/// Transfer指令数据 +/// +/// 预期的账户: +/// 0. [writable] 源账户。 +/// 1. [writable] 目标账户。 +/// 2. [signer] 源账户的所有者。 +struct Transfer { + amount: u64, +} +``` + +### 加密 + +由于`Account`状态存储在链上,任何人都可以查看与任何用户关联的余额。在加密扩展中,我们使用最基本的方法来隐藏这些余额:使用公钥加密方案(PKE)对其进行加密。为简单起见,让我们用以下语法来模拟公钥加密方案。 + +```rust +trait PKE { + type SecretKey; + type PublicKey; + type Ciphertext; + + keygen() -> (SecretKey, PublicKey); + encrypt(PublicKey, Message) -> Ciphertext; + decrypt(SecretKey, Ciphertext) -> Message; +} +``` + +以`Account`状态为例: + +```rust +Account { + mint: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB, + owner: 5vBrLAPeMjJr9UfssGbjUaBmWtrXTg2vZuMN6L4c8HE6, + amount: 50, + ... +} +``` + +为隐藏账户余额,在存储上链前,我们使用账户所有者的公钥对账户余额进行加密。 + +```rust +Account { + mint: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB, + owner: 5vBrLAPeMjJr9UfssGbjUaBmWtrXTg2vZuMN6L4c8HE6, // pubkey_owner + amount: PKE::encrypt(pubkey_owner, 50), // 余额加密 + ... +} +``` + +我们同样可以使用加密来隐藏交易中的转账金额。参考下方的转账指令,为了隐藏交易金额,我们可以在将其提交到链上之前,使用发送者的公钥对其进行加密。 + +```rust +Transfer { + amount: PKE::encrypt(pubkey_owner, 10), +} +``` + +通过对账户余额和转账金额进行加密,我们能为代币程序增加保密性。 + + +### 线性同态性 + +这种简单方法的一个问题是,代币程序无法对账户进行交易金额的扣除或添加,因为它们都是加密形式。解决这个问题的一种方法是使用一类*线性同态*(Linear homomorphism)加密方案,例如ElGamal加密方案。如果对于任意两个数字`x_0`、`x_1`及其在相同公钥下的加密`ct_0`、`ct_1`,存在特定于密文的加法和减法操作并使得加密方案具有线性同态性,那么: + +```rust +let (sk, pk) = PKE::keygen(); + +let ct_0 = PKE::encrypt(pk, x_0); +let ct_1 = PKE::encrypt(pk, x_1); + +assert_eq!(x_0 + x_1, PKE::decrypt(sk, ct_0 + ct_1)); +``` + +换句话说,线性同态加密方案允许在加密形式下对数字进行加法和减法运算。`x_0`和`x_1`单独加密的和与差,结果是一个等同于`x_0`和`x_1`的和与差的加密密文。 + +通过使用线性同态加密方案来加密余额和转账金额,我们可以允许代币程序以加密形式处理余额和转账金额。由于线性同态性仅在密文在相同公钥下加密时成立,我们要求转账金额同时使用发送方和接收方的公钥进行加密。 + + +```rust +Transfer { + amount_sender: PKE::encrypt(pubkey_sender, 10), + amount_receiver: PKE::encrypt(pubkey_receiver, 10), +} +``` + +然后,在收到这种形式的转账指令时,代币程序可以相应地对源账户和目标账户的密文进行减法和加法操作。 + +```rust +Account { + mint: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB, + owner: 5vBrLAPeMjJr9UfssGbjUaBmWtrXTg2vZuMN6L4c8HE6, // pubkey_sender + amount: PKE::encrypt(pubkey_sender, 50) - PKE::encrypt(pubkey_sender, 10), + ... +} +``` + +```rust +Account { + mint: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB, + owner: 0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7, // pubkey_receiver + amount: PKE::encrypt(pubkey_receiver, 50) + PKE::encrypt(pubkey_receiver, 10), + ... +} +``` + +### 零知识证明 + +加密账户余额和转账金额的另一个问题是,代币程序无法检查转账金额的有效性。例如,一个账户余额为50个代币的用户不能向另一个账户转账70个代币。对于常规的SPL代币,代币程序可以轻松检测用户账户中是否有足够的资金,但如果账户余额和转账金额都被加密,那么代币程序将无法验证交易的有效性。 + +为了解决这个问题,我们要求转账指令包含验证其正确性的零知识证明。简单来说,零知识证明由两对算法`prove`和`verify`组成,这些算法对公开和私有数据进行操作。`prove`算法生成一个"证明",证明公开和私有数据的某些属性是真实的。`verify`算法检查证明是否有效。 + +```rust +trait ZKP { + type Proof; + + prove(PublicData, PrivateData) -> Proof; + verify(PublicData, Proof) -> bool; +} +``` + +`证明`不会泄露任何关于实际私有数据的信息是零知识证明系统的一个特殊属性。 + +在转账指令中,我们需要以下类型的零知识证明: + ++ **范围证明**:范围证明是一种特殊类型的零知识证明,它允许用户生成一个证明 `proof`,值 `x` 经证明密文 `ct` 加密后,能够落在指定的范围 `lower_bound` 和 `upper_bound` 之间: + + + 若果x满足 `lower_bound <= x < upper_bound`,Proof将成功: + + ```rust + let ct = PKE::encrypt(pk, x); + let public_data = (pk, ct); + let private_data = (sk, x); + + let proof = RangeProof::prove(public_data, private_data); + assert_eq!(RangeProof::verify(public_data, proof), true); + ``` + + + 如果x超出界限,任何Proof都将失败: + + ```rust + let ct = PKE::encrypt(pk, x); + let public_data = (pk, ct); + + assert_eq!(RangeProof::verify(public_data, proof), false); + ``` + + 零知识证明可以保证会证明`lower_bound <= x < upper_bound`,而生成的证明不会泄露输入`x`的实际值。 + + 在加密扩展中,我们要求转账指令包含一个范围证明,以证明以下几点: + + + 通过证明确认源账户中有足够的资金。具体而言,设`ct_source`是源账户加密后的余额,`ct_transfer`是加密后的转账金额。那么`x`是经过加密后的`ct_source - ct_transfer`值,且这个`x`满足`0 <= x < u64::MAX`,其中`u64::MAX`代表64位无符号整数的最大值。 + + + 证明应当确认转账金额本身是一个正的64位数字。设`ct_transfer`是加密后的转账金额。那么这个证明应该确认`ct_transfer`加密了一个值`x`,且满足`0 <= x < u64::MAX`。 + ++ *等值证明*(*Equality proof*):一个转账指令包含了转账值`x`的两个密文:一个是使用发送方公钥`pk_sender`加密的`ct_sender = PKE::encrypt(pk_sender, x)`,另一个是使用接收方公钥`pk_receiver`加密的`ct_receiver = PKE::encrypt(pk_receiver, x)`。一个恶意用户可能会对`ct_sender`和`ct_receiver`加密两个不同的值。 + + 等值证明是一种特殊类型的零知识证明,允许用户证明两个密文 `ct_0` 和 `ct_1` 加密相同的值 `x`。在加密扩展程序中,我们要求转账指令包含一个等值证明,以证明这两个密文加密的值相同。 + + 零知识证明保证 `proof_eq` 不会泄露 `x_0` 和 `x_1` 的实际值,只会证明 `x_0 == x_1`。 + +我们将在后续章节中介绍这些算法。 + +## 可用功能 + +### 加密密钥 + +在前一章节中,我们使用账户所有者的公钥加密账户的余额。而在加密扩展程序的实际使用中,我们使用一个独立的、针对每个账户的加密密钥来加密账户余额。 + +```rust +Account { + mint: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB, + owner: 5vBrLAPeMjJr9UfssGbjUaBmWtrXTg2vZuMN6L4c8HE6, + encryption_key: mpbpvs1LksLmdMhCEzyu5UEWEb3dsRPbB5, // pke_pubkey + amount: PKE::encrypt(pke_pubkey, 50), + ... +} +``` + +账户所有者可以使用 `ConfidentialTransferInstruction::ConfigureAccount` 指令设置账户专用的 `encryption_key`。对应的私钥可以私密地存储在客户端钱包中,也可以由所有者签名密钥确定性地派生。 + +因为这可能存在潜在的漏洞,不建议直接重用签名密钥进行加密。加密扩展的设计为尽可能通用、提供更灵活的接口,使用专用密钥分别签署交易和解密交易金额。 + +在潜在的应用中,特定账户的解密密钥可以在多个用户(例如监管者)之间共享,这些用户有权访问账户余额。尽管这些用户可以解密账户余额,但只有拥有所有者签名密钥的账户才能签署发起代币转账的交易。账户所有者可以使用 `ConfigureAccount` 指令更新账户的加密密钥。 + + +### 全局审计员 + +由于每个用户账户都关联有独立的解密密钥,用户可以给予潜在审计员对*特定*账户余额的读取权限。加密扩展同样提供了一个可选的*全局*审计员功能,可在铸造代币时选择启用。具体而言,在加密扩展中,铸造数据结构维护了一个额外的全局审计员加密密钥。这个审计员加密密钥可以在铸造初始化时指定,并通过`ConfidentialTransferInstruction::ConfigureMint`指令进行更新。如果铸造中的转账审计员加密密钥不是`None`,那么任何转账指令必须额外包含审计员加密密钥加密的转账金额。 + +```rust +Transfer { + amount_sender: PKE::encrypt(pke_pubkey_sender, 10), + amount_receiver: PKE::encrypt(pke_pubkey_receiver, 10), + amount_auditor: PKE::encrypt(pke_pubkey_auditor, 10), + range_proof: RangeProof, + equality_proof: EqualityProof, + ... +} +``` + +这允许任何持有审计员私钥的实体能够解密特定铸造代币的任何转账金额。 + +不诚实的发送者可能在源和目的地公钥下加密不一致的转账金额,他们也可能在审计员加密密钥下加密不一致的转账金额。如果在代币铸造时审计员加密密钥不是`None`,代币程序为证明加密是一致的,会要求转账指令中的转账金额要包含额外的零知识证明。 + +### 待处理余额和可用余额 + +攻击者可以通过使用*抢跑交易*来破坏加密扩展账户的使用。零知识证明是相对于账户的加密余额进行验证的。假设用户Alice基于她当前的加密账户余额生成了一个证明。如果另一个用户Bob向Alice转移了一些代币,并且Bob的交易先被处理,那么Alice的交易将被代币程序拒绝,因为证明将无法对更新的账户状态进行验证。 + +在正常情况下,一旦交易被程序拒绝,Alice可以查找更新的密文并提交新的交易。然而,如果一个恶意攻击者不断向网络中发送转账到Alice账户的交易,那么理论上该账户可能变得无法使用。为了防止这种攻击,我们修改了账户数据结构,使账户的加密余额被分为两个独立的部分:*待处理*余额和*可用*余额。 + +```rust +let ct_pending = PKE::encrypt(pke_pubkey, 10); +let ct_available = PKE::encryption(pke_pubkey, 50); + +Account { + mint: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB, + owner: 5vBrLAPeMjJr9UfssGbjUaBmWtrXTg2vZuMN6L4c8HE6, + encryption_key: mpbpvs1LksLmdMhCEzyu5UEWEb3dsRPbB5, + pending_balance: ct_pending, + account_balance: ct_available, + ... +} +``` + +从账户转出的任何资金都从其可用余额中扣除。账户接收的任何资金都会添加到其待处理余额中。 + +例如,将10个代币从发送者账户转移到接收者账户的转账指令。 + +```rust +let ct_transfer_sender = PKE::encrypt(pke_pubkey_sender, 10); +let ct_transfer_receiver = PKE::encrypt(pke_pubkey_receiver, 10); +let ct_transfer_auditor = PKE::encrypt(pke_pubkey_auditor, 10); + +Transfer { + amount_sender: ct_transfer_sender, + amount_receiver: ct_transfer_receiver, + amount_auditor: ct_transfer_auditor, + range_proof: RangeProof, + equality_proof: EqualityProof, + ... +} +``` + +在收到并验证该交易后,代币程序从发送者的可用余额中扣除加密的金额,并将加密的金额添加到接收者的待处理余额中。 + +```rust +Account { + mint: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB, + owner: 5vBrLAPeMjJr9UfssGbjUaBmWtrXTg2vZuMN6L4c8HE6, + encryption_key: mpbpvs1LksLmdMhCEzyu5UEWEb3dsRPbB5, + pending_balance: ct_sender_pending, + available_balance: ct_sender_available - ct_transfer_sender, + ... +} +``` + +此修改消除了发送者更改接收者可用余额的能力。由于区间证明是相对于可用余额生成和验证的,这防止了用户的交易因另一个用户生成的交易而失效。 + +通过 `ApplyPendingBalance` 指令,可以将账户的待处理余额合并到可用余额中,该操作只能由账户所有者授权。收到该指令并验证账户所有者签署了交易后,代币程序将待处理余额添加到可用余额中。 + +```rust +Account { + mint: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB, + owner: 5vBrLAPeMjJr9UfssGbjUaBmWtrXTg2vZuMN6L4c8HE6, + encryption_key: mpbpvs1LksLmdMhCEzyu5UEWEb3dsRPbB5, + pending_balance: ct_pending_receiver - ct_transfer_receiver, + available_balance: ct_available_receiver, + ... +} +``` + +## 加密优化 + +### 处理离散对数问题 + +使用线性同态ElGamal加密的主要限制是解密的低效性。为了恢复原本加密的值,必须解决一个称为*离散对数*的计算问题,即使使用正确的私钥,也需要指数时间来解决。在加密扩展程序中,我们通过以下两种方式解决这个问题: + ++ 转账金额限制为48位数字 ++ 转账金额和账户待处理余额被加密为两个独立的密文 ++ 账户可用余额还使用对称加密方案进行额外加密 + +有关更多详细信息,请参阅后续章节和源代码中的文档。 + +### Twisted ElGamal 加密 + +设计任何隐私支付系统的关键挑战之一是尽量减小交易的大小。在加密扩展中,我们进行了许多优化以减少交易大小。在这些优化中,使用*twisted* ElGamal 加密(在[CMTA19](https://eprint.iacr.org/2019/319)中提出)带来了大量的节省。twisted ElGamal 加密是标准 ElGamal 加密方案的一个简单变体,其中密文被分为两个部分: + ++ 加密消息的*Pedersen承诺*,它独立于任何ElGamal公钥。 ++ *解密核心*,它根据特定的ElGamal公钥编码加密随机性,且独立于加密消息。 + +我们将在后续提供twisted ElGamal加密的更多细节。 diff --git a/docs/SolanaProgramLibrary/confidential-token/deep-dive/zkps.md b/docs/SolanaProgramLibrary/confidential-token/deep-dive/zkps.md new file mode 100644 index 0000000..90fd759 --- /dev/null +++ b/docs/SolanaProgramLibrary/confidential-token/deep-dive/zkps.md @@ -0,0 +1,213 @@ +# 零知识证明 + +零知识证明是一种允许用户证明加密数据某些特性的工具。在加密扩展中使用的大部分零知识证明都是相对较小的系统,专为加密扩展设计。由于其简洁性,程序中使用所有零知识系统都不需要受信设置或复杂的电路设计。 + +在加密扩展中使用的零知识证明可以分为两类:*sigma*协议和*bulletproofs*。Sigma协议是为加密扩展定制的简单系统。Bulletproofs是一种现有的、源于研究论文[Bulletproofs: Short Proofs for Confidential Transactions and More](https://eprint.iacr.org/2017/1066)的范围证明系统。 + + +## 转账指令数据 + +加密扩展的 `Transfer` 指令数据需要多个加密组件。我们通过一系列步骤构建转账数据,以便直观理解每个组件。 + +```rust +struct TransferData { ...} +``` + +### ElGamal 公钥 + +转账指令关联有三个 ElGamal 公钥:发送方、接收方和审计方(sender, receiver, and auditor)。转账指令数据必须包含这三个加密公钥。 + +```rust +struct TransferPubkeys { + source_pubkey: ElGamalPubkey, + destination_pubkey: ElGamal Pubkey, + auditor_pubkey: ElGamalPubkey, +} + +struct TransferData { + transfer_pubkeys: TransferPubkeys, +} +``` + +如果没有与代币相关联的审计方,那么审计方的公钥为32个零字节。 + +### 低位和高位加密 + +转账指令数据必须包含三个经ElGamal公钥加密的转账金额。为了应对上一节讨论的ElGamal解密问题,转账金额被限制为48位数字,并且被加密为两个独立的数字:表示低16位的 `amount_lo` 和表示高32位的 `amount_hi`。 + +每个 `amount_lo` 和 `amount_hi` 都在三个 ElGamal 公钥下加密。为了避免在转账数据中包含三个独立的密文,我们利用了ElGamal加密的随机重用特性来最小化密文的大小。 + +```rust +/// 使用三个ElGamal公钥加密的转账金额的密文结构 +/// public keys +struct TransferAmountEncryption { + commitment: PedersenCommitment, + source_handle: DecryptHandle, + destination_handle: DecryptionHandle, + auditor_handle: DecryptHandle, +} + +struct TransferData { + ciphertext_lo: TransferAmountEncryption, + ciphertext_hi: TransferAmountEncryption, + transfer_pubkeys: TransferPubkeys, +} +``` + +除了这些密文之外,转账数据还必须包含证明这些密文是正确生成的证明。用户可能有两种方式欺骗程序。首先,用户可能提供格式错误的密文。例如,即使用户可能使用错误的公钥加密转账金额,程序也无法检查密文的有效性。因此,我们要求转账数据需要一个密文*有效性*证明,以证明密文是正确生成的。 + +密文有效性证明只能保证twisted ElGamal密文是正确生成的。然而,它并不能证明密文中加密金额的任何属性。例如,恶意用户可以加密负值,但程序仅通过检查密文无法检测到这一点。因此,除了密文有效性证明之外,转账指令还必须包含一个*范围证明*,以证明加密的金额`amount_lo`和`amount_hi`分别是16位和32位的正数值。 + +```rust +struct TransferProof { + validity_proof: ValidityProof, + range_proof: RangeProof, +} + +struct TransferData { + ciphertext_lo: TransferAmountEncryption, + ciphertext_hi: TransferAmountEncryption, + transfer_pubkeys: TransferPubkeys, + proof: TransferProof, +} +``` + +### 验证净余额(Net-Balance) + +最后,除了证明转账金额被正确加密,用户还必须包含一个证明,证实源账户有足够的余额进行转账。最常见的方式是用户生成一个范围证明,证明密文`source_available_balance - (ciphertext_lo + 2^16 * ciphertext_hi)`(即源账户的可用余额减去转账金额)加密的是一个正的64位数值。由于Bulletproofs支持证明的聚合,这个额外的范围证明可以与原有关于转账金额的范围证明整合在一起。 + + +```rust +struct TransferProof { + validity_proof: ValidityProof, + range_proof: RangeProof, // 证明密文金额和净余额 +} + +struct TransferData { + ciphertext_lo: TransferAmountEncryption, + ciphertext_hi: TransferAmountEncryption, + transfer_pubkeys: TransferPubkeys, + proof: TransferProof, +} +``` + +上述方案存在一个技术性问题,即尽管转账的发起者掌握着ElGamal解密密钥,可以用来解密`source_available_balance`的密文,但他们不一定能获取到用于生成`source_available_balance - (ciphertext_lo + 2^16 * ciphertext_hi)`密文上范围证明所需的Pedersen承诺的开启值。因此,在转账指令中,我们要求发送者在客户端解密`source_available_balance - (ciphertext_lo + 2^16 * ciphertext_hi)`的密文,并且附加一个对更新后源账户余额`new_source_commitment`的新的Pedersen承诺。此外,还需要提供一个*等值证明*,用以确保`source_available_balance - (ciphertext_lo + 2^16 * ciphertext_hi)`的密文与`new_source_commitment`加密的是同一个消息。 + + +```rust +struct TransferProof { + new_source_commitment: PedersenCommitment, + equality_proof: CtxtCommEqualityProof, + validity_proof: ValidityProof, + range_proof: RangeProof, +} + +struct TransferData { + ciphertext_lo: TransferAmountEncryption, + ciphertext_hi: TransferAmountEncryption, + transfer_pubkeys: TransferPubkeys, + proof: TransferProof, +} +``` + +## 转账的费用指令数据 + +可以为扩展了费用的代币启用加密扩展。如果某个代币启用了费用扩展,任何相应代币的保密转账都必须使用加密扩展的 `TransferWithFee` 指令。除了 `Transfer` 指令所需的数据外,`TransferWithFee` 指令还需要与费用相关的额外加密组件。 + +### 转账费用原理 + +如果代币启用了费用扩展,那么涉及该代币的转账需要按转账金额百分比计算支付转账费用。交易费用由两个参数决定: + ++ `bp`: 表示手续费率的基点。这是一个正整数,代表一个小数点后两位的百分比率。 + + 比如,`bp = 1` 代表手续费率为0.01%,`bp = 100` 代表手续费率为1%,而 `bp = 10000` 代表手续费率为100%。 + ++ `max_fee`: 最大手续费率。转账手续费是根据由`bp`确定的费率来计算的,但其值不会超过`max_fee`的上限。 + + 假设转账金额为200个代币。 + + + 对于手续费参数 `bp = 100` 和 `max_fee = 3`,手续费就是转账金额的1%,也就是2。 + + 对于手续费参数 `bp = 200` 和 `max_fee = 3`,手续费为3,因为200的2%是4,这超过了最大手续费3的限制。 + +转账费用向上取整到最接近的正整数。例如,如果转账金额为 `100` 且费用参数为 `bp = 110` 和 `max_fee = 3`,则费用为 `2`,这是从转账金额的1.1%向上取整得出的。 + +费用参数可以在启用了费用扩展的代币中指定。除了费用参数外,启用了费用扩展的代币还包含 `withdraw_withheld_authority` 字段,该字段指定可以收取从转账金额中扣除的费用权限的公钥。 + +启用了费用扩展的代币账户有一个关联字段 `withheld_amount`。从转账金额中扣除的任何转账费用都会聚合到转账目标账户的 `withheld_amount` 字段中。提取扣留费用的权限可以使用 `TransferFeeInstructions::WithdrawWithheldTokensFromAccounts` 将 `withheld_amount` 提取到特定账户,或使用 `TransferFeeInstructions::HarvestWithheldTokensToMint` 提取到代币账户中。累积在代币账户中的扣留费用可以使用 `TransferFeeInstructions::WithdrawWithheldTokensFromMint` 提取到一个账户中。 + +### 费用加密 + +因为转账金额可以从费用中推断出来,加密扩展的 `TransferWithFee` 指令中不能以明文形式包含实际的转账费用金额。在加密扩展中,转账费使用目标账户和提取权限的 ElGamal 公钥加密。 + +```rust +struct FeeEncryption { + commitment: PedersenCommitment, + destination_handle: DecryptHandle, + withdraw_withheld_authority_handle: DecryptHandle, +} + +struct TransferWithFeeData { + ... // `TransferData` 组件 + fee_ciphertext: FeeEncryption, +} +``` + +在收到 `TransferWithFee` 指令后,代币程序会从同一公钥下的加密转账金额中扣除目标账户 ElGamal 公钥下的加密费用。然后,将在提取扣留权限的 ElGamal 公钥下加密的费用密文聚合到目标账户的 `withheld_fee` 组件中。 + +### 验证费用密文 + +验证费用密文部分涉及到`TransferWithFee`指令数据中用于确认加密费用有效性的必要字段。由于费用是加密的,代币程序不能仅通过检查密文来验证费用是否正确计算。因此,`TransferWithFee`必须包含三个额外的证明来确保费用密文的有效性: + +- **密文有效性证明**(*ciphertext validity proof*):这部分证明确保实际的费用密文是在正确的接收方以及提现扣留授权的ElGamal公钥下正确生成的。 +- **费用sigma证明**(*fee sigma proof*):结合范围证明组件,费用sigma证明确保在`fee_ciphertext`中加密的费用根据费用参数被适当地计算。 +- **范围证明**(*range proof*):结合费用sigma证明组件,范围证明组件确保在`fee_ciphertext`中的加密费用根据费用参数被适当地计算。 + +对于这些证明的具体规范,我们参考以下更详细的说明。 + +## Sigma 证明 + +### (公钥)有效性证明 + +公钥有效性证明可以确认一个 twisted ElGamal 公钥是一个格式正确的公钥。系统的具体描述在以下注释中说明。 + +[[注释]](https://spl.solana.com/assets/files/pubkey_proof-9d51d5965c7b089fd264ddb2ce4e3e7f.pdf) + +公钥有效性证明是 `ConfigureAccount` 指令所必需的。 + +### (密文)有效性证明 + +密文有效性证明可以确认一个 twisted ElGamal 密文是一个格式正确的密文。系统的具体描述在以下注释中详细说明。 + +[[注释]](https://spl.solana.com/assets/files/validity_proof-0f656f3de18e5794d6ddd39a2fd223f5.pdf) + +有效性证明是`Withdraw`、`Transfer` 和 `TransferWithFee` 指令所必需的。这些指令要求客户端在指令数据中包含twisted ElGamal密文。附带的有效性证明保证了这些ElGamal密文是格式正确,是按照加密协议正确构造的。 + +### 零余额证明(Zero-balance Proof) + +零余额证明可以确认一个 twisted ElGamal 密文加密的是数字零。系统的具体描述在以下注释中详细说明。 + +[注释](https://spl.solana.com/assets/files/zero_proof-3ec2a7e45f813a9467989be213439424.pdf)。 + +零余额证明是 `EmptyAccount` 指令所必需的,该指令用于准备关闭一个代币账户。只有当账户余额为零时,账户才可以被关闭。由于余额在机密扩展中是加密的,Token 程序无法通过检查账户状态直接验证账户中的加密余额是否为零。因此,程序通过验证附在 `EmptyAccount` 指令中的零余额证明来检查余额是否确实为零。 + +### 等值证明(Equality Proof) + +加密扩展使用了两种类型的等值证明。第一种是“密文-承诺”等值证明,它证实了一个扭曲的ElGamal密文和一个Pedersen承诺编码了相同的信息。第二种是“密文-密文”等值证明,它证实了两个扭曲的ElGamal密文加密的是同一信息。系统具体的描述在以下的注释文档中给出。 + +[[注释]](https://spl.solana.com/assets/files/equality_proof-c6a1d284e7c945c6fbef90929cf852d7.pdf). + + +等值证明的两种变体对于不同的交易指令至关重要。在`Transfer`和`TransferWithFee`指令中,需要使用“密文-承诺”等值证明;而在`WithdrawWithheldTokensFromMint`和`WithdrawWithheldTokensFromAccounts`指令中,则需要使用“密文-密文”等值证明。这些证明确保了在执行相关操作时,涉及的密文确实代表了相同的资产值,从而保障了交易的安全性和准确性。 + +### Sigma 费用证明(Fee Sigma Proof) + +Sigma 费用证明确保已提交的转账费用被正确计算。系统的具体描述在以下注释中详细说明。 + +[注释] + +Sigma 费用证明是`TransferWithFee`指令必需的。 + + +## 范围证明(Range Proofs) + +加密扩展使用 Bulletproofs 进行范围证明。参阅[academic paper](https://eprint.iacr.org/2017/1066)和[dalek](https://doc-internal.dalek.rs/bulletproofs/notes/index.html)了解详细信息。 diff --git a/docs/SolanaProgramLibrary/confidential-token/quickstart.md b/docs/SolanaProgramLibrary/confidential-token/quickstart.md new file mode 100644 index 0000000..c6ab34e --- /dev/null +++ b/docs/SolanaProgramLibrary/confidential-token/quickstart.md @@ -0,0 +1,86 @@ +# 快速入门指南 + +Token-2022 程序通过加密转账扩展提供加密转账功能。 + +本指南解释了如何使用加密转账扩展。 + +请参阅 [Token-2022 介绍](https://spl.solana.com/token-2022) 了解更多关于 Token-2022 及其扩展概念的信息。 + +## 设置 + +参考[SPL Token设置指南](https://spl.solana.com/token#setup)来安装客户端工具。Token-2022为了最大限度的兼容性,共享相同的CLI和NPM包。 + +这里所有的命令都在一个[辅助脚本](https://github.com/solana-labs/solana-program-library/tree/master/token/cli/examples/confidential-transfer.sh)中,该脚本位于[TOKEN CLI 示例](https://github.com/solana-labs/solana-program-library/tree/master/token/cli/examples)目录下。 + +### 示例:创建带有加密转账功能的铸币厂 + +要创建一个启用了加密转账的新铸币厂,运行: + +```bash +$ spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-confidential-transfers auto +``` + + +`auto` 关键词意味着任何代币用户都可以无须许可地配置他们的账户以执行加密转账。 + +如果你想限制加密转账功能仅对特定用户开放,你可以将批准策略设置为 `manual`。采用这种批准策略,所有用户必须手动获得批准才能执行加密转账。即便如此,任何人仍然可以非加密的方式使用该代币。 + +需要注意的是,你必须在创建代币时就配置好加密转账功能,之后无法再添加此项功能。 + +### 示例:为加密转账配置一个代币账户 + +账户创建过程如下: + +```bash +$ spl-token create-account +``` + +一旦用户创建了他们的账户,他们就可以配置该账户以支持加密转账: + +```bash +$ spl-token configure-confidential-transfer-account --address +``` + +请注意,只有账户所有者可以为他们的账户配置加密转账:他们应该为自己的账户设置加密密钥。这与普通账户(如关联代币账户)不同,在这种账户类型中,其他人有可能创建另一个人的账户。 + +### 示例:存入加密代币 + +一旦用户为其账户配置了加密转账功能并且拥有非加密的代币余额,他们必须将代币从非加密状态存入到加密状态: + +```bash +$ spl-token deposit-confidential-tokens --address +``` + +需要注意的是,存入加密余额的代币将不再出现在账户的非加密余额中:它们已经被完全转移到了加密余额里。 + + +### 示例:待定余额 + +每当账户通过转账或存款收到加密代币时,余额将显示在“待定”余额中,这意味着用户不能立即访问这些资金。 + +运行以下命令,将余额从“待定”状态变为“可用”状态: + +```bash +$ spl-token apply-pending-balance --address +``` + +### 示例:转账加密代币 + +一旦账户有了可用余额,用户最终可以将这些代币转到另一个已经配置好加密转账功能的账户! + +```bash +$ spl-token transfer --confidential +``` + +因为它需要多个相互依赖的交易,这个操作会稍微花费更长一点的时间,但即便如此,也只需几秒钟的时间。 + +### 示例:提取加密代币 + +一个账户里有可用加密余额的用户,可以将这些代币撤回到他们的非加密余额中。 + +```bash +$ spl-token withdraw-confidential-tokens --address +``` + +在运行命令前,应转换所有待处理的余额,以确保所有代币都可用。 +