diff --git a/Cargo.lock b/Cargo.lock index 2acd3d4311c..6ac47cf5ed4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6989,6 +6989,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-elgamal-registry" +version = "0.1.0" +dependencies = [ + "bytemuck", + "solana-program", + "solana-zk-sdk", + "spl-pod 0.4.0", + "spl-token-confidential-transfer-proof-extraction", +] + [[package]] name = "spl-example-cross-program-invocation" version = "1.0.0" @@ -7588,6 +7599,7 @@ dependencies = [ "solana-sdk", "solana-security-txt", "solana-zk-sdk", + "spl-elgamal-registry", "spl-memo 5.0.0", "spl-pod 0.4.0", "spl-tlv-account-resolution 0.8.1", @@ -7614,6 +7626,7 @@ dependencies = [ "solana-program-test", "solana-sdk", "spl-associated-token-account 5.0.1", + "spl-elgamal-registry", "spl-instruction-padding", "spl-memo 5.0.0", "spl-pod 0.4.0", @@ -7621,6 +7634,7 @@ dependencies = [ "spl-tlv-account-resolution 0.8.1", "spl-token-2022 5.0.2", "spl-token-client", + "spl-token-confidential-transfer-proof-extraction", "spl-token-confidential-transfer-proof-generation", "spl-token-group-interface 0.4.2", "spl-token-metadata-interface 0.5.1", @@ -7685,10 +7699,12 @@ dependencies = [ "solana-rpc-client-api", "solana-sdk", "spl-associated-token-account-client", + "spl-elgamal-registry", "spl-memo 5.0.0", "spl-record", "spl-token 6.0.0", "spl-token-2022 5.0.2", + "spl-token-confidential-transfer-proof-extraction", "spl-token-confidential-transfer-proof-generation", "spl-token-group-interface 0.4.2", "spl-token-metadata-interface 0.5.1", @@ -7732,7 +7748,9 @@ version = "0.1.0" dependencies = [ "bytemuck", "solana-curve25519", + "solana-program", "solana-zk-sdk", + "spl-pod 0.4.0", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 76890c34176..1e8fb54a826 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ members = [ "token/confidential-transfer/proof-extraction", "token/confidential-transfer/proof-generation", "token/confidential-transfer/proof-tests", + "token/confidential-transfer/elgamal-registry", "token/client", "utils/cgen", "utils/test-client", diff --git a/token/client/Cargo.toml b/token/client/Cargo.toml index 2ecf6710e04..b5d526e9965 100644 --- a/token/client/Cargo.toml +++ b/token/client/Cargo.toml @@ -20,6 +20,7 @@ solana-rpc-client = "2.0.3" solana-rpc-client-api = "2.0.3" solana-sdk = "2.0.3" spl-associated-token-account-client = { version = "1.0.0", path = "../../associated-token-account/client" } +spl-elgamal-registry = { version = "0.1.0", path = "../confidential-transfer/elgamal-registry"} spl-memo = { version = "5.0", path = "../../memo/program", features = [ "no-entrypoint", ] } @@ -27,6 +28,7 @@ spl-record = { version = "0.2.1", path = "../../record/program", features = ["no spl-token = { version = "6.0", path = "../program", features = [ "no-entrypoint", ] } +spl-token-confidential-transfer-proof-extraction = { version = "0.1.0", path = "../confidential-transfer/proof-extraction" } spl-token-confidential-transfer-proof-generation = { version = "0.1.0", path = "../confidential-transfer/proof-generation" } spl-token-2022 = { version = "5.0.2", path = "../program-2022" } spl-token-group-interface = { version = "0.4.2", path = "../../token-group/interface" } diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 165616a645f..33bab151499 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -47,7 +47,6 @@ use { BaseStateWithExtensions, Extension, ExtensionType, StateWithExtensionsOwned, }, instruction, offchain, - proof::{zk_proof_type_to_instruction, ProofData, ProofLocation}, solana_zk_sdk::{ encryption::{ auth_encryption::AeKey, @@ -63,6 +62,9 @@ use { }, state::{Account, AccountState, Mint, Multisig}, }, + spl_token_confidential_transfer_proof_extraction::instruction::{ + zk_proof_type_to_instruction, ProofData, ProofLocation, + }, spl_token_confidential_transfer_proof_generation::{ transfer::TransferProofData, transfer_with_fee::TransferWithFeeProofData, withdraw::WithdrawProofData, @@ -1972,6 +1974,29 @@ where .await } + /// Configures confidential transfers for a token account using an ElGamal + /// registry account + pub async fn confidential_transfer_configure_token_account_with_registry( + &self, + account: &Pubkey, + elgamal_registry_account: &Pubkey, + payer: Option<&Pubkey>, + ) -> TokenResult { + self.process_ixs::<[&dyn Signer; 0]>( + &[ + confidential_transfer::instruction::configure_account_with_registry( + &self.program_id, + account, + &self.pubkey, + elgamal_registry_account, + payer, + )?, + ], + &[], + ) + .await + } + /// Approves a token account for confidential transfers pub async fn confidential_transfer_approve_account( &self, diff --git a/token/confidential-transfer/elgamal-registry/Cargo.toml b/token/confidential-transfer/elgamal-registry/Cargo.toml new file mode 100644 index 00000000000..5c1ac47e916 --- /dev/null +++ b/token/confidential-transfer/elgamal-registry/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "spl-elgamal-registry" +version = "0.1.0" +description = "Solana ElGamal Registry Program" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" + +[features] +no-entrypoint = [] +test-sbf = [] + +[dependencies] +bytemuck = { version = "1.18.0", features = ["derive"] } +solana-program = "2.0.3" +solana-zk-sdk = "2.0.3" +spl-pod = { version = "0.4.0", path = "../../../libraries/pod" } +spl-token-confidential-transfer-proof-extraction = { version = "0.1.0", path = "../proof-extraction" } + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/token/confidential-transfer/elgamal-registry/src/entrypoint.rs b/token/confidential-transfer/elgamal-registry/src/entrypoint.rs new file mode 100644 index 00000000000..62a02f2a465 --- /dev/null +++ b/token/confidential-transfer/elgamal-registry/src/entrypoint.rs @@ -0,0 +1,14 @@ +//! Program entrypoint + +#![cfg(all(target_os = "solana", not(feature = "no-entrypoint")))] + +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; + +solana_program::entrypoint!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + crate::processor::process_instruction(program_id, accounts, instruction_data) +} diff --git a/token/confidential-transfer/elgamal-registry/src/instruction.rs b/token/confidential-transfer/elgamal-registry/src/instruction.rs new file mode 100644 index 00000000000..0fa33c9ca1e --- /dev/null +++ b/token/confidential-transfer/elgamal-registry/src/instruction.rs @@ -0,0 +1,194 @@ +use { + crate::{get_elgamal_registry_address, id}, + solana_program::{ + instruction::{AccountMeta, Instruction}, + program_error::ProgramError, + pubkey::Pubkey, + system_program, sysvar, + }, + solana_zk_sdk::zk_elgamal_proof_program::{ + instruction::ProofInstruction, proof_data::PubkeyValidityProofData, + }, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, +}; + +#[derive(Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RegistryInstruction { + /// Initialize an ElGamal public key registry. + /// + /// 0. `[writable]` The account to be created + /// 1. `[signer]` The wallet address (will also be the owner address for the + /// registry account) + /// 2. `[]` System program + /// 3. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the + /// same transaction or context state account if `VerifyPubkeyValidity` + /// is pre-verified into a context state account. + /// 4. `[]` (Optional) Record account if the accompanying proof is to be + /// read from a record account. + CreateRegistry { + /// Relative location of the `ProofInstruction::PubkeyValidityProof` + /// instruction to the `CreateElGamalRegistry` instruction in the + /// transaction. If the offset is `0`, then use a context state account + /// for the proof. + proof_instruction_offset: i8, + }, + /// Update an ElGamal public key registry with a new ElGamal public key. + /// + /// 0. `[writable]` The ElGamal registry account + /// 1. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the + /// same transaction or context state account if `VerifyPubkeyValidity` + /// is pre-verified into a context state account. + /// 2. `[]` (Optional) Record account if the accompanying proof is to be + /// read from a record account. + /// 3. `[signer]` The owner of the ElGamal public key registry + UpdateRegistry { + /// Relative location of the `ProofInstruction::PubkeyValidityProof` + /// instruction to the `UpdateElGamalRegistry` instruction in the + /// transaction. If the offset is `0`, then use a context state account + /// for the proof. + proof_instruction_offset: i8, + }, +} + +impl RegistryInstruction { + /// Unpacks a byte buffer into a `RegistryInstruction` + pub fn unpack(input: &[u8]) -> Result { + let (&tag, rest) = input + .split_first() + .ok_or(ProgramError::InvalidInstructionData)?; + + Ok(match tag { + 0 => { + let proof_instruction_offset = + *rest.first().ok_or(ProgramError::InvalidInstructionData)?; + Self::CreateRegistry { + proof_instruction_offset: proof_instruction_offset as i8, + } + } + 1 => { + let proof_instruction_offset = + *rest.first().ok_or(ProgramError::InvalidInstructionData)?; + Self::UpdateRegistry { + proof_instruction_offset: proof_instruction_offset as i8, + } + } + _ => return Err(ProgramError::InvalidInstructionData), + }) + } + + /// Packs a `RegistryInstruction` into a byte buffer. + pub fn pack(&self) -> Vec { + let mut buf = vec![]; + match self { + Self::CreateRegistry { + proof_instruction_offset, + } => { + buf.push(0); + buf.extend_from_slice(&proof_instruction_offset.to_le_bytes()); + } + Self::UpdateRegistry { + proof_instruction_offset, + } => { + buf.push(1); + buf.extend_from_slice(&proof_instruction_offset.to_le_bytes()); + } + }; + buf + } +} + +/// Create a `RegistryInstruction::CreateRegistry` instruction +pub fn create_registry( + owner_address: &Pubkey, + proof_location: ProofLocation, +) -> Result, ProgramError> { + let elgamal_registry_address = get_elgamal_registry_address(owner_address, &id()); + + let mut accounts = vec![ + AccountMeta::new(elgamal_registry_address, false), + AccountMeta::new_readonly(*owner_address, true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location); + + let mut instructions = vec![Instruction { + program_id: id(), + accounts, + data: RegistryInstruction::CreateRegistry { + proof_instruction_offset, + } + .pack(), + }]; + append_zk_elgamal_proof(&mut instructions, proof_location)?; + Ok(instructions) +} + +/// Create a `RegistryInstruction::UpdateRegistry` instruction +pub fn update_registry( + owner_address: &Pubkey, + proof_location: ProofLocation, +) -> Result, ProgramError> { + let elgamal_registry_address = get_elgamal_registry_address(owner_address, &id()); + + let mut accounts = vec![AccountMeta::new(elgamal_registry_address, false)]; + let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location); + accounts.push(AccountMeta::new_readonly(*owner_address, true)); + + let mut instructions = vec![Instruction { + program_id: id(), + accounts, + data: RegistryInstruction::UpdateRegistry { + proof_instruction_offset, + } + .pack(), + }]; + append_zk_elgamal_proof(&mut instructions, proof_location)?; + Ok(instructions) +} + +/// Takes a `ProofLocation`, updates the list of accounts, and returns a +/// suitable proof location +fn proof_instruction_offset( + accounts: &mut Vec, + proof_location: ProofLocation, +) -> i8 { + match proof_location { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + } +} + +/// Takes a `RegistryInstruction` and appends the pubkey validity proof +/// instruction +fn append_zk_elgamal_proof( + instructions: &mut Vec, + proof_data_location: ProofLocation, +) -> Result<(), ProgramError> { + if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = + proof_data_location + { + let proof_instruction_offset: i8 = proof_instruction_offset.into(); + if proof_instruction_offset != 1 { + return Err(ProgramError::InvalidArgument); + } + match proof_data { + ProofData::InstructionData(data) => instructions + .push(ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(None, data)), + ProofData::RecordAccount(address, offset) => instructions.push( + ProofInstruction::VerifyPubkeyValidity + .encode_verify_proof_from_account(None, address, offset), + ), + } + } + Ok(()) +} diff --git a/token/confidential-transfer/elgamal-registry/src/lib.rs b/token/confidential-transfer/elgamal-registry/src/lib.rs new file mode 100644 index 00000000000..895f42daad2 --- /dev/null +++ b/token/confidential-transfer/elgamal-registry/src/lib.rs @@ -0,0 +1,28 @@ +mod entrypoint; +pub mod instruction; +pub mod processor; +pub mod state; + +use solana_program::pubkey::Pubkey; + +/// Seed for the ElGamal registry program-derived address +pub const REGISTRY_ADDRESS_SEED: &[u8] = b"elgamal-registry"; + +/// Derives the ElGamal registry account address and seed for the given wallet +/// address +pub fn get_elgamal_registry_address_and_bump_seed( + wallet_address: &Pubkey, + program_id: &Pubkey, +) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[REGISTRY_ADDRESS_SEED, wallet_address.as_ref()], + program_id, + ) +} + +/// Derives the ElGamal registry account address for the given wallet address +pub fn get_elgamal_registry_address(wallet_address: &Pubkey, program_id: &Pubkey) -> Pubkey { + get_elgamal_registry_address_and_bump_seed(wallet_address, program_id).0 +} + +solana_program::declare_id!("regVYJW7tcT8zipN5YiBvHsvR5jXW1uLFxaHSbugABg"); diff --git a/token/confidential-transfer/elgamal-registry/src/processor.rs b/token/confidential-transfer/elgamal-registry/src/processor.rs new file mode 100644 index 00000000000..53be973c636 --- /dev/null +++ b/token/confidential-transfer/elgamal-registry/src/processor.rs @@ -0,0 +1,166 @@ +use { + crate::{ + get_elgamal_registry_address_and_bump_seed, + instruction::RegistryInstruction, + state::{ElGamalRegistry, ELGAMAL_REGISTRY_ACCOUNT_LEN}, + REGISTRY_ADDRESS_SEED, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + program::invoke_signed, + program_error::ProgramError, + pubkey::Pubkey, + rent::Rent, + system_instruction, + sysvar::Sysvar, + }, + solana_zk_sdk::zk_elgamal_proof_program::proof_data::pubkey_validity::{ + PubkeyValidityProofContext, PubkeyValidityProofData, + }, + spl_pod::bytemuck::pod_from_bytes_mut, + spl_token_confidential_transfer_proof_extraction::instruction::verify_and_extract_context, +}; + +/// Processes `CreateRegistry` instruction +pub fn process_create_registry_account( + program_id: &Pubkey, + accounts: &[AccountInfo], + proof_instruction_offset: i64, +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let elgamal_registry_account_info = next_account_info(account_info_iter)?; + let wallet_account_info = next_account_info(account_info_iter)?; + let system_program_info = next_account_info(account_info_iter)?; + + if !wallet_account_info.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + + // zero-knowledge proof certifies that the supplied ElGamal public key is valid + let proof_context = verify_and_extract_context::< + PubkeyValidityProofData, + PubkeyValidityProofContext, + >(account_info_iter, proof_instruction_offset, None)?; + + let (elgamal_registry_account_address, bump_seed) = + get_elgamal_registry_address_and_bump_seed(wallet_account_info.key, program_id); + if elgamal_registry_account_address != *elgamal_registry_account_info.key { + msg!("Error: ElGamal registry account address does not match seed derivation"); + return Err(ProgramError::InvalidSeeds); + } + + let elgamal_registry_account_seeds: &[&[_]] = &[ + REGISTRY_ADDRESS_SEED, + wallet_account_info.key.as_ref(), + &[bump_seed], + ]; + let rent = Rent::get()?; + + create_pda_account( + &rent, + ELGAMAL_REGISTRY_ACCOUNT_LEN, + program_id, + system_program_info, + elgamal_registry_account_info, + elgamal_registry_account_seeds, + )?; + + let elgamal_registry_account_data = &mut elgamal_registry_account_info.data.borrow_mut(); + let elgamal_registry_account = + pod_from_bytes_mut::(elgamal_registry_account_data)?; + elgamal_registry_account.owner = *wallet_account_info.key; + elgamal_registry_account.elgamal_pubkey = proof_context.pubkey; + + Ok(()) +} + +/// Processes `UpdateRegistry` instruction +pub fn process_update_registry_account( + _program_id: &Pubkey, + accounts: &[AccountInfo], + proof_instruction_offset: i64, +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let elgamal_registry_account_info = next_account_info(account_info_iter)?; + let elgamal_registry_account_data = &mut elgamal_registry_account_info.data.borrow_mut(); + let elgamal_registry_account = + pod_from_bytes_mut::(elgamal_registry_account_data)?; + + // zero-knowledge proof certifies that the supplied ElGamal public key is valid + let proof_context = verify_and_extract_context::< + PubkeyValidityProofData, + PubkeyValidityProofContext, + >(account_info_iter, proof_instruction_offset, None)?; + + let owner_info = next_account_info(account_info_iter)?; + validate_owner(owner_info, &elgamal_registry_account.owner)?; + + elgamal_registry_account.elgamal_pubkey = proof_context.pubkey; + Ok(()) +} + +/// Instruction processor +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + input: &[u8], +) -> ProgramResult { + let instruction = RegistryInstruction::unpack(input)?; + match instruction { + RegistryInstruction::CreateRegistry { + proof_instruction_offset, + } => { + msg!("ElGamalRegistryInstruction::CreateRegistry"); + process_create_registry_account(program_id, accounts, proof_instruction_offset as i64) + } + RegistryInstruction::UpdateRegistry { + proof_instruction_offset, + } => { + msg!("ElGamalRegistryInstruction::UpdateRegistry"); + process_update_registry_account(program_id, accounts, proof_instruction_offset as i64) + } + } +} + +fn validate_owner(owner_info: &AccountInfo, expected_owner: &Pubkey) -> ProgramResult { + if expected_owner != owner_info.key { + return Err(ProgramError::InvalidAccountOwner); + } + if !owner_info.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + Ok(()) +} + +/// Allocate ElGamal registry account using Program Derived Address for the +/// given seeds +pub fn create_pda_account<'a>( + rent: &Rent, + space: usize, + owner: &Pubkey, + system_program: &AccountInfo<'a>, + new_pda_account: &AccountInfo<'a>, + new_pda_signer_seeds: &[&[u8]], +) -> ProgramResult { + let required_lamports = rent + .minimum_balance(space) + .saturating_sub(new_pda_account.lamports()); + + if required_lamports > 0 { + return Err(ProgramError::AccountNotRentExempt); + } + + invoke_signed( + &system_instruction::allocate(new_pda_account.key, space as u64), + &[new_pda_account.clone(), system_program.clone()], + &[new_pda_signer_seeds], + )?; + + invoke_signed( + &system_instruction::assign(new_pda_account.key, owner), + &[new_pda_account.clone(), system_program.clone()], + &[new_pda_signer_seeds], + ) +} diff --git a/token/confidential-transfer/elgamal-registry/src/state.rs b/token/confidential-transfer/elgamal-registry/src/state.rs new file mode 100644 index 00000000000..55e77689df6 --- /dev/null +++ b/token/confidential-transfer/elgamal-registry/src/state.rs @@ -0,0 +1,18 @@ +use { + bytemuck::{Pod, Zeroable}, + solana_program::pubkey::Pubkey, + solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey, +}; + +pub const ELGAMAL_REGISTRY_ACCOUNT_LEN: usize = 64; + +/// ElGamal public key registry. It contains an ElGamal public key that is +/// associated with a wallet account, but independent of any specific mint. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] +pub struct ElGamalRegistry { + /// The owner of the registry + pub owner: Pubkey, + /// The ElGamal public key associated with an account + pub elgamal_pubkey: PodElGamalPubkey, +} diff --git a/token/confidential-transfer/proof-extraction/Cargo.toml b/token/confidential-transfer/proof-extraction/Cargo.toml index bbe07977862..c9065f2a72b 100644 --- a/token/confidential-transfer/proof-extraction/Cargo.toml +++ b/token/confidential-transfer/proof-extraction/Cargo.toml @@ -10,7 +10,9 @@ edition = "2021" [dependencies] bytemuck = "1.19.0" solana-curve25519 = "2.0.3" +solana-program = "2.0.3" solana-zk-sdk = "2.0.3" +spl-pod = { version = "0.4.0", path = "../../../libraries/pod" } thiserror = "1.0.65" [lib] diff --git a/token/program-2022/src/proof.rs b/token/confidential-transfer/proof-extraction/src/instruction.rs similarity index 91% rename from token/program-2022/src/proof.rs rename to token/confidential-transfer/proof-extraction/src/instruction.rs index 1bebfabe827..761a2b3fcfd 100644 --- a/token/program-2022/src/proof.rs +++ b/token/confidential-transfer/proof-extraction/src/instruction.rs @@ -1,10 +1,11 @@ -//! Helper for processing instruction data from ZK ElGamal proof program +//! Utility functions to simplify the handling of ZK ElGamal proof program +//! instruction data in SPL crates use { - crate::check_zk_elgamal_proof_program_account, bytemuck::Pod, solana_program::{ account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, instruction::Instruction, msg, program_error::ProgramError, @@ -21,6 +22,17 @@ use { std::{num::NonZeroI8, slice::Iter}, }; +/// Checks that the supplied program ID is correct for the ZK ElGamal proof +/// program +pub fn check_zk_elgamal_proof_program_account( + zk_elgamal_proof_program_id: &Pubkey, +) -> ProgramResult { + if zk_elgamal_proof_program_id != &solana_zk_sdk::zk_elgamal_proof_program::id() { + return Err(ProgramError::IncorrectProgramId); + } + Ok(()) +} + /// If a proof is to be read from a record account, the proof instruction data /// must be 5 bytes: 1 byte for the proof type and 4 bytes for the u32 offset const INSTRUCTION_DATA_LENGTH_WITH_RECORD_ACCOUNT: usize = 5; @@ -167,15 +179,3 @@ pub fn zk_proof_type_to_instruction( ProofType::Uninitialized => Err(ProgramError::InvalidInstructionData), } } - -/// Instruction options for when using split context state accounts -#[derive(Clone, Copy)] -pub struct SplitContextStateAccountsConfig { - /// If true, execute no op when an associated split proof context state - /// account is not initialized. Otherwise, fail on an uninitialized - /// context state account. - pub no_op_on_uninitialized_split_context_state: bool, - /// Close associated context states after a complete execution of the - /// transfer instruction. - pub close_split_context_state_on_execution: bool, -} diff --git a/token/confidential-transfer/proof-extraction/src/lib.rs b/token/confidential-transfer/proof-extraction/src/lib.rs index f3f99bc1072..82dcff0d31b 100644 --- a/token/confidential-transfer/proof-extraction/src/lib.rs +++ b/token/confidential-transfer/proof-extraction/src/lib.rs @@ -1,6 +1,7 @@ pub mod burn; pub mod encryption; pub mod errors; +pub mod instruction; pub mod mint; pub mod transfer; pub mod transfer_with_fee; diff --git a/token/program-2022-test/Cargo.toml b/token/program-2022-test/Cargo.toml index 79f9c7c114d..7f6fd1beef9 100644 --- a/token/program-2022-test/Cargo.toml +++ b/token/program-2022-test/Cargo.toml @@ -24,6 +24,7 @@ solana-program = "2.0.3" solana-program-test = "2.0.3" solana-sdk = "2.0.3" spl-associated-token-account = { version = "5.0.1", path = "../../associated-token-account/program" } +spl-elgamal-registry = { version = "0.1.0", path = "../confidential-transfer/elgamal-registry" } spl-memo = { version = "5.0.0", path = "../../memo/program", features = [ "no-entrypoint", ] } @@ -34,6 +35,7 @@ spl-record = { version = "0.2.1", path = "../../record/program", features = [ spl-token-2022 = { version = "5.0.2", path = "../program-2022", features = [ "no-entrypoint", ] } +spl-token-confidential-transfer-proof-extraction = { version = "0.1.0", path = "../confidential-transfer/proof-extraction" } spl-token-confidential-transfer-proof-generation = { version = "0.1.0", path = "../confidential-transfer/proof-generation" } spl-instruction-padding = { version = "0.2.0", path = "../../instruction-padding/program", features = [ "no-entrypoint", diff --git a/token/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index 64ae39c72c9..3e756082ce2 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -13,14 +13,17 @@ use { pubkey::Pubkey, signature::Signer, signer::{keypair::Keypair, signers::Signers}, - transaction::TransactionError, + system_instruction, + transaction::{Transaction, TransactionError}, transport::TransportError, }, + spl_elgamal_registry::state::ELGAMAL_REGISTRY_ACCOUNT_LEN, spl_record::state::RecordData, spl_token_2022::{ error::TokenError, extension::{ confidential_transfer::{ + self, account_info::{EmptyAccountAccountInfo, TransferAccountInfo, WithdrawAccountInfo}, ConfidentialTransferAccount, MAXIMUM_DEPOSIT_TRANSFER_AMOUNT, }, @@ -38,6 +41,7 @@ use { TokenResult, }, }, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, spl_token_confidential_transfer_proof_generation::{ transfer::TransferProofData, transfer_with_fee::TransferWithFeeProofData, withdraw::WithdrawProofData, @@ -2810,3 +2814,126 @@ async fn confidential_transfer_transfer_with_fee_and_memo_option( ) .await; } + +#[tokio::test] +async fn confidential_transfer_configure_token_account_with_registry() { + let authority = Keypair::new(); + let auto_approve_new_accounts = false; + let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); + let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); + + let mut context = TestContext::new().await; + context + .init_token_with_mint(vec![ + ExtensionInitializationParams::ConfidentialTransferMint { + authority: Some(authority.pubkey()), + auto_approve_new_accounts, + auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), + }, + ]) + .await + .unwrap(); + + let TokenContext { token, alice, .. } = context.token_context.unwrap(); + let alice_account_keypair = Keypair::new(); + token + .create_auxiliary_token_account_with_extension_space( + &alice_account_keypair, + &alice.pubkey(), + vec![ExtensionType::ConfidentialTransferAccount], + ) + .await + .unwrap(); + let elgamal_keypair = ElGamalKeypair::new_rand(); + + // create ElGamal registry + let mut ctx = context.context.lock().await; + let proof_data = + confidential_transfer::instruction::PubkeyValidityProofData::new(&elgamal_keypair).unwrap(); + let proof_location = ProofLocation::InstructionOffset( + 1.try_into().unwrap(), + ProofData::InstructionData(&proof_data), + ); + + let elgamal_registry_address = spl_elgamal_registry::get_elgamal_registry_address( + &alice.pubkey(), + &spl_elgamal_registry::id(), + ); + + let rent = ctx.banks_client.get_rent().await.unwrap(); + let space = ELGAMAL_REGISTRY_ACCOUNT_LEN; + let system_instruction = system_instruction::transfer( + &ctx.payer.pubkey(), + &elgamal_registry_address, + rent.minimum_balance(space), + ); + let create_registry_instructions = + spl_elgamal_registry::instruction::create_registry(&alice.pubkey(), proof_location) + .unwrap(); + + let instructions = [&[system_instruction], &create_registry_instructions[..]].concat(); + let tx = Transaction::new_signed_with_payer( + &instructions, + Some(&ctx.payer.pubkey()), + &[&ctx.payer, &alice], + ctx.last_blockhash, + ); + ctx.banks_client.process_transaction(tx).await.unwrap(); + + // update ElGamal registry + let new_elgamal_keypair = + ElGamalKeypair::new_from_signer(&alice, &alice_account_keypair.pubkey().to_bytes()) + .unwrap(); + let proof_data = + confidential_transfer::instruction::PubkeyValidityProofData::new(&new_elgamal_keypair) + .unwrap(); + let proof_location = ProofLocation::InstructionOffset( + 1.try_into().unwrap(), + ProofData::InstructionData(&proof_data), + ); + + let payer_pubkey = ctx.payer.pubkey(); + let instructions = + spl_elgamal_registry::instruction::update_registry(&alice.pubkey(), proof_location) + .unwrap(); + let tx = Transaction::new_signed_with_payer( + &instructions, + Some(&ctx.payer.pubkey()), + &[&ctx.payer, &alice], + ctx.last_blockhash, + ); + ctx.banks_client.process_transaction(tx).await.unwrap(); + drop(ctx); + + // configure account using ElGamal registry + let alice_account_keypair = Keypair::new(); + let alice_token_account = alice_account_keypair.pubkey(); + token + .create_auxiliary_token_account_with_extension_space( + &alice_account_keypair, + &alice.pubkey(), + vec![], // do not allocate space for confidential transfers + ) + .await + .unwrap(); + + token + .confidential_transfer_configure_token_account_with_registry( + &alice_account_keypair.pubkey(), + &elgamal_registry_address, + Some(&payer_pubkey), // test account allocation + ) + .await + .unwrap(); + + let state = token.get_account_info(&alice_token_account).await.unwrap(); + let extension = state + .get_extension::() + .unwrap(); + assert!(!bool::from(&extension.approved)); + assert!(bool::from(&extension.allow_confidential_credits)); + assert_eq!( + extension.elgamal_pubkey, + (*new_elgamal_keypair.pubkey()).into() + ); +} diff --git a/token/program-2022-test/tests/program_test.rs b/token/program-2022-test/tests/program_test.rs index 84846c5ab04..1fbd749dae6 100644 --- a/token/program-2022-test/tests/program_test.rs +++ b/token/program-2022-test/tests/program_test.rs @@ -50,6 +50,11 @@ impl TestContext { spl_record::id(), processor!(spl_record::processor::process_instruction), ); + program_test.add_program( + "spl_elgamal_registry", + spl_elgamal_registry::id(), + processor!(spl_elgamal_registry::processor::process_instruction), + ); let context = program_test.start_with_context().await; let context = Arc::new(Mutex::new(context)); diff --git a/token/program-2022/Cargo.toml b/token/program-2022/Cargo.toml index a62489d4001..145a59f7cc3 100644 --- a/token/program-2022/Cargo.toml +++ b/token/program-2022/Cargo.toml @@ -25,6 +25,7 @@ num_enum = "0.7.3" solana-program = "2.0.3" solana-security-txt = "1.1.1" solana-zk-sdk = "2.0.3" +spl-elgamal-registry = { version = "0.1.0", path = "../confidential-transfer/elgamal-registry", features = ["no-entrypoint"] } spl-memo = { version = "5.0", path = "../../memo/program", features = [ "no-entrypoint" ] } spl-token = { version = "6.0", path = "../program", features = ["no-entrypoint"] } spl-token-confidential-transfer-ciphertext-arithmetic = { version = "0.1.0", path = "../confidential-transfer/ciphertext-arithmetic" } diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index ddbfb242635..e82f4f521af 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -11,7 +11,6 @@ use { check_program_account, extension::confidential_transfer::*, instruction::{encode_instruction, TokenInstruction}, - proof::{ProofData, ProofLocation}, }, bytemuck::Zeroable, num_enum::{IntoPrimitive, TryFromPrimitive}, @@ -19,8 +18,9 @@ use { instruction::{AccountMeta, Instruction}, program_error::ProgramError, pubkey::Pubkey, - sysvar, + system_program, sysvar, }, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, }; /// Confidential Transfer extension instructions @@ -472,6 +472,37 @@ pub enum ConfidentialTransferInstruction { /// Data expected by this instruction: /// `TransferWithFeeInstructionData` TransferWithFee, + + /// Configures confidential transfers for a token account. + /// + /// This instruction is identical to the `ConfigureAccount` account except + /// that a valid `ElGamalRegistry` account is expected in place of the + /// `VerifyPubkeyValidity` proof. + /// + /// An `ElGamalRegistry` account is valid if it shares the same owner with + /// the token account. If a valid `ElGamalRegistry` account is provided, + /// then the program skips the verification of the ElGamal pubkey + /// validity proof as well as the token owner signature. + /// + /// If the token account is not large enough to include the new + /// cconfidential transfer extension, then optionally reallocate the + /// account to increase the data size. To reallocate, a payer account to + /// fund the reallocation and the system account should be included in the + /// instruction. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The SPL Token account. + /// 1. `[]` The corresponding SPL Token mint. + /// 2. `[]` The ElGamal registry account. + /// 3. `[signer, writable]` (Optional) The payer account to fund + /// reallocation + /// 4. `[]` (Optional) System program for reallocation funding + /// + /// Data expected by this instruction: + /// None + ConfigureAccountWithRegistry, } /// Data expected by `ConfidentialTransferInstruction::InitializeMint` @@ -1701,3 +1732,31 @@ pub fn transfer_with_fee( Ok(instructions) } + +/// Create a `ConfigureAccountWithRegistry` instruction +pub fn configure_account_with_registry( + token_program_id: &Pubkey, + token_account: &Pubkey, + mint: &Pubkey, + elgamal_registry_account: &Pubkey, + payer: Option<&Pubkey>, +) -> Result { + check_program_account(token_program_id)?; + let mut accounts = vec![ + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(*mint, false), + AccountMeta::new_readonly(*elgamal_registry_account, false), + ]; + if let Some(payer) = payer { + accounts.push(AccountMeta::new(*payer, true)); + accounts.push(AccountMeta::new_readonly(system_program::id(), false)); + } + + Ok(encode_instruction( + token_program_id, + accounts, + TokenInstruction::ConfidentialTransferExtension, + ConfidentialTransferInstruction::ConfigureAccountWithRegistry, + &(), + )) +} diff --git a/token/program-2022/src/extension/confidential_transfer/mod.rs b/token/program-2022/src/extension/confidential_transfer/mod.rs index f779db36938..0558938af61 100644 --- a/token/program-2022/src/extension/confidential_transfer/mod.rs +++ b/token/program-2022/src/extension/confidential_transfer/mod.rs @@ -23,6 +23,9 @@ pub const MAXIMUM_DEPOSIT_TRANSFER_AMOUNT: u64 = (u16::MAX as u64) + (1 << 16) * /// Bit length of the low bits of pending balance plaintext pub const PENDING_BALANCE_LO_BIT_LENGTH: u32 = 16; +/// The default maximum pending balance credit counter. +pub const DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER: u64 = 65536; + /// Confidential Transfer Extension instructions pub mod instruction; diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index 49f6ba2d8f7..a93b5e85268 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -6,7 +6,7 @@ use { }; use { crate::{ - check_program_account, + check_elgamal_registry_program_account, check_program_account, error::TokenError, extension::{ confidential_transfer::{instruction::*, verify_proof::*, *}, @@ -15,6 +15,7 @@ use { EncryptedWithheldAmount, }, memo_transfer::{check_previous_sibling_instruction_is_memo, memo_required}, + set_account_type, transfer_fee::TransferFeeConfig, transfer_hook, BaseStateWithExtensions, BaseStateWithExtensionsMut, PodStateWithExtensions, PodStateWithExtensionsMut, @@ -22,19 +23,24 @@ use { instruction::{decode_instruction_data, decode_instruction_type}, pod::{PodAccount, PodMint}, processor::Processor, - proof::verify_and_extract_context, + state::Account, }, solana_program::{ account_info::{next_account_info, AccountInfo}, clock::Clock, entrypoint::ProgramResult, msg, + program::invoke, program_error::ProgramError, pubkey::Pubkey, - sysvar::Sysvar, + system_instruction, + sysvar::{rent::Rent, Sysvar}, }, + spl_elgamal_registry::state::ElGamalRegistry, + spl_pod::bytemuck::pod_from_bytes, spl_token_confidential_transfer_proof_extraction::{ - transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext, + instruction::verify_and_extract_context, transfer::TransferProofContext, + transfer_with_fee::TransferWithFeeProofContext, }, }; @@ -92,26 +98,128 @@ fn process_update_mint( Ok(()) } +enum ElGamalPubkeySource<'a> { + ProofInstructionOffset(i64), + ElGamalRegistry(&'a ElGamalRegistry), +} + +/// Processes a [ConfigureAccountWithRegistry] instruction. +fn process_configure_account_with_registry( + program_id: &Pubkey, + accounts: &[AccountInfo], +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let token_account_info = next_account_info(account_info_iter)?; + let _mint_info = next_account_info(account_info_iter)?; + let elgamal_registry_account = next_account_info(account_info_iter)?; + + check_elgamal_registry_program_account(elgamal_registry_account.owner)?; + + // if a payer account for reallcation is provided, then reallocate + if let Ok(payer_info) = next_account_info(account_info_iter) { + let system_program_info = next_account_info(account_info_iter)?; + reallocate_for_configure_account_with_registry( + token_account_info, + payer_info, + system_program_info, + )?; + } + + let elgamal_registry_account_data = &elgamal_registry_account.data.borrow(); + let elgamal_registry_account = + pod_from_bytes::(elgamal_registry_account_data)?; + + let decryptable_zero_balance = PodAeCiphertext::default(); + let maximum_pending_balance_credit_counter = + DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER.into(); + + process_configure_account( + program_id, + accounts, + &decryptable_zero_balance, + &maximum_pending_balance_credit_counter, + ElGamalPubkeySource::ElGamalRegistry(elgamal_registry_account), + ) +} + +fn reallocate_for_configure_account_with_registry<'a>( + token_account_info: &AccountInfo<'a>, + payer_info: &AccountInfo<'a>, + system_program_info: &AccountInfo<'a>, +) -> ProgramResult { + let mut current_extension_types = { + let token_account = token_account_info.data.borrow(); + let account = PodStateWithExtensions::::unpack(&token_account)?; + account.get_extension_types()? + }; + // `try_calculate_account_len` dedupes extension types, so always push + // the `ConfidentialTransferAccount` type + current_extension_types.push(ExtensionType::ConfidentialTransferAccount); + let needed_account_len = + ExtensionType::try_calculate_account_len::(¤t_extension_types)?; + + // if account is already large enough, return early + if token_account_info.data_len() >= needed_account_len { + return Ok(()); + } + + // reallocate + msg!( + "account needs realloc, +{:?} bytes", + needed_account_len - token_account_info.data_len() + ); + token_account_info.realloc(needed_account_len, false)?; + + // if additional lamports needed to remain rent-exempt, transfer them + let rent = Rent::get()?; + let new_rent_exempt_reserve = rent.minimum_balance(needed_account_len); + + let current_lamport_reserve = token_account_info.lamports(); + let lamports_diff = new_rent_exempt_reserve.saturating_sub(current_lamport_reserve); + if lamports_diff > 0 { + invoke( + &system_instruction::transfer(payer_info.key, token_account_info.key, lamports_diff), + &[ + payer_info.clone(), + token_account_info.clone(), + system_program_info.clone(), + ], + )?; + } + + // set account_type, if needed + let mut token_account_data = token_account_info.data.borrow_mut(); + set_account_type::(&mut token_account_data)?; + + Ok(()) +} + /// Processes a [ConfigureAccount] instruction. fn process_configure_account( program_id: &Pubkey, accounts: &[AccountInfo], decryptable_zero_balance: &DecryptableBalance, maximum_pending_balance_credit_counter: &PodU64, - proof_instruction_offset: i64, + elgamal_pubkey_source: ElGamalPubkeySource, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let token_account_info = next_account_info(account_info_iter)?; let mint_info = next_account_info(account_info_iter)?; - // zero-knowledge proof certifies that the supplied ElGamal public key is valid - let proof_context = verify_and_extract_context::< - PubkeyValidityProofData, - PubkeyValidityProofContext, - >(account_info_iter, proof_instruction_offset, None)?; - - let authority_info = next_account_info(account_info_iter)?; - let authority_info_data_len = authority_info.data_len(); + let elgamal_pubkey = match elgamal_pubkey_source { + ElGamalPubkeySource::ProofInstructionOffset(offset) => { + // zero-knowledge proof certifies that the supplied ElGamal public key is valid + let proof_context = verify_and_extract_context::< + PubkeyValidityProofData, + PubkeyValidityProofContext, + >(account_info_iter, offset, None)?; + proof_context.pubkey + } + ElGamalPubkeySource::ElGamalRegistry(elgamal_registry_account) => { + let _elgamal_registry_account = next_account_info(account_info_iter)?; + elgamal_registry_account.elgamal_pubkey + } + }; check_program_account(token_account_info.owner)?; let token_account_data = &mut token_account_info.data.borrow_mut(); @@ -121,13 +229,28 @@ fn process_configure_account( return Err(TokenError::MintMismatch.into()); } - Processor::validate_owner( - program_id, - &token_account.base.owner, - authority_info, - authority_info_data_len, - account_info_iter.as_slice(), - )?; + match elgamal_pubkey_source { + ElGamalPubkeySource::ProofInstructionOffset(_) => { + let authority_info = next_account_info(account_info_iter)?; + let authority_info_data_len = authority_info.data_len(); + + Processor::validate_owner( + program_id, + &token_account.base.owner, + authority_info, + authority_info_data_len, + account_info_iter.as_slice(), + )?; + } + ElGamalPubkeySource::ElGamalRegistry(elgamal_registry_account) => { + // if ElGamal registry was provided, then just verify that the owners of the + // registry and token accounts match, then skip the signature + // verification check + if elgamal_registry_account.owner != token_account.base.owner { + return Err(TokenError::OwnerMismatch.into()); + } + } + }; check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow(); @@ -140,7 +263,7 @@ fn process_configure_account( let confidential_transfer_account = token_account.init_extension::(false)?; confidential_transfer_account.approved = confidential_transfer_mint.auto_approve_new_accounts; - confidential_transfer_account.elgamal_pubkey = proof_context.pubkey; + confidential_transfer_account.elgamal_pubkey = elgamal_pubkey; confidential_transfer_account.maximum_pending_balance_credit_counter = *maximum_pending_balance_credit_counter; @@ -1108,7 +1231,7 @@ pub(crate) fn process_instruction( accounts, &data.decryptable_zero_balance, &data.maximum_pending_balance_credit_counter, - data.proof_instruction_offset as i64, + ElGamalPubkeySource::ProofInstructionOffset(data.proof_instruction_offset as i64), ) } ConfidentialTransferInstruction::ApproveAccount => { @@ -1217,5 +1340,9 @@ pub(crate) fn process_instruction( #[cfg(not(feature = "zk-ops"))] Err(ProgramError::InvalidInstructionData) } + ConfidentialTransferInstruction::ConfigureAccountWithRegistry => { + msg!("ConfidentialTransferInstruction::ConfigureAccountWithRegistry"); + process_configure_account_with_registry(program_id, accounts) + } } } diff --git a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs index 912d3c66d3d..88df4074436 100644 --- a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs @@ -2,15 +2,14 @@ use { crate::{ error::TokenError, extension::{confidential_transfer::instruction::*, transfer_fee::TransferFee}, - proof::verify_and_extract_context, }, solana_program::{ account_info::{next_account_info, AccountInfo}, program_error::ProgramError, }, spl_token_confidential_transfer_proof_extraction::{ - transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext, - withdraw::WithdrawProofContext, + instruction::verify_and_extract_context, transfer::TransferProofContext, + transfer_with_fee::TransferWithFeeProofContext, withdraw::WithdrawProofContext, }, std::slice::Iter, }; diff --git a/token/program-2022/src/extension/confidential_transfer_fee/instruction.rs b/token/program-2022/src/extension/confidential_transfer_fee/instruction.rs index aeb243303bf..6c625df3c20 100644 --- a/token/program-2022/src/extension/confidential_transfer_fee/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer_fee/instruction.rs @@ -11,7 +11,6 @@ use { instruction::CiphertextCiphertextEqualityProofData, DecryptableBalance, }, instruction::{encode_instruction, TokenInstruction}, - proof::{ProofData, ProofLocation}, solana_zk_sdk::{ encryption::pod::elgamal::PodElGamalPubkey, zk_elgamal_proof_program::instruction::ProofInstruction, @@ -26,6 +25,7 @@ use { sysvar, }, spl_pod::optional_keys::OptionalNonZeroPubkey, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, std::convert::TryFrom, }; diff --git a/token/program-2022/src/extension/confidential_transfer_fee/processor.rs b/token/program-2022/src/extension/confidential_transfer_fee/processor.rs index 82549130f0e..f71880cbb6c 100644 --- a/token/program-2022/src/extension/confidential_transfer_fee/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer_fee/processor.rs @@ -27,7 +27,6 @@ use { instruction::{decode_instruction_data, decode_instruction_type}, pod::{PodAccount, PodMint}, processor::Processor, - proof::verify_and_extract_context, solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey, }, bytemuck::Zeroable, @@ -39,6 +38,7 @@ use { pubkey::Pubkey, }, spl_pod::optional_keys::OptionalNonZeroPubkey, + spl_token_confidential_transfer_proof_extraction::instruction::verify_and_extract_context, }; /// Processes an [InitializeConfidentialTransferFeeConfig] instruction. diff --git a/token/program-2022/src/lib.rs b/token/program-2022/src/lib.rs index 573fd0f18d7..d413d5903a5 100644 --- a/token/program-2022/src/lib.rs +++ b/token/program-2022/src/lib.rs @@ -14,7 +14,6 @@ pub mod onchain; pub mod pod; pub mod pod_instruction; pub mod processor; -pub mod proof; #[cfg(feature = "serde-traits")] pub mod serialization; pub mod state; @@ -130,3 +129,13 @@ pub fn check_system_program_account(system_program_id: &Pubkey) -> ProgramResult } Ok(()) } + +/// Checks if the supplied program ID is that of the ElGamal registry program +pub(crate) fn check_elgamal_registry_program_account( + elgamal_registry_account_program_id: &Pubkey, +) -> ProgramResult { + if elgamal_registry_account_program_id != &spl_elgamal_registry::id() { + return Err(ProgramError::IncorrectProgramId); + } + Ok(()) +}