Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 126 additions & 1 deletion programs/zk-token-proof-tests/tests/process_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use {
solana_zk_token_sdk::{
encryption::{
elgamal::ElGamalKeypair,
grouped_elgamal::GroupedElGamal,
pedersen::{Pedersen, PedersenOpening},
},
instruction::*,
Expand All @@ -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,
Expand All @@ -33,6 +34,8 @@ const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 11] = [
ProofInstruction::VerifyBatchedRangeProofU128,
ProofInstruction::VerifyBatchedRangeProofU256,
ProofInstruction::VerifyCiphertextCommitmentEquality,
ProofInstruction::VerifyGroupedCiphertext2HandlesValidity,
ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity,
];

#[tokio::test]
Expand Down Expand Up @@ -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,
Expand Down
23 changes: 23 additions & 0 deletions programs/zk-token-proof/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
});
206 changes: 206 additions & 0 deletions zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading