Skip to main content

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}