From 8cf2dc3443be02ff018bc45a44a69e3517761c80 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Wed, 9 Oct 2024 12:23:02 +0900 Subject: [PATCH] add support for ElGamal registry program in the token program --- Cargo.lock | 27 +++++ token/program-2022/Cargo.toml | 1 + .../confidential_transfer/instruction.rs | 7 +- .../extension/confidential_transfer/mod.rs | 3 + .../confidential_transfer/processor.rs | 100 +++++++++++++----- .../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 +- 9 files changed, 125 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19571906ee7..f7cb47f75fd 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", @@ -7732,7 +7757,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/program-2022/Cargo.toml b/token/program-2022/Cargo.toml index fa0c5319b7b..4873fd652c4 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..170580c8e54 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 @@ -81,6 +81,8 @@ pub enum ConfidentialTransferInstruction { /// /// Accounts expected by this instruction: /// + /// TODO: Add an option to include an `ElGamalRegistry` address + /// /// * Single owner/delegate /// 0. `[writeable]` The SPL Token account. /// 1. `[]` The corresponding SPL Token mint. @@ -106,7 +108,8 @@ pub enum ConfidentialTransferInstruction { /// account. /// /// Data expected by this instruction: - /// `ConfigureAccountInstructionData` + /// None if an `ElGamalRegistry` address is provided in the list of + /// accounts `ConfigureAccountInstructionData` otherwise ConfigureAccount, /// Approves a token account for confidential transfers. 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..05126a493cf 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,59 @@ fn process_update_mint( Ok(()) } +/// Processes a [ConfigureAccount] instruction with the assumption that an +/// ElGamal registry is provided. +fn process_configure_account_from_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, + None, + Some(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, + proof_instruction_offset: Option, + elgamal_registry_account: Option<&ElGamalRegistry>, ) -> 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 = if let Some(offset) = proof_instruction_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 + } else { + // if proof instruction offset is `None`, then assume that the proof + // was already verified in an ElGamal registry account + let _elgamal_registry_account = next_account_info(account_info_iter)?; + elgamal_registry_account.unwrap().elgamal_pubkey + }; let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); @@ -121,13 +159,21 @@ 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(), - )?; + if let Some(registry_account) = elgamal_registry_account { + // if ElGamal registry was provided, then just verify that the registry owner + // and the account match, then skip the signature verification check + if registry_account.owner != *authority_info.key { + return Err(TokenError::OwnerMismatch.into()); + } + } else { + Processor::validate_owner( + program_id, + &token_account.base.owner, + authority_info, + authority_info_data_len, + account_info_iter.as_slice(), + )?; + } check_program_account(mint_info.owner)?; let mint_data = &mut mint_info.data.borrow(); @@ -140,7 +186,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; @@ -1102,14 +1148,20 @@ pub(crate) fn process_instruction( } ConfidentialTransferInstruction::ConfigureAccount => { msg!("ConfidentialTransferInstruction::ConfigureAccount"); - let data = decode_instruction_data::(input)?; - process_configure_account( - program_id, - accounts, - &data.decryptable_zero_balance, - &data.maximum_pending_balance_credit_counter, - data.proof_instruction_offset as i64, - ) + if input.is_empty() { + // instruction data is empty, so assume an ElGamal registry is provided + process_configure_account_from_registry(program_id, accounts) + } else { + let data = decode_instruction_data::(input)?; + process_configure_account( + program_id, + accounts, + &data.decryptable_zero_balance, + &data.maximum_pending_balance_credit_counter, + Some(data.proof_instruction_offset as i64), + None, + ) + } } ConfidentialTransferInstruction::ApproveAccount => { msg!("ConfidentialTransferInstruction::ApproveAccount"); 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(()) +}