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