[zk-token-sdk] Add `VerifyGroupedCiphertext2HandlesValidity` and `VerifyBatchedGroupedCiphertext2HandlesValidity` proof instructions (#31816)

* add grouped ciphertext validity proof data

* add batched grouped ciphertext validity proof data

* rename proof contexts and data for consistency

* add grouped ciphertext validity proof instructions

* Update zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity.rs

Co-authored-by: Tyera <teulberg@gmail.com>

---------

Co-authored-by: Tyera <teulberg@gmail.com>
This commit is contained in:
samkim-crypto 2023-06-01 15:58:36 +09:00 committed by GitHub
parent 405db3e436
commit 0495051a67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 598 additions and 1 deletions

View File

@ -11,6 +11,7 @@ use {
solana_zk_token_sdk::{
encryption::{
elgamal::ElGamalKeypair,
grouped_elgamal::GroupedElGamal,
pedersen::{Pedersen, PedersenOpening},
},
instruction::*,
@ -21,7 +22,7 @@ use {
std::mem::size_of,
};
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 11] = [
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 13] = [
ProofInstruction::VerifyZeroBalance,
ProofInstruction::VerifyWithdraw,
ProofInstruction::VerifyCiphertextCiphertextEquality,
@ -33,6 +34,8 @@ const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 11] = [
ProofInstruction::VerifyBatchedRangeProofU128,
ProofInstruction::VerifyBatchedRangeProofU256,
ProofInstruction::VerifyCiphertextCommitmentEquality,
ProofInstruction::VerifyGroupedCiphertext2HandlesValidity,
ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity,
];
#[tokio::test]
@ -572,6 +575,128 @@ async fn test_ciphertext_commitment_equality() {
.await;
}
#[tokio::test]
async fn test_grouped_ciphertext_2_handles_validity() {
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let amount: u64 = 55;
let opening = PedersenOpening::new_rand();
let grouped_ciphertext =
GroupedElGamal::encrypt_with([&destination_pubkey, &auditor_pubkey], amount, &opening);
let success_proof_data = GroupedCiphertext2HandlesValidityProofData::new(
&destination_pubkey,
&auditor_pubkey,
&grouped_ciphertext,
amount,
&opening,
)
.unwrap();
let incorrect_opening = PedersenOpening::new_rand();
let fail_proof_data = GroupedCiphertext2HandlesValidityProofData::new(
&destination_pubkey,
&auditor_pubkey,
&grouped_ciphertext,
amount,
&incorrect_opening,
)
.unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyGroupedCiphertext2HandlesValidity,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyGroupedCiphertext2HandlesValidity,
size_of::<ProofContextState<GroupedCiphertext2HandlesValidityProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyGroupedCiphertext2HandlesValidity,
size_of::<ProofContextState<GroupedCiphertext2HandlesValidityProofContext>>(),
&success_proof_data,
)
.await;
}
#[tokio::test]
async fn test_batched_grouped_ciphertext_2_handles_validity() {
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let amount_lo: u64 = 55;
let amount_hi: u64 = 22;
let opening_lo = PedersenOpening::new_rand();
let opening_hi = PedersenOpening::new_rand();
let grouped_ciphertext_lo = GroupedElGamal::encrypt_with(
[&destination_pubkey, &auditor_pubkey],
amount_lo,
&opening_lo,
);
let grouped_ciphertext_hi = GroupedElGamal::encrypt_with(
[&destination_pubkey, &auditor_pubkey],
amount_hi,
&opening_hi,
);
let success_proof_data = BatchedGroupedCiphertext2HandlesValidityProofData::new(
&destination_pubkey,
&auditor_pubkey,
&grouped_ciphertext_lo,
&grouped_ciphertext_hi,
amount_lo,
amount_hi,
&opening_lo,
&opening_hi,
)
.unwrap();
let incorrect_opening = PedersenOpening::new_rand();
let fail_proof_data = BatchedGroupedCiphertext2HandlesValidityProofData::new(
&destination_pubkey,
&auditor_pubkey,
&grouped_ciphertext_lo,
&grouped_ciphertext_hi,
amount_lo,
amount_hi,
&incorrect_opening,
&opening_hi,
)
.unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity,
size_of::<ProofContextState<BatchedGroupedCiphertext2HandlesValidityProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity,
size_of::<ProofContextState<BatchedGroupedCiphertext2HandlesValidityProofContext>>(),
&success_proof_data,
)
.await;
}
async fn test_verify_proof_without_context<T, U>(
proof_instruction: ProofInstruction,
success_proof_data: &T,

View File

@ -244,5 +244,28 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| {
CiphertextCommitmentEqualityProofContext,
>(invoke_context)
}
ProofInstruction::VerifyGroupedCiphertext2HandlesValidity => {
invoke_context
.consume_checked(6_440)
.map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
ic_msg!(invoke_context, "VerifyGroupedCiphertext2HandlesValidity");
process_verify_proof::<
GroupedCiphertext2HandlesValidityProofData,
GroupedCiphertext2HandlesValidityProofContext,
>(invoke_context)
}
ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity => {
invoke_context
.consume_checked(12_575)
.map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
ic_msg!(
invoke_context,
"VerifyBatchedGroupedCiphertext2HandlesValidity"
);
process_verify_proof::<
BatchedGroupedCiphertext2HandlesValidityProofData,
BatchedGroupedCiphertext2HandlesValidityProofContext,
>(invoke_context)
}
}
});

