From 30e12aef9a00825ed8f27a66508ac771e026bdc7 Mon Sep 17 00:00:00 2001 From: Sam Kim Date: Wed, 8 Dec 2021 12:06:18 -0500 Subject: [PATCH] Update withdraw instruction to use equality proof --- zk-token-sdk/src/equality_proof/mod.rs | 16 +-- zk-token-sdk/src/instruction/withdraw.rs | 125 +++++++++++++---------- 2 files changed, 75 insertions(+), 66 deletions(-) diff --git a/zk-token-sdk/src/equality_proof/mod.rs b/zk-token-sdk/src/equality_proof/mod.rs index 563bdfc88..f0f375c51 100644 --- a/zk-token-sdk/src/equality_proof/mod.rs +++ b/zk-token-sdk/src/equality_proof/mod.rs @@ -6,12 +6,10 @@ use { }, curve25519_dalek::traits::MultiscalarMul, rand::rngs::OsRng, - subtle::{Choice, ConditionallySelectable}, }; use { crate::{errors::ProofError, transcript::TranscriptProtocol}, arrayref::{array_ref, array_refs}, - core::iter, curve25519_dalek::{ ristretto::{CompressedRistretto, RistrettoPoint}, scalar::Scalar, @@ -37,7 +35,6 @@ impl EqualityProof { pub fn new( elgamal_keypair: &ElGamalKeypair, ciphertext: &ElGamalCiphertext, - commitment: &PedersenCommitment, message: u64, opening: &PedersenOpening, transcript: &mut Transcript, @@ -47,11 +44,8 @@ impl EqualityProof { let H = PedersenBase::default().H; let P_EG = elgamal_keypair.public.get_point(); - let C_EG = ciphertext.message_comm.get_point(); let D_EG = ciphertext.decrypt_handle.get_point(); - let C_Ped = commitment.get_point(); - let s = elgamal_keypair.secret.get_scalar(); let x = Scalar::from(message); let r = opening.get_scalar(); @@ -67,7 +61,6 @@ impl EqualityProof { // record public key, ciphertext, and commitment in transcript and generate challenge // scalar - transcript.append_point(b"Y_0", &Y_0); transcript.append_point(b"Y_1", &Y_1); transcript.append_point(b"Y_2", &Y_2); @@ -107,6 +100,7 @@ impl EqualityProof { let C_Ped = commitment.get_point(); + // include Y_0, Y_1, Y_2 to transcript and extract challenges transcript.validate_and_append_point(b"Y_0", &self.Y_0)?; transcript.validate_and_append_point(b"Y_1", &self.Y_1)?; transcript.validate_and_append_point(b"Y_2", &self.Y_2)?; @@ -119,7 +113,8 @@ impl EqualityProof { let w = transcript.challenge_scalar(b"w"); let ww = w * w; - let check = RistrettoPoint::multiscalar_mul( + // check that the required algebraic condition holds + let check = RistrettoPoint::vartime_multiscalar_mul( vec![ self.z_s, -c, @@ -143,7 +138,7 @@ impl EqualityProof { } } - pub fn to_bytes(self) -> [u8; 192] { + pub fn to_bytes(&self) -> [u8; 192] { let mut buf = [0_u8; 192]; buf[..32].copy_from_slice(self.Y_0.as_bytes()); buf[32..64].copy_from_slice(self.Y_1.as_bytes()); @@ -197,7 +192,6 @@ mod test { let proof = EqualityProof::new( &elgamal_keypair, &ciphertext, - &commitment, message, &opening, &mut transcript_prover, @@ -226,7 +220,6 @@ mod test { let proof = EqualityProof::new( &elgamal_keypair, &ciphertext, - &commitment, message, &opening, &mut transcript_prover, @@ -240,6 +233,5 @@ mod test { &mut transcript_verifier ) .is_err()); - } } diff --git a/zk-token-sdk/src/instruction/withdraw.rs b/zk-token-sdk/src/instruction/withdraw.rs index a19223a63..4ddfa19ab 100644 --- a/zk-token-sdk/src/instruction/withdraw.rs +++ b/zk-token-sdk/src/instruction/withdraw.rs @@ -6,8 +6,8 @@ use { use { crate::{ encryption::{ - elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey}, - pedersen::{Pedersen, PedersenBase, PedersenOpening}, + elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + pedersen::{Pedersen, PedersenCommitment, PedersenOpening}, }, equality_proof::EqualityProof, errors::ProofError, @@ -15,9 +15,7 @@ use { range_proof::RangeProof, transcript::TranscriptProtocol, }, - curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar, traits::MultiscalarMul}, merlin::Transcript, - rand::rngs::OsRng, std::convert::TryInto, }; @@ -31,11 +29,14 @@ use { #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct WithdrawData { + /// The source account ElGamal pubkey + pub elgamal_pubkey: pod::ElGamalPubkey, // 32 bytes + /// The source account available balance *after* the withdraw (encrypted by /// `source_pk` pub final_balance_ct: pod::ElGamalCiphertext, // 64 bytes - /// Proof that the account is solvent + /// Range proof pub proof: WithdrawProof, // 736 bytes } @@ -62,6 +63,7 @@ impl WithdrawData { let proof = WithdrawProof::new(source_keypair, final_balance, &final_balance_ct); Self { + elgamal_pubkey: source_keypair.public.into(), final_balance_ct: final_balance_ct.into(), proof, } @@ -71,8 +73,9 @@ impl WithdrawData { #[cfg(not(target_arch = "bpf"))] impl Verifiable for WithdrawData { fn verify(&self) -> Result<(), ProofError> { + let elgamal_pubkey = self.elgamal_pubkey.try_into()?; let final_balance_ct = self.final_balance_ct.try_into()?; - self.proof.verify(&final_balance_ct) + self.proof.verify(&elgamal_pubkey, &final_balance_ct) } } @@ -82,6 +85,9 @@ impl Verifiable for WithdrawData { #[repr(C)] #[allow(non_snake_case)] pub struct WithdrawProof { + /// New Pedersen commitment + pub commitment: pod::PedersenCommitment, + /// Associated equality proof pub equality_proof: pod::EqualityProof, @@ -115,6 +121,7 @@ impl WithdrawProof { let D_EG = final_balance_ct.decrypt_handle.get_point(); let C_Ped = commitment.get_point(); + // append all current state to the transcript transcript.append_point(b"P_EG", &P_EG.compress()); transcript.append_point(b"C_EG", &C_EG.compress()); transcript.append_point(b"D_EG", &D_EG.compress()); @@ -124,7 +131,6 @@ impl WithdrawProof { let equality_proof = EqualityProof::new( source_keypair, final_balance_ct, - &commitment, final_balance, &opening, &mut transcript, @@ -138,36 +144,52 @@ impl WithdrawProof { ); WithdrawProof { + commitment: commitment.into(), equality_proof: equality_proof.try_into().expect("equality proof"), range_proof: range_proof.try_into().expect("range proof"), } } - pub fn verify(&self, final_balance_ct: &ElGamalCiphertext) -> Result<(), ProofError> { - // let mut transcript = Self::transcript_new(); + pub fn verify( + &self, + source_pk: &ElGamalPubkey, + final_balance_ct: &ElGamalCiphertext, + ) -> Result<(), ProofError> { + let mut transcript = Self::transcript_new(); - // // Add a domain separator to record the start of the protocol - // transcript.withdraw_proof_domain_sep(); + let commitment: PedersenCommitment = self.commitment.try_into()?; + let equality_proof: EqualityProof = self.equality_proof.try_into()?; + let range_proof: RangeProof = self.range_proof.try_into()?; - // // Extract the relevant scalar and Ristretto points from the input - // let C = final_balance_ct.message_comm.get_point(); - // let D = final_balance_ct.decrypt_handle.get_point(); + // add a domain separator to record the start of the protocol + transcript.withdraw_proof_domain_sep(); - // let R = self.R.into(); - // let z: Scalar = self.z.into(); + // extract the relevant scalar and Ristretto points from the inputs + let P_EG = source_pk.get_point(); + let C_EG = final_balance_ct.message_comm.get_point(); + let D_EG = final_balance_ct.decrypt_handle.get_point(); + let C_Ped = commitment.get_point(); - // // generate a challenge scalar - // transcript.validate_and_append_point(b"R", &R)?; - // let c = transcript.challenge_scalar(b"c"); + // append all current state to the transcript + transcript.append_point(b"P_EG", &P_EG.compress()); + transcript.append_point(b"C_EG", &C_EG.compress()); + transcript.append_point(b"D_EG", &D_EG.compress()); + transcript.append_point(b"C_Ped", &C_Ped.compress()); - // // decompress R or return verification error - // let R = R.decompress().ok_or(ProofError::VerificationError)?; + // verify equality proof + // + // TODO: we can also consider verifying equality and range proof in a batch + equality_proof.verify(source_pk, final_balance_ct, &commitment, &mut transcript)?; - // // compute new Pedersen commitment to verify range proof with - // let new_comm = RistrettoPoint::multiscalar_mul(vec![Scalar::one(), -z, c], vec![C, D, R]); + // verify range proof + // + // TODO: double compressing here - consider modifying range proof input type to `PedersenCommitment` + range_proof.verify( + vec![&commitment.get_point().compress()], + vec![64_usize], + &mut transcript, + )?; - // let range_proof: RangeProof = self.range_proof.try_into()?; - // range_proof.verify(vec![&new_comm.compress()], vec![64_usize], &mut transcript) Ok(()) } } @@ -176,37 +198,32 @@ impl WithdrawProof { mod test { use {super::*, crate::encryption::elgamal::ElGamalKeypair}; - // #[test] - // #[ignore] - // fn test_withdraw_correctness() { - // // generate and verify proof for the proper setting - // let ElGamalKeypair { public, secret } = ElGamalKeypair::default(); + #[test] + fn test_withdraw_correctness() { + // generate and verify proof for the proper setting + let elgamal_keypair = ElGamalKeypair::default(); - // let current_balance: u64 = 77; - // let current_balance_ct = public.encrypt(current_balance); + let current_balance: u64 = 77; + let current_balance_ct = elgamal_keypair.public.encrypt(current_balance); - // let withdraw_amount: u64 = 55; + let withdraw_amount: u64 = 55; - // let data = WithdrawData::new( - // withdraw_amount, - // public, - // &secret, - // current_balance, - // current_balance_ct, - // ); - // assert!(data.verify().is_ok()); + let data = WithdrawData::new( + withdraw_amount, + &elgamal_keypair, + current_balance, + current_balance_ct, + ); + assert!(data.verify().is_ok()); - // // generate and verify proof with wrong balance - // let wrong_balance: u64 = 99; - // let data = WithdrawData::new( - // withdraw_amount, - // public, - // &secret, - // wrong_balance, - // current_balance_ct, - // ); - // assert!(data.verify().is_err()); - - // // TODO: test for ciphertexts that encrypt numbers outside the 0, 2^64 range - // } + // generate and verify proof with wrong balance + let wrong_balance: u64 = 99; + let data = WithdrawData::new( + withdraw_amount, + &elgamal_keypair, + wrong_balance, + current_balance_ct, + ); + assert!(data.verify().is_err()); + } }