From 3d0cca2f4d89bb7e8cbd24e4755104c02b6b76e9 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 17 Oct 2024 18:01:10 +0900 Subject: [PATCH 01/22] refactor proof extraction logic into a `spl-token-confidential-transfer-proof-extraction` --- .../proof-extraction/Cargo.toml | 2 + .../proof-extraction/src/lib.rs | 179 +++++++++++++++++ token/program-2022/src/proof.rs | 181 ------------------ 3 files changed, 181 insertions(+), 181 deletions(-) delete mode 100644 token/program-2022/src/proof.rs diff --git a/token/confidential-transfer/proof-extraction/Cargo.toml b/token/confidential-transfer/proof-extraction/Cargo.toml index bbe07977862..5f10403c6c6 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 = "0.4.0" thiserror = "1.0.65" [lib] diff --git a/token/confidential-transfer/proof-extraction/src/lib.rs b/token/confidential-transfer/proof-extraction/src/lib.rs index f3f99bc1072..6ff7f8187aa 100644 --- a/token/confidential-transfer/proof-extraction/src/lib.rs +++ b/token/confidential-transfer/proof-extraction/src/lib.rs @@ -5,3 +5,182 @@ pub mod mint; pub mod transfer; pub mod transfer_with_fee; pub mod withdraw; + +use { + bytemuck::Pod, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + instruction::Instruction, + msg, + program_error::ProgramError, + pubkey::Pubkey, + sysvar::instructions::get_instruction_relative, + }, + solana_zk_sdk::zk_elgamal_proof_program::{ + self, + instruction::ProofInstruction, + proof_data::{ProofType, ZkProofData}, + state::ProofContextState, + }, + spl_pod::bytemuck::pod_from_bytes, + 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; + +/// Decodes the proof context data associated with a zero-knowledge proof +/// instruction. +pub fn decode_proof_instruction_context, U: Pod>( + account_info_iter: &mut Iter<'_, AccountInfo<'_>>, + expected: ProofInstruction, + instruction: &Instruction, +) -> Result { + if instruction.program_id != zk_elgamal_proof_program::id() + || ProofInstruction::instruction_type(&instruction.data) != Some(expected) + { + msg!("Unexpected proof instruction"); + return Err(ProgramError::InvalidInstructionData); + } + + // If the instruction data size is exactly 5 bytes, then interpret it as an + // offset byte for a record account. This behavior is identical to that of + // the ZK ElGamal proof program. + if instruction.data.len() == INSTRUCTION_DATA_LENGTH_WITH_RECORD_ACCOUNT { + let record_account = next_account_info(account_info_iter)?; + + // first byte is the proof type + let start_offset = u32::from_le_bytes(instruction.data[1..].try_into().unwrap()) as usize; + let end_offset = start_offset + .checked_add(std::mem::size_of::()) + .ok_or(ProgramError::InvalidAccountData)?; + + let record_account_data = record_account.data.borrow(); + let raw_proof_data = record_account_data + .get(start_offset..end_offset) + .ok_or(ProgramError::AccountDataTooSmall)?; + + bytemuck::try_from_bytes::(raw_proof_data) + .map(|proof_data| *ZkProofData::context_data(proof_data)) + .map_err(|_| ProgramError::InvalidAccountData) + } else { + ProofInstruction::proof_data::(&instruction.data) + .map(|proof_data| *ZkProofData::context_data(proof_data)) + .ok_or(ProgramError::InvalidInstructionData) + } +} + +/// A proof location type meant to be used for arguments to instruction +/// constructors. +#[derive(Clone, Copy)] +pub enum ProofLocation<'a, T> { + /// The proof is included in the same transaction of a corresponding + /// token-2022 instruction. + InstructionOffset(NonZeroI8, ProofData<'a, T>), + /// The proof is pre-verified into a context state account. + ContextStateAccount(&'a Pubkey), +} + +impl<'a, T> ProofLocation<'a, T> { + /// Returns true if the proof location is an instruction offset + pub fn is_instruction_offset(&self) -> bool { + match self { + Self::InstructionOffset(_, _) => true, + Self::ContextStateAccount(_) => false, + } + } +} + +/// A proof data type to distinguish between proof data included as part of +/// zk-token proof instruction data and proof data stored in a record account. +#[derive(Clone, Copy)] +pub enum ProofData<'a, T> { + /// The proof data + InstructionData(&'a T), + /// The address of a record account containing the proof data and its byte + /// offset + RecordAccount(&'a Pubkey, u32), +} + +/// Verify zero-knowledge proof and return the corresponding proof context. +pub fn verify_and_extract_context<'a, T: Pod + ZkProofData, U: Pod>( + account_info_iter: &mut Iter<'_, AccountInfo<'a>>, + proof_instruction_offset: i64, + sysvar_account_info: Option<&'_ AccountInfo<'a>>, +) -> Result { + if proof_instruction_offset == 0 { + // interpret `account_info` as a context state account + let context_state_account_info = next_account_info(account_info_iter)?; + check_zk_elgamal_proof_program_account(context_state_account_info.owner)?; + let context_state_account_data = context_state_account_info.data.borrow(); + let context_state = pod_from_bytes::>(&context_state_account_data)?; + + if context_state.proof_type != T::PROOF_TYPE.into() { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(context_state.proof_context) + } else { + // if sysvar account is not provided, then get the sysvar account + let sysvar_account_info = if let Some(sysvar_account_info) = sysvar_account_info { + sysvar_account_info + } else { + next_account_info(account_info_iter)? + }; + let zkp_instruction = + get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; + let expected_proof_type = zk_proof_type_to_instruction(T::PROOF_TYPE)?; + Ok(decode_proof_instruction_context::( + account_info_iter, + expected_proof_type, + &zkp_instruction, + )?) + } +} + +/// Converts a zk proof type to a corresponding ZK ElGamal proof program +/// instruction that verifies the proof. +pub fn zk_proof_type_to_instruction( + proof_type: ProofType, +) -> Result { + match proof_type { + ProofType::ZeroCiphertext => Ok(ProofInstruction::VerifyZeroCiphertext), + ProofType::CiphertextCiphertextEquality => { + Ok(ProofInstruction::VerifyCiphertextCiphertextEquality) + } + ProofType::PubkeyValidity => Ok(ProofInstruction::VerifyPubkeyValidity), + ProofType::BatchedRangeProofU64 => Ok(ProofInstruction::VerifyBatchedRangeProofU64), + ProofType::BatchedRangeProofU128 => Ok(ProofInstruction::VerifyBatchedRangeProofU128), + ProofType::BatchedRangeProofU256 => Ok(ProofInstruction::VerifyBatchedRangeProofU256), + ProofType::CiphertextCommitmentEquality => { + Ok(ProofInstruction::VerifyCiphertextCommitmentEquality) + } + ProofType::GroupedCiphertext2HandlesValidity => { + Ok(ProofInstruction::VerifyGroupedCiphertext2HandlesValidity) + } + ProofType::BatchedGroupedCiphertext2HandlesValidity => { + Ok(ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity) + } + ProofType::PercentageWithCap => Ok(ProofInstruction::VerifyPercentageWithCap), + ProofType::GroupedCiphertext3HandlesValidity => { + Ok(ProofInstruction::VerifyGroupedCiphertext3HandlesValidity) + } + ProofType::BatchedGroupedCiphertext3HandlesValidity => { + Ok(ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity) + } + ProofType::Uninitialized => Err(ProgramError::InvalidInstructionData), + } +} diff --git a/token/program-2022/src/proof.rs b/token/program-2022/src/proof.rs deleted file mode 100644 index 1bebfabe827..00000000000 --- a/token/program-2022/src/proof.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! Helper for processing instruction data from ZK ElGamal proof program - -use { - crate::check_zk_elgamal_proof_program_account, - bytemuck::Pod, - solana_program::{ - account_info::{next_account_info, AccountInfo}, - instruction::Instruction, - msg, - program_error::ProgramError, - pubkey::Pubkey, - sysvar::instructions::get_instruction_relative, - }, - solana_zk_sdk::zk_elgamal_proof_program::{ - self, - instruction::ProofInstruction, - proof_data::{ProofType, ZkProofData}, - state::ProofContextState, - }, - spl_pod::bytemuck::pod_from_bytes, - std::{num::NonZeroI8, slice::Iter}, -}; - -/// 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; - -/// Decodes the proof context data associated with a zero-knowledge proof -/// instruction. -pub fn decode_proof_instruction_context, U: Pod>( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, - expected: ProofInstruction, - instruction: &Instruction, -) -> Result { - if instruction.program_id != zk_elgamal_proof_program::id() - || ProofInstruction::instruction_type(&instruction.data) != Some(expected) - { - msg!("Unexpected proof instruction"); - return Err(ProgramError::InvalidInstructionData); - } - - // If the instruction data size is exactly 5 bytes, then interpret it as an - // offset byte for a record account. This behavior is identical to that of - // the ZK ElGamal proof program. - if instruction.data.len() == INSTRUCTION_DATA_LENGTH_WITH_RECORD_ACCOUNT { - let record_account = next_account_info(account_info_iter)?; - - // first byte is the proof type - let start_offset = u32::from_le_bytes(instruction.data[1..].try_into().unwrap()) as usize; - let end_offset = start_offset - .checked_add(std::mem::size_of::()) - .ok_or(ProgramError::InvalidAccountData)?; - - let record_account_data = record_account.data.borrow(); - let raw_proof_data = record_account_data - .get(start_offset..end_offset) - .ok_or(ProgramError::AccountDataTooSmall)?; - - bytemuck::try_from_bytes::(raw_proof_data) - .map(|proof_data| *ZkProofData::context_data(proof_data)) - .map_err(|_| ProgramError::InvalidAccountData) - } else { - ProofInstruction::proof_data::(&instruction.data) - .map(|proof_data| *ZkProofData::context_data(proof_data)) - .ok_or(ProgramError::InvalidInstructionData) - } -} - -/// A proof location type meant to be used for arguments to instruction -/// constructors. -#[derive(Clone, Copy)] -pub enum ProofLocation<'a, T> { - /// The proof is included in the same transaction of a corresponding - /// token-2022 instruction. - InstructionOffset(NonZeroI8, ProofData<'a, T>), - /// The proof is pre-verified into a context state account. - ContextStateAccount(&'a Pubkey), -} - -impl<'a, T> ProofLocation<'a, T> { - /// Returns true if the proof location is an instruction offset - pub fn is_instruction_offset(&self) -> bool { - match self { - Self::InstructionOffset(_, _) => true, - Self::ContextStateAccount(_) => false, - } - } -} - -/// A proof data type to distinguish between proof data included as part of -/// zk-token proof instruction data and proof data stored in a record account. -#[derive(Clone, Copy)] -pub enum ProofData<'a, T> { - /// The proof data - InstructionData(&'a T), - /// The address of a record account containing the proof data and its byte - /// offset - RecordAccount(&'a Pubkey, u32), -} - -/// Verify zero-knowledge proof and return the corresponding proof context. -pub fn verify_and_extract_context<'a, T: Pod + ZkProofData, U: Pod>( - account_info_iter: &mut Iter<'_, AccountInfo<'a>>, - proof_instruction_offset: i64, - sysvar_account_info: Option<&'_ AccountInfo<'a>>, -) -> Result { - if proof_instruction_offset == 0 { - // interpret `account_info` as a context state account - let context_state_account_info = next_account_info(account_info_iter)?; - check_zk_elgamal_proof_program_account(context_state_account_info.owner)?; - let context_state_account_data = context_state_account_info.data.borrow(); - let context_state = pod_from_bytes::>(&context_state_account_data)?; - - if context_state.proof_type != T::PROOF_TYPE.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(context_state.proof_context) - } else { - // if sysvar account is not provided, then get the sysvar account - let sysvar_account_info = if let Some(sysvar_account_info) = sysvar_account_info { - sysvar_account_info - } else { - next_account_info(account_info_iter)? - }; - let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - let expected_proof_type = zk_proof_type_to_instruction(T::PROOF_TYPE)?; - Ok(decode_proof_instruction_context::( - account_info_iter, - expected_proof_type, - &zkp_instruction, - )?) - } -} - -/// Converts a zk proof type to a corresponding ZK ElGamal proof program -/// instruction that verifies the proof. -pub fn zk_proof_type_to_instruction( - proof_type: ProofType, -) -> Result { - match proof_type { - ProofType::ZeroCiphertext => Ok(ProofInstruction::VerifyZeroCiphertext), - ProofType::CiphertextCiphertextEquality => { - Ok(ProofInstruction::VerifyCiphertextCiphertextEquality) - } - ProofType::PubkeyValidity => Ok(ProofInstruction::VerifyPubkeyValidity), - ProofType::BatchedRangeProofU64 => Ok(ProofInstruction::VerifyBatchedRangeProofU64), - ProofType::BatchedRangeProofU128 => Ok(ProofInstruction::VerifyBatchedRangeProofU128), - ProofType::BatchedRangeProofU256 => Ok(ProofInstruction::VerifyBatchedRangeProofU256), - ProofType::CiphertextCommitmentEquality => { - Ok(ProofInstruction::VerifyCiphertextCommitmentEquality) - } - ProofType::GroupedCiphertext2HandlesValidity => { - Ok(ProofInstruction::VerifyGroupedCiphertext2HandlesValidity) - } - ProofType::BatchedGroupedCiphertext2HandlesValidity => { - Ok(ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity) - } - ProofType::PercentageWithCap => Ok(ProofInstruction::VerifyPercentageWithCap), - ProofType::GroupedCiphertext3HandlesValidity => { - Ok(ProofInstruction::VerifyGroupedCiphertext3HandlesValidity) - } - ProofType::BatchedGroupedCiphertext3HandlesValidity => { - Ok(ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity) - } - 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, -} From e41f90d4b33897a3e16aadf0a4c3ef30a756462e Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 17 Oct 2024 18:02:11 +0900 Subject: [PATCH 02/22] create ElGamal registry program --- Cargo.toml | 1 + .../elgamal-registry/Cargo.toml | 25 +++ .../elgamal-registry/src/entrypoint.rs | 14 ++ .../elgamal-registry/src/instruction.rs | 196 ++++++++++++++++++ .../elgamal-registry/src/lib.rs | 28 +++ .../elgamal-registry/src/processor.rs | 191 +++++++++++++++++ .../elgamal-registry/src/state.rs | 18 ++ 7 files changed, 473 insertions(+) create mode 100644 token/confidential-transfer/elgamal-registry/Cargo.toml create mode 100644 token/confidential-transfer/elgamal-registry/src/entrypoint.rs create mode 100644 token/confidential-transfer/elgamal-registry/src/instruction.rs create mode 100644 token/confidential-transfer/elgamal-registry/src/lib.rs create mode 100644 token/confidential-transfer/elgamal-registry/src/processor.rs create mode 100644 token/confidential-transfer/elgamal-registry/src/state.rs 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/confidential-transfer/elgamal-registry/Cargo.toml b/token/confidential-transfer/elgamal-registry/Cargo.toml new file mode 100644 index 00000000000..9501994e2de --- /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" +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..c080cf7aee2 --- /dev/null +++ b/token/confidential-transfer/elgamal-registry/src/instruction.rs @@ -0,0 +1,196 @@ +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::{ProofData, ProofLocation}, +}; + +#[derive(Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RegistryInstruction { + /// Initialize an ElGamal public key registry. + /// + /// 0. `[writeable, signer]` The funding account (must be a system account) + /// 1. `[writable]` The account to be created + /// 2. `[]` The wallet address (will also be the owner address for the + /// registry account) + /// 3. `[]` System program + /// 4. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the + /// same transaction or context state account if `VerifyPubkeyValidity` + /// is pre-verified into a context state account. + /// 5. `[]` (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( + funding_address: &Pubkey, + 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(*funding_address, true), + AccountMeta::new(elgamal_registry_address, false), + AccountMeta::new_readonly(*owner_address, false), + AccountMeta::new_readonly(system_program::id(), false), + ]; + let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location); + + let registry_instruction = Instruction { + program_id: id(), + accounts, + data: RegistryInstruction::CreateRegistry { + proof_instruction_offset, + } + .pack(), + }; + append_zk_elgamal_proof(registry_instruction, proof_location) +} + +/// Create a `RegistryInstruction::UpdateRegistry` instruction +pub fn update_registry( + registry_account: &Pubkey, + owner: &Pubkey, + proof_location: ProofLocation, +) -> Result, ProgramError> { + let mut accounts = vec![AccountMeta::new(*registry_account, false)]; + let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location); + accounts.push(AccountMeta::new_readonly(*owner, true)); + + let registry_instruction = Instruction { + program_id: id(), + accounts, + data: RegistryInstruction::UpdateRegistry { + proof_instruction_offset, + } + .pack(), + }; + append_zk_elgamal_proof(registry_instruction, proof_location) +} + +/// 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( + registry_instruction: Instruction, + proof_data_location: ProofLocation, +) -> Result, ProgramError> { + let mut instructions = vec![registry_instruction]; + + 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(instructions) +} 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..ed616401745 --- /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] = "elgamal-registry".as_bytes(); + +/// 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.to_bytes()], + 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..af061361158 --- /dev/null +++ b/token/confidential-transfer/elgamal-registry/src/processor.rs @@ -0,0 +1,191 @@ +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, 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::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 funding_account_info = next_account_info(account_info_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)?; + + // 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.to_bytes(), + &[bump_seed], + ]; + let rent = Rent::get()?; + + create_pda_account( + funding_account_info, + &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!("RegistryInstruction::CreateRegistry"); + process_create_registry_account(program_id, accounts, proof_instruction_offset as i64) + } + RegistryInstruction::UpdateRegistry { + proof_instruction_offset, + } => { + msg!("RegistryInstruction::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(()) +} + +/// Creates ElGamal registry account using Program Derived Address for the given +/// seeds +pub fn create_pda_account<'a>( + payer: &AccountInfo<'a>, + rent: &Rent, + space: usize, + owner: &Pubkey, + system_program: &AccountInfo<'a>, + new_pda_account: &AccountInfo<'a>, + new_pda_signer_seeds: &[&[u8]], +) -> ProgramResult { + if new_pda_account.lamports() > 0 { + let required_lamports = rent + .minimum_balance(space) + .max(1) + .saturating_sub(new_pda_account.lamports()); + + if required_lamports > 0 { + invoke( + &system_instruction::transfer(payer.key, new_pda_account.key, required_lamports), + &[ + payer.clone(), + new_pda_account.clone(), + system_program.clone(), + ], + )?; + } + + 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], + ) + } else { + invoke_signed( + &system_instruction::create_account( + payer.key, + new_pda_account.key, + rent.minimum_balance(space).max(1), + space as u64, + owner, + ), + &[ + payer.clone(), + 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, +} From 421cefa95511b2b0282b630a0c535bb5b586a79b Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 17 Oct 2024 18:03:58 +0900 Subject: [PATCH 03/22] add support for ElGamal registry program in the token program --- token/program-2022/Cargo.toml | 1 + .../confidential_transfer/instruction.rs | 50 +++++++++- .../extension/confidential_transfer/mod.rs | 3 + .../confidential_transfer/processor.rs | 91 +++++++++++++++---- .../confidential_transfer/verify_proof.rs | 3 +- .../confidential_transfer_fee/instruction.rs | 2 +- .../confidential_transfer_fee/processor.rs | 2 +- token/program-2022/src/lib.rs | 11 ++- 8 files changed, 140 insertions(+), 23 deletions(-) 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..3619bd8216a 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}, @@ -21,6 +20,7 @@ use { pubkey::Pubkey, sysvar, }, + spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation}, }; /// Confidential Transfer extension instructions @@ -472,6 +472,29 @@ 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. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writeable]` The SPL Token account. + /// 1. `[]` The corresponding SPL Token mint. + /// 2. `[]` The ElGamal registry account. + /// 3. `[]` The account owner. + /// + /// Data expected by this instruction: + /// None + ConfigureAccountWithRegistry, } /// Data expected by `ConfidentialTransferInstruction::InitializeMint` @@ -1701,3 +1724,28 @@ 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, + authority: &Pubkey, +) -> Result { + check_program_account(token_program_id)?; + let accounts = vec![ + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(*mint, false), + AccountMeta::new_readonly(*elgamal_registry_account, false), + AccountMeta::new_readonly(*authority, 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..c2f4d07809f 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::*, *}, @@ -22,7 +22,6 @@ use { instruction::{decode_instruction_data, decode_instruction_type}, pod::{PodAccount, PodMint}, processor::Processor, - proof::verify_and_extract_context, }, solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -33,8 +32,11 @@ use { pubkey::Pubkey, sysvar::Sysvar, }, + spl_elgamal_registry::state::ElGamalRegistry, + spl_pod::bytemuck::pod_from_bytes, spl_token_confidential_transfer_proof_extraction::{ transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext, + verify_and_extract_context, }, }; @@ -92,23 +94,62 @@ 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 elgamal_registry_account = accounts.get(2).ok_or(ProgramError::NotEnoughAccountKeys)?; + check_elgamal_registry_program_account(elgamal_registry_account.owner)?; + + 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), + ) +} + /// 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 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 + } + }; let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); @@ -121,13 +162,25 @@ 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(_) => { + 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 != *authority_info.key { + return Err(TokenError::OwnerMismatch.into()); + } + } + }; check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow(); @@ -140,7 +193,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 +1161,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 +1270,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..6354641ebfe 100644 --- a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs @@ -2,7 +2,6 @@ use { crate::{ error::TokenError, extension::{confidential_transfer::instruction::*, transfer_fee::TransferFee}, - proof::verify_and_extract_context, }, solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -10,7 +9,7 @@ use { }, spl_token_confidential_transfer_proof_extraction::{ transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext, - withdraw::WithdrawProofContext, + verify_and_extract_context, 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..939219159f9 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::{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..581f1aa5e75 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::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..ae8105fcbac 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 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(()) +} From 8a11d6edd1ba08705b84445c99efa498f59986b5 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 17 Oct 2024 18:04:45 +0900 Subject: [PATCH 04/22] add support for `ConfigureAccountWithRegistry` in the token-client --- Cargo.lock | 31 +++++++++++++++++++++++++++++++ token/client/Cargo.toml | 2 ++ token/client/src/token.rs | 27 ++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2acd3d4311c..ac78f53aec7 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" @@ -7295,6 +7306,19 @@ dependencies = [ "spl-program-error 0.5.0", ] +[[package]] +name = "spl-pod" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e38c99f09d58df06ca9a29fc0211786a4c34f4d099c1df27b1abaa206569a4" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "solana-program", + "solana-zk-sdk", + "spl-program-error 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "spl-program-error" version = "0.5.0" @@ -7588,6 +7612,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 +7639,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 +7647,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 +7712,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 +7761,9 @@ version = "0.1.0" dependencies = [ "bytemuck", "solana-curve25519", + "solana-program", "solana-zk-sdk", + "spl-pod 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror", ] 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..f244f19dd73 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::{ + 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, + authority: &Pubkey, + ) -> TokenResult { + self.process_ixs::<[&dyn Signer; 0]>( + &[ + confidential_transfer::instruction::configure_account_with_registry( + &self.program_id, + account, + &self.pubkey, + elgamal_registry_account, + authority, + )?, + ], + &[], + ) + .await + } + /// Approves a token account for confidential transfers pub async fn confidential_transfer_approve_account( &self, From 7ee89e24f8825c9ec8f0c4b1dff30e1e6321fbf7 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 17 Oct 2024 18:05:42 +0900 Subject: [PATCH 05/22] add tests for configure account with registry --- token/program-2022-test/Cargo.toml | 2 + .../tests/confidential_transfer.rs | 123 +++++++++++++++++- token/program-2022-test/tests/program_test.rs | 5 + 3 files changed, 129 insertions(+), 1 deletion(-) 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..b9dc63dfe4d 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -13,7 +13,7 @@ use { pubkey::Pubkey, signature::Signer, signer::{keypair::Keypair, signers::Signers}, - transaction::TransactionError, + transaction::{Transaction, TransactionError}, transport::TransportError, }, spl_record::state::RecordData, @@ -21,6 +21,7 @@ use { error::TokenError, extension::{ confidential_transfer::{ + self, account_info::{EmptyAccountAccountInfo, TransferAccountInfo, WithdrawAccountInfo}, ConfidentialTransferAccount, MAXIMUM_DEPOSIT_TRANSFER_AMOUNT, }, @@ -38,6 +39,7 @@ use { TokenResult, }, }, + spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation}, spl_token_confidential_transfer_proof_generation::{ transfer::TransferProofData, transfer_with_fee::TransferWithFeeProofData, withdraw::WithdrawProofData, @@ -2810,3 +2812,122 @@ 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 instructions = spl_elgamal_registry::instruction::create_registry( + &ctx.payer.pubkey(), + &alice.pubkey(), + proof_location, + ) + .unwrap(); + let tx = Transaction::new_signed_with_payer( + &instructions, + Some(&ctx.payer.pubkey()), + &[&ctx.payer], + 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 elgamal_registry_address = spl_elgamal_registry::get_elgamal_registry_address( + &alice.pubkey(), + &spl_elgamal_registry::id(), + ); + + let instructions = spl_elgamal_registry::instruction::update_registry( + &elgamal_registry_address, + &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_token_account, + vec![ExtensionType::ConfidentialTransferAccount], + ) + .await + .unwrap(); + + token + .confidential_transfer_configure_token_account_with_registry( + &alice_account_keypair.pubkey(), + &elgamal_registry_address, + &alice.pubkey(), + ) + .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)); From 84cf6b65abd8f206058980cb082bb2876f189f0c Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 22 Oct 2024 07:28:55 +0900 Subject: [PATCH 06/22] use local `spl-pod` for dependency --- Cargo.lock | 15 +-------------- rust-toolchain.toml | 2 +- .../proof-extraction/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac78f53aec7..6ac47cf5ed4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7306,19 +7306,6 @@ dependencies = [ "spl-program-error 0.5.0", ] -[[package]] -name = "spl-pod" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e38c99f09d58df06ca9a29fc0211786a4c34f4d099c1df27b1abaa206569a4" -dependencies = [ - "bytemuck", - "bytemuck_derive", - "solana-program", - "solana-zk-sdk", - "spl-program-error 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "spl-program-error" version = "0.5.0" @@ -7763,7 +7750,7 @@ dependencies = [ "solana-curve25519", "solana-program", "solana-zk-sdk", - "spl-pod 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spl-pod 0.4.0", "thiserror", ] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 51985806fca..628740b12ff 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.78.0" +channel = "1.79.0" diff --git a/token/confidential-transfer/proof-extraction/Cargo.toml b/token/confidential-transfer/proof-extraction/Cargo.toml index 5f10403c6c6..c9065f2a72b 100644 --- a/token/confidential-transfer/proof-extraction/Cargo.toml +++ b/token/confidential-transfer/proof-extraction/Cargo.toml @@ -12,7 +12,7 @@ bytemuck = "1.19.0" solana-curve25519 = "2.0.3" solana-program = "2.0.3" solana-zk-sdk = "2.0.3" -spl-pod = "0.4.0" +spl-pod = { version = "0.4.0", path = "../../../libraries/pod" } thiserror = "1.0.65" [lib] From 69001ecc6a20c646d91d0b6f84c54e0ba7a5537b Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 22 Oct 2024 07:31:34 +0900 Subject: [PATCH 07/22] Apply suggestions from code review Co-authored-by: Jon C --- token/confidential-transfer/elgamal-registry/Cargo.toml | 2 +- token/confidential-transfer/elgamal-registry/src/instruction.rs | 2 +- token/confidential-transfer/elgamal-registry/src/lib.rs | 2 +- .../src/extension/confidential_transfer/instruction.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/token/confidential-transfer/elgamal-registry/Cargo.toml b/token/confidential-transfer/elgamal-registry/Cargo.toml index 9501994e2de..5c1ac47e916 100644 --- a/token/confidential-transfer/elgamal-registry/Cargo.toml +++ b/token/confidential-transfer/elgamal-registry/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "spl-elgamal-registry" version = "0.1.0" -description = "Solana ElGamal Registry" +description = "Solana ElGamal Registry Program" authors = ["Solana Labs Maintainers "] repository = "https://github.com/solana-labs/solana-program-library" license = "Apache-2.0" diff --git a/token/confidential-transfer/elgamal-registry/src/instruction.rs b/token/confidential-transfer/elgamal-registry/src/instruction.rs index c080cf7aee2..98731713096 100644 --- a/token/confidential-transfer/elgamal-registry/src/instruction.rs +++ b/token/confidential-transfer/elgamal-registry/src/instruction.rs @@ -17,7 +17,7 @@ use { pub enum RegistryInstruction { /// Initialize an ElGamal public key registry. /// - /// 0. `[writeable, signer]` The funding account (must be a system account) + /// 0. `[writable, signer]` The funding account (must be a system account) /// 1. `[writable]` The account to be created /// 2. `[]` The wallet address (will also be the owner address for the /// registry account) diff --git a/token/confidential-transfer/elgamal-registry/src/lib.rs b/token/confidential-transfer/elgamal-registry/src/lib.rs index ed616401745..95139d3aef3 100644 --- a/token/confidential-transfer/elgamal-registry/src/lib.rs +++ b/token/confidential-transfer/elgamal-registry/src/lib.rs @@ -6,7 +6,7 @@ pub mod state; use solana_program::pubkey::Pubkey; /// Seed for the ElGamal registry program-derived address -pub const REGISTRY_ADDRESS_SEED: &[u8] = "elgamal-registry".as_bytes(); +pub const REGISTRY_ADDRESS_SEED: &[u8] = b"elgamal-registry"; /// Derives the ElGamal registry account address and seed for the given wallet /// address diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index 3619bd8216a..7886ccc6af2 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -487,7 +487,7 @@ pub enum ConfidentialTransferInstruction { /// Accounts expected by this instruction: /// /// * Single owner/delegate - /// 0. `[writeable]` The SPL Token account. + /// 0. `[writable]` The SPL Token account. /// 1. `[]` The corresponding SPL Token mint. /// 2. `[]` The ElGamal registry account. /// 3. `[]` The account owner. From acedb89b3377b4d1d92ef3a4202be3c0788fc8fb Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 22 Oct 2024 07:44:48 +0900 Subject: [PATCH 08/22] require owner signature when creating registry account --- .../confidential-transfer/elgamal-registry/src/instruction.rs | 4 ++-- token/confidential-transfer/elgamal-registry/src/processor.rs | 4 ++++ token/program-2022-test/tests/confidential_transfer.rs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/token/confidential-transfer/elgamal-registry/src/instruction.rs b/token/confidential-transfer/elgamal-registry/src/instruction.rs index 98731713096..851664d1ae1 100644 --- a/token/confidential-transfer/elgamal-registry/src/instruction.rs +++ b/token/confidential-transfer/elgamal-registry/src/instruction.rs @@ -19,7 +19,7 @@ pub enum RegistryInstruction { /// /// 0. `[writable, signer]` The funding account (must be a system account) /// 1. `[writable]` The account to be created - /// 2. `[]` The wallet address (will also be the owner address for the + /// 2. `[signer]` The wallet address (will also be the owner address for the /// registry account) /// 3. `[]` System program /// 4. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the @@ -110,7 +110,7 @@ pub fn create_registry( let mut accounts = vec![ AccountMeta::new(*funding_address, true), AccountMeta::new(elgamal_registry_address, false), - AccountMeta::new_readonly(*owner_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); diff --git a/token/confidential-transfer/elgamal-registry/src/processor.rs b/token/confidential-transfer/elgamal-registry/src/processor.rs index af061361158..45d500d2b53 100644 --- a/token/confidential-transfer/elgamal-registry/src/processor.rs +++ b/token/confidential-transfer/elgamal-registry/src/processor.rs @@ -35,6 +35,10 @@ pub fn process_create_registry_account( 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, diff --git a/token/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index b9dc63dfe4d..9300cfa97a3 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -2862,7 +2862,7 @@ async fn confidential_transfer_configure_token_account_with_registry() { let tx = Transaction::new_signed_with_payer( &instructions, Some(&ctx.payer.pubkey()), - &[&ctx.payer], + &[&ctx.payer, &alice], ctx.last_blockhash, ); ctx.banks_client.process_transaction(tx).await.unwrap(); From d2f04a1d88be6462a75e0678d35575742826b282 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 22 Oct 2024 07:51:47 +0900 Subject: [PATCH 09/22] derive registry address in `UpdateRegistry` instruction constructor --- .../elgamal-registry/src/instruction.rs | 9 +++++---- token/program-2022-test/tests/confidential_transfer.rs | 9 +++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/token/confidential-transfer/elgamal-registry/src/instruction.rs b/token/confidential-transfer/elgamal-registry/src/instruction.rs index 851664d1ae1..aedd4177f0d 100644 --- a/token/confidential-transfer/elgamal-registry/src/instruction.rs +++ b/token/confidential-transfer/elgamal-registry/src/instruction.rs @@ -128,13 +128,14 @@ pub fn create_registry( /// Create a `RegistryInstruction::UpdateRegistry` instruction pub fn update_registry( - registry_account: &Pubkey, - owner: &Pubkey, + owner_address: &Pubkey, proof_location: ProofLocation, ) -> Result, ProgramError> { - let mut accounts = vec![AccountMeta::new(*registry_account, false)]; + 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, true)); + accounts.push(AccountMeta::new_readonly(*owner_address, true)); let registry_instruction = Instruction { program_id: id(), diff --git a/token/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index 9300cfa97a3..09138a47605 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -2884,12 +2884,9 @@ async fn confidential_transfer_configure_token_account_with_registry() { &spl_elgamal_registry::id(), ); - let instructions = spl_elgamal_registry::instruction::update_registry( - &elgamal_registry_address, - &alice.pubkey(), - proof_location, - ) - .unwrap(); + 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()), From 6d9cf4c172576a52e9b560f4973abb40f312c49c Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 22 Oct 2024 07:55:36 +0900 Subject: [PATCH 10/22] make `append_zk_elgamal_proof` function more general --- .../elgamal-registry/src/instruction.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/token/confidential-transfer/elgamal-registry/src/instruction.rs b/token/confidential-transfer/elgamal-registry/src/instruction.rs index aedd4177f0d..10fc164016c 100644 --- a/token/confidential-transfer/elgamal-registry/src/instruction.rs +++ b/token/confidential-transfer/elgamal-registry/src/instruction.rs @@ -115,15 +115,16 @@ pub fn create_registry( ]; let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location); - let registry_instruction = Instruction { + let mut instructions = vec![Instruction { program_id: id(), accounts, data: RegistryInstruction::CreateRegistry { proof_instruction_offset, } .pack(), - }; - append_zk_elgamal_proof(registry_instruction, proof_location) + }]; + append_zk_elgamal_proof(&mut instructions, proof_location)?; + Ok(instructions) } /// Create a `RegistryInstruction::UpdateRegistry` instruction @@ -137,15 +138,16 @@ pub fn update_registry( let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location); accounts.push(AccountMeta::new_readonly(*owner_address, true)); - let registry_instruction = Instruction { + let mut instructions = vec![Instruction { program_id: id(), accounts, data: RegistryInstruction::UpdateRegistry { proof_instruction_offset, } .pack(), - }; - append_zk_elgamal_proof(registry_instruction, proof_location) + }]; + append_zk_elgamal_proof(&mut instructions, proof_location)?; + Ok(instructions) } /// Takes a `ProofLocation`, updates the list of accounts, and returns a @@ -172,11 +174,9 @@ fn proof_instruction_offset( /// Takes a `RegistryInstruction` and appends the pubkey validity proof /// instruction fn append_zk_elgamal_proof( - registry_instruction: Instruction, + instructions: &mut Vec, proof_data_location: ProofLocation, -) -> Result, ProgramError> { - let mut instructions = vec![registry_instruction]; - +) -> Result<(), ProgramError> { if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = proof_data_location { @@ -193,5 +193,5 @@ fn append_zk_elgamal_proof( ), } } - Ok(instructions) + Ok(()) } From 5d9dc1b67e65858d2ca0ffffd37bf4f36926fc57 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 22 Oct 2024 10:00:19 +0900 Subject: [PATCH 11/22] make `check_elgamal_registry_program_account` `pub(crate)` --- token/program-2022/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token/program-2022/src/lib.rs b/token/program-2022/src/lib.rs index ae8105fcbac..d413d5903a5 100644 --- a/token/program-2022/src/lib.rs +++ b/token/program-2022/src/lib.rs @@ -131,7 +131,7 @@ pub fn check_system_program_account(system_program_id: &Pubkey) -> ProgramResult } /// Checks if the supplied program ID is that of the ElGamal registry program -pub fn check_elgamal_registry_program_account( +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() { From ca920288592eec1fe6543d9831f9fac6eece4079 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 22 Oct 2024 10:09:09 +0900 Subject: [PATCH 12/22] refactor zk elgamal instruction data logic into a separate file --- token/client/src/token.rs | 2 +- .../elgamal-registry/src/instruction.rs | 2 +- .../elgamal-registry/src/processor.rs | 2 +- .../proof-extraction/src/instruction.rs | 181 ++++++++++++++++++ .../proof-extraction/src/lib.rs | 180 +---------------- .../tests/confidential_transfer.rs | 2 +- .../confidential_transfer/instruction.rs | 2 +- .../confidential_transfer/processor.rs | 4 +- .../confidential_transfer/verify_proof.rs | 4 +- .../confidential_transfer_fee/instruction.rs | 2 +- .../confidential_transfer_fee/processor.rs | 2 +- 11 files changed, 193 insertions(+), 190 deletions(-) create mode 100644 token/confidential-transfer/proof-extraction/src/instruction.rs diff --git a/token/client/src/token.rs b/token/client/src/token.rs index f244f19dd73..c125007bd3b 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -62,7 +62,7 @@ use { }, state::{Account, AccountState, Mint, Multisig}, }, - spl_token_confidential_transfer_proof_extraction::{ + spl_token_confidential_transfer_proof_extraction::instruction::{ zk_proof_type_to_instruction, ProofData, ProofLocation, }, spl_token_confidential_transfer_proof_generation::{ diff --git a/token/confidential-transfer/elgamal-registry/src/instruction.rs b/token/confidential-transfer/elgamal-registry/src/instruction.rs index 10fc164016c..bf0026644c3 100644 --- a/token/confidential-transfer/elgamal-registry/src/instruction.rs +++ b/token/confidential-transfer/elgamal-registry/src/instruction.rs @@ -9,7 +9,7 @@ use { solana_zk_sdk::zk_elgamal_proof_program::{ instruction::ProofInstruction, proof_data::PubkeyValidityProofData, }, - spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation}, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, }; #[derive(Clone, Debug, PartialEq)] diff --git a/token/confidential-transfer/elgamal-registry/src/processor.rs b/token/confidential-transfer/elgamal-registry/src/processor.rs index 45d500d2b53..53ddfdb52e4 100644 --- a/token/confidential-transfer/elgamal-registry/src/processor.rs +++ b/token/confidential-transfer/elgamal-registry/src/processor.rs @@ -20,7 +20,7 @@ use { PubkeyValidityProofContext, PubkeyValidityProofData, }, spl_pod::bytemuck::pod_from_bytes_mut, - spl_token_confidential_transfer_proof_extraction::verify_and_extract_context, + spl_token_confidential_transfer_proof_extraction::instruction::verify_and_extract_context, }; /// Processes `CreateRegistry` instruction diff --git a/token/confidential-transfer/proof-extraction/src/instruction.rs b/token/confidential-transfer/proof-extraction/src/instruction.rs new file mode 100644 index 00000000000..761a2b3fcfd --- /dev/null +++ b/token/confidential-transfer/proof-extraction/src/instruction.rs @@ -0,0 +1,181 @@ +//! Utility functions to simplify the handling of ZK ElGamal proof program +//! instruction data in SPL crates + +use { + bytemuck::Pod, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + instruction::Instruction, + msg, + program_error::ProgramError, + pubkey::Pubkey, + sysvar::instructions::get_instruction_relative, + }, + solana_zk_sdk::zk_elgamal_proof_program::{ + self, + instruction::ProofInstruction, + proof_data::{ProofType, ZkProofData}, + state::ProofContextState, + }, + spl_pod::bytemuck::pod_from_bytes, + 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; + +/// Decodes the proof context data associated with a zero-knowledge proof +/// instruction. +pub fn decode_proof_instruction_context, U: Pod>( + account_info_iter: &mut Iter<'_, AccountInfo<'_>>, + expected: ProofInstruction, + instruction: &Instruction, +) -> Result { + if instruction.program_id != zk_elgamal_proof_program::id() + || ProofInstruction::instruction_type(&instruction.data) != Some(expected) + { + msg!("Unexpected proof instruction"); + return Err(ProgramError::InvalidInstructionData); + } + + // If the instruction data size is exactly 5 bytes, then interpret it as an + // offset byte for a record account. This behavior is identical to that of + // the ZK ElGamal proof program. + if instruction.data.len() == INSTRUCTION_DATA_LENGTH_WITH_RECORD_ACCOUNT { + let record_account = next_account_info(account_info_iter)?; + + // first byte is the proof type + let start_offset = u32::from_le_bytes(instruction.data[1..].try_into().unwrap()) as usize; + let end_offset = start_offset + .checked_add(std::mem::size_of::()) + .ok_or(ProgramError::InvalidAccountData)?; + + let record_account_data = record_account.data.borrow(); + let raw_proof_data = record_account_data + .get(start_offset..end_offset) + .ok_or(ProgramError::AccountDataTooSmall)?; + + bytemuck::try_from_bytes::(raw_proof_data) + .map(|proof_data| *ZkProofData::context_data(proof_data)) + .map_err(|_| ProgramError::InvalidAccountData) + } else { + ProofInstruction::proof_data::(&instruction.data) + .map(|proof_data| *ZkProofData::context_data(proof_data)) + .ok_or(ProgramError::InvalidInstructionData) + } +} + +/// A proof location type meant to be used for arguments to instruction +/// constructors. +#[derive(Clone, Copy)] +pub enum ProofLocation<'a, T> { + /// The proof is included in the same transaction of a corresponding + /// token-2022 instruction. + InstructionOffset(NonZeroI8, ProofData<'a, T>), + /// The proof is pre-verified into a context state account. + ContextStateAccount(&'a Pubkey), +} + +impl<'a, T> ProofLocation<'a, T> { + /// Returns true if the proof location is an instruction offset + pub fn is_instruction_offset(&self) -> bool { + match self { + Self::InstructionOffset(_, _) => true, + Self::ContextStateAccount(_) => false, + } + } +} + +/// A proof data type to distinguish between proof data included as part of +/// zk-token proof instruction data and proof data stored in a record account. +#[derive(Clone, Copy)] +pub enum ProofData<'a, T> { + /// The proof data + InstructionData(&'a T), + /// The address of a record account containing the proof data and its byte + /// offset + RecordAccount(&'a Pubkey, u32), +} + +/// Verify zero-knowledge proof and return the corresponding proof context. +pub fn verify_and_extract_context<'a, T: Pod + ZkProofData, U: Pod>( + account_info_iter: &mut Iter<'_, AccountInfo<'a>>, + proof_instruction_offset: i64, + sysvar_account_info: Option<&'_ AccountInfo<'a>>, +) -> Result { + if proof_instruction_offset == 0 { + // interpret `account_info` as a context state account + let context_state_account_info = next_account_info(account_info_iter)?; + check_zk_elgamal_proof_program_account(context_state_account_info.owner)?; + let context_state_account_data = context_state_account_info.data.borrow(); + let context_state = pod_from_bytes::>(&context_state_account_data)?; + + if context_state.proof_type != T::PROOF_TYPE.into() { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(context_state.proof_context) + } else { + // if sysvar account is not provided, then get the sysvar account + let sysvar_account_info = if let Some(sysvar_account_info) = sysvar_account_info { + sysvar_account_info + } else { + next_account_info(account_info_iter)? + }; + let zkp_instruction = + get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; + let expected_proof_type = zk_proof_type_to_instruction(T::PROOF_TYPE)?; + Ok(decode_proof_instruction_context::( + account_info_iter, + expected_proof_type, + &zkp_instruction, + )?) + } +} + +/// Converts a zk proof type to a corresponding ZK ElGamal proof program +/// instruction that verifies the proof. +pub fn zk_proof_type_to_instruction( + proof_type: ProofType, +) -> Result { + match proof_type { + ProofType::ZeroCiphertext => Ok(ProofInstruction::VerifyZeroCiphertext), + ProofType::CiphertextCiphertextEquality => { + Ok(ProofInstruction::VerifyCiphertextCiphertextEquality) + } + ProofType::PubkeyValidity => Ok(ProofInstruction::VerifyPubkeyValidity), + ProofType::BatchedRangeProofU64 => Ok(ProofInstruction::VerifyBatchedRangeProofU64), + ProofType::BatchedRangeProofU128 => Ok(ProofInstruction::VerifyBatchedRangeProofU128), + ProofType::BatchedRangeProofU256 => Ok(ProofInstruction::VerifyBatchedRangeProofU256), + ProofType::CiphertextCommitmentEquality => { + Ok(ProofInstruction::VerifyCiphertextCommitmentEquality) + } + ProofType::GroupedCiphertext2HandlesValidity => { + Ok(ProofInstruction::VerifyGroupedCiphertext2HandlesValidity) + } + ProofType::BatchedGroupedCiphertext2HandlesValidity => { + Ok(ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity) + } + ProofType::PercentageWithCap => Ok(ProofInstruction::VerifyPercentageWithCap), + ProofType::GroupedCiphertext3HandlesValidity => { + Ok(ProofInstruction::VerifyGroupedCiphertext3HandlesValidity) + } + ProofType::BatchedGroupedCiphertext3HandlesValidity => { + Ok(ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity) + } + ProofType::Uninitialized => Err(ProgramError::InvalidInstructionData), + } +} diff --git a/token/confidential-transfer/proof-extraction/src/lib.rs b/token/confidential-transfer/proof-extraction/src/lib.rs index 6ff7f8187aa..82dcff0d31b 100644 --- a/token/confidential-transfer/proof-extraction/src/lib.rs +++ b/token/confidential-transfer/proof-extraction/src/lib.rs @@ -1,186 +1,8 @@ pub mod burn; pub mod encryption; pub mod errors; +pub mod instruction; pub mod mint; pub mod transfer; pub mod transfer_with_fee; pub mod withdraw; - -use { - bytemuck::Pod, - solana_program::{ - account_info::{next_account_info, AccountInfo}, - entrypoint::ProgramResult, - instruction::Instruction, - msg, - program_error::ProgramError, - pubkey::Pubkey, - sysvar::instructions::get_instruction_relative, - }, - solana_zk_sdk::zk_elgamal_proof_program::{ - self, - instruction::ProofInstruction, - proof_data::{ProofType, ZkProofData}, - state::ProofContextState, - }, - spl_pod::bytemuck::pod_from_bytes, - 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; - -/// Decodes the proof context data associated with a zero-knowledge proof -/// instruction. -pub fn decode_proof_instruction_context, U: Pod>( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, - expected: ProofInstruction, - instruction: &Instruction, -) -> Result { - if instruction.program_id != zk_elgamal_proof_program::id() - || ProofInstruction::instruction_type(&instruction.data) != Some(expected) - { - msg!("Unexpected proof instruction"); - return Err(ProgramError::InvalidInstructionData); - } - - // If the instruction data size is exactly 5 bytes, then interpret it as an - // offset byte for a record account. This behavior is identical to that of - // the ZK ElGamal proof program. - if instruction.data.len() == INSTRUCTION_DATA_LENGTH_WITH_RECORD_ACCOUNT { - let record_account = next_account_info(account_info_iter)?; - - // first byte is the proof type - let start_offset = u32::from_le_bytes(instruction.data[1..].try_into().unwrap()) as usize; - let end_offset = start_offset - .checked_add(std::mem::size_of::()) - .ok_or(ProgramError::InvalidAccountData)?; - - let record_account_data = record_account.data.borrow(); - let raw_proof_data = record_account_data - .get(start_offset..end_offset) - .ok_or(ProgramError::AccountDataTooSmall)?; - - bytemuck::try_from_bytes::(raw_proof_data) - .map(|proof_data| *ZkProofData::context_data(proof_data)) - .map_err(|_| ProgramError::InvalidAccountData) - } else { - ProofInstruction::proof_data::(&instruction.data) - .map(|proof_data| *ZkProofData::context_data(proof_data)) - .ok_or(ProgramError::InvalidInstructionData) - } -} - -/// A proof location type meant to be used for arguments to instruction -/// constructors. -#[derive(Clone, Copy)] -pub enum ProofLocation<'a, T> { - /// The proof is included in the same transaction of a corresponding - /// token-2022 instruction. - InstructionOffset(NonZeroI8, ProofData<'a, T>), - /// The proof is pre-verified into a context state account. - ContextStateAccount(&'a Pubkey), -} - -impl<'a, T> ProofLocation<'a, T> { - /// Returns true if the proof location is an instruction offset - pub fn is_instruction_offset(&self) -> bool { - match self { - Self::InstructionOffset(_, _) => true, - Self::ContextStateAccount(_) => false, - } - } -} - -/// A proof data type to distinguish between proof data included as part of -/// zk-token proof instruction data and proof data stored in a record account. -#[derive(Clone, Copy)] -pub enum ProofData<'a, T> { - /// The proof data - InstructionData(&'a T), - /// The address of a record account containing the proof data and its byte - /// offset - RecordAccount(&'a Pubkey, u32), -} - -/// Verify zero-knowledge proof and return the corresponding proof context. -pub fn verify_and_extract_context<'a, T: Pod + ZkProofData, U: Pod>( - account_info_iter: &mut Iter<'_, AccountInfo<'a>>, - proof_instruction_offset: i64, - sysvar_account_info: Option<&'_ AccountInfo<'a>>, -) -> Result { - if proof_instruction_offset == 0 { - // interpret `account_info` as a context state account - let context_state_account_info = next_account_info(account_info_iter)?; - check_zk_elgamal_proof_program_account(context_state_account_info.owner)?; - let context_state_account_data = context_state_account_info.data.borrow(); - let context_state = pod_from_bytes::>(&context_state_account_data)?; - - if context_state.proof_type != T::PROOF_TYPE.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(context_state.proof_context) - } else { - // if sysvar account is not provided, then get the sysvar account - let sysvar_account_info = if let Some(sysvar_account_info) = sysvar_account_info { - sysvar_account_info - } else { - next_account_info(account_info_iter)? - }; - let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - let expected_proof_type = zk_proof_type_to_instruction(T::PROOF_TYPE)?; - Ok(decode_proof_instruction_context::( - account_info_iter, - expected_proof_type, - &zkp_instruction, - )?) - } -} - -/// Converts a zk proof type to a corresponding ZK ElGamal proof program -/// instruction that verifies the proof. -pub fn zk_proof_type_to_instruction( - proof_type: ProofType, -) -> Result { - match proof_type { - ProofType::ZeroCiphertext => Ok(ProofInstruction::VerifyZeroCiphertext), - ProofType::CiphertextCiphertextEquality => { - Ok(ProofInstruction::VerifyCiphertextCiphertextEquality) - } - ProofType::PubkeyValidity => Ok(ProofInstruction::VerifyPubkeyValidity), - ProofType::BatchedRangeProofU64 => Ok(ProofInstruction::VerifyBatchedRangeProofU64), - ProofType::BatchedRangeProofU128 => Ok(ProofInstruction::VerifyBatchedRangeProofU128), - ProofType::BatchedRangeProofU256 => Ok(ProofInstruction::VerifyBatchedRangeProofU256), - ProofType::CiphertextCommitmentEquality => { - Ok(ProofInstruction::VerifyCiphertextCommitmentEquality) - } - ProofType::GroupedCiphertext2HandlesValidity => { - Ok(ProofInstruction::VerifyGroupedCiphertext2HandlesValidity) - } - ProofType::BatchedGroupedCiphertext2HandlesValidity => { - Ok(ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity) - } - ProofType::PercentageWithCap => Ok(ProofInstruction::VerifyPercentageWithCap), - ProofType::GroupedCiphertext3HandlesValidity => { - Ok(ProofInstruction::VerifyGroupedCiphertext3HandlesValidity) - } - ProofType::BatchedGroupedCiphertext3HandlesValidity => { - Ok(ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity) - } - ProofType::Uninitialized => Err(ProgramError::InvalidInstructionData), - } -} diff --git a/token/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index 09138a47605..7d6154f6b75 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -39,7 +39,7 @@ use { TokenResult, }, }, - spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation}, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, spl_token_confidential_transfer_proof_generation::{ transfer::TransferProofData, transfer_with_fee::TransferWithFeeProofData, withdraw::WithdrawProofData, diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index 7886ccc6af2..6fe5c8cdc04 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -20,7 +20,7 @@ use { pubkey::Pubkey, sysvar, }, - spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation}, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, }; /// Confidential Transfer extension instructions diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index c2f4d07809f..c87773a856c 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -35,8 +35,8 @@ use { spl_elgamal_registry::state::ElGamalRegistry, spl_pod::bytemuck::pod_from_bytes, spl_token_confidential_transfer_proof_extraction::{ - transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext, - verify_and_extract_context, + instruction::verify_and_extract_context, transfer::TransferProofContext, + transfer_with_fee::TransferWithFeeProofContext, }, }; 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 6354641ebfe..88df4074436 100644 --- a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs @@ -8,8 +8,8 @@ use { program_error::ProgramError, }, spl_token_confidential_transfer_proof_extraction::{ - transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext, - verify_and_extract_context, 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 939219159f9..6c625df3c20 100644 --- a/token/program-2022/src/extension/confidential_transfer_fee/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer_fee/instruction.rs @@ -25,7 +25,7 @@ use { sysvar, }, spl_pod::optional_keys::OptionalNonZeroPubkey, - spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation}, + 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 581f1aa5e75..f71880cbb6c 100644 --- a/token/program-2022/src/extension/confidential_transfer_fee/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer_fee/processor.rs @@ -38,7 +38,7 @@ use { pubkey::Pubkey, }, spl_pod::optional_keys::OptionalNonZeroPubkey, - spl_token_confidential_transfer_proof_extraction::verify_and_extract_context, + spl_token_confidential_transfer_proof_extraction::instruction::verify_and_extract_context, }; /// Processes an [InitializeConfidentialTransferFeeConfig] instruction. From f9d886e4edfd4f50bfc51c811715a3a826af6b0c Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 22 Oct 2024 10:44:39 +0900 Subject: [PATCH 13/22] fix registry and token account owner check --- token/client/src/token.rs | 2 -- token/program-2022-test/tests/confidential_transfer.rs | 3 +-- .../src/extension/confidential_transfer/instruction.rs | 3 --- .../src/extension/confidential_transfer/processor.rs | 8 ++++---- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/token/client/src/token.rs b/token/client/src/token.rs index c125007bd3b..cedacc6acec 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -1980,7 +1980,6 @@ where &self, account: &Pubkey, elgamal_registry_account: &Pubkey, - authority: &Pubkey, ) -> TokenResult { self.process_ixs::<[&dyn Signer; 0]>( &[ @@ -1989,7 +1988,6 @@ where account, &self.pubkey, elgamal_registry_account, - authority, )?, ], &[], diff --git a/token/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index 7d6154f6b75..de8014496c2 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -2902,7 +2902,7 @@ async fn confidential_transfer_configure_token_account_with_registry() { token .create_auxiliary_token_account_with_extension_space( &alice_account_keypair, - &alice_token_account, + &alice.pubkey(), vec![ExtensionType::ConfidentialTransferAccount], ) .await @@ -2912,7 +2912,6 @@ async fn confidential_transfer_configure_token_account_with_registry() { .confidential_transfer_configure_token_account_with_registry( &alice_account_keypair.pubkey(), &elgamal_registry_address, - &alice.pubkey(), ) .await .unwrap(); diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index 6fe5c8cdc04..954ff883a92 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -490,7 +490,6 @@ pub enum ConfidentialTransferInstruction { /// 0. `[writable]` The SPL Token account. /// 1. `[]` The corresponding SPL Token mint. /// 2. `[]` The ElGamal registry account. - /// 3. `[]` The account owner. /// /// Data expected by this instruction: /// None @@ -1731,14 +1730,12 @@ pub fn configure_account_with_registry( token_account: &Pubkey, mint: &Pubkey, elgamal_registry_account: &Pubkey, - authority: &Pubkey, ) -> Result { check_program_account(token_program_id)?; let accounts = vec![ AccountMeta::new(*token_account, false), AccountMeta::new_readonly(*mint, false), AccountMeta::new_readonly(*elgamal_registry_account, false), - AccountMeta::new_readonly(*authority, false), ]; Ok(encode_instruction( diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index c87773a856c..5b72fcea7ab 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -151,9 +151,6 @@ fn process_configure_account( } }; - let authority_info = next_account_info(account_info_iter)?; - let authority_info_data_len = authority_info.data_len(); - check_program_account(token_account_info.owner)?; let token_account_data = &mut token_account_info.data.borrow_mut(); let mut token_account = PodStateWithExtensionsMut::::unpack(token_account_data)?; @@ -164,6 +161,9 @@ fn process_configure_account( 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, @@ -176,7 +176,7 @@ fn process_configure_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 != *authority_info.key { + if elgamal_registry_account.owner != token_account.base.owner { return Err(TokenError::OwnerMismatch.into()); } } From 0a78c52b3301a9cd2b5ff759dc393f0d9b022a83 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Wed, 23 Oct 2024 05:58:06 +0900 Subject: [PATCH 14/22] remove `max(1)` condition on pda creation --- token/confidential-transfer/elgamal-registry/src/processor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/token/confidential-transfer/elgamal-registry/src/processor.rs b/token/confidential-transfer/elgamal-registry/src/processor.rs index 53ddfdb52e4..0ceffa1cfd7 100644 --- a/token/confidential-transfer/elgamal-registry/src/processor.rs +++ b/token/confidential-transfer/elgamal-registry/src/processor.rs @@ -150,7 +150,6 @@ pub fn create_pda_account<'a>( if new_pda_account.lamports() > 0 { let required_lamports = rent .minimum_balance(space) - .max(1) .saturating_sub(new_pda_account.lamports()); if required_lamports > 0 { @@ -180,7 +179,7 @@ pub fn create_pda_account<'a>( &system_instruction::create_account( payer.key, new_pda_account.key, - rent.minimum_balance(space).max(1), + rent.minimum_balance(space), space as u64, owner, ), From 7afec1f02b45ce1e530b9d4b5a5b19df129204c7 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Wed, 23 Oct 2024 06:00:16 +0900 Subject: [PATCH 15/22] revert rust version bump --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 628740b12ff..51985806fca 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.79.0" +channel = "1.78.0" From c5f78e14751cf2448297e6b2d139f22268f28537 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Wed, 23 Oct 2024 12:12:52 +0900 Subject: [PATCH 16/22] remove mutable signer in `CreateRegistry` instruction --- .../elgamal-registry/src/instruction.rs | 13 ++-- .../elgamal-registry/src/processor.rs | 68 ++++++------------- .../tests/confidential_transfer.rs | 28 +++++--- 3 files changed, 43 insertions(+), 66 deletions(-) diff --git a/token/confidential-transfer/elgamal-registry/src/instruction.rs b/token/confidential-transfer/elgamal-registry/src/instruction.rs index bf0026644c3..0fa33c9ca1e 100644 --- a/token/confidential-transfer/elgamal-registry/src/instruction.rs +++ b/token/confidential-transfer/elgamal-registry/src/instruction.rs @@ -17,15 +17,14 @@ use { pub enum RegistryInstruction { /// Initialize an ElGamal public key registry. /// - /// 0. `[writable, signer]` The funding account (must be a system account) - /// 1. `[writable]` The account to be created - /// 2. `[signer]` The wallet address (will also be the owner address for the + /// 0. `[writable]` The account to be created + /// 1. `[signer]` The wallet address (will also be the owner address for the /// registry account) - /// 3. `[]` System program - /// 4. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the + /// 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. - /// 5. `[]` (Optional) Record account if the accompanying proof is to be + /// 4. `[]` (Optional) Record account if the accompanying proof is to be /// read from a record account. CreateRegistry { /// Relative location of the `ProofInstruction::PubkeyValidityProof` @@ -101,14 +100,12 @@ impl RegistryInstruction { /// Create a `RegistryInstruction::CreateRegistry` instruction pub fn create_registry( - funding_address: &Pubkey, 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(*funding_address, true), AccountMeta::new(elgamal_registry_address, false), AccountMeta::new_readonly(*owner_address, true), AccountMeta::new_readonly(system_program::id(), false), diff --git a/token/confidential-transfer/elgamal-registry/src/processor.rs b/token/confidential-transfer/elgamal-registry/src/processor.rs index 0ceffa1cfd7..60df368f86f 100644 --- a/token/confidential-transfer/elgamal-registry/src/processor.rs +++ b/token/confidential-transfer/elgamal-registry/src/processor.rs @@ -9,7 +9,7 @@ use { account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, msg, - program::{invoke, invoke_signed}, + program::invoke_signed, program_error::ProgramError, pubkey::Pubkey, rent::Rent, @@ -30,7 +30,6 @@ pub fn process_create_registry_account( proof_instruction_offset: i64, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); - let funding_account_info = next_account_info(account_info_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)?; @@ -60,7 +59,6 @@ pub fn process_create_registry_account( let rent = Rent::get()?; create_pda_account( - funding_account_info, &rent, ELGAMAL_REGISTRY_ACCOUNT_LEN, program_id, @@ -136,10 +134,9 @@ fn validate_owner(owner_info: &AccountInfo, expected_owner: &Pubkey) -> ProgramR Ok(()) } -/// Creates ElGamal registry account using Program Derived Address for the given -/// seeds +/// Allocate ElGamal registry account using Program Derived Address for the +/// given seeds pub fn create_pda_account<'a>( - payer: &AccountInfo<'a>, rent: &Rent, space: usize, owner: &Pubkey, @@ -147,48 +144,23 @@ pub fn create_pda_account<'a>( new_pda_account: &AccountInfo<'a>, new_pda_signer_seeds: &[&[u8]], ) -> ProgramResult { - if new_pda_account.lamports() > 0 { - let required_lamports = rent - .minimum_balance(space) - .saturating_sub(new_pda_account.lamports()); - - if required_lamports > 0 { - invoke( - &system_instruction::transfer(payer.key, new_pda_account.key, required_lamports), - &[ - payer.clone(), - new_pda_account.clone(), - system_program.clone(), - ], - )?; - } + let required_lamports = rent + .minimum_balance(space) + .saturating_sub(new_pda_account.lamports()); - 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], - ) - } else { - invoke_signed( - &system_instruction::create_account( - payer.key, - new_pda_account.key, - rent.minimum_balance(space), - space as u64, - owner, - ), - &[ - payer.clone(), - new_pda_account.clone(), - system_program.clone(), - ], - &[new_pda_signer_seeds], - ) + 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/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index de8014496c2..ddf590f08c2 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -13,9 +13,11 @@ use { pubkey::Pubkey, signature::Signer, signer::{keypair::Keypair, signers::Signers}, + system_instruction, transaction::{Transaction, TransactionError}, transport::TransportError, }, + spl_elgamal_registry::state::ELGAMAL_REGISTRY_ACCOUNT_LEN, spl_record::state::RecordData, spl_token_2022::{ error::TokenError, @@ -2853,12 +2855,23 @@ async fn confidential_transfer_configure_token_account_with_registry() { ProofData::InstructionData(&proof_data), ); - let instructions = spl_elgamal_registry::instruction::create_registry( - &ctx.payer.pubkey(), + let elgamal_registry_address = spl_elgamal_registry::get_elgamal_registry_address( &alice.pubkey(), - proof_location, - ) - .unwrap(); + &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()), @@ -2879,11 +2892,6 @@ async fn confidential_transfer_configure_token_account_with_registry() { ProofData::InstructionData(&proof_data), ); - let elgamal_registry_address = spl_elgamal_registry::get_elgamal_registry_address( - &alice.pubkey(), - &spl_elgamal_registry::id(), - ); - let instructions = spl_elgamal_registry::instruction::update_registry(&alice.pubkey(), proof_location) .unwrap(); From 549b09328b070e3f0b3d6b2efd00b8b29b73353e Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Wed, 23 Oct 2024 18:32:49 +0900 Subject: [PATCH 17/22] add an option to realloc account when configuring account with registry --- token/client/src/token.rs | 2 + .../tests/confidential_transfer.rs | 4 +- .../confidential_transfer/instruction.rs | 36 +++++++- .../confidential_transfer/processor.rs | 83 ++++++++++++++++++- 4 files changed, 116 insertions(+), 9 deletions(-) diff --git a/token/client/src/token.rs b/token/client/src/token.rs index cedacc6acec..33bab151499 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -1980,6 +1980,7 @@ where &self, account: &Pubkey, elgamal_registry_account: &Pubkey, + payer: Option<&Pubkey>, ) -> TokenResult { self.process_ixs::<[&dyn Signer; 0]>( &[ @@ -1988,6 +1989,7 @@ where account, &self.pubkey, elgamal_registry_account, + payer, )?, ], &[], diff --git a/token/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index ddf590f08c2..3e756082ce2 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -2892,6 +2892,7 @@ async fn confidential_transfer_configure_token_account_with_registry() { ProofData::InstructionData(&proof_data), ); + let payer_pubkey = ctx.payer.pubkey(); let instructions = spl_elgamal_registry::instruction::update_registry(&alice.pubkey(), proof_location) .unwrap(); @@ -2911,7 +2912,7 @@ async fn confidential_transfer_configure_token_account_with_registry() { .create_auxiliary_token_account_with_extension_space( &alice_account_keypair, &alice.pubkey(), - vec![ExtensionType::ConfidentialTransferAccount], + vec![], // do not allocate space for confidential transfers ) .await .unwrap(); @@ -2920,6 +2921,7 @@ async fn confidential_transfer_configure_token_account_with_registry() { .confidential_transfer_configure_token_account_with_registry( &alice_account_keypair.pubkey(), &elgamal_registry_address, + Some(&payer_pubkey), // test account allocation ) .await .unwrap(); diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index 954ff883a92..b9c7dc2d511 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -18,7 +18,7 @@ use { instruction::{AccountMeta, Instruction}, program_error::ProgramError, pubkey::Pubkey, - sysvar, + system_program, sysvar, }, spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, }; @@ -484,15 +484,22 @@ pub enum ConfidentialTransferInstruction { /// 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. + /// /// 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 + /// `ConfigureAccountWithRegistryInstructionData` ConfigureAccountWithRegistry, } @@ -669,6 +676,17 @@ pub struct TransferWithFeeInstructionData { pub range_proof_instruction_offset: i8, } +/// Data expected by +/// `ConfidentialTransferInstruction::ConfigureAccountWithRegistry` +#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +#[repr(C)] +pub struct ConfigureAccountWithRegistryInstructionData { + /// Reallocate token account if it is not large enough for the + /// `ConfidentialTransfer` extension. + pub reallocate_account: PodBool, +} + /// Create a `InitializeMint` instruction pub fn initialize_mint( token_program_id: &Pubkey, @@ -1730,19 +1748,29 @@ pub fn configure_account_with_registry( token_account: &Pubkey, mint: &Pubkey, elgamal_registry_account: &Pubkey, + payer: Option<&Pubkey>, ) -> Result { check_program_account(token_program_id)?; - let accounts = vec![ + let mut accounts = vec![ AccountMeta::new(*token_account, false), AccountMeta::new_readonly(*mint, false), AccountMeta::new_readonly(*elgamal_registry_account, false), ]; + let reallocate_account = if let Some(payer) = payer { + accounts.push(AccountMeta::new(*payer, true)); + accounts.push(AccountMeta::new_readonly(system_program::id(), false)); + true + } else { + false + }; Ok(encode_instruction( token_program_id, accounts, TokenInstruction::ConfidentialTransferExtension, ConfidentialTransferInstruction::ConfigureAccountWithRegistry, - &(), + &ConfigureAccountWithRegistryInstructionData { + reallocate_account: reallocate_account.into(), + }, )) } diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index 5b72fcea7ab..7f7ed9fe6f5 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -15,22 +15,26 @@ use { EncryptedWithheldAmount, }, memo_transfer::{check_previous_sibling_instruction_is_memo, memo_required}, + set_account_type, transfer_fee::TransferFeeConfig, transfer_hook, BaseStateWithExtensions, BaseStateWithExtensionsMut, - PodStateWithExtensions, PodStateWithExtensionsMut, + PodStateWithExtensions, PodStateWithExtensionsMut, StateWithExtensions, }, instruction::{decode_instruction_data, decode_instruction_type}, pod::{PodAccount, PodMint}, processor::Processor, + 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, @@ -103,10 +107,25 @@ enum ElGamalPubkeySource<'a> { fn process_configure_account_with_registry( program_id: &Pubkey, accounts: &[AccountInfo], + reallocate: bool, ) -> ProgramResult { - let elgamal_registry_account = accounts.get(2).ok_or(ProgramError::NotEnoughAccountKeys)?; + 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 reallocate { + let 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)?; @@ -124,6 +143,56 @@ fn process_configure_account_with_registry( ) } +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 = StateWithExtensions::::unpack(&token_account)?; + account.get_extension_types()? + }; + 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, @@ -1272,7 +1341,13 @@ pub(crate) fn process_instruction( } ConfidentialTransferInstruction::ConfigureAccountWithRegistry => { msg!("ConfidentialTransferInstruction::ConfigureAccountWithRegistry"); - process_configure_account_with_registry(program_id, accounts) + let data = + decode_instruction_data::(input)?; + process_configure_account_with_registry( + program_id, + accounts, + data.reallocate_account.into(), + ) } } } From 2c626e249507eaed147be00945a5660cfe022cf4 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 24 Oct 2024 17:36:02 +0900 Subject: [PATCH 18/22] Apply suggestions from code review Co-authored-by: Jon C --- token/confidential-transfer/elgamal-registry/src/processor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/token/confidential-transfer/elgamal-registry/src/processor.rs b/token/confidential-transfer/elgamal-registry/src/processor.rs index 60df368f86f..c578d8ddc4e 100644 --- a/token/confidential-transfer/elgamal-registry/src/processor.rs +++ b/token/confidential-transfer/elgamal-registry/src/processor.rs @@ -112,13 +112,13 @@ pub fn process_instruction( RegistryInstruction::CreateRegistry { proof_instruction_offset, } => { - msg!("RegistryInstruction::CreateRegistry"); + msg!("ElGamalRegistryInstruction::CreateRegistry"); process_create_registry_account(program_id, accounts, proof_instruction_offset as i64) } RegistryInstruction::UpdateRegistry { proof_instruction_offset, } => { - msg!("RegistryInstruction::UpdateRegistry"); + msg!("ElGamalRegistryInstruction::UpdateRegistry"); process_update_registry_account(program_id, accounts, proof_instruction_offset as i64) } } From 05bd8a61dfce3c520315ec4c446fa1e25682f246 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 24 Oct 2024 17:55:57 +0900 Subject: [PATCH 19/22] remove `ConfigureAccountWithRegistryInstructionData` --- .../confidential_transfer/instruction.rs | 28 +++++-------------- .../confidential_transfer/processor.rs | 13 ++------- 2 files changed, 10 insertions(+), 31 deletions(-) diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index b9c7dc2d511..e82f4f521af 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -486,7 +486,9 @@ pub enum ConfidentialTransferInstruction { /// /// 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. + /// 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: /// @@ -499,7 +501,7 @@ pub enum ConfidentialTransferInstruction { /// 4. `[]` (Optional) System program for reallocation funding /// /// Data expected by this instruction: - /// `ConfigureAccountWithRegistryInstructionData` + /// None ConfigureAccountWithRegistry, } @@ -676,17 +678,6 @@ pub struct TransferWithFeeInstructionData { pub range_proof_instruction_offset: i8, } -/// Data expected by -/// `ConfidentialTransferInstruction::ConfigureAccountWithRegistry` -#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -#[repr(C)] -pub struct ConfigureAccountWithRegistryInstructionData { - /// Reallocate token account if it is not large enough for the - /// `ConfidentialTransfer` extension. - pub reallocate_account: PodBool, -} - /// Create a `InitializeMint` instruction pub fn initialize_mint( token_program_id: &Pubkey, @@ -1756,21 +1747,16 @@ pub fn configure_account_with_registry( AccountMeta::new_readonly(*mint, false), AccountMeta::new_readonly(*elgamal_registry_account, false), ]; - let reallocate_account = if let Some(payer) = payer { + if let Some(payer) = payer { accounts.push(AccountMeta::new(*payer, true)); accounts.push(AccountMeta::new_readonly(system_program::id(), false)); - true - } else { - false - }; + } Ok(encode_instruction( token_program_id, accounts, TokenInstruction::ConfidentialTransferExtension, ConfidentialTransferInstruction::ConfigureAccountWithRegistry, - &ConfigureAccountWithRegistryInstructionData { - reallocate_account: reallocate_account.into(), - }, + &(), )) } diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index 7f7ed9fe6f5..c543ffb2eec 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -107,7 +107,6 @@ enum ElGamalPubkeySource<'a> { fn process_configure_account_with_registry( program_id: &Pubkey, accounts: &[AccountInfo], - reallocate: bool, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let token_account_info = next_account_info(account_info_iter)?; @@ -116,8 +115,8 @@ fn process_configure_account_with_registry( check_elgamal_registry_program_account(elgamal_registry_account.owner)?; - if reallocate { - let payer_info = next_account_info(account_info_iter)?; + // 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, @@ -1341,13 +1340,7 @@ pub(crate) fn process_instruction( } ConfidentialTransferInstruction::ConfigureAccountWithRegistry => { msg!("ConfidentialTransferInstruction::ConfigureAccountWithRegistry"); - let data = - decode_instruction_data::(input)?; - process_configure_account_with_registry( - program_id, - accounts, - data.reallocate_account.into(), - ) + process_configure_account_with_registry(program_id, accounts) } } } From 1213b108c920674eb2a46ae9900bc67b2eff38ab Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 24 Oct 2024 17:56:11 +0900 Subject: [PATCH 20/22] Replace `to_bytes()` of `Pubkey` to `as_bytes()` --- token/confidential-transfer/elgamal-registry/src/lib.rs | 2 +- token/confidential-transfer/elgamal-registry/src/processor.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/token/confidential-transfer/elgamal-registry/src/lib.rs b/token/confidential-transfer/elgamal-registry/src/lib.rs index 95139d3aef3..895f42daad2 100644 --- a/token/confidential-transfer/elgamal-registry/src/lib.rs +++ b/token/confidential-transfer/elgamal-registry/src/lib.rs @@ -15,7 +15,7 @@ pub fn get_elgamal_registry_address_and_bump_seed( program_id: &Pubkey, ) -> (Pubkey, u8) { Pubkey::find_program_address( - &[REGISTRY_ADDRESS_SEED, &wallet_address.to_bytes()], + &[REGISTRY_ADDRESS_SEED, wallet_address.as_ref()], program_id, ) } diff --git a/token/confidential-transfer/elgamal-registry/src/processor.rs b/token/confidential-transfer/elgamal-registry/src/processor.rs index c578d8ddc4e..53be973c636 100644 --- a/token/confidential-transfer/elgamal-registry/src/processor.rs +++ b/token/confidential-transfer/elgamal-registry/src/processor.rs @@ -53,7 +53,7 @@ pub fn process_create_registry_account( let elgamal_registry_account_seeds: &[&[_]] = &[ REGISTRY_ADDRESS_SEED, - &wallet_account_info.key.to_bytes(), + wallet_account_info.key.as_ref(), &[bump_seed], ]; let rent = Rent::get()?; From 1c3c19b09a8f46faa761c5a24ca6063f63bc3b0d Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 24 Oct 2024 17:56:30 +0900 Subject: [PATCH 21/22] use `PodStateWithExtensions` instead of `StateWithExtensions` --- .../src/extension/confidential_transfer/processor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index c543ffb2eec..23fcaaaca06 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -18,7 +18,7 @@ use { set_account_type, transfer_fee::TransferFeeConfig, transfer_hook, BaseStateWithExtensions, BaseStateWithExtensionsMut, - PodStateWithExtensions, PodStateWithExtensionsMut, StateWithExtensions, + PodStateWithExtensions, PodStateWithExtensionsMut, }, instruction::{decode_instruction_data, decode_instruction_type}, pod::{PodAccount, PodMint}, @@ -149,7 +149,7 @@ fn reallocate_for_configure_account_with_registry<'a>( ) -> ProgramResult { let mut current_extension_types = { let token_account = token_account_info.data.borrow(); - let account = StateWithExtensions::::unpack(&token_account)?; + let account = PodStateWithExtensions::::unpack(&token_account)?; account.get_extension_types()? }; current_extension_types.push(ExtensionType::ConfidentialTransferAccount); From 7b77b83ae6785285a201da20d6f99b767621b655 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 24 Oct 2024 17:56:46 +0900 Subject: [PATCH 22/22] add a clarifying comment on `try_calculate_account_len` dedupe --- .../src/extension/confidential_transfer/processor.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index 23fcaaaca06..a93b5e85268 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -152,6 +152,8 @@ fn reallocate_for_configure_account_with_registry<'a>( 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)?;