View File

@ -0,0 +1,206 @@
//! The batched grouped-ciphertext validity proof instruction.
//!
//! A batched grouped-ciphertext validity proof certifies the validity of two grouped ElGamal
//! ciphertext that are encrypted using the same set of ElGamal public keys. A batched
//! grouped-ciphertext validity proof is shorter and more efficient than two individual
//! grouped-ciphertext validity proofs.
//!
//! Currently, the batched grouped-ciphertext validity proof is restricted to ciphertexts with two
//! handles. In accordance with the SPL Token program application, the first decryption handle
//! associated with the proof is referred to as the "destination" handle and the second decryption
//! handle is referred to as the "auditor" handle. Furthermore, the first grouped ciphertext is
//! referred to as the "lo" ciphertext and the second grouped ciphertext is referred to as the "hi"
//! ciphertext.
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::{
elgamal::ElGamalPubkey, grouped_elgamal::GroupedElGamalCiphertext,
pedersen::PedersenOpening,
},
errors::ProofError,
sigma_proofs::validity_proof::AggregatedValidityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// The instruction data that is needed for the
/// `ProofInstruction::VerifyBatchedGroupedCiphertextValidity` instruction.
///
/// It includes the cryptographic proof as well as the context data information needed to verify
/// the proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct BatchedGroupedCiphertext2HandlesValidityProofData {
pub context: BatchedGroupedCiphertext2HandlesValidityProofContext,
pub proof: pod::AggregatedValidityProof,
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct BatchedGroupedCiphertext2HandlesValidityProofContext {
pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
pub auditor_pubkey: pod::ElGamalPubkey, // 32 bytes
pub grouped_ciphertext_lo: pod::GroupedElGamalCiphertext2Handles, // 96 bytes
pub grouped_ciphertext_hi: pod::GroupedElGamalCiphertext2Handles, // 96 bytes
}
#[cfg(not(target_os = "solana"))]
impl BatchedGroupedCiphertext2HandlesValidityProofData {
pub fn new(
destination_pubkey: &ElGamalPubkey,
auditor_pubkey: &ElGamalPubkey,
grouped_ciphertext_lo: &GroupedElGamalCiphertext<2>,
grouped_ciphertext_hi: &GroupedElGamalCiphertext<2>,
amount_lo: u64,
amount_hi: u64,
opening_lo: &PedersenOpening,
opening_hi: &PedersenOpening,
) -> Result<Self, ProofError> {
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes());
let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.to_bytes());
let pod_grouped_ciphertext_lo = (*grouped_ciphertext_lo).into();
let pod_grouped_ciphertext_hi = (*grouped_ciphertext_hi).into();
let context = BatchedGroupedCiphertext2HandlesValidityProofContext {
destination_pubkey: pod_destination_pubkey,
auditor_pubkey: pod_auditor_pubkey,
grouped_ciphertext_lo: pod_grouped_ciphertext_lo,
grouped_ciphertext_hi: pod_grouped_ciphertext_hi,
};
let mut transcript = context.new_transcript();
let proof = AggregatedValidityProof::new(
(destination_pubkey, auditor_pubkey),
(amount_lo, amount_hi),
(opening_lo, opening_hi),
&mut transcript,
)
.into();
Ok(Self { context, proof })
}
}
impl ZkProofData<BatchedGroupedCiphertext2HandlesValidityProofContext>
for BatchedGroupedCiphertext2HandlesValidityProofData
{
const PROOF_TYPE: ProofType = ProofType::BatchedGroupedCiphertext2HandlesValidity;
fn context_data(&self) -> &BatchedGroupedCiphertext2HandlesValidityProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = self.context.new_transcript();
let destination_pubkey = self.context.destination_pubkey.try_into()?;
let auditor_pubkey = self.context.auditor_pubkey.try_into()?;
let grouped_ciphertext_lo: GroupedElGamalCiphertext<2> =
self.context.grouped_ciphertext_lo.try_into()?;
let grouped_ciphertext_hi: GroupedElGamalCiphertext<2> =
self.context.grouped_ciphertext_hi.try_into()?;
let destination_handle_lo = grouped_ciphertext_lo.handles.get(0).unwrap();
let auditor_handle_lo = grouped_ciphertext_lo.handles.get(1).unwrap();
let destination_handle_hi = grouped_ciphertext_hi.handles.get(0).unwrap();
let auditor_handle_hi = grouped_ciphertext_hi.handles.get(1).unwrap();
let proof: AggregatedValidityProof = self.proof.try_into()?;
proof
.verify(
(&destination_pubkey, &auditor_pubkey),
(
&grouped_ciphertext_lo.commitment,
&grouped_ciphertext_hi.commitment,
),
(destination_handle_lo, destination_handle_hi),
(auditor_handle_lo, auditor_handle_hi),
&mut transcript,
)
.map_err(|e| e.into())
}
}
#[cfg(not(target_os = "solana"))]
impl BatchedGroupedCiphertext2HandlesValidityProofContext {
fn new_transcript(&self) -> Transcript {
let mut transcript = Transcript::new(b"BatchedGroupedCiphertextValidityProof");
transcript.append_pubkey(b"destination-pubkey", &self.destination_pubkey);
transcript.append_pubkey(b"auditor-pubkey", &self.auditor_pubkey);
transcript.append_grouped_ciphertext_2_handles(
b"grouped-ciphertext-lo",
&self.grouped_ciphertext_lo,
);
transcript.append_grouped_ciphertext_2_handles(
b"grouped-ciphertext-hi",
&self.grouped_ciphertext_hi,
);
transcript
}
}
#[cfg(test)]
mod test {
use {
super::*,
crate::encryption::{elgamal::ElGamalKeypair, grouped_elgamal::GroupedElGamal},
};
#[test]
fn test_ciphertext_validity_proof_instruction_correctness() {
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let amount_lo: u64 = 11;
let amount_hi: u64 = 22;
let opening_lo = PedersenOpening::new_rand();
let opening_hi = PedersenOpening::new_rand();
let grouped_ciphertext_lo = GroupedElGamal::encrypt_with(
[&destination_pubkey, &auditor_pubkey],
amount_lo,
&opening_lo,
);
let grouped_ciphertext_hi = GroupedElGamal::encrypt_with(
[&destination_pubkey, &auditor_pubkey],
amount_hi,
&opening_hi,
);
let proof_data = BatchedGroupedCiphertext2HandlesValidityProofData::new(
&destination_pubkey,
&auditor_pubkey,
&grouped_ciphertext_lo,
&grouped_ciphertext_hi,
amount_lo,
amount_hi,
&opening_lo,
&opening_hi,
)
.unwrap();
assert!(proof_data.verify_proof().is_ok());
}
}

