From 9dfb0f2be15621d375aafe730fa26b5aa0dd6119 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 12 Jan 2024 00:40:19 +0900 Subject: [PATCH 01/13] add functionality to read proof from accounts instead of instruction data --- programs/zk-token-proof/src/lib.rs | 55 ++++++++++++++++--- .../src/zk_token_proof_instruction.rs | 31 +++++++++++ 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index cedf42712377dc..e499168e71552f 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 PROOF_OFFSET_LENGTH: usize = 5; + fn process_verify_proof(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> where T: Pod + ZkProofData, @@ -40,24 +42,59 @@ 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 - })?; + + // number of accessed accounts so far + let mut accessed_accounts = 0_u16; + + // if instruction data is exactly 4 bytes, then read proof from an account + let proof_data = if instruction_data.len() == PROOF_OFFSET_LENGTH { + let proof_data_account = instruction_context + .try_borrow_instruction_account(transaction_context, accessed_accounts)?; + accessed_accounts += 1; + + let proof_data_offset = u32::from_le_bytes( + instruction_data[1..PROOF_OFFSET_LENGTH] + .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 = proof_data_account + .get_data() + .get(proof_data_start..proof_data_end) + .ok_or(InstructionError::InvalidAccountData)?; + + *bytemuck::try_from_bytes::(proof_data).map_err(|_| { + ic_msg!(invoke_context, "invalid proof data"); + InstructionError::InvalidInstructionData + })? + } else { + *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 { + // 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); diff --git a/zk-token-sdk/src/zk_token_proof_instruction.rs b/zk-token-sdk/src/zk_token_proof_instruction.rs index 81616beb6f449e..041647f7dae019 100644 --- a/zk-token-sdk/src/zk_token_proof_instruction.rs +++ b/zk-token-sdk/src/zk_token_proof_instruction.rs @@ -54,6 +54,11 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// + /// * TODO: Read proof from an account + /// 0. `[]` Proof account + /// 1. `[writable]` The proof context account + /// 2. `[]` The proof context account owner + /// /// * Creating a proof context account /// 0. `[writable]` The proof context account /// 1. `[]` The proof context account owner @@ -474,6 +479,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() From 893aafc0278665a904d2f8ef8ded1d4623eee89e Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 12 Jan 2024 00:40:26 +0900 Subject: [PATCH 02/13] update add tests --- .../tests/process_transaction.rs | 319 +++++++++++++++++- 1 file changed, 317 insertions(+), 2 deletions(-) diff --git a/programs/zk-token-proof-tests/tests/process_transaction.rs b/programs/zk-token-proof-tests/tests/process_transaction.rs index f00d68fbf235c1..cd27a7252bea92 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, From 68a13fc569695cd40cfab739860bd3919fcb07f4 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 12 Jan 2024 01:28:00 +0900 Subject: [PATCH 03/13] clippy --- programs/zk-token-proof/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index e499168e71552f..f231229de9c278 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -50,7 +50,7 @@ where let proof_data = if instruction_data.len() == PROOF_OFFSET_LENGTH { let proof_data_account = instruction_context .try_borrow_instruction_account(transaction_context, accessed_accounts)?; - accessed_accounts += 1; + accessed_accounts = accessed_accounts.checked_add(1).unwrap(); let proof_data_offset = u32::from_le_bytes( instruction_data[1..PROOF_OFFSET_LENGTH] From bed05003990909dea258fe6fe1cff9136db7829f Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 12 Jan 2024 09:21:19 +0900 Subject: [PATCH 04/13] clarify instruction data discriminator --- programs/zk-token-proof/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index f231229de9c278..d69af15657f898 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -46,13 +46,14 @@ where // number of accessed accounts so far let mut accessed_accounts = 0_u16; - // if instruction data is exactly 4 bytes, then read proof from an account + // if instruction data is exactly 5 bytes, then read proof from an account let proof_data = if instruction_data.len() == PROOF_OFFSET_LENGTH { 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..PROOF_OFFSET_LENGTH] .try_into() .map_err(|_| InstructionError::InvalidInstructionData)?, From c5dae4575caeacd1db4647f9680ca0157221f933 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 12 Jan 2024 09:27:58 +0900 Subject: [PATCH 05/13] avoid cloning entire proof data --- programs/zk-token-proof/src/lib.rs | 39 +++++++++++++++++------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index d69af15657f898..911ac02f7b511c 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -47,7 +47,7 @@ where let mut accessed_accounts = 0_u16; // if instruction data is exactly 5 bytes, then read proof from an account - let proof_data = if instruction_data.len() == PROOF_OFFSET_LENGTH { + let context_data = if instruction_data.len() == PROOF_OFFSET_LENGTH { let proof_data_account = instruction_context .try_borrow_instruction_account(transaction_context, accessed_accounts)?; accessed_accounts = accessed_accounts.checked_add(1).unwrap(); @@ -64,26 +64,34 @@ where let proof_data_end = proof_data_start .checked_add(std::mem::size_of::()) .ok_or(InstructionError::InvalidInstructionData)?; - let proof_data = proof_data_account + let proof_data_raw = proof_data_account .get_data() .get(proof_data_start..proof_data_end) .ok_or(InstructionError::InvalidAccountData)?; - *bytemuck::try_from_bytes::(proof_data).map_err(|_| { + 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 { - *ProofInstruction::proof_data::(instruction_data).ok_or_else(|| { - ic_msg!(invoke_context, "invalid proof 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 - })? - }; + })?; - 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 { @@ -108,11 +116,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); From 208ebfda8c3c7f2b9f4dc6ef5bed247b2cd22fa4 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 12 Jan 2024 09:30:12 +0900 Subject: [PATCH 06/13] Update programs/zk-token-proof/src/lib.rs Co-authored-by: Jon C --- programs/zk-token-proof/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index 911ac02f7b511c..3ea30927d76518 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -32,7 +32,7 @@ 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 PROOF_OFFSET_LENGTH: usize = 5; +const INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT: usize = 5; fn process_verify_proof(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> where From dd866afd6f216c44521717732305fdd5a73c8589 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 12 Jan 2024 10:02:06 +0900 Subject: [PATCH 07/13] update `PROOF_OFFSET_LENGTH` to `INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT` --- programs/zk-token-proof/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index 3ea30927d76518..ea22a441d7db30 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -47,14 +47,14 @@ where 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() == PROOF_OFFSET_LENGTH { + let context_data = if instruction_data.len() == INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT { 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..PROOF_OFFSET_LENGTH] + instruction_data[1..INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT] .try_into() .map_err(|_| InstructionError::InvalidInstructionData)?, ); From 08d5ff67a81073e3da64889353aa0617afd9bd83 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 12 Jan 2024 10:02:14 +0900 Subject: [PATCH 08/13] update instruction docs --- .../src/zk_token_proof_instruction.rs | 219 ++++++++---------- 1 file changed, 98 insertions(+), 121 deletions(-) diff --git a/zk-token-sdk/src/zk_token_proof_instruction.rs b/zk-token-sdk/src/zk_token_proof_instruction.rs index 041647f7dae019..ef29cd95755086 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 4 bytes, then the program assumes that the first account provided with the +//! instruction contains the zero-knowledge 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, 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,20 +64,13 @@ pub enum ProofInstruction { /// /// Accounts expected by this instruction: /// - /// * TODO: Read proof from an account - /// 0. `[]` Proof account - /// 1. `[writable]` The proof context account - /// 2. `[]` The proof context account owner - /// - /// * 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: - /// `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, @@ -78,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 + /// 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: - /// `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, @@ -97,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, @@ -116,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 + /// 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: - /// `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, @@ -135,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 + /// 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: - /// `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, @@ -154,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 + /// 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: - /// `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, @@ -173,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, @@ -199,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. `BatchedRangeProof64Data` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedRangeProofU64, @@ -219,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 + /// 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: - /// `BatchedRangeProof128Data` + /// The instruction expects either: + /// i. `BatchedRangeProof128Data` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedRangeProofU128, @@ -239,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 + /// 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: - /// `BatchedRangeProof256Data` + /// The instruction expects either: + /// i. `BatchedRangeProof256Data` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedRangeProofU256, @@ -258,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 - /// - /// * 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: - /// `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, @@ -278,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. `GroupedCiphertextValidityProofContext` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyGroupedCiphertext2HandlesValidity, @@ -299,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 + /// 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: - /// `BatchedGroupedCiphertextValidityProofContext` + /// The instruction expects either: + /// i. `BatchedGroupedCiphertextValidityProofContext` if proof is provided as instruction data + /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedGroupedCiphertext2HandlesValidity, @@ -318,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 + /// 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: - /// `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, } From a8d97bbda3e6b347448dfd8fc0c8424015f0f923 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 12 Jan 2024 10:26:19 +0900 Subject: [PATCH 09/13] add feature gate --- programs/zk-token-proof/src/lib.rs | 7 +++++++ sdk/src/feature_set.rs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index ea22a441d7db30..e0db1e9797b668 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -48,6 +48,13 @@ where // 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_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(); diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 0a06cdd864eb58..a8c9596a886af4 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_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_from_account::id(), "Enable zk token proof program to read proof from accounts instead of instruction data"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() From 9b4b7e68a1d6408ad702ba4002b264e3250b4214 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Sat, 13 Jan 2024 00:03:49 +0900 Subject: [PATCH 10/13] Update sdk/src/feature_set.rs Co-authored-by: Jon C --- sdk/src/feature_set.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index a8c9596a886af4..25643d5e334562 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -949,7 +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_from_account::id(), "Enable zk token proof program to read proof from accounts instead of instruction data"), + (enable_zk_from_account::id(), "Enable zk token proof program to read proof from accounts instead of instruction data #34750"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() From 3e8f3cee702ec954f6eec7c491a9feada285aea1 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Sat, 13 Jan 2024 00:07:27 +0900 Subject: [PATCH 11/13] update feature name `enable_zk_from_account` to `enable_zk_proof_from_account` --- programs/zk-token-proof/src/lib.rs | 2 +- sdk/src/feature_set.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index e0db1e9797b668..3bf507106500f3 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -50,7 +50,7 @@ where let context_data = if instruction_data.len() == INSTRUCTION_DATA_LENGTH_WITH_PROOF_ACCOUNT { if !invoke_context .feature_set - .is_active(&feature_set::enable_zk_from_account::id()) + .is_active(&feature_set::enable_zk_proof_from_account::id()) { return Err(InstructionError::InvalidInstructionData); } diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 25643d5e334562..6c3a2bfb3b4b8f 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -760,7 +760,7 @@ pub mod deprecate_executable_meta_update_in_bpf_loader { solana_sdk::declare_id!("k6uR1J9VtKJnTukBV2Eo15BEy434MBg8bT6hHQgmU8v"); } -pub mod enable_zk_from_account { +pub mod enable_zk_proof_from_account { solana_sdk::declare_id!("zkiTNuzBKxrCLMKehzuQeKZyLtX2yvFcEKMML8nExU8"); } @@ -949,7 +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_from_account::id(), "Enable zk token proof program to read proof from accounts instead of instruction data #34750"), + (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() From 83d0440c389cefaba7d6182704bb09d65a9bf2a1 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Sat, 13 Jan 2024 00:09:27 +0900 Subject: [PATCH 12/13] Apply suggestions from code review Co-authored-by: Jon C --- zk-token-sdk/src/zk_token_proof_instruction.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/zk-token-sdk/src/zk_token_proof_instruction.rs b/zk-token-sdk/src/zk_token_proof_instruction.rs index ef29cd95755086..23236b848b4620 100644 --- a/zk-token-sdk/src/zk_token_proof_instruction.rs +++ b/zk-token-sdk/src/zk_token_proof_instruction.rs @@ -13,13 +13,13 @@ //! 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 4 bytes, then the program assumes that the first account provided with the -//! instruction contains the zero-knowledge and verifies the account data at the offset specified -//! in the instruction data. Otherwise, the program assumes that the zero-knowledge proof is +//! instruction contains the zero-knowledge proof and verifies the account data at the offset specified +//! in the instruction data as an unsigned 32-bit integer. 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, then the program writes the context data to +//! 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 @@ -195,7 +195,7 @@ pub enum ProofInstruction { /// 2. `[]` (Optional) The proof context account owner /// /// The instruction expects either: - /// i. `BatchedRangeProof64Data` if proof is provided as instruction data + /// i. `BatchedRangeProofU64Data` if proof is provided as instruction data /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedRangeProofU64, @@ -213,7 +213,7 @@ pub enum ProofInstruction { /// 2. `[]` (Optional) The proof context account owner /// /// The instruction expects either: - /// i. `BatchedRangeProof128Data` if proof is provided as instruction data + /// i. `BatchedRangeProofU128Data` if proof is provided as instruction data /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedRangeProofU128, @@ -231,7 +231,7 @@ pub enum ProofInstruction { /// 2. `[]` (Optional) The proof context account owner /// /// The instruction expects either: - /// i. `BatchedRangeProof256Data` if proof is provided as instruction data + /// i. `BatchedRangeProofU256Data` if proof is provided as instruction data /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedRangeProofU256, @@ -266,7 +266,7 @@ pub enum ProofInstruction { /// 2. `[]` (Optional) The proof context account owner /// /// The instruction expects either: - /// i. `GroupedCiphertextValidityProofContext` if proof is provided as instruction data + /// i. `GroupedCiphertext2HandlesValidityProofData` if proof is provided as instruction data /// ii. `u32` byte offset if proof is provided as an account /// VerifyGroupedCiphertext2HandlesValidity, @@ -285,7 +285,7 @@ pub enum ProofInstruction { /// 2. `[]` (Optional) The proof context account owner /// /// The instruction expects either: - /// i. `BatchedGroupedCiphertextValidityProofContext` if proof is provided as instruction data + /// i. `BatchedGroupedCiphertext2HandlesValidityProofData` if proof is provided as instruction data /// ii. `u32` byte offset if proof is provided as an account /// VerifyBatchedGroupedCiphertext2HandlesValidity, From 550f7e640e7403d87ea5602065375de37202cd3e Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Sat, 13 Jan 2024 00:17:55 +0900 Subject: [PATCH 13/13] clarify the instruction data length more precisely --- zk-token-sdk/src/zk_token_proof_instruction.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zk-token-sdk/src/zk_token_proof_instruction.rs b/zk-token-sdk/src/zk_token_proof_instruction.rs index 23236b848b4620..eea771bfbf63a0 100644 --- a/zk-token-sdk/src/zk_token_proof_instruction.rs +++ b/zk-token-sdk/src/zk_token_proof_instruction.rs @@ -12,10 +12,10 @@ //! 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 4 bytes, 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 as an unsigned 32-bit integer. Otherwise, the program assumes that the zero-knowledge proof is -//! provided as part of 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