spl_token_2022/extension/confidential_transfer/
mod.rs

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