View File

@ -0,0 +1,166 @@
//! The grouped-ciphertext validity proof instruction.
//!
//! A grouped-ciphertext validity proof certifies that a grouped ElGamal ciphertext is
//! well-defined, i.e. the ciphertext can be decrypted by private keys associated with its
//! decryption handles. To generate the proof, a prover must provide the Pedersen opening
//! associated with the grouped ciphertext's commitment.
//!
//! Currently, the grouped-ciphertext validity proof is restricted to ciphertexts with two handles.
//! In accordance with the SPL Token program application, the first decryption handle associated
//! with the proof is referred to as the "destination" handle and the second decryption handle is
//! referred to as the "auditor" handle.
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::{
elgamal::ElGamalPubkey, grouped_elgamal::GroupedElGamalCiphertext,
pedersen::PedersenOpening,
},
errors::ProofError,
sigma_proofs::validity_proof::ValidityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// The instruction data that is needed for the `ProofInstruction::VerifyGroupedCiphertextValidity`
/// instruction.
///
/// It includes the cryptographic proof as well as the context data information needed to verify
/// the proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct GroupedCiphertext2HandlesValidityProofData {
pub context: GroupedCiphertext2HandlesValidityProofContext,
pub proof: pod::ValidityProof,
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct GroupedCiphertext2HandlesValidityProofContext {
pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
pub auditor_pubkey: pod::ElGamalPubkey, // 32 bytes
pub grouped_ciphertext: pod::GroupedElGamalCiphertext2Handles, // 96 bytes
}
#[cfg(not(target_os = "solana"))]
impl GroupedCiphertext2HandlesValidityProofData {
pub fn new(
destination_pubkey: &ElGamalPubkey,
auditor_pubkey: &ElGamalPubkey,
grouped_ciphertext: &GroupedElGamalCiphertext<2>,
amount: u64,
opening: &PedersenOpening,
) -> Result<Self, ProofError> {
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes());
let pod_auditor_pubkey = pod::ElGamalPubkey(auditor_pubkey.to_bytes());
let pod_grouped_ciphertext = (*grouped_ciphertext).into();
let context = GroupedCiphertext2HandlesValidityProofContext {
destination_pubkey: pod_destination_pubkey,
auditor_pubkey: pod_auditor_pubkey,
grouped_ciphertext: pod_grouped_ciphertext,
};
let mut transcript = context.new_transcript();
let proof = ValidityProof::new(
(destination_pubkey, auditor_pubkey),
amount,
opening,
&mut transcript,
)
.into();
Ok(Self { context, proof })
}
}
impl ZkProofData<GroupedCiphertext2HandlesValidityProofContext>
for GroupedCiphertext2HandlesValidityProofData
{
const PROOF_TYPE: ProofType = ProofType::GroupedCiphertext2HandlesValidity;
fn context_data(&self) -> &GroupedCiphertext2HandlesValidityProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = self.context.new_transcript();
let destination_pubkey = self.context.destination_pubkey.try_into()?;
let auditor_pubkey = self.context.auditor_pubkey.try_into()?;
let grouped_ciphertext: GroupedElGamalCiphertext<2> =
self.context.grouped_ciphertext.try_into()?;
let destination_handle = grouped_ciphertext.handles.get(0).unwrap();
let auditor_handle = grouped_ciphertext.handles.get(1).unwrap();
let proof: ValidityProof = self.proof.try_into()?;
proof
.verify(
&grouped_ciphertext.commitment,
(&destination_pubkey, &auditor_pubkey),
(destination_handle, auditor_handle),
&mut transcript,
)
.map_err(|e| e.into())
}
}
#[cfg(not(target_os = "solana"))]
impl GroupedCiphertext2HandlesValidityProofContext {
fn new_transcript(&self) -> Transcript {
let mut transcript = Transcript::new(b"CiphertextValidityProof");
transcript.append_pubkey(b"destination-pubkey", &self.destination_pubkey);
transcript.append_pubkey(b"auditor-pubkey", &self.auditor_pubkey);
transcript
.append_grouped_ciphertext_2_handles(b"grouped-ciphertext", &self.grouped_ciphertext);
transcript
}
}
#[cfg(test)]
mod test {
use {
super::*,
crate::encryption::{elgamal::ElGamalKeypair, grouped_elgamal::GroupedElGamal},
};
#[test]
fn test_ciphertext_validity_proof_instruction_correctness() {
let destination_pubkey = ElGamalKeypair::new_rand().public;
let auditor_pubkey = ElGamalKeypair::new_rand().public;
let amount: u64 = 55;
let opening = PedersenOpening::new_rand();
let grouped_ciphertext =
GroupedElGamal::encrypt_with([&destination_pubkey, &auditor_pubkey], amount, &opening);
let proof_data = GroupedCiphertext2HandlesValidityProofData::new(
&destination_pubkey,
&auditor_pubkey,
&grouped_ciphertext,
amount,
&opening,
)
.unwrap();
assert!(proof_data.verify_proof().is_ok());
}
}

