spl_token_2022/extension/confidential_transfer/mod.rs
1use {
2 crate::{
3 error::TokenError,
4 extension::{Extension, ExtensionType},
5 },
6 bytemuck::{Pod, Zeroable},
7 solana_program::entrypoint::ProgramResult,
8 solana_zk_sdk::encryption::pod::{
9 auth_encryption::PodAeCiphertext,
10 elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
11 },
12 spl_pod::{
13 optional_keys::{OptionalNonZeroElGamalPubkey, OptionalNonZeroPubkey},
14 primitives::{PodBool, PodU64},
15 },
16};
17
18/// Maximum bit length of any deposit or transfer amount
19///
20/// Any deposit or transfer amount must be less than `2^48`
21pub const MAXIMUM_DEPOSIT_TRANSFER_AMOUNT: u64 = (u16::MAX as u64) + (1 << 16) * (u32::MAX as u64);
22
23/// Bit length of the low bits of pending balance plaintext
24pub const PENDING_BALANCE_LO_BIT_LENGTH: u32 = 16;
25
26/// The default maximum pending balance credit counter.
27pub const DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER: u64 = 65536;
28
29/// Confidential Transfer Extension instructions
30pub mod instruction;
31
32/// Confidential Transfer Extension processor
33pub mod processor;
34
35/// Helper functions to verify zero-knowledge proofs in the Confidential
36/// Transfer Extension
37pub mod verify_proof;
38
39/// Confidential Transfer Extension account information needed for instructions
40#[cfg(not(target_os = "solana"))]
41pub mod account_info;
42
43/// ElGamal ciphertext containing an account balance
44pub type EncryptedBalance = PodElGamalCiphertext;
45/// Authenticated encryption containing an account balance
46pub type DecryptableBalance = PodAeCiphertext;
47
48/// Confidential transfer mint configuration
49#[repr(C)]
50#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
51pub struct ConfidentialTransferMint {
52 /// Authority to modify the `ConfidentialTransferMint` configuration and to
53 /// approve new accounts (if `auto_approve_new_accounts` is true)
54 ///
55 /// The legacy Token Multisig account is not supported as the authority
56 pub authority: OptionalNonZeroPubkey,
57
58 /// Indicate if newly configured accounts must be approved by the
59 /// `authority` before they may be used by the user.
60 ///
61 /// * If `true`, no approval is required and new accounts may be used
62 /// immediately
63 /// * If `false`, the authority must approve newly configured accounts (see
64 /// `ConfidentialTransferInstruction::ConfigureAccount`)
65 pub auto_approve_new_accounts: PodBool,
66
67 /// Authority to decode any transfer amount in a confidential transfer.
68 pub auditor_elgamal_pubkey: OptionalNonZeroElGamalPubkey,
69}
70
71impl Extension for ConfidentialTransferMint {
72 const TYPE: ExtensionType = ExtensionType::ConfidentialTransferMint;
73}
74
75/// Confidential account state
76#[repr(C)]
77#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
78pub struct ConfidentialTransferAccount {
79 /// `true` if this account has been approved for use. All confidential
80 /// transfer operations for the account will fail until approval is
81 /// granted.
82 pub approved: PodBool,
83
84 /// The public key associated with ElGamal encryption
85 pub elgamal_pubkey: PodElGamalPubkey,
86
87 /// The low 16 bits of the pending balance (encrypted by `elgamal_pubkey`)
88 pub pending_balance_lo: EncryptedBalance,
89
90 /// The high 48 bits of the pending balance (encrypted by `elgamal_pubkey`)
91 pub pending_balance_hi: EncryptedBalance,
92
93 /// The available balance (encrypted by `encryption_pubkey`)
94 pub available_balance: EncryptedBalance,
95
96 /// The decryptable available balance
97 pub decryptable_available_balance: DecryptableBalance,
98
99 /// If `false`, the extended account rejects any incoming confidential
100 /// transfers
101 pub allow_confidential_credits: PodBool,
102
103 /// If `false`, the base account rejects any incoming transfers
104 pub allow_non_confidential_credits: PodBool,
105
106 /// The total number of `Deposit` and `Transfer` instructions that have
107 /// credited `pending_balance`
108 pub pending_balance_credit_counter: PodU64,
109
110 /// The maximum number of `Deposit` and `Transfer` instructions that can
111 /// credit `pending_balance` before the `ApplyPendingBalance`
112 /// instruction is executed
113 pub maximum_pending_balance_credit_counter: PodU64,
114
115 /// The `expected_pending_balance_credit_counter` value that was included in
116 /// the last `ApplyPendingBalance` instruction
117 pub expected_pending_balance_credit_counter: PodU64,
118
119 /// The actual `pending_balance_credit_counter` when the last
120 /// `ApplyPendingBalance` instruction was executed
121 pub actual_pending_balance_credit_counter: PodU64,
122}
123
124impl Extension for ConfidentialTransferAccount {
125 const TYPE: ExtensionType = ExtensionType::ConfidentialTransferAccount;
126}
127
128impl ConfidentialTransferAccount {
129 /// Check if a `ConfidentialTransferAccount` has been approved for use.
130 pub fn approved(&self) -> ProgramResult {
131 if bool::from(&self.approved) {
132 Ok(())
133 } else {
134 Err(TokenError::ConfidentialTransferAccountNotApproved.into())
135 }
136 }
137
138 /// Check if a `ConfidentialTransferAccount` is in a closable state.
139 pub fn closable(&self) -> ProgramResult {
140 if self.pending_balance_lo == EncryptedBalance::zeroed()
141 && self.pending_balance_hi == EncryptedBalance::zeroed()
142 && self.available_balance == EncryptedBalance::zeroed()
143 {
144 Ok(())
145 } else {
146 Err(TokenError::ConfidentialTransferAccountHasBalance.into())
147 }
148 }
149
150 /// Check if a base account of a `ConfidentialTransferAccount` accepts
151 /// non-confidential transfers.
152 pub fn non_confidential_transfer_allowed(&self) -> ProgramResult {
153 if bool::from(&self.allow_non_confidential_credits) {
154 Ok(())
155 } else {
156 Err(TokenError::NonConfidentialTransfersDisabled.into())
157 }
158 }
159
160 /// Checks if a `ConfidentialTransferAccount` is configured to send funds.
161 pub fn valid_as_source(&self) -> ProgramResult {
162 self.approved()
163 }
164
165 /// Checks if a confidential extension is configured to receive funds.
166 ///
167 /// A destination account can receive funds if the following conditions are
168 /// satisfied:
169 /// 1. The account is approved by the confidential transfer mint authority
170 /// 2. The account is not disabled by the account owner
171 /// 3. The number of credits into the account has not reached the maximum
172 /// credit counter
173 pub fn valid_as_destination(&self) -> ProgramResult {
174 self.approved()?;
175
176 if !bool::from(self.allow_confidential_credits) {
177 return Err(TokenError::ConfidentialTransferDepositsAndTransfersDisabled.into());
178 }
179
180 let new_destination_pending_balance_credit_counter =
181 u64::from(self.pending_balance_credit_counter)
182 .checked_add(1)
183 .ok_or(TokenError::Overflow)?;
184 if new_destination_pending_balance_credit_counter
185 > u64::from(self.maximum_pending_balance_credit_counter)
186 {
187 return Err(TokenError::MaximumPendingBalanceCreditCounterExceeded.into());
188 }
189
190 Ok(())
191 }
192
193 /// Increments a confidential extension pending balance credit counter.
194 pub fn increment_pending_balance_credit_counter(&mut self) -> ProgramResult {
195 self.pending_balance_credit_counter = (u64::from(self.pending_balance_credit_counter)
196 .checked_add(1)
197 .ok_or(TokenError::Overflow)?)
198 .into();
199 Ok(())
200 }
201}