From b222fdf3d72da9ad44fff5bd0463277008dd9dfd Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Sat, 13 Jan 2024 08:15:44 +0900 Subject: [PATCH] [zk-token-proof] Add functionality to read proof from accounts instead of instruction data (#34750) * add functionality to read proof from accounts instead of instruction data * update add tests * clippy * clarify instruction data discriminator * avoid cloning entire proof data * Update programs/zk-token-proof/src/lib.rs Co-authored-by: Jon C * update `PROOF_OFFSET_LENGTH` to `INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT` * update instruction docs * add feature gate * Update sdk/src/feature_set.rs Co-authored-by: Jon C * update feature name `enable_zk_from_account` to `enable_zk_proof_from_account` * Apply suggestions from code review Co-authored-by: Jon C * clarify the instruction data length more precisely --------- Co-authored-by: Jon C --- .../tests/process_transaction.rs | 319 +++++++++++++++++- programs/zk-token-proof/src/lib.rs | 90 +++-- sdk/src/feature_set.rs | 5 + .../src/zk_token_proof_instruction.rs | 240 ++++++------- 4 files changed, 516 insertions(+), 138 deletions(-) diff --git a/programs/zk-token-proof-tests/tests/process_transaction.rs b/programs/zk-token-proof-tests/tests/process_transaction.rs index 8e1d968ccec5bc..438e76bed9fef2 100644 --- a/programs/zk-token-proof-tests/tests/process_transaction.rs +++ b/programs/zk-token-proof-tests/tests/process_transaction.rs @@ -1,9 +1,11 @@ use { - bytemuck::Pod, + bytemuck::{bytes_of, Pod}, curve25519_dalek::scalar::Scalar, solana_program_test::*, solana_sdk::{ + account::Account, instruction::InstructionError, + pubkey::Pubkey, signature::Signer, signer::keypair::Keypair, system_instruction, @@ -60,6 +62,14 @@ async fn test_zero_balance() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyZeroBalance, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_verify_proof_with_context( ProofInstruction::VerifyZeroBalance, size_of::>(), @@ -128,6 +138,13 @@ async fn test_ciphertext_ciphertext_equality() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyCiphertextCiphertextEquality, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; test_close_context_state( ProofInstruction::VerifyCiphertextCiphertextEquality, size_of::>(), @@ -186,6 +203,14 @@ async fn test_transfer() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyTransfer, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyTransfer, size_of::>(), @@ -256,6 +281,14 @@ async fn test_transfer_with_fee() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyTransferWithFee, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyTransferWithFee, size_of::>(), @@ -307,6 +340,14 @@ async fn test_withdraw() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyWithdraw, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyWithdraw, size_of::>(), @@ -342,6 +383,14 @@ async fn test_pubkey_validity() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyPubkeyValidity, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyPubkeyValidity, size_of::>(), @@ -375,6 +424,14 @@ async fn test_range_proof_u64() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyRangeProofU64, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyRangeProofU64, size_of::>(), @@ -423,6 +480,14 @@ async fn test_batched_range_proof_u64() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyBatchedRangeProofU64, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyBatchedRangeProofU64, size_of::>(), @@ -471,6 +536,14 @@ async fn test_batched_range_proof_u128() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyBatchedRangeProofU128, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyBatchedRangeProofU128, size_of::>(), @@ -523,6 +596,14 @@ async fn test_batched_range_proof_u256() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyBatchedRangeProofU256, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyBatchedRangeProofU256, size_of::>(), @@ -575,6 +656,14 @@ async fn test_ciphertext_commitment_equality() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyCiphertextCommitmentEquality, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyCiphertextCommitmentEquality, size_of::>(), @@ -630,6 +719,14 @@ async fn test_grouped_ciphertext_2_handles_validity() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyGroupedCiphertext2HandlesValidity, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyGroupedCiphertext2HandlesValidity, size_of::>(), @@ -697,6 +794,14 @@ async fn test_batched_grouped_ciphertext_2_handles_validity() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity, size_of::>(), @@ -766,6 +871,14 @@ async fn test_fee_sigma() { ) .await; + test_verify_proof_from_account_with_context( + ProofInstruction::VerifyFeeSigma, + size_of::>(), + &success_proof_data, + &fail_proof_data, + ) + .await; + test_close_context_state( ProofInstruction::VerifyFeeSigma, size_of::>(), @@ -782,7 +895,28 @@ async fn test_verify_proof_without_context( T: Pod + ZkProofData, U: Pod, { - let mut context = ProgramTest::default().start_with_context().await; + let mut program_test = ProgramTest::default(); + let success_proof_account = Pubkey::new_unique(); + program_test.add_account( + success_proof_account, + Account { + lamports: 1_000_000_000, + data: bytes_of(success_proof_data).to_vec(), + owner: Pubkey::new_unique(), + ..Account::default() + }, + ); + let fail_proof_account = Pubkey::new_unique(); + program_test.add_account( + fail_proof_account, + Account { + lamports: 1_000_000_000, + data: bytes_of(fail_proof_data).to_vec(), + owner: Pubkey::new_unique(), + ..Account::default() + }, + ); + let mut context = program_test.start_with_context().await; let client = &mut context.banks_client; let payer = &context.payer; @@ -840,6 +974,36 @@ async fn test_verify_proof_without_context( TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) ); } + + // verify a valid proof from an account + let instruction = + vec![proof_instruction.encode_verify_proof_from_account(None, &success_proof_account, 0)]; + let transaction = Transaction::new_signed_with_payer( + &instruction, + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + client.process_transaction(transaction).await.unwrap(); + + // try to verify an invalid proof from an account + let instruction = + vec![proof_instruction.encode_verify_proof_from_account(None, &fail_proof_account, 0)]; + let transaction = Transaction::new_signed_with_payer( + &instruction, + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(); + assert_eq!( + err, + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) + ) } async fn test_verify_proof_with_context( @@ -1045,6 +1209,157 @@ async fn test_verify_proof_with_context( client.process_transaction(transaction).await.unwrap(); } +async fn test_verify_proof_from_account_with_context( + instruction_type: ProofInstruction, + space: usize, + success_proof_data: &T, + fail_proof_data: &T, +) where + T: Pod + ZkProofData, + U: Pod, +{ + let mut program_test = ProgramTest::default(); + let success_proof_account = Pubkey::new_unique(); + program_test.add_account( + success_proof_account, + Account { + lamports: 1_000_000_000, + data: bytes_of(success_proof_data).to_vec(), + owner: Pubkey::new_unique(), + ..Account::default() + }, + ); + let fail_proof_account = Pubkey::new_unique(); + program_test.add_account( + fail_proof_account, + Account { + lamports: 1_000_000_000, + data: bytes_of(fail_proof_data).to_vec(), + owner: Pubkey::new_unique(), + ..Account::default() + }, + ); + let mut context = program_test.start_with_context().await; + let rent = context.banks_client.get_rent().await.unwrap(); + + let client = &mut context.banks_client; + let payer = &context.payer; + let recent_blockhash = context.last_blockhash; + + let context_state_account = Keypair::new(); + let context_state_authority = Keypair::new(); + + let context_state_info = ContextStateInfo { + context_state_account: &context_state_account.pubkey(), + context_state_authority: &context_state_authority.pubkey(), + }; + + // try to create proof context state with an invalid proof + let instructions = vec![ + system_instruction::create_account( + &payer.pubkey(), + &context_state_account.pubkey(), + rent.minimum_balance(space), + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof_from_account( + Some(context_state_info), + &fail_proof_account, + 0, + ), + ]; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&payer.pubkey()), + &[payer, &context_state_account], + recent_blockhash, + ); + let err = client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(); + assert_eq!( + err, + TransactionError::InstructionError(1, InstructionError::InvalidInstructionData) + ); + + // successfully create a proof context state + let instructions = vec![ + system_instruction::create_account( + &payer.pubkey(), + &context_state_account.pubkey(), + rent.minimum_balance(space), + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof_from_account( + Some(context_state_info), + &success_proof_account, + 0, + ), + ]; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&payer.pubkey()), + &[payer, &context_state_account], + recent_blockhash, + ); + client.process_transaction(transaction).await.unwrap(); + + // try overwriting the context state + let instructions = vec![instruction_type.encode_verify_proof_from_account( + Some(context_state_info), + &success_proof_account, + 0, + )]; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + let err = client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(); + assert_eq!( + err, + TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized) + ); + + // self-owned context state account + let context_state_account_and_authority = Keypair::new(); + let context_state_info = ContextStateInfo { + context_state_account: &context_state_account_and_authority.pubkey(), + context_state_authority: &context_state_account_and_authority.pubkey(), + }; + + let instructions = vec![ + system_instruction::create_account( + &payer.pubkey(), + &context_state_account_and_authority.pubkey(), + rent.minimum_balance(space), + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof_from_account( + Some(context_state_info), + &success_proof_account, + 0, + ), + ]; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&payer.pubkey()), + &[payer, &context_state_account_and_authority], + recent_blockhash, + ); + client.process_transaction(transaction).await.unwrap(); +} + async fn test_close_context_state( instruction_type: ProofInstruction, space: usize, diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index a4a8dad956c2a8..21c09b4ef123f2 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -32,6 +32,8 @@ pub const VERIFY_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 6_40 pub const VERIFY_BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_COMPUTE_UNITS: u64 = 13_000; pub const VERIFY_FEE_SIGMA_COMPUTE_UNITS: u64 = 6_500; +const INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT: usize = 5; + fn process_verify_proof(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> where T: Pod + ZkProofData, @@ -40,24 +42,75 @@ where let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_data = instruction_context.get_instruction_data(); - let proof_data = ProofInstruction::proof_data::(instruction_data).ok_or_else(|| { - ic_msg!(invoke_context, "invalid proof data"); - InstructionError::InvalidInstructionData - })?; - - proof_data.verify_proof().map_err(|err| { - ic_msg!(invoke_context, "proof_verification failed: {:?}", err); - InstructionError::InvalidInstructionData - })?; - - // create context state if accounts are provided with the instruction - if instruction_context.get_number_of_instruction_accounts() > 0 { + + // number of accessed accounts so far + let mut accessed_accounts = 0_u16; + + // if instruction data is exactly 5 bytes, then read proof from an account + let context_data = if instruction_data.len() == INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT { + if !invoke_context + .feature_set + .is_active(&feature_set::enable_zk_proof_from_account::id()) + { + return Err(InstructionError::InvalidInstructionData); + } + + let proof_data_account = instruction_context + .try_borrow_instruction_account(transaction_context, accessed_accounts)?; + accessed_accounts = accessed_accounts.checked_add(1).unwrap(); + + let proof_data_offset = u32::from_le_bytes( + // the first byte is the instruction discriminator + instruction_data[1..INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT] + .try_into() + .map_err(|_| InstructionError::InvalidInstructionData)?, + ); + let proof_data_start: usize = proof_data_offset + .try_into() + .map_err(|_| InstructionError::InvalidInstructionData)?; + let proof_data_end = proof_data_start + .checked_add(std::mem::size_of::()) + .ok_or(InstructionError::InvalidInstructionData)?; + let proof_data_raw = proof_data_account + .get_data() + .get(proof_data_start..proof_data_end) + .ok_or(InstructionError::InvalidAccountData)?; + + let proof_data = bytemuck::try_from_bytes::(proof_data_raw).map_err(|_| { + ic_msg!(invoke_context, "invalid proof data"); + InstructionError::InvalidInstructionData + })?; + proof_data.verify_proof().map_err(|err| { + ic_msg!(invoke_context, "proof verification failed: {:?}", err); + InstructionError::InvalidInstructionData + })?; + + *proof_data.context_data() + } else { + let proof_data = + ProofInstruction::proof_data::(instruction_data).ok_or_else(|| { + ic_msg!(invoke_context, "invalid proof data"); + InstructionError::InvalidInstructionData + })?; + proof_data.verify_proof().map_err(|err| { + ic_msg!(invoke_context, "proof_verification failed: {:?}", err); + InstructionError::InvalidInstructionData + })?; + + *proof_data.context_data() + }; + + // create context state if additional accounts are provided with the instruction + if instruction_context.get_number_of_instruction_accounts() > accessed_accounts { let context_state_authority = *instruction_context - .try_borrow_instruction_account(transaction_context, 1)? + .try_borrow_instruction_account( + transaction_context, + accessed_accounts.checked_add(1).unwrap(), + )? .get_key(); - let mut proof_context_account = - instruction_context.try_borrow_instruction_account(transaction_context, 0)?; + let mut proof_context_account = instruction_context + .try_borrow_instruction_account(transaction_context, accessed_accounts)?; if *proof_context_account.get_owner() != id() { return Err(InstructionError::InvalidAccountOwner); @@ -70,11 +123,8 @@ where return Err(InstructionError::AccountAlreadyInitialized); } - let context_state_data = ProofContextState::encode( - &context_state_authority, - T::PROOF_TYPE, - proof_data.context_data(), - ); + let context_state_data = + ProofContextState::encode(&context_state_authority, T::PROOF_TYPE, &context_data); if proof_context_account.get_data().len() != context_state_data.len() { return Err(InstructionError::InvalidAccountData); diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 0a06cdd864eb58..6c3a2bfb3b4b8f 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -760,6 +760,10 @@ pub mod deprecate_executable_meta_update_in_bpf_loader { solana_sdk::declare_id!("k6uR1J9VtKJnTukBV2Eo15BEy434MBg8bT6hHQgmU8v"); } +pub mod enable_zk_proof_from_account { + solana_sdk::declare_id!("zkiTNuzBKxrCLMKehzuQeKZyLtX2yvFcEKMML8nExU8"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -945,6 +949,7 @@ lazy_static! { (merkle_conflict_duplicate_proofs::id(), "generate duplicate proofs for merkle root conflicts #34270"), (disable_bpf_loader_instructions::id(), "disable bpf loader management instructions #34194"), (deprecate_executable_meta_update_in_bpf_loader::id(), "deprecate executable meta flag update in bpf loader #34194"), + (enable_zk_proof_from_account::id(), "Enable zk token proof program to read proof from accounts instead of instruction data #34750"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/zk-token-sdk/src/zk_token_proof_instruction.rs b/zk-token-sdk/src/zk_token_proof_instruction.rs index 81616beb6f449e..eea771bfbf63a0 100644 --- a/zk-token-sdk/src/zk_token_proof_instruction.rs +++ b/zk-token-sdk/src/zk_token_proof_instruction.rs @@ -6,11 +6,21 @@ //! Each proof verification instruction verifies a certain type of zero-knowledge proof. These //! instructions are processed by the program in two steps: //! 1. The program verifies the zero-knowledge proof. -//! 2. The program optionally stores the context component of the instruction data to a +//! 2. The program optionally stores the context component of the zero-knowledge proof to a //! dedicated [`context-state`] account. -//! If no accounts are provided with the instruction, the program simply verifies the proofs. If -//! accounts are provided with the instruction, then the program writes the context data to the -//! specified context-state account. +//! +//! In step 1, the zero-knowledge proof can be included directly as the instruction data or +//! pre-written to an account. The program determines whether the proof is provided as instruction +//! data or pre-written to an account by inspecting the length of the data. If the instruction data +//! is exactly 5 bytes (instruction disciminator + unsigned 32-bit integer), then the program +//! assumes that the first account provided with the instruction contains the zero-knowledge proof +//! and verifies the account data at the offset specified in the instruction data. Otherwise, the +//! program assumes that the zero-knowledge proof is provided as part of the instruction data. +//! +//! In step 2, the program determines whether to create a context-state account by inspecting the +//! number of accounts provided with the instruction. If two additional accounts are provided with +//! the instruction after verifying the zero-knowledge proof, then the program writes the context data to +//! the specified context-state account. //! //! NOTE: A context-state account must be pre-allocated to the exact size of the context data that //! is expected for a proof type before it is included in a proof verification instruction. @@ -54,15 +64,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `ZeroBalanceProofData` + /// The instruction expects either: + /// i. `ZeroBalanceProofData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyZeroBalance, @@ -73,15 +81,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `WithdrawData` + /// The instruction expects either: + /// i. `WithdrawData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyWithdraw, @@ -92,15 +98,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// * Otherwise - /// None - /// - /// Data expected by this instruction: - /// `CiphertextCiphertextEqualityProofData` + /// The instruction expects either: + /// i. `CiphertextCiphertextEqualityProofData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyCiphertextCiphertextEquality, @@ -111,15 +115,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `TransferData` + /// The instruction expects either: + /// i. `TransferData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyTransfer, @@ -130,15 +132,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `TransferWithFeeData` + /// The instruction expects either: + /// i. `TransferWithFeeData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyTransferWithFee, @@ -149,15 +149,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `PubkeyValidityData` + /// The instruction expects either: + /// i. `PubkeyValidityData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyPubkeyValidity, @@ -168,15 +166,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// * Otherwise - /// None - /// - /// Data expected by this instruction: - /// `RangeProofU64Data` + /// The instruction expects either: + /// i. `RangeProofU64Data` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyRangeProofU64, @@ -194,15 +190,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `BatchedRangeProof64Data` + /// The instruction expects either: + /// i. `BatchedRangeProofU64Data` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedRangeProofU64, @@ -214,15 +208,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `BatchedRangeProof128Data` + /// The instruction expects either: + /// i. `BatchedRangeProofU128Data` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedRangeProofU128, @@ -234,15 +226,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `BatchedRangeProof256Data` + /// The instruction expects either: + /// i. `BatchedRangeProofU256Data` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedRangeProofU256, @@ -253,15 +243,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// * Otherwise - /// None - /// - /// Data expected by this instruction: - /// `CiphertextCommitmentEqualityProofData` + /// The instruction expects either: + /// i. `CiphertextCommitmentEqualityProofData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyCiphertextCommitmentEquality, @@ -273,15 +261,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `GroupedCiphertextValidityProofContext` + /// The instruction expects either: + /// i. `GroupedCiphertext2HandlesValidityProofData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyGroupedCiphertext2HandlesValidity, @@ -294,15 +280,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `BatchedGroupedCiphertextValidityProofContext` + /// The instruction expects either: + /// i. `BatchedGroupedCiphertext2HandlesValidityProofData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedGroupedCiphertext2HandlesValidity, @@ -313,15 +297,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * Creating a proof context account - /// 0. `[writable]` The proof context account - /// 1. `[]` The proof context account owner - /// - /// * Otherwise - /// None + /// 0. `[]` (Optional) Account to read the proof from + /// 1. `[writable]` (Optional) The proof context account + /// 2. `[]` (Optional) The proof context account owner /// - /// Data expected by this instruction: - /// `FeeSigmaProofData` + /// The instruction expects either: + /// i. `FeeSigmaProofData` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyFeeSigma, } @@ -474,6 +456,32 @@ impl ProofInstruction { } } + pub fn encode_verify_proof_from_account( + &self, + context_state_info: Option, + proof_account: &Pubkey, + offset: u32, + ) -> Instruction { + let accounts = if let Some(context_state_info) = context_state_info { + vec![ + AccountMeta::new(*proof_account, false), + AccountMeta::new(*context_state_info.context_state_account, false), + AccountMeta::new_readonly(*context_state_info.context_state_authority, false), + ] + } else { + vec![AccountMeta::new(*proof_account, false)] + }; + + let mut data = vec![ToPrimitive::to_u8(self).unwrap()]; + data.extend_from_slice(&offset.to_le_bytes()); + + Instruction { + program_id: crate::zk_token_proof_program::id(), + accounts, + data, + } + } + pub fn instruction_type(input: &[u8]) -> Option { input .first()