View File

@ -1,6 +1,8 @@
pub mod batched_grouped_ciphertext_validity;
pub mod batched_range_proof;
pub mod ciphertext_ciphertext_equality;
pub mod ciphertext_commitment_equality;
pub mod grouped_ciphertext_validity;
pub mod pubkey_validity;
pub mod range_proof;
pub mod transfer;
@ -11,6 +13,10 @@ pub mod zero_balance;
use crate::errors::ProofError;
use num_derive::{FromPrimitive, ToPrimitive};
pub use {
batched_grouped_ciphertext_validity::{
BatchedGroupedCiphertext2HandlesValidityProofContext,
BatchedGroupedCiphertext2HandlesValidityProofData,
},
batched_range_proof::{
batched_range_proof_u128::BatchedRangeProofU128Data,
batched_range_proof_u256::BatchedRangeProofU256Data,
@ -23,6 +29,9 @@ pub use {
ciphertext_commitment_equality::{
CiphertextCommitmentEqualityProofContext, CiphertextCommitmentEqualityProofData,
},
grouped_ciphertext_validity::{
GroupedCiphertext2HandlesValidityProofContext, GroupedCiphertext2HandlesValidityProofData,
},
pubkey_validity::{PubkeyValidityData, PubkeyValidityProofContext},
range_proof::{RangeProofContext, RangeProofU64Data},
transfer::{
@ -49,6 +58,8 @@ pub enum ProofType {
BatchedRangeProofU128,
BatchedRangeProofU256,
CiphertextCommitmentEquality,
GroupedCiphertext2HandlesValidity,
BatchedGroupedCiphertext2HandlesValidity,
}
pub trait ZkProofData<T: Pod> {

View File

@ -37,6 +37,13 @@ pub trait TranscriptProtocol {
/// Append an ElGamal ciphertext with the given `label`.
fn append_ciphertext(&mut self, label: &'static [u8], point: &pod::ElGamalCiphertext);
/// Append a grouped ElGamal ciphertext with the given `label`.
fn append_grouped_ciphertext_2_handles(
&mut self,
label: &'static [u8],
point: &pod::GroupedElGamalCiphertext2Handles,
);
/// Append a Pedersen commitment with the given `label`.
fn append_commitment(&mut self, label: &'static [u8], point: &pod::PedersenCommitment);
@ -137,6 +144,14 @@ impl TranscriptProtocol for Transcript {
self.append_message(label, &ciphertext.0);
}
fn append_grouped_ciphertext_2_handles(
&mut self,
label: &'static [u8],
grouped_ciphertext: &pod::GroupedElGamalCiphertext2Handles,
) {
self.append_message(label, &grouped_ciphertext.0);
}
fn append_commitment(&mut self, label: &'static [u8], commitment: &pod::PedersenCommitment) {
self.append_message(label, &commitment.0);
}

View File

@ -276,6 +276,57 @@ pub enum ProofInstruction {
/// `CiphertextCommitmentEqualityProofData`
///
VerifyCiphertextCommitmentEquality,
/// Verify a grouped-ciphertext validity proof.
///
/// A grouped-ciphertext validity proof certifies that a grouped ElGamal ciphertext is
/// well-defined, i.e. the ciphertext can be decrypted by private keys associated with its
/// decryption handles.
///
/// This instruction can be configured to optionally initialize a proof context state account.
/// If creating a context state account, an account must be pre-allocated to the exact size of
/// `ProofContextState<GroupedCiphertextValidityProofContext>` and assigned to the ZkToken
/// proof program prior to the execution of this instruction.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
/// `GroupedCiphertextValidityProofContext`
///
VerifyGroupedCiphertext2HandlesValidity,
/// Verify a batched grouped-ciphertext validity proof.
///
/// A batched grouped-ciphertext validity proof certifies the validity of two grouped ElGamal
/// ciphertext that are encrypted using the same set of ElGamal public keys. A batched
/// grouped-ciphertext validity proof is shorter and more efficient than two individual
/// grouped-ciphertext validity proofs.
///
/// This instruction can be configured to optionally initialize a proof context state account.
/// If creating a context state account, an account must be pre-allocated to the exact size of
/// `ProofContextState<BatchedGroupedCiphertextValidityProofContext>` and assigned to the
/// ZkToken proof program prior to the execution of this instruction.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
/// `BatchedGroupedCiphertextValidityProofContext`
///
VerifyBatchedGroupedCiphertext2HandlesValidity,
}
/// Pubkeys associated with a context state account to be used as parameters to functions.