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}