Skip to main content

hopper_runtime/
token_confidential_ix.rs

1//! Token-2022 confidential-transfer instructions.
2//!
3//! The builders carry ciphertexts and ElGamal keys as opaque byte arrays:
4//! a program that moves confidential balances receives them from its
5//! caller (they are produced off chain with the account's keys) and hands
6//! them to Token-2022 unchanged. Nothing here encrypts, decrypts, or
7//! builds a proof.
8//!
9//! A zero-knowledge proof that an instruction needs lives in one of two
10//! places, named by [`ProofLocation`]: another instruction of the same
11//! transaction, addressed by its offset from the Token-2022 instruction
12//! (the Instructions sysvar account is then part of the account list), or
13//! a context-state account that the ZK ElGamal proof program verified
14//! earlier. The account order follows the Token-2022 processor: the
15//! instruction's own accounts, the Instructions sysvar once if any proof
16//! is given by offset, each context-state account in proof order, the
17//! authority, and the multisig signers.
18//!
19//! Instruction data is `[27][sub-discriminator][fields]` with the field
20//! layouts of `spl-token-2022-interface`; the tests compare every builder
21//! with that crate's constructors.
22
23use crate::account::AccountView;
24use crate::address::Address;
25use crate::error::ProgramError;
26use crate::instruction::{InstructionAccount, Signer};
27use crate::token::{
28    authority_meta, require_authority_signed_direct, require_multisig_signers_direct, Invoke,
29    TokenInstruction, TokenSink, Trailing,
30};
31use crate::ProgramResult;
32use core::num::NonZeroI8;
33
34/// The Token-2022 instruction that carries every confidential-transfer
35/// sub-instruction.
36pub const IX_CONFIDENTIAL_TRANSFER: u8 = 27;
37
38/// An authenticated-encryption ciphertext of a balance
39/// (`DecryptableBalance`, `PodAeCiphertext`).
40pub const DECRYPTABLE_BALANCE_LEN: usize = 36;
41/// A twisted-ElGamal ciphertext (`PodElGamalCiphertext`).
42pub const ELGAMAL_CIPHERTEXT_LEN: usize = 64;
43/// An ElGamal public key (`PodElGamalPubkey`).
44pub const ELGAMAL_PUBKEY_LEN: usize = 32;
45
46/// A balance ciphertext only the account's owner can decrypt.
47pub type DecryptableBalance = [u8; DECRYPTABLE_BALANCE_LEN];
48/// A twisted-ElGamal ciphertext.
49pub type ElGamalCiphertext = [u8; ELGAMAL_CIPHERTEXT_LEN];
50/// An ElGamal public key.
51pub type ElGamalPubkey = [u8; ELGAMAL_PUBKEY_LEN];
52
53/// Where a zero-knowledge proof is found.
54#[derive(Clone, Copy)]
55pub enum ProofLocation<'a> {
56    /// In another instruction of this transaction, at this offset from
57    /// the Token-2022 instruction.
58    InstructionOffset(NonZeroI8),
59    /// In a context-state account the proof program verified earlier.
60    ContextStateAccount(&'a AccountView<'a>),
61}
62
63impl ProofLocation<'_> {
64    /// The offset byte the instruction data carries: the offset, or zero
65    /// for a context-state account.
66    #[inline(always)]
67    pub const fn offset_byte(&self) -> u8 {
68        match self {
69            Self::InstructionOffset(offset) => offset.get() as u8,
70            Self::ContextStateAccount(_) => 0,
71        }
72    }
73
74    #[inline(always)]
75    const fn is_offset(&self) -> bool {
76        matches!(self, Self::InstructionOffset(_))
77    }
78}
79
80/// The proof accounts of one instruction, in the order the processor
81/// reads them: the Instructions sysvar once if any proof is given by
82/// offset, then every context-state account in proof order.
83struct ProofAccounts<'a> {
84    views: [Option<&'a AccountView<'a>>; 6],
85    len: usize,
86}
87
88impl<'a> ProofAccounts<'a> {
89    #[inline(always)]
90    fn gather<'x: 'a>(
91        instructions_sysvar: Option<&'x AccountView<'x>>,
92        proofs: &[ProofLocation<'x>],
93    ) -> Result<Self, ProgramError> {
94        let mut out = Self {
95            views: [None; 6],
96            len: 0,
97        };
98        if proofs.iter().any(ProofLocation::is_offset) {
99            // A proof by offset is read through the Instructions sysvar;
100            // without the account the CPI could not reach it.
101            let sysvar = instructions_sysvar.ok_or(ProgramError::NotEnoughAccountKeys)?;
102            out.views[0] = Some(sysvar);
103            out.len = 1;
104        }
105        for proof in proofs {
106            if let ProofLocation::ContextStateAccount(account) = proof {
107                out.views[out.len] = Some(*account);
108                out.len += 1;
109            }
110        }
111        Ok(out)
112    }
113
114    /// The gathered views as a dense array and its length.
115    #[inline(always)]
116    fn dense(&self, fallback: &'a AccountView<'a>) -> ([&'a AccountView<'a>; 6], usize) {
117        let mut dense = [fallback; 6];
118        let mut i = 0;
119        while i < self.len {
120            if let Some(view) = self.views[i] {
121                dense[i] = view;
122            }
123            i += 1;
124        }
125        (dense, self.len)
126    }
127}
128
129/// Byte-exact encoders, shared by the builders and the tests.
130pub mod encoders {
131    use super::*;
132
133    #[inline(always)]
134    fn nullable_32(out: &mut [u8], value: Option<&[u8; 32]>) -> ProgramResult {
135        match value {
136            Some(value) if value == &[0u8; 32] => Err(ProgramError::InvalidArgument),
137            Some(value) => {
138                out.copy_from_slice(value);
139                Ok(())
140            }
141            None => Ok(()),
142        }
143    }
144
145    /// `[27][0][authority: nullable 32][auto_approve][auditor key: nullable 32]`.
146    #[inline(always)]
147    pub fn encode_initialize_mint(
148        authority: Option<&Address>,
149        auto_approve_new_accounts: bool,
150        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
151    ) -> Result<[u8; 67], ProgramError> {
152        let mut data = [0u8; 67];
153        data[0] = IX_CONFIDENTIAL_TRANSFER;
154        data[1] = 0;
155        nullable_32(&mut data[2..34], authority.map(|a| a.as_array()))?;
156        data[34] = u8::from(auto_approve_new_accounts);
157        nullable_32(&mut data[35..67], auditor_elgamal_pubkey)?;
158        Ok(data)
159    }
160
161    /// `[27][1][auto_approve][auditor key: nullable 32]`.
162    #[inline(always)]
163    pub fn encode_update_mint(
164        auto_approve_new_accounts: bool,
165        auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
166    ) -> Result<[u8; 35], ProgramError> {
167        let mut data = [0u8; 35];
168        data[0] = IX_CONFIDENTIAL_TRANSFER;
169        data[1] = 1;
170        data[2] = u8::from(auto_approve_new_accounts);
171        nullable_32(&mut data[3..35], auditor_elgamal_pubkey)?;
172        Ok(data)
173    }
174
175    /// `[27][2][decryptable zero balance: 36][max pending credits: u64][proof offset]`.
176    #[inline(always)]
177    pub fn encode_configure_account(
178        decryptable_zero_balance: &DecryptableBalance,
179        maximum_pending_balance_credit_counter: u64,
180        proof_offset: u8,
181    ) -> [u8; 47] {
182        let mut data = [0u8; 47];
183        data[0] = IX_CONFIDENTIAL_TRANSFER;
184        data[1] = 2;
185        data[2..38].copy_from_slice(decryptable_zero_balance);
186        data[38..46].copy_from_slice(&maximum_pending_balance_credit_counter.to_le_bytes());
187        data[46] = proof_offset;
188        data
189    }
190
191    /// `[27][sub]`: approve account (3), the four credit toggles (9 to
192    /// 12), configure with registry (14).
193    #[inline(always)]
194    pub const fn encode_bare(sub: u8) -> [u8; 2] {
195        [IX_CONFIDENTIAL_TRANSFER, sub]
196    }
197
198    /// `[27][4][proof offset]`.
199    #[inline(always)]
200    pub const fn encode_empty_account(proof_offset: u8) -> [u8; 3] {
201        [IX_CONFIDENTIAL_TRANSFER, 4, proof_offset]
202    }
203
204    /// `[27][5][amount: u64][decimals]`.
205    #[inline(always)]
206    pub fn encode_deposit(amount: u64, decimals: u8) -> [u8; 11] {
207        let mut data = [0u8; 11];
208        data[0] = IX_CONFIDENTIAL_TRANSFER;
209        data[1] = 5;
210        data[2..10].copy_from_slice(&amount.to_le_bytes());
211        data[10] = decimals;
212        data
213    }
214
215    /// `[27][6][amount: u64][decimals][new decryptable balance: 36][equality offset][range offset]`.
216    #[inline(always)]
217    pub fn encode_withdraw(
218        amount: u64,
219        decimals: u8,
220        new_decryptable_available_balance: &DecryptableBalance,
221        equality_proof_offset: u8,
222        range_proof_offset: u8,
223    ) -> [u8; 49] {
224        let mut data = [0u8; 49];
225        data[0] = IX_CONFIDENTIAL_TRANSFER;
226        data[1] = 6;
227        data[2..10].copy_from_slice(&amount.to_le_bytes());
228        data[10] = decimals;
229        data[11..47].copy_from_slice(new_decryptable_available_balance);
230        data[47] = equality_proof_offset;
231        data[48] = range_proof_offset;
232        data
233    }
234
235    /// `[27][sub][new source decryptable balance: 36][auditor lo: 64][auditor hi: 64][offsets]`
236    /// with three offsets for `Transfer` (7) and five for
237    /// `TransferWithFee` (13).
238    #[inline(always)]
239    pub fn encode_transfer<const N: usize>(
240        sub: u8,
241        new_source_decryptable_available_balance: &DecryptableBalance,
242        transfer_amount_auditor_ciphertext_lo: &ElGamalCiphertext,
243        transfer_amount_auditor_ciphertext_hi: &ElGamalCiphertext,
244        proof_offsets: [u8; N],
245    ) -> ([u8; 171], usize) {
246        let mut data = [0u8; 171];
247        data[0] = IX_CONFIDENTIAL_TRANSFER;
248        data[1] = sub;
249        data[2..38].copy_from_slice(new_source_decryptable_available_balance);
250        data[38..102].copy_from_slice(transfer_amount_auditor_ciphertext_lo);
251        data[102..166].copy_from_slice(transfer_amount_auditor_ciphertext_hi);
252        let mut i = 0;
253        while i < N && i < 5 {
254            data[166 + i] = proof_offsets[i];
255            i += 1;
256        }
257        (data, 166 + i)
258    }
259
260    /// `[27][8][expected pending credits: u64][new decryptable balance: 36]`.
261    #[inline(always)]
262    pub fn encode_apply_pending_balance(
263        expected_pending_balance_credit_counter: u64,
264        new_decryptable_available_balance: &DecryptableBalance,
265    ) -> [u8; 46] {
266        let mut data = [0u8; 46];
267        data[0] = IX_CONFIDENTIAL_TRANSFER;
268        data[1] = 8;
269        data[2..10].copy_from_slice(&expected_pending_balance_credit_counter.to_le_bytes());
270        data[10..46].copy_from_slice(new_decryptable_available_balance);
271        data
272    }
273}
274
275use encoders::*;
276
277/// Token-2022 entry points for a builder with an authority.
278macro_rules! confidential_methods {
279    ($name:ident, authority = $auth:ident) => {
280        impl $name<'_> {
281            /// Send to Token-2022 with the authority signed directly.
282            #[inline]
283            pub fn invoke(&self) -> ProgramResult {
284                require_authority_signed_direct(self.$auth)?;
285                self.emit(&[], &mut Invoke::token_2022(&[]))
286            }
287
288            /// Send to Token-2022 with PDA signers.
289            #[inline]
290            pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
291                self.emit(&[], &mut Invoke::token_2022(signers))
292            }
293
294            /// Send to Token-2022 with a multisig authority whose signers
295            /// signed directly.
296            #[inline]
297            pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
298                require_multisig_signers_direct(multisig_signers)?;
299                self.emit(multisig_signers, &mut Invoke::token_2022(&[]))
300            }
301
302            /// Send to Token-2022 with a multisig authority and PDA
303            /// signers.
304            #[inline]
305            pub fn invoke_signed_multisig(
306                &self,
307                multisig_signers: &[&AccountView<'_>],
308                signers: &[Signer<'_, '_>],
309            ) -> ProgramResult {
310                self.emit(multisig_signers, &mut Invoke::token_2022(signers))
311            }
312        }
313    };
314}
315
316// ---------------------------------------------------------------------
317// Mint configuration
318
319/// `InitializeMint` (27/0): enable confidential transfers on a
320/// not-yet-initialized mint. Runs before `InitializeMint2`.
321pub struct InitializeConfidentialTransferMint<'a> {
322    pub mint: &'a AccountView<'a>,
323    pub authority: Option<&'a Address>,
324    pub auto_approve_new_accounts: bool,
325    pub auditor_elgamal_pubkey: Option<&'a ElGamalPubkey>,
326}
327
328impl<'a, 'x: 'a> TokenInstruction<'a> for InitializeConfidentialTransferMint<'x> {
329    #[inline(always)]
330    fn emit(
331        &self,
332        _multisig_signers: &[&'a AccountView<'a>],
333        sink: &mut impl TokenSink<'a>,
334    ) -> ProgramResult {
335        let data = encode_initialize_mint(
336            self.authority,
337            self.auto_approve_new_accounts,
338            self.auditor_elgamal_pubkey,
339        )?;
340        let accounts = [InstructionAccount::writable(self.mint.address())];
341        let views = [self.mint];
342        sink.emit(&data, accounts, views, &[])
343    }
344}
345
346impl InitializeConfidentialTransferMint<'_> {
347    /// Send to Token-2022.
348    #[inline]
349    pub fn invoke(&self) -> ProgramResult {
350        self.emit(&[], &mut Invoke::token_2022(&[]))
351    }
352}
353
354/// `UpdateMint` (27/1): change the auto-approve policy and the auditor.
355pub struct UpdateConfidentialTransferMint<'a> {
356    pub mint: &'a AccountView<'a>,
357    pub authority: &'a AccountView<'a>,
358    pub auto_approve_new_accounts: bool,
359    pub auditor_elgamal_pubkey: Option<&'a ElGamalPubkey>,
360}
361
362impl<'a, 'x: 'a> TokenInstruction<'a> for UpdateConfidentialTransferMint<'x> {
363    #[inline(always)]
364    fn emit(
365        &self,
366        multisig_signers: &[&'a AccountView<'a>],
367        sink: &mut impl TokenSink<'a>,
368    ) -> ProgramResult {
369        let data = encode_update_mint(self.auto_approve_new_accounts, self.auditor_elgamal_pubkey)?;
370        let accounts = [
371            InstructionAccount::writable(self.mint.address()),
372            authority_meta(self.authority, multisig_signers),
373        ];
374        let views = [self.mint, self.authority];
375        sink.emit(
376            &data,
377            accounts,
378            views,
379            &[Trailing::signers(multisig_signers)],
380        )
381    }
382}
383
384confidential_methods!(UpdateConfidentialTransferMint, authority = authority);
385
386// ---------------------------------------------------------------------
387// Instructions without a proof
388
389/// An instruction over a writable token account, optionally its mint, and
390/// the authority, with fixed data and no proof.
391macro_rules! account_instruction {
392    ($(#[$doc:meta])* $name:ident { account = $account:ident $(, @mint $mint:ident)? $(, $field:ident : $ty:ty)* $(,)? } data = |$s:ident| $data:expr;) => {
393        $(#[$doc])*
394        pub struct $name<'a> {
395            pub $account: &'a AccountView<'a>,
396            $(pub $mint: &'a AccountView<'a>,)?
397            pub authority: &'a AccountView<'a>,
398            $(pub $field: $ty,)*
399        }
400
401        impl<'a, 'x: 'a> TokenInstruction<'a> for $name<'x> {
402            #[inline(always)]
403            fn emit(
404                &self,
405                multisig_signers: &[&'a AccountView<'a>],
406                sink: &mut impl TokenSink<'a>,
407            ) -> ProgramResult {
408                let $s = self;
409                let data = $data;
410                let accounts = [
411                    InstructionAccount::writable(self.$account.address()),
412                    $(InstructionAccount::readonly(self.$mint.address()),)?
413                    authority_meta(self.authority, multisig_signers),
414                ];
415                let views = [self.$account, $(self.$mint,)? self.authority];
416                sink.emit(&data, accounts, views, &[Trailing::signers(multisig_signers)])
417            }
418        }
419
420        confidential_methods!($name, authority = authority);
421    };
422}
423
424account_instruction! {
425    /// `ApproveAccount` (27/3): the mint's confidential-transfer authority
426    /// approves a configured account.
427    ApproveConfidentialAccount { account = account, @mint mint }
428    data = |_s| encode_bare(3);
429}
430
431account_instruction! {
432    /// `Deposit` (27/5): move `amount` from the account's public balance
433    /// into its pending confidential balance.
434    ConfidentialDeposit { account = account, @mint mint, amount: u64, decimals: u8 }
435    data = |s| encode_deposit(s.amount, s.decimals);
436}
437
438account_instruction! {
439    /// `ApplyPendingBalance` (27/8): fold the pending balance into the
440    /// available balance.
441    ApplyPendingConfidentialBalance {
442        account = account,
443        expected_pending_balance_credit_counter: u64,
444        new_decryptable_available_balance: &'a DecryptableBalance,
445    }
446    data = |s| encode_apply_pending_balance(
447        s.expected_pending_balance_credit_counter,
448        s.new_decryptable_available_balance,
449    );
450}
451
452account_instruction! {
453    /// `EnableConfidentialCredits` (27/9).
454    EnableConfidentialCredits { account = account }
455    data = |_s| encode_bare(9);
456}
457
458account_instruction! {
459    /// `DisableConfidentialCredits` (27/10).
460    DisableConfidentialCredits { account = account }
461    data = |_s| encode_bare(10);
462}
463
464account_instruction! {
465    /// `EnableNonConfidentialCredits` (27/11).
466    EnableNonConfidentialCredits { account = account }
467    data = |_s| encode_bare(11);
468}
469
470account_instruction! {
471    /// `DisableNonConfidentialCredits` (27/12).
472    DisableNonConfidentialCredits { account = account }
473    data = |_s| encode_bare(12);
474}
475
476/// `ConfigureAccountWithRegistry` (27/14): configure a token account from
477/// an ElGamal registry account. With a `payer` (and the System program),
478/// Token-2022 reallocates the account and the payer funds it.
479pub struct ConfigureConfidentialAccountWithRegistry<'a> {
480    pub account: &'a AccountView<'a>,
481    pub mint: &'a AccountView<'a>,
482    pub elgamal_registry: &'a AccountView<'a>,
483    /// The payer and the System program account, when the account must
484    /// grow.
485    pub payer: Option<(&'a AccountView<'a>, &'a AccountView<'a>)>,
486}
487
488impl<'a, 'x: 'a> TokenInstruction<'a> for ConfigureConfidentialAccountWithRegistry<'x> {
489    #[inline(always)]
490    fn emit(
491        &self,
492        _multisig_signers: &[&'a AccountView<'a>],
493        sink: &mut impl TokenSink<'a>,
494    ) -> ProgramResult {
495        let data = encode_bare(14);
496        let accounts = [
497            InstructionAccount::writable(self.account.address()),
498            InstructionAccount::readonly(self.mint.address()),
499            InstructionAccount::readonly(self.elgamal_registry.address()),
500        ];
501        let views = [self.account, self.mint, self.elgamal_registry];
502        match self.payer {
503            Some((payer, system_program)) => sink.emit(
504                &data,
505                accounts,
506                views,
507                &[
508                    Trailing {
509                        views: &[payer],
510                        writable: true,
511                        signer: true,
512                    },
513                    Trailing::readonly(&[system_program]),
514                ],
515            ),
516            None => sink.emit(&data, accounts, views, &[]),
517        }
518    }
519}
520
521impl ConfigureConfidentialAccountWithRegistry<'_> {
522    /// Send to Token-2022.
523    #[inline]
524    pub fn invoke(&self) -> ProgramResult {
525        self.emit(&[], &mut Invoke::token_2022(&[]))
526    }
527
528    /// Send to Token-2022 with PDA signers (a PDA payer).
529    #[inline]
530    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
531        self.emit(&[], &mut Invoke::token_2022(signers))
532    }
533}
534
535// ---------------------------------------------------------------------
536// Instructions that carry proofs
537
538/// Emit an instruction whose fixed accounts are followed by the proof
539/// accounts, the authority, and the multisig signers.
540// One parameter per part of the instruction; a struct would only rename them.
541#[allow(clippy::too_many_arguments)]
542#[inline(always)]
543fn emit_with_proofs<'a, 'x: 'a, const N: usize>(
544    sink: &mut impl TokenSink<'a>,
545    data: &[u8],
546    accounts: [InstructionAccount<'a>; N],
547    views: [&'a AccountView<'a>; N],
548    instructions_sysvar: Option<&'x AccountView<'x>>,
549    proofs: &[ProofLocation<'x>],
550    authority: &'x AccountView<'x>,
551    multisig_signers: &[&'a AccountView<'a>],
552) -> ProgramResult {
553    let gathered = ProofAccounts::gather(instructions_sysvar, proofs)?;
554    let (dense, len) = gathered.dense(authority);
555    let authority_run: [&'a AccountView<'a>; 1] = [authority];
556    sink.emit(
557        data,
558        accounts,
559        views,
560        &[
561            Trailing::readonly(&dense[..len]),
562            Trailing {
563                views: &authority_run,
564                writable: false,
565                signer: multisig_signers.is_empty(),
566            },
567            Trailing::signers(multisig_signers),
568        ],
569    )
570}
571
572/// `ConfigureAccount` (27/2): set a token account up for confidential
573/// transfers. The proof is a public-key validity proof.
574pub struct ConfigureConfidentialAccount<'a> {
575    pub account: &'a AccountView<'a>,
576    pub mint: &'a AccountView<'a>,
577    pub authority: &'a AccountView<'a>,
578    pub decryptable_zero_balance: &'a DecryptableBalance,
579    pub maximum_pending_balance_credit_counter: u64,
580    pub proof: ProofLocation<'a>,
581    /// Required when the proof is given by instruction offset.
582    pub instructions_sysvar: Option<&'a AccountView<'a>>,
583}
584
585impl<'a, 'x: 'a> TokenInstruction<'a> for ConfigureConfidentialAccount<'x> {
586    #[inline(always)]
587    fn emit(
588        &self,
589        multisig_signers: &[&'a AccountView<'a>],
590        sink: &mut impl TokenSink<'a>,
591    ) -> ProgramResult {
592        let data = encode_configure_account(
593            self.decryptable_zero_balance,
594            self.maximum_pending_balance_credit_counter,
595            self.proof.offset_byte(),
596        );
597        let accounts = [
598            InstructionAccount::writable(self.account.address()),
599            InstructionAccount::readonly(self.mint.address()),
600        ];
601        emit_with_proofs(
602            sink,
603            &data,
604            accounts,
605            [self.account, self.mint],
606            self.instructions_sysvar,
607            &[self.proof],
608            self.authority,
609            multisig_signers,
610        )
611    }
612}
613
614confidential_methods!(ConfigureConfidentialAccount, authority = authority);
615
616/// `EmptyAccount` (27/4): prove the confidential balance is zero so the
617/// account can be closed. The proof is a zero-ciphertext proof.
618pub struct EmptyConfidentialAccount<'a> {
619    pub account: &'a AccountView<'a>,
620    pub authority: &'a AccountView<'a>,
621    pub proof: ProofLocation<'a>,
622    /// Required when the proof is given by instruction offset.
623    pub instructions_sysvar: Option<&'a AccountView<'a>>,
624}
625
626impl<'a, 'x: 'a> TokenInstruction<'a> for EmptyConfidentialAccount<'x> {
627    #[inline(always)]
628    fn emit(
629        &self,
630        multisig_signers: &[&'a AccountView<'a>],
631        sink: &mut impl TokenSink<'a>,
632    ) -> ProgramResult {
633        let data = encode_empty_account(self.proof.offset_byte());
634        let accounts = [InstructionAccount::writable(self.account.address())];
635        emit_with_proofs(
636            sink,
637            &data,
638            accounts,
639            [self.account],
640            self.instructions_sysvar,
641            &[self.proof],
642            self.authority,
643            multisig_signers,
644        )
645    }
646}
647
648confidential_methods!(EmptyConfidentialAccount, authority = authority);
649
650/// `Withdraw` (27/6): move `amount` from the confidential balance to the
651/// public balance. Proofs: ciphertext-commitment equality, then range.
652pub struct ConfidentialWithdraw<'a> {
653    pub account: &'a AccountView<'a>,
654    pub mint: &'a AccountView<'a>,
655    pub authority: &'a AccountView<'a>,
656    pub amount: u64,
657    pub decimals: u8,
658    pub new_decryptable_available_balance: &'a DecryptableBalance,
659    pub equality_proof: ProofLocation<'a>,
660    pub range_proof: ProofLocation<'a>,
661    /// Required when any proof is given by instruction offset.
662    pub instructions_sysvar: Option<&'a AccountView<'a>>,
663}
664
665impl<'a, 'x: 'a> TokenInstruction<'a> for ConfidentialWithdraw<'x> {
666    #[inline(always)]
667    fn emit(
668        &self,
669        multisig_signers: &[&'a AccountView<'a>],
670        sink: &mut impl TokenSink<'a>,
671    ) -> ProgramResult {
672        let data = encode_withdraw(
673            self.amount,
674            self.decimals,
675            self.new_decryptable_available_balance,
676            self.equality_proof.offset_byte(),
677            self.range_proof.offset_byte(),
678        );
679        let accounts = [
680            InstructionAccount::writable(self.account.address()),
681            InstructionAccount::readonly(self.mint.address()),
682        ];
683        emit_with_proofs(
684            sink,
685            &data,
686            accounts,
687            [self.account, self.mint],
688            self.instructions_sysvar,
689            &[self.equality_proof, self.range_proof],
690            self.authority,
691            multisig_signers,
692        )
693    }
694}
695
696confidential_methods!(ConfidentialWithdraw, authority = authority);
697
698/// `Transfer` (27/7): a confidential transfer. Proofs: equality,
699/// ciphertext validity, range.
700pub struct ConfidentialTransfer<'a> {
701    pub source: &'a AccountView<'a>,
702    pub mint: &'a AccountView<'a>,
703    pub destination: &'a AccountView<'a>,
704    pub authority: &'a AccountView<'a>,
705    pub new_source_decryptable_available_balance: &'a DecryptableBalance,
706    pub transfer_amount_auditor_ciphertext_lo: &'a ElGamalCiphertext,
707    pub transfer_amount_auditor_ciphertext_hi: &'a ElGamalCiphertext,
708    pub equality_proof: ProofLocation<'a>,
709    pub ciphertext_validity_proof: ProofLocation<'a>,
710    pub range_proof: ProofLocation<'a>,
711    /// Required when any proof is given by instruction offset.
712    pub instructions_sysvar: Option<&'a AccountView<'a>>,
713}
714
715impl<'a, 'x: 'a> TokenInstruction<'a> for ConfidentialTransfer<'x> {
716    #[inline(always)]
717    fn emit(
718        &self,
719        multisig_signers: &[&'a AccountView<'a>],
720        sink: &mut impl TokenSink<'a>,
721    ) -> ProgramResult {
722        let (data, len) = encode_transfer(
723            7,
724            self.new_source_decryptable_available_balance,
725            self.transfer_amount_auditor_ciphertext_lo,
726            self.transfer_amount_auditor_ciphertext_hi,
727            [
728                self.equality_proof.offset_byte(),
729                self.ciphertext_validity_proof.offset_byte(),
730                self.range_proof.offset_byte(),
731            ],
732        );
733        let accounts = [
734            InstructionAccount::writable(self.source.address()),
735            InstructionAccount::readonly(self.mint.address()),
736            InstructionAccount::writable(self.destination.address()),
737        ];
738        emit_with_proofs(
739            sink,
740            &data[..len],
741            accounts,
742            [self.source, self.mint, self.destination],
743            self.instructions_sysvar,
744            &[
745                self.equality_proof,
746                self.ciphertext_validity_proof,
747                self.range_proof,
748            ],
749            self.authority,
750            multisig_signers,
751        )
752    }
753}
754
755confidential_methods!(ConfidentialTransfer, authority = authority);
756
757/// `TransferWithFee` (27/13): a confidential transfer on a mint with a
758/// transfer fee. Proofs: equality, transfer-amount ciphertext validity,
759/// fee sigma, fee ciphertext validity, range.
760pub struct ConfidentialTransferWithFee<'a> {
761    pub source: &'a AccountView<'a>,
762    pub mint: &'a AccountView<'a>,
763    pub destination: &'a AccountView<'a>,
764    pub authority: &'a AccountView<'a>,
765    pub new_source_decryptable_available_balance: &'a DecryptableBalance,
766    pub transfer_amount_auditor_ciphertext_lo: &'a ElGamalCiphertext,
767    pub transfer_amount_auditor_ciphertext_hi: &'a ElGamalCiphertext,
768    pub equality_proof: ProofLocation<'a>,
769    pub transfer_amount_ciphertext_validity_proof: ProofLocation<'a>,
770    pub fee_sigma_proof: ProofLocation<'a>,
771    pub fee_ciphertext_validity_proof: ProofLocation<'a>,
772    pub range_proof: ProofLocation<'a>,
773    /// Required when any proof is given by instruction offset.
774    pub instructions_sysvar: Option<&'a AccountView<'a>>,
775}
776
777impl<'a, 'x: 'a> TokenInstruction<'a> for ConfidentialTransferWithFee<'x> {
778    #[inline(always)]
779    fn emit(
780        &self,
781        multisig_signers: &[&'a AccountView<'a>],
782        sink: &mut impl TokenSink<'a>,
783    ) -> ProgramResult {
784        let (data, len) = encode_transfer(
785            13,
786            self.new_source_decryptable_available_balance,
787            self.transfer_amount_auditor_ciphertext_lo,
788            self.transfer_amount_auditor_ciphertext_hi,
789            [
790                self.equality_proof.offset_byte(),
791                self.transfer_amount_ciphertext_validity_proof.offset_byte(),
792                self.fee_sigma_proof.offset_byte(),
793                self.fee_ciphertext_validity_proof.offset_byte(),
794                self.range_proof.offset_byte(),
795            ],
796        );
797        let accounts = [
798            InstructionAccount::writable(self.source.address()),
799            InstructionAccount::readonly(self.mint.address()),
800            InstructionAccount::writable(self.destination.address()),
801        ];
802        emit_with_proofs(
803            sink,
804            &data[..len],
805            accounts,
806            [self.source, self.mint, self.destination],
807            self.instructions_sysvar,
808            &[
809                self.equality_proof,
810                self.transfer_amount_ciphertext_validity_proof,
811                self.fee_sigma_proof,
812                self.fee_ciphertext_validity_proof,
813                self.range_proof,
814            ],
815            self.authority,
816            multisig_signers,
817        )
818    }
819}
820
821confidential_methods!(ConfidentialTransferWithFee, authority = authority);
822
823#[cfg(test)]
824mod tests {
825    use super::encoders::*;
826    use super::*;
827
828    #[test]
829    fn fixed_layouts_have_the_interface_sizes() {
830        assert_eq!(encode_bare(9), [27, 9]);
831        assert_eq!(encode_empty_account(3), [27, 4, 3]);
832        assert_eq!(encode_deposit(1, 6), [27, 5, 1, 0, 0, 0, 0, 0, 0, 0, 6]);
833        let balance = [9u8; DECRYPTABLE_BALANCE_LEN];
834        let configure = encode_configure_account(&balance, 65_536, 1);
835        assert_eq!(&configure[..2], &[27, 2]);
836        assert_eq!(&configure[2..38], &balance);
837        assert_eq!(&configure[38..46], &65_536u64.to_le_bytes());
838        assert_eq!(configure[46], 1);
839        let withdraw = encode_withdraw(5, 2, &balance, 0, 0xff);
840        assert_eq!(withdraw.len(), 49);
841        assert_eq!(&withdraw[47..], &[0, 0xff]);
842        let apply = encode_apply_pending_balance(4, &balance);
843        assert_eq!(&apply[2..10], &4u64.to_le_bytes());
844        let lo = [1u8; ELGAMAL_CIPHERTEXT_LEN];
845        let hi = [2u8; ELGAMAL_CIPHERTEXT_LEN];
846        let (transfer, len) = encode_transfer(7, &balance, &lo, &hi, [1, 2, 3]);
847        assert_eq!(len, 169);
848        assert_eq!(&transfer[166..169], &[1, 2, 3]);
849        let (with_fee, len) = encode_transfer(13, &balance, &lo, &hi, [1, 2, 3, 4, 5]);
850        assert_eq!(len, 171);
851        assert_eq!(&with_fee[166..171], &[1, 2, 3, 4, 5]);
852    }
853
854    #[test]
855    fn a_present_zero_key_is_refused_and_an_absent_one_is_zero() {
856        let key = [4u8; ELGAMAL_PUBKEY_LEN];
857        let authority = Address::new_from_array([3; 32]);
858        let data = encode_initialize_mint(Some(&authority), true, Some(&key)).unwrap();
859        assert_eq!(&data[2..34], &[3u8; 32]);
860        assert_eq!(data[34], 1);
861        assert_eq!(&data[35..], &key);
862        let data = encode_initialize_mint(None, false, None).unwrap();
863        assert_eq!(&data[2..], &[0u8; 65]);
864        assert!(encode_update_mint(true, Some(&[0u8; 32])).is_err());
865        assert!(
866            encode_initialize_mint(Some(&Address::new_from_array([0; 32])), true, None).is_err()
867        );
868    }
869
870    #[test]
871    fn a_negative_offset_is_its_twos_complement_byte() {
872        let back = ProofLocation::InstructionOffset(NonZeroI8::new(-1).unwrap());
873        assert_eq!(back.offset_byte(), 0xff);
874        let next = ProofLocation::InstructionOffset(NonZeroI8::new(2).unwrap());
875        assert_eq!(next.offset_byte(), 2);
876    }
877}