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