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_program_error::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/// ElGamal ciphertext containing an account balance
33pub type EncryptedBalance = PodElGamalCiphertext;
34/// Authenticated encryption containing an account balance
35pub type DecryptableBalance = PodAeCiphertext;
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
42    /// approve new 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
48    /// `authority` before they may be used by the user.
49    ///
50    /// * If `true`, no approval is required and new accounts may be used
51    ///   immediately
52    /// * If `false`, the authority must approve newly configured accounts (see
53    ///   `ConfidentialTransferInstruction::ConfigureAccount`)
54    pub auto_approve_new_accounts: PodBool,
55
56    /// Authority to decode any transfer amount in a confidential transfer.
57    pub auditor_elgamal_pubkey: OptionalNonZeroElGamalPubkey,
58}
59
60impl Extension for ConfidentialTransferMint {
61    const TYPE: ExtensionType = ExtensionType::ConfidentialTransferMint;
62}
63
64/// Confidential account state
65#[repr(C)]
66#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
67pub struct ConfidentialTransferAccount {
68    /// `true` if this account has been approved for use. All confidential
69    /// transfer operations for the account will fail until approval is
70    /// granted.
71    pub approved: PodBool,
72
73    /// The public key associated with ElGamal encryption
74    pub elgamal_pubkey: PodElGamalPubkey,
75
76    /// The low 16 bits of the pending balance (encrypted by `elgamal_pubkey`)
77    pub pending_balance_lo: EncryptedBalance,
78
79    /// The high 32 bits of the pending balance (encrypted by `elgamal_pubkey`)
80    pub pending_balance_hi: EncryptedBalance,
81
82    /// The available balance (encrypted by `encryption_pubkey`)
83    pub available_balance: EncryptedBalance,
84
85    /// The decryptable available balance
86    pub decryptable_available_balance: DecryptableBalance,
87
88    /// If `false`, the extended account rejects any incoming confidential
89    /// transfers
90    pub allow_confidential_credits: PodBool,
91
92    /// If `false`, the base account rejects any incoming transfers
93    pub allow_non_confidential_credits: PodBool,
94
95    /// The total number of `Deposit` and `Transfer` instructions that have
96    /// credited `pending_balance`
97    pub pending_balance_credit_counter: PodU64,
98
99    /// The maximum number of `Deposit` and `Transfer` instructions that can
100    /// credit `pending_balance` before the `ApplyPendingBalance`
101    /// instruction is executed
102    pub maximum_pending_balance_credit_counter: PodU64,
103
104    /// The `expected_pending_balance_credit_counter` value that was included in
105    /// the last `ApplyPendingBalance` instruction
106    pub expected_pending_balance_credit_counter: PodU64,
107
108    /// The actual `pending_balance_credit_counter` when the last
109    /// `ApplyPendingBalance` instruction was executed
110    pub actual_pending_balance_credit_counter: PodU64,
111}
112
113impl Extension for ConfidentialTransferAccount {
114    const TYPE: ExtensionType = ExtensionType::ConfidentialTransferAccount;
115}
116
117impl ConfidentialTransferAccount {
118    /// Check if a `ConfidentialTransferAccount` has been approved for use.
119    pub fn approved(&self) -> ProgramResult {
120        if bool::from(&self.approved) {
121            Ok(())
122        } else {
123            Err(TokenError::ConfidentialTransferAccountNotApproved.into())
124        }
125    }
126
127    /// Check if a `ConfidentialTransferAccount` is in a closable state.
128    pub fn closable(&self) -> ProgramResult {
129        if self.pending_balance_lo == EncryptedBalance::zeroed()
130            && self.pending_balance_hi == EncryptedBalance::zeroed()
131            && self.available_balance == EncryptedBalance::zeroed()
132        {
133            Ok(())
134        } else {
135            Err(TokenError::ConfidentialTransferAccountHasBalance.into())
136        }
137    }
138
139    /// Check if a base account of a `ConfidentialTransferAccount` accepts
140    /// non-confidential transfers.
141    pub fn non_confidential_transfer_allowed(&self) -> ProgramResult {
142        if bool::from(&self.allow_non_confidential_credits) {
143            Ok(())
144        } else {
145            Err(TokenError::NonConfidentialTransfersDisabled.into())
146        }
147    }
148
149    /// Checks if a `ConfidentialTransferAccount` is configured to send funds.
150    pub fn valid_as_source(&self) -> ProgramResult {
151        self.approved()
152    }
153
154    /// Checks if a confidential extension is configured to receive funds.
155    ///
156    /// A destination account can receive funds if the following conditions are
157    /// satisfied:
158    ///   1. The account is approved by the confidential transfer mint authority
159    ///   2. The account is not disabled by the account owner
160    ///   3. The number of credits into the account has not reached the maximum
161    ///      credit counter
162    pub fn valid_as_destination(&self) -> ProgramResult {
163        self.approved()?;
164
165        if !bool::from(self.allow_confidential_credits) {
166            return Err(TokenError::ConfidentialTransferDepositsAndTransfersDisabled.into());
167        }
168
169        let new_destination_pending_balance_credit_counter =
170            u64::from(self.pending_balance_credit_counter)
171                .checked_add(1)
172                .ok_or(TokenError::Overflow)?;
173        if new_destination_pending_balance_credit_counter
174            > u64::from(self.maximum_pending_balance_credit_counter)
175        {
176            return Err(TokenError::MaximumPendingBalanceCreditCounterExceeded.into());
177        }
178
179        Ok(())
180    }
181
182    /// Increments a confidential extension pending balance credit counter.
183    pub fn increment_pending_balance_credit_counter(&mut self) -> ProgramResult {
184        self.pending_balance_credit_counter = (u64::from(self.pending_balance_credit_counter)
185            .checked_add(1)
186            .ok_or(TokenError::Overflow)?)
187        .into();
188        Ok(())
189    }
190}