Skip to content

Commit

Permalink
[zk-token-sdk] Add VerifyGroupedCiphertext2HandlesValidity and `Ver…
Browse files Browse the repository at this point in the history
…ifyBatchedGroupedCiphertext2HandlesValidity` 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 <[email protected]>

---------

Co-authored-by: Tyera <[email protected]>
(cherry picked from commit 0495051)
  • Loading branch information
samkim-crypto authored and mergify[bot] committed Jun 1, 2023
1 parent 05e6267 commit 53ad316
Show file tree
Hide file tree
Showing 7 changed files with 598 additions and 1 deletion.
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

0 comments on commit 53ad316

Please sign in to comment.