From 40d0b38eb16e4fe1d964107d867d16c6c1b52fbd Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 15 Oct 2024 18:35:03 +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 | 49 +++++++++- .../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 ++- 9 files changed, 166 insertions(+), 23 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..71d9b343a95 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 single source account owner. + /// + /// Data expected by this instruction: + /// None + ConfigureAccountWithRegistry, } /// Data expected by `ConfidentialTransferInstruction::InitializeMint` @@ -1701,3 +1724,27 @@ 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: &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, 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(()) +}