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

v1.14: [zk-token-sdk] rename and restructure CloseAccount and WithdrawWithheld proof instructions (backport of #31608) #31704

Merged
merged 2 commits into from
May 19, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
62 changes: 37 additions & 25 deletions programs/zk-token-proof-tests/tests/process_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,69 +9,79 @@ use {
transaction::{Transaction, TransactionError},
},
solana_zk_token_sdk::{
encryption::elgamal::ElGamalKeypair, instruction::*, zk_token_proof_instruction::*,
zk_token_proof_program, zk_token_proof_state::ProofContextState,
encryption::{elgamal::ElGamalKeypair, pedersen::PedersenOpening},
instruction::*,
zk_token_proof_instruction::*,
zk_token_proof_program,
zk_token_proof_state::ProofContextState,
},
std::mem::size_of,
};

const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 6] = [
ProofInstruction::VerifyCloseAccount,
ProofInstruction::VerifyZeroBalance,
ProofInstruction::VerifyWithdraw,
ProofInstruction::VerifyWithdrawWithheldTokens,
ProofInstruction::VerifyCiphertextCiphertextEquality,
ProofInstruction::VerifyTransfer,
ProofInstruction::VerifyTransferWithFee,
ProofInstruction::VerifyPubkeyValidity,
];

#[tokio::test]
async fn test_close_account() {
async fn test_zero_balance() {
let elgamal_keypair = ElGamalKeypair::new_rand();

let zero_ciphertext = elgamal_keypair.public.encrypt(0_u64);
let success_proof_data = CloseAccountData::new(&elgamal_keypair, &zero_ciphertext).unwrap();
let success_proof_data = ZeroBalanceProofData::new(&elgamal_keypair, &zero_ciphertext).unwrap();

let incorrect_keypair = ElGamalKeypair {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = CloseAccountData::new(&incorrect_keypair, &zero_ciphertext).unwrap();
let fail_proof_data = ZeroBalanceProofData::new(&incorrect_keypair, &zero_ciphertext).unwrap();

test_verify_proof_without_context(
ProofInstruction::VerifyCloseAccount,
ProofInstruction::VerifyZeroBalance,
&success_proof_data,
&fail_proof_data,
)
.await;

test_verify_proof_with_context(
ProofInstruction::VerifyCloseAccount,
size_of::<ProofContextState<CloseAccountProofContext>>(),
ProofInstruction::VerifyZeroBalance,
size_of::<ProofContextState<ZeroBalanceProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;

test_close_context_state(
ProofInstruction::VerifyCloseAccount,
size_of::<ProofContextState<CloseAccountProofContext>>(),
ProofInstruction::VerifyZeroBalance,
size_of::<ProofContextState<ZeroBalanceProofContext>>(),
&success_proof_data,
)
.await;
}

#[tokio::test]
async fn test_withdraw_withheld_tokens() {
let elgamal_keypair = ElGamalKeypair::new_rand();
async fn test_ciphertext_ciphertext_equality() {
let source_keypair = ElGamalKeypair::new_rand();
let destination_keypair = ElGamalKeypair::new_rand();

let amount: u64 = 0;
let withdraw_withheld_authority_ciphertext = elgamal_keypair.public.encrypt(amount);
let source_ciphertext = source_keypair.public.encrypt(amount);

let success_proof_data = WithdrawWithheldTokensData::new(
&elgamal_keypair,
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(amount, &destination_opening);

let success_proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
&destination_keypair.public,
&withdraw_withheld_authority_ciphertext,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();
Expand All @@ -80,32 +90,34 @@ async fn test_withdraw_withheld_tokens() {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = WithdrawWithheldTokensData::new(
let fail_proof_data = CiphertextCiphertextEqualityProofData::new(
&incorrect_keypair,
&destination_keypair.public,
&withdraw_withheld_authority_ciphertext,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();

test_verify_proof_without_context(
ProofInstruction::VerifyWithdrawWithheldTokens,
ProofInstruction::VerifyCiphertextCiphertextEquality,
&success_proof_data,
&fail_proof_data,
)
.await;

test_verify_proof_with_context(
ProofInstruction::VerifyWithdrawWithheldTokens,
size_of::<ProofContextState<WithdrawWithheldTokensProofContext>>(),
ProofInstruction::VerifyCiphertextCiphertextEquality,
size_of::<ProofContextState<CiphertextCiphertextEqualityProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;

test_close_context_state(
ProofInstruction::VerifyWithdrawWithheldTokens,
size_of::<ProofContextState<WithdrawWithheldTokensProofContext>>(),
ProofInstruction::VerifyCiphertextCiphertextEquality,
size_of::<ProofContextState<CiphertextCiphertextEqualityProofContext>>(),
&success_proof_data,
)
.await;
Expand Down
13 changes: 7 additions & 6 deletions programs/zk-token-proof/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,20 @@ pub fn process_instruction(
ic_msg!(invoke_context, "CloseContextState");
process_close_proof_context(invoke_context)
}
ProofInstruction::VerifyCloseAccount => {
ProofInstruction::VerifyZeroBalance => {
ic_msg!(invoke_context, "VerifyCloseAccount");
process_verify_proof::<CloseAccountData, CloseAccountProofContext>(invoke_context)
process_verify_proof::<ZeroBalanceProofData, ZeroBalanceProofContext>(invoke_context)
}
ProofInstruction::VerifyWithdraw => {
ic_msg!(invoke_context, "VerifyWithdraw");
process_verify_proof::<WithdrawData, WithdrawProofContext>(invoke_context)
}
ProofInstruction::VerifyWithdrawWithheldTokens => {
ProofInstruction::VerifyCiphertextCiphertextEquality => {
ic_msg!(invoke_context, "VerifyWithdrawWithheldTokens");
process_verify_proof::<WithdrawWithheldTokensData, WithdrawWithheldTokensProofContext>(
invoke_context,
)
process_verify_proof::<
CiphertextCiphertextEqualityProofData,
CiphertextCiphertextEqualityProofContext,
>(invoke_context)
}
ProofInstruction::VerifyTransfer => {
ic_msg!(invoke_context, "VerifyTransfer");
Expand Down
228 changes: 228 additions & 0 deletions zk-token-sdk/src/instruction/ctxt_ctxt_equality.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
//! The ciphertext-ciphertext equality proof instruction.
//!
//! A ciphertext-ciphertext equality proof is defined with respect to two twisted ElGamal
//! ciphertexts. The proof certifies that the two ciphertexts encrypt the same message. To generate
//! the proof, a prover must provide the decryption key for the first ciphertext and the randomness
//! used to generate the second ciphertext.
//!
//! The first ciphertext associated with the proof is referred to as the "source" ciphertext. The
//! second ciphertext associated with the proof is referred to as the "destination" ciphertext.

#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::PedersenOpening,
},
errors::ProofError,
sigma_proofs::ctxt_ctxt_equality_proof::CiphertextCiphertextEqualityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};

/// The instruction data that is needed for the
/// `ProofInstruction::VerifyCiphertextCiphertextEquality` 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 CiphertextCiphertextEqualityProofData {
pub context: CiphertextCiphertextEqualityProofContext,

pub proof: pod::CiphertextCiphertextEqualityProof,
}

/// The context data needed to verify a ciphertext-ciphertext equality proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CiphertextCiphertextEqualityProofContext {
pub source_pubkey: pod::ElGamalPubkey, // 32 bytes

pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes

pub source_ciphertext: pod::ElGamalCiphertext, // 64 bytes

pub destination_ciphertext: pod::ElGamalCiphertext, // 64 bytes
}

#[cfg(not(target_os = "solana"))]
impl CiphertextCiphertextEqualityProofData {
pub fn new(
source_keypair: &ElGamalKeypair,
destination_pubkey: &ElGamalPubkey,
source_ciphertext: &ElGamalCiphertext,
destination_ciphertext: &ElGamalCiphertext,
destination_opening: &PedersenOpening,
amount: u64,
) -> Result<Self, ProofError> {
let pod_source_pubkey = pod::ElGamalPubkey(source_keypair.public.to_bytes());
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes());
let pod_source_ciphertext = pod::ElGamalCiphertext(source_ciphertext.to_bytes());
let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes());

let context = CiphertextCiphertextEqualityProofContext {
source_pubkey: pod_source_pubkey,
destination_pubkey: pod_destination_pubkey,
source_ciphertext: pod_source_ciphertext,
destination_ciphertext: pod_destination_ciphertext,
};

let mut transcript = CiphertextCiphertextEqualityProof::transcript_new(
&pod_source_pubkey,
&pod_destination_pubkey,
&pod_source_ciphertext,
&pod_destination_ciphertext,
);

let proof = CiphertextCiphertextEqualityProof::new(
source_keypair,
destination_pubkey,
source_ciphertext,
destination_opening,
amount,
&mut transcript,
)
.into();

Ok(Self { context, proof })
}
}

impl ZkProofData<CiphertextCiphertextEqualityProofContext>
for CiphertextCiphertextEqualityProofData
{
const PROOF_TYPE: ProofType = ProofType::CiphertextCiphertextEquality;

fn context_data(&self) -> &CiphertextCiphertextEqualityProofContext {
&self.context
}

#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = CiphertextCiphertextEqualityProof::transcript_new(
&self.context.source_pubkey,
&self.context.destination_pubkey,
&self.context.source_ciphertext,
&self.context.destination_ciphertext,
);

let source_pubkey = self.context.source_pubkey.try_into()?;
let destination_pubkey = self.context.destination_pubkey.try_into()?;
let source_ciphertext = self.context.source_ciphertext.try_into()?;
let destination_ciphertext = self.context.destination_ciphertext.try_into()?;
let proof: CiphertextCiphertextEqualityProof = self.proof.try_into()?;

proof
.verify(
&source_pubkey,
&destination_pubkey,
&source_ciphertext,
&destination_ciphertext,
&mut transcript,
)
.map_err(|e| e.into())
}
}

#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl CiphertextCiphertextEqualityProof {
fn transcript_new(
source_pubkey: &pod::ElGamalPubkey,
destination_pubkey: &pod::ElGamalPubkey,
source_ciphertext: &pod::ElGamalCiphertext,
destination_ciphertext: &pod::ElGamalCiphertext,
) -> Transcript {
let mut transcript = Transcript::new(b"CiphertextCiphertextEqualityProof");

transcript.append_pubkey(b"pubkey-source", source_pubkey);
transcript.append_pubkey(b"pubkey-dest", destination_pubkey);

transcript.append_ciphertext(b"ciphertext-source", source_ciphertext);
transcript.append_ciphertext(b"ciphertext-dest", destination_ciphertext);

transcript
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_ciphertext_ciphertext_instruction_correctness() {
let source_keypair = ElGamalKeypair::new_rand();
let destination_keypair = ElGamalKeypair::new_rand();

let amount: u64 = 0;
let source_ciphertext = source_keypair.public.encrypt(amount);

let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(amount, &destination_opening);

let proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();

assert!(proof_data.verify_proof().is_ok());

let amount: u64 = 55;
let source_ciphertext = source_keypair.public.encrypt(amount);

let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(amount, &destination_opening);

let proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();

assert!(proof_data.verify_proof().is_ok());

let amount = u64::max_value();
let source_ciphertext = source_keypair.public.encrypt(amount);

let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(amount, &destination_opening);

let proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();

assert!(proof_data.verify_proof().is_ok());
}
}