diff --git a/zk-token-sdk/src/instruction/mod.rs b/zk-token-sdk/src/instruction/mod.rs index 145cecdc1f..221d1f90ae 100644 --- a/zk-token-sdk/src/instruction/mod.rs +++ b/zk-token-sdk/src/instruction/mod.rs @@ -1,6 +1,5 @@ mod close_account; mod transfer; -mod update_account_pk; mod withdraw; #[cfg(not(target_arch = "bpf"))] @@ -8,7 +7,6 @@ use crate::errors::ProofError; pub use { close_account::CloseAccountData, transfer::{TransferCommitments, TransferData, TransferPubKeys}, - update_account_pk::UpdateAccountPkData, withdraw::WithdrawData, }; diff --git a/zk-token-sdk/src/instruction/update_account_pk.rs b/zk-token-sdk/src/instruction/update_account_pk.rs deleted file mode 100644 index c7410f8e21..0000000000 --- a/zk-token-sdk/src/instruction/update_account_pk.rs +++ /dev/null @@ -1,362 +0,0 @@ -use { - crate::zk_token_elgamal::pod, - bytemuck::{Pod, Zeroable}, -}; -#[cfg(not(target_arch = "bpf"))] -use { - crate::{ - encryption::{ - elgamal::{ElGamalCiphertext, ElGamalPubkey, ElGamalSecretKey}, - pedersen::PedersenBase, - }, - errors::ProofError, - instruction::Verifiable, - transcript::TranscriptProtocol, - }, - curve25519_dalek::{ - ristretto::RistrettoPoint, - scalar::Scalar, - traits::{IsIdentity, MultiscalarMul}, - }, - merlin::Transcript, - rand::rngs::OsRng, - std::convert::TryInto, -}; - -/// This struct includes the cryptographic proof *and* the account data information needed to verify -/// the proof -/// -/// - The pre-instruction should call UpdateAccountPKData::verify(&self) -/// - The actual program should check that `current_ct` is consistent with what is -/// currently stored in the confidential token account -/// -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -pub struct UpdateAccountPkData { - /// Current ElGamal encryption key - pub current_pk: pod::ElGamalPubkey, // 32 bytes - - /// Current encrypted available balance - pub current_ct: pod::ElGamalCiphertext, // 64 bytes - - /// New ElGamal encryption key - pub new_pk: pod::ElGamalPubkey, // 32 bytes - - /// New encrypted available balance - pub new_ct: pod::ElGamalCiphertext, // 64 bytes - - /// Proof that the current and new ciphertexts are consistent - pub proof: UpdateAccountPkProof, // 160 bytes -} - -impl UpdateAccountPkData { - #[cfg(not(target_arch = "bpf"))] - pub fn new( - current_balance: u64, - current_ct: ElGamalCiphertext, - current_pk: ElGamalPubkey, - current_sk: &ElGamalSecretKey, - new_pk: ElGamalPubkey, - new_sk: &ElGamalSecretKey, - ) -> Self { - let new_ct = new_pk.encrypt(current_balance); - - let proof = - UpdateAccountPkProof::new(current_balance, current_sk, new_sk, ¤t_ct, &new_ct); - - Self { - current_pk: current_pk.into(), - current_ct: current_ct.into(), - new_ct: new_ct.into(), - new_pk: new_pk.into(), - proof, - } - } -} - -#[cfg(not(target_arch = "bpf"))] -impl Verifiable for UpdateAccountPkData { - fn verify(&self) -> Result<(), ProofError> { - let current_ct = self.current_ct.try_into()?; - let new_ct = self.new_ct.try_into()?; - self.proof.verify(¤t_ct, &new_ct) - } -} - -/// This struct represents the cryptographic proof component that certifies that the current_ct and -/// new_ct encrypt equal values -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -#[allow(non_snake_case)] -pub struct UpdateAccountPkProof { - pub R_0: pod::CompressedRistretto, // 32 bytes - pub R_1: pod::CompressedRistretto, // 32 bytes - pub z_sk_0: pod::Scalar, // 32 bytes - pub z_sk_1: pod::Scalar, // 32 bytes - pub z_x: pod::Scalar, // 32 bytes -} - -#[allow(non_snake_case)] -#[cfg(not(target_arch = "bpf"))] -impl UpdateAccountPkProof { - fn transcript_new() -> Transcript { - Transcript::new(b"UpdateAccountPkProof") - } - - fn new( - current_balance: u64, - current_sk: &ElGamalSecretKey, - new_sk: &ElGamalSecretKey, - current_ct: &ElGamalCiphertext, - new_ct: &ElGamalCiphertext, - ) -> Self { - let mut transcript = Self::transcript_new(); - - // add a domain separator to record the start of the protocol - transcript.update_account_public_key_proof_domain_sep(); - - // extract the relevant scalar and Ristretto points from the input - let s_0 = current_sk.get_scalar(); - let s_1 = new_sk.get_scalar(); - let x = Scalar::from(current_balance); - - let D_0 = current_ct.decrypt_handle.get_point(); - let D_1 = new_ct.decrypt_handle.get_point(); - - let G = PedersenBase::default().G; - - // generate a random masking factor that also serves as a nonce - let r_sk_0 = Scalar::random(&mut OsRng); - let r_sk_1 = Scalar::random(&mut OsRng); - let r_x = Scalar::random(&mut OsRng); - - let R_0 = (r_sk_0 * D_0 + r_x * G).compress(); - let R_1 = (r_sk_1 * D_1 + r_x * G).compress(); - - // record R_0, R_1 on transcript and receive a challenge scalar - transcript.append_point(b"R_0", &R_0); - transcript.append_point(b"R_1", &R_1); - let c = transcript.challenge_scalar(b"c"); - let _w = transcript.challenge_scalar(b"w"); // for consistency of transcript - - // compute the masked secret keys and amount - let z_sk_0 = c * s_0 + r_sk_0; - let z_sk_1 = c * s_1 + r_sk_1; - let z_x = c * x + r_x; - - UpdateAccountPkProof { - R_0: R_0.into(), - R_1: R_1.into(), - z_sk_0: z_sk_0.into(), - z_sk_1: z_sk_1.into(), - z_x: z_x.into(), - } - } - - fn verify( - &self, - current_ct: &ElGamalCiphertext, - new_ct: &ElGamalCiphertext, - ) -> Result<(), ProofError> { - let mut transcript = Self::transcript_new(); - - // add a domain separator to record the start of the protocol - transcript.update_account_public_key_proof_domain_sep(); - - // extract the relevant scalar and Ristretto points from the input - let C_0 = current_ct.message_comm.get_point(); - let D_0 = current_ct.decrypt_handle.get_point(); - - let C_1 = new_ct.message_comm.get_point(); - let D_1 = new_ct.decrypt_handle.get_point(); - - let R_0 = self.R_0.into(); - let R_1 = self.R_1.into(); - let z_sk_0 = self.z_sk_0.into(); - let z_sk_1: Scalar = self.z_sk_1.into(); - let z_x = self.z_x.into(); - - let G = PedersenBase::default().G; - - // generate a challenge scalar - transcript.validate_and_append_point(b"R_0", &R_0)?; - transcript.validate_and_append_point(b"R_1", &R_1)?; - let c = transcript.challenge_scalar(b"c"); - let w = transcript.challenge_scalar(b"w"); - - // decompress R_0, R_1 or return verification error - let R_0 = R_0.decompress().ok_or(ProofError::VerificationError)?; - let R_1 = R_1.decompress().ok_or(ProofError::VerificationError)?; - - // check the required algebraic relation - let check = RistrettoPoint::multiscalar_mul( - vec![ - z_sk_0, - z_x, - -c, - -Scalar::one(), - w * z_sk_1, - w * z_x, - -w * c, - -w * Scalar::one(), - ], - vec![D_0, G, C_0, R_0, D_1, G, C_1, R_1], - ); - - if check.is_identity() { - Ok(()) - } else { - Err(ProofError::VerificationError) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::encryption::elgamal::ElGamalKeypair; - use crate::encryption::pedersen::{Pedersen, PedersenDecryptHandle, PedersenOpening}; - - #[test] - fn test_update_account_public_key_general_cases() { - let current = ElGamalKeypair::default(); - let new = ElGamalKeypair::default(); - - // If current_ct and new_ct encrypt same values, then the proof verification should succeed - let balance: u64 = 77; - let current_ct = current.public.encrypt(balance); - let new_ct = new.public.encrypt(balance); - - let proof = - UpdateAccountPkProof::new(balance, ¤t.secret, &new.secret, ¤t_ct, &new_ct); - assert!(proof.verify(¤t_ct, &new_ct).is_ok()); - - // If current_ct and new_ct encrypt different values, then the proof verification should fail - let new_ct = new.public.encrypt(55_u64); - - let proof = - UpdateAccountPkProof::new(balance, ¤t.secret, &new.secret, ¤t_ct, &new_ct); - assert!(proof.verify(¤t_ct, &new_ct).is_err()); - } - - #[test] - fn test_update_account_public_key_zeroed_ciphertexts() { - let current = ElGamalKeypair::default(); - let new = ElGamalKeypair::default(); - - // A zeroed cipehrtext should be considered as an account balance of 0 - let balance: u64 = 0; - let zeroed_ct_as_current_ct: ElGamalCiphertext = - pod::ElGamalCiphertext::zeroed().try_into().unwrap(); - let new_ct: ElGamalCiphertext = new.public.encrypt(balance); - let proof = UpdateAccountPkProof::new( - balance, - ¤t.secret, - &new.secret, - &zeroed_ct_as_current_ct, - &new_ct, - ); - assert!(proof.verify(&zeroed_ct_as_current_ct, &new_ct).is_ok()); - - let current_ct = current.public.encrypt(balance); - let zeroed_ct_as_new_ct: ElGamalCiphertext = - pod::ElGamalCiphertext::zeroed().try_into().unwrap(); - let proof = UpdateAccountPkProof::new( - balance, - ¤t.secret, - &new.secret, - ¤t_ct, - &zeroed_ct_as_new_ct, - ); - assert!(proof.verify(¤t_ct, &zeroed_ct_as_new_ct).is_ok()); - - let zeroed_ct_as_current_ct: ElGamalCiphertext = - pod::ElGamalCiphertext::zeroed().try_into().unwrap(); - let zeroed_ct_as_new_ct: ElGamalCiphertext = - pod::ElGamalCiphertext::zeroed().try_into().unwrap(); - let proof = UpdateAccountPkProof::new( - balance, - ¤t.secret, - &new.secret, - &zeroed_ct_as_current_ct, - &zeroed_ct_as_new_ct, - ); - assert!(proof - .verify(&zeroed_ct_as_current_ct, &zeroed_ct_as_new_ct) - .is_ok()); - } - - #[test] - fn test_update_account_public_key_partially_zeroed_ciphertexts() { - let current = ElGamalKeypair::default(); - let new = ElGamalKeypair::default(); - - let balance = 0_u64; - let balance_ciphertext = new.public.encrypt(balance); - - let zeroed_comm = Pedersen::with(0_u64, &PedersenOpening::default()); - let handle = balance_ciphertext.decrypt_handle; - - // Partially zeroed ciphertext as current ciphertext - let zeroed_comm_ciphertext = ElGamalCiphertext { - message_comm: zeroed_comm, - decrypt_handle: handle, - }; - let new_ct: ElGamalCiphertext = new.public.encrypt(balance); - - let proof = UpdateAccountPkProof::new( - balance, - ¤t.secret, - &new.secret, - &zeroed_comm_ciphertext, - &new_ct, - ); - assert!(proof.verify(&zeroed_comm_ciphertext, &new_ct).is_err()); - - let zeroed_handle_ciphertext = ElGamalCiphertext { - message_comm: balance_ciphertext.message_comm, - decrypt_handle: PedersenDecryptHandle::default(), - }; - - let proof = UpdateAccountPkProof::new( - balance, - ¤t.secret, - &new.secret, - &zeroed_handle_ciphertext, - &new_ct, - ); - assert!(proof.verify(&zeroed_handle_ciphertext, &new_ct).is_err()); - - // Partially zeroed cipehrtext as new ciphertext - let zeroed_comm_ciphertext = ElGamalCiphertext { - message_comm: zeroed_comm, - decrypt_handle: handle, - }; - let current_ct: ElGamalCiphertext = current.public.encrypt(balance); - - let proof = UpdateAccountPkProof::new( - balance, - ¤t.secret, - &new.secret, - ¤t_ct, - &zeroed_comm_ciphertext, - ); - assert!(proof.verify(¤t_ct, &zeroed_comm_ciphertext).is_err()); - - let zeroed_handle_ciphertext = ElGamalCiphertext { - message_comm: balance_ciphertext.message_comm, - decrypt_handle: PedersenDecryptHandle::default(), - }; - - let proof = UpdateAccountPkProof::new( - balance, - ¤t.secret, - &new.secret, - ¤t_ct, - &zeroed_handle_ciphertext, - ); - assert!(proof - .verify(¤t_ct, &zeroed_handle_ciphertext) - .is_err()); - } -} diff --git a/zk-token-sdk/src/zk_token_proof_instruction.rs b/zk-token-sdk/src/zk_token_proof_instruction.rs index add0224bd3..30c314f690 100644 --- a/zk-token-sdk/src/zk_token_proof_instruction.rs +++ b/zk-token-sdk/src/zk_token_proof_instruction.rs @@ -11,16 +11,6 @@ use { #[derive(Clone, Copy, Debug, FromPrimitive, ToPrimitive, PartialEq)] #[repr(u8)] pub enum ProofInstruction { - /// Verify a `UpdateAccountPkData` struct - /// - /// Accounts expected by this instruction: - /// None - /// - /// Data expected by this instruction: - /// `UpdateAccountPkData` - /// - VerifyUpdateAccountPk, - /// Verify a `CloseAccountData` struct /// /// Accounts expected by this instruction: @@ -84,10 +74,6 @@ pub fn verify_close_account(proof_data: &CloseAccountData) -> Instruction { ProofInstruction::VerifyCloseAccount.encode(proof_data) } -pub fn verify_update_account_pk(proof_data: &UpdateAccountPkData) -> Instruction { - ProofInstruction::VerifyUpdateAccountPk.encode(proof_data) -} - pub fn verify_withdraw(proof_data: &WithdrawData) -> Instruction { ProofInstruction::VerifyWithdraw.encode(proof_data) }