refactor: create a separate zero-balance-proof for CloseAccount instruction

This commit is contained in:
Sam Kim 2021-12-16 09:00:57 -05:00 committed by Michael Vines
parent c6cd0a5591
commit 208621e3cf
1 changed files with 191 additions and 0 deletions

View File

@ -0,0 +1,191 @@
#[cfg(not(target_arch = "bpf"))]
use {
crate::encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::PedersenBase,
},
curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng,
};
use {
crate::{errors::ProofError, transcript::TranscriptProtocol},
arrayref::{array_ref, array_refs},
curve25519_dalek::{
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::IsIdentity,
},
merlin::Transcript,
};
#[allow(non_snake_case)]
#[derive(Clone)]
pub struct ZeroBalanceProof {
pub Y_P: CompressedRistretto,
pub Y_D: CompressedRistretto,
pub z: Scalar,
}
#[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))]
impl ZeroBalanceProof {
pub fn new(
elgamal_keypair: &ElGamalKeypair,
elgamal_ciphertext: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Self {
// extract the relevant scalar and Ristretto points from the input
let P = elgamal_keypair.public.get_point();
let s = elgamal_keypair.secret.get_scalar();
let C = elgamal_ciphertext.message_comm.get_point();
let D = elgamal_ciphertext.decrypt_handle.get_point();
// record ElGamal pubkey and ciphertext in the transcript
transcript.append_point(b"P", &P.compress());
transcript.append_point(b"C", &C.compress());
transcript.append_point(b"D", &D.compress());
// generate a random masking factor that also serves as a nonce
let y = Scalar::random(&mut OsRng);
let Y_P = (y * P).compress();
let Y_D = (y * D).compress();
// record Y in transcript and receive a challenge scalar
transcript.append_point(b"Y_P", &Y_P);
transcript.append_point(b"Y_D", &Y_D);
let c = transcript.challenge_scalar(b"c");
transcript.challenge_scalar(b"w");
// compute the masked secret key
let z = c * s + y;
Self {
Y_P,
Y_D,
z,
}
}
pub fn verify(
self,
elgamal_pubkey: &ElGamalPubkey,
elgamal_ciphertext: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<(), ProofError> {
// extract the relevant scalar and Ristretto points from the input
let P = elgamal_pubkey.get_point();
let C = elgamal_ciphertext.message_comm.get_point();
let D = elgamal_ciphertext.decrypt_handle.get_point();
let H = PedersenBase::default().H;
// record ElGamal pubkey and ciphertext in the transcript
transcript.validate_and_append_point(b"P", &P.compress())?;
transcript.append_point(b"C", &C.compress());
transcript.append_point(b"D", &D.compress());
// record Y in transcript and receive challenge scalars
transcript.validate_and_append_point(b"Y_P", &self.Y_P)?;
transcript.append_point(b"Y_D", &self.Y_D);
let c = transcript.challenge_scalar(b"c");
let w = transcript.challenge_scalar(b"w"); // w used for multiscalar multiplication verification
// decompress R or return verification error
let Y_P = self.Y_P.decompress().ok_or(ProofError::VerificationError)?;
let Y_D = self.Y_D.decompress().ok_or(ProofError::VerificationError)?;
let z = self.z;
// check the required algebraic relation
let check = RistrettoPoint::multiscalar_mul(
vec![z, -c, -Scalar::one(), w * z, -w * c, -w],
vec![P, H, Y_P, D, C, Y_D],
);
if check.is_identity() {
Ok(())
} else {
Err(ProofError::VerificationError)
}
}
pub fn to_bytes(&self) -> [u8; 96] {
let mut buf = [0_u8; 96];
buf[..32].copy_from_slice(self.Y_P.as_bytes());
buf[32..64].copy_from_slice(self.Y_D.as_bytes());
buf[64..96].copy_from_slice(self.z.as_bytes());
buf
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
let bytes = array_ref![bytes, 0, 96];
let (Y_P, Y_D, z) = array_refs![bytes, 32, 32, 32];
let Y_P = CompressedRistretto::from_slice(Y_P);
let Y_D = CompressedRistretto::from_slice(Y_D);
let z = Scalar::from_canonical_bytes(*z).ok_or(ProofError::FormatError)?;
Ok(ZeroBalanceProof {
Y_P,
Y_D,
z,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::encryption::{elgamal::ElGamalKeypair, pedersen::Pedersen};
#[test]
fn test_zero_balance_proof() {
let source_keypair = ElGamalKeypair::default();
let mut transcript_prover = Transcript::new(b"test");
let mut transcript_verifier = Transcript::new(b"test");
// general case: encryption of 0
let elgamal_ciphertext = source_keypair.public.encrypt(0_u64);
let proof = ZeroBalanceProof::new(&source_keypair, &elgamal_ciphertext, &mut transcript_prover);
assert!(proof.verify(&source_keypair.public, &elgamal_ciphertext, &mut transcript_verifier).is_ok());
// general case: encryption of > 0
let elgamal_ciphertext = source_keypair.public.encrypt(1_u64);
let proof = ZeroBalanceProof::new(&source_keypair, &elgamal_ciphertext, &mut transcript_prover);
assert!(proof.verify(&source_keypair.public, &elgamal_ciphertext, &mut transcript_verifier).is_err());
// // edge case: all zero ciphertext - such ciphertext should always be a valid encryption of 0
let zeroed_ct = ElGamalCiphertext::default();
let proof = ZeroBalanceProof::new(&source_keypair, &zeroed_ct, &mut transcript_prover);
assert!(proof.verify(&source_keypair.public, &zeroed_ct, &mut transcript_verifier).is_ok());
// edge cases: only C or D is zero - such ciphertext is always invalid
let zeroed_comm = Pedersen::with(0_u64, &PedersenOpening::default());
let handle = elgamal_ciphertext.decrypt_handle;
let zeroed_comm_ciphertext = ElGamalCiphertext {
message_comm: zeroed_comm,
decrypt_handle: handle,
};
let proof = ZeroBalanceProof::new(&source_keypair, &zeroed_comm_ciphertext, &mut transcript_prover);
assert!(proof
.verify(&source_keypair.public, &zeroed_comm_ciphertext, &mut transcript_verifier)
.is_err());
let (zero_comm, _) = Pedersen::new(0_u64);
let zeroed_handle_ciphertext = ElGamalCiphertext {
message_comm: zero_comm,
decrypt_handle: PedersenDecryptHandle::default(),
};
let proof = ZeroBalanceProof::new(&source_keypair, &zeroed_handle_ciphertext, &mut transcript_prover);
assert!(proof
.verify(&source_keypair.public, &zeroed_handle_ciphertext, &mut transcript_verifier)
.is_err());
}
}