Elgamal pass (#22632)

* zk-token-sdk: change G and H to static and optimize pedersen arithmetic

* zk-token-sdk: remove unnecessary copy in elgamal arithmetic

* zk-token-sdk: fix elgamal tests for new syntax

* zk-token-sdk: use lazy-static for pedersen base

* zk-token-sdk: add dlog test for elgamal decryption

* zk-token-sdk: reflect changes in elgamal in the rest of the sdk

* zk-token-sdk: rustfmt and clippy

* zk-token-sdk: some documentation for elgamal and pedersen

* zk-token-sdk: minor remove whitespace

* zk-token-sdk: update lock files

* zk-token-sdk: change random() to new_rand()

* zk-token-sdk: add explanation for suppressing clippy::op_ref
This commit is contained in:
samkim-crypto 2022-01-21 21:56:27 -04:00 committed by GitHub
parent 8dd62854fa
commit d8cbb2a952
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 577 additions and 652 deletions

View File

@ -4531,9 +4531,9 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
dependencies = [
"zeroize_derive",
]

View File

@ -31,7 +31,7 @@ sha3 = "0.9"
solana-sdk = { path = "../sdk", version = "=1.10.0" }
subtle = "2"
thiserror = "1"
zeroize = { version = "1.2.0", default-features = false, features = ["zeroize_derive"] }
zeroize = { version = "1.3", default-features = false, features = ["zeroize_derive"] }
[dev-dependencies]
time = "0.1.40"

View File

@ -1,12 +1,25 @@
//! The twisted ElGamal encryption implementation.
//!
//! The message space consists of any number that is representable as a scalar (a.k.a. "exponent")
//! for Curve25519.
//!
//! A twisted ElGamal ciphertext consists of two components:
//! - A Pedersen commitment that encodes a message to be encrypted
//! - A "decryption handle" that binds the Pedersen opening to a specific public key
//! In contrast to the traditional ElGamal encryption scheme, the twisted ElGamal encodes messages
//! directly as a Pedersen commitment. Therefore, proof systems that are designed specifically for
//! Pedersen commitments can be used on the twisted ElGamal ciphertexts.
//!
//! As the messages are encrypted as scalar elements (a.k.a. in the "exponent"), the encryption
//! scheme requires solving discrete log to recover the original plaintext.
use {
crate::encryption::{
discrete_log::{DecodeU32Precomputation, DiscreteLog},
pedersen::{
Pedersen, PedersenBase, PedersenCommitment, PedersenDecryptHandle, PedersenOpening,
},
pedersen::{Pedersen, PedersenCommitment, PedersenOpening, G, H},
},
arrayref::{array_ref, array_refs},
core::ops::{Add, Div, Mul, Sub},
core::ops::{Add, Mul, Sub},
curve25519_dalek::{
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
@ -25,7 +38,7 @@ use {
};
#[cfg(not(target_arch = "bpf"))]
use {
rand::{rngs::OsRng, CryptoRng, RngCore},
rand::rngs::OsRng,
sha3::Sha3_512,
std::{
fmt,
@ -35,109 +48,96 @@ use {
},
};
/// Algorithm handle for the twisted ElGamal encryption scheme
struct ElGamal;
impl ElGamal {
/// The function generates the public and secret keys for ElGamal encryption from the provided
/// randomness generator
/// Generates an ElGamal keypair.
///
/// This function is randomized. It internally samples a scalar element using `OsRng`.
#[cfg(not(target_arch = "bpf"))]
#[allow(non_snake_case)]
fn keygen<T: RngCore + CryptoRng>(rng: &mut T) -> ElGamalKeypair {
// sample a non-zero scalar
let mut s: Scalar;
loop {
s = Scalar::random(rng);
fn keygen() -> ElGamalKeypair {
// secret scalar should be zero with negligible probability
let mut s = Scalar::random(&mut OsRng);
let keypair = Self::keygen_with_scalar(&s);
if s != Scalar::zero() {
break;
}
}
Self::keygen_with_scalar(s)
s.zeroize();
keypair
}
/// Generates the public and secret keys for ElGamal encryption from a non-zero Scalar
/// Generates an ElGamal keypair from a scalar input that determines the ElGamal private key.
#[cfg(not(target_arch = "bpf"))]
#[allow(non_snake_case)]
fn keygen_with_scalar(s: Scalar) -> ElGamalKeypair {
assert!(s != Scalar::zero());
fn keygen_with_scalar(s: &Scalar) -> ElGamalKeypair {
assert!(s != &Scalar::zero());
let H = PedersenBase::default().H;
let P = s.invert() * H;
let P = s.invert() * &(*H);
ElGamalKeypair {
public: ElGamalPubkey(P),
secret: ElGamalSecretKey(s),
secret: ElGamalSecretKey(*s),
}
}
/// On input a public key and a message to be encrypted, the function
/// returns an ElGamal ciphertext of the message under the public key.
/// On input an ElGamal public key and a mesage to be encrypted, the function returns a
/// corresponding ElGamal ciphertext.
///
/// This function is randomized. It internally samples a scalar element using `OsRng`.
#[cfg(not(target_arch = "bpf"))]
fn encrypt<T: Into<Scalar>>(public: &ElGamalPubkey, amount: T) -> ElGamalCiphertext {
let (message_comm, open) = Pedersen::new(amount);
let decrypt_handle = public.decrypt_handle(&open);
let (commitment, opening) = Pedersen::new(amount);
let handle = public.decrypt_handle(&opening);
ElGamalCiphertext {
message_comm,
decrypt_handle,
}
ElGamalCiphertext { commitment, handle }
}
/// On input a public key, message, and Pedersen opening, the function
/// returns an ElGamal ciphertext of the message under the public key using
/// the opening.
/// returns the corresponding ElGamal ciphertext.
fn encrypt_with<T: Into<Scalar>>(
public: &ElGamalPubkey,
amount: T,
open: &PedersenOpening,
public: &ElGamalPubkey,
opening: &PedersenOpening,
) -> ElGamalCiphertext {
let message_comm = Pedersen::with(amount, open);
let decrypt_handle = public.decrypt_handle(open);
let commitment = Pedersen::with(amount, opening);
let handle = public.decrypt_handle(opening);
ElGamalCiphertext {
message_comm,
decrypt_handle,
}
ElGamalCiphertext { commitment, handle }
}
/// On input a secret key and a ciphertext, the function decrypts the ciphertext.
/// On input a secret key and a ciphertext, the function returns the decrypted message.
///
/// The output of the function is of type `DiscreteLog`. The exact message
/// can be recovered via the DiscreteLog's decode method.
fn decrypt(secret: &ElGamalSecretKey, ct: &ElGamalCiphertext) -> DiscreteLog {
let ElGamalSecretKey(s) = secret;
let ElGamalCiphertext {
message_comm,
decrypt_handle,
} = ct;
/// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted
/// message, use `DiscreteLog::decode`.
fn decrypt(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> DiscreteLog {
DiscreteLog {
generator: PedersenBase::default().G,
target: message_comm.get_point() - s * decrypt_handle.get_point(),
generator: *G,
target: &ciphertext.commitment.0 - &(&secret.0 * &ciphertext.handle.0),
}
}
/// On input a secret key and a ciphertext, the function decrypts the
/// ciphertext for a u32 value.
fn decrypt_u32(secret: &ElGamalSecretKey, ct: &ElGamalCiphertext) -> Option<u32> {
let discrete_log_instance = Self::decrypt(secret, ct);
/// On input a secret key and a ciphertext, the function returns the decrypted message
/// interpretted as type `u32`.
fn decrypt_u32(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> Option<u32> {
let discrete_log_instance = Self::decrypt(secret, ciphertext);
discrete_log_instance.decode_u32()
}
/// On input a secret key, ciphertext, and hashmap, the function decrypts the
/// ciphertext for a u32 value.
/// On input a secret key, a ciphertext, and a pre-computed hashmap, the function returns the
/// decrypted message interpretted as type `u32`.
fn decrypt_u32_online(
secret: &ElGamalSecretKey,
ct: &ElGamalCiphertext,
ciphertext: &ElGamalCiphertext,
hashmap: &DecodeU32Precomputation,
) -> Option<u32> {
let discrete_log_instance = Self::decrypt(secret, ct);
let discrete_log_instance = Self::decrypt(secret, ciphertext);
discrete_log_instance.decode_u32_online(hashmap)
}
}
/// A (twisted) ElGamal encryption keypair.
#[derive(PartialEq)]
///
/// The instances of the secret key are zeroized on drop.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Zeroize)]
pub struct ElGamalKeypair {
/// The public half of this keypair.
pub public: ElGamalPubkey,
@ -146,8 +146,7 @@ pub struct ElGamalKeypair {
}
impl ElGamalKeypair {
/// Generates the public and secret keys for ElGamal encryption from Ed25519 signing key and an
/// address.
/// Deterministically derives an ElGamal keypair from an Ed25519 signing key and a Solana address.
#[cfg(not(target_arch = "bpf"))]
#[allow(non_snake_case)]
pub fn new(signer: &dyn Signer, address: &Pubkey) -> Result<Self, SignerError> {
@ -167,29 +166,28 @@ impl ElGamalKeypair {
return Err(SignerError::Custom("Rejecting default signature".into()));
}
let scalar = Scalar::hash_from_bytes::<Sha3_512>(signature.as_ref());
Ok(ElGamal::keygen_with_scalar(scalar))
let mut scalar = Scalar::hash_from_bytes::<Sha3_512>(signature.as_ref());
let keypair = ElGamal::keygen_with_scalar(&scalar);
// TODO: zeroize signature?
scalar.zeroize();
Ok(keypair)
}
/// Generates the public and secret keys for ElGamal encryption.
///
/// This function is randomized. It internally samples a scalar element using `OsRng`.
#[cfg(not(target_arch = "bpf"))]
#[allow(clippy::new_ret_no_self)]
pub fn default() -> Self {
Self::with(&mut OsRng) // using OsRng for now
}
/// On input a randomness generator, the function generates the public and
/// secret keys for ElGamal encryption.
#[cfg(not(target_arch = "bpf"))]
#[allow(non_snake_case)]
pub fn with<T: RngCore + CryptoRng>(rng: &mut T) -> Self {
ElGamal::keygen(rng)
pub fn new_rand() -> Self {
ElGamal::keygen()
}
pub fn to_bytes(&self) -> [u8; 64] {
let mut bytes = self.public.to_bytes().to_vec();
bytes.extend(self.secret.to_bytes());
bytes.try_into().expect("incorrect length")
let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(&self.public.to_bytes());
bytes[32..].copy_from_slice(self.secret.as_bytes());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
@ -256,18 +254,17 @@ impl ElGamalKeypair {
}
/// Public key for the ElGamal encryption scheme.
#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Zeroize)]
pub struct ElGamalPubkey(RistrettoPoint);
impl ElGamalPubkey {
/// Derive the `ElGamalPubkey` that uniquely corresponds to an `ElGamalSecretKey`
/// Derives the `ElGamalPubkey` that uniquely corresponds to an `ElGamalSecretKey`.
#[allow(non_snake_case)]
pub fn new(secret: &ElGamalSecretKey) -> Self {
let H = PedersenBase::default().H;
ElGamalPubkey(secret.0 * H)
ElGamalPubkey(&secret.0 * &(*H))
}
pub fn get_point(&self) -> RistrettoPoint {
self.0
pub fn get_point(&self) -> &RistrettoPoint {
&self.0
}
#[allow(clippy::wrong_self_convention)]
@ -281,31 +278,27 @@ impl ElGamalPubkey {
))
}
/// Utility method for code ergonomics.
/// Encrypts an amount under the public key.
///
/// This function is randomized. It internally samples a scalar element using `OsRng`.
#[cfg(not(target_arch = "bpf"))]
pub fn encrypt<T: Into<Scalar>>(&self, msg: T) -> ElGamalCiphertext {
ElGamal::encrypt(self, msg)
pub fn encrypt<T: Into<Scalar>>(&self, amount: T) -> ElGamalCiphertext {
ElGamal::encrypt(self, amount)
}
/// Utility method for code ergonomics.
/// Encrypts an amount under the public key and an input Pedersen opening.
pub fn encrypt_with<T: Into<Scalar>>(
&self,
msg: T,
open: &PedersenOpening,
amount: T,
opening: &PedersenOpening,
) -> ElGamalCiphertext {
ElGamal::encrypt_with(self, msg, open)
ElGamal::encrypt_with(amount, self, opening)
}
/// Generate a decryption token from an ElGamal public key and a Pedersen
/// Generates a decryption handle for an ElGamal public key under a Pedersen
/// opening.
pub fn decrypt_handle(self, open: &PedersenOpening) -> PedersenDecryptHandle {
PedersenDecryptHandle::new(&self, open)
}
}
impl From<RistrettoPoint> for ElGamalPubkey {
fn from(point: RistrettoPoint) -> ElGamalPubkey {
ElGamalPubkey(point)
pub fn decrypt_handle(self, opening: &PedersenOpening) -> DecryptHandle {
DecryptHandle::new(&self, opening)
}
}
@ -316,10 +309,14 @@ impl fmt::Display for ElGamalPubkey {
}
/// Secret key for the ElGamal encryption scheme.
#[derive(Serialize, Deserialize, Debug, Zeroize)]
///
/// Instances of ElGamal secret key are zeroized on drop.
#[derive(Clone, Debug, Deserialize, Serialize, Zeroize)]
#[zeroize(drop)]
pub struct ElGamalSecretKey(Scalar);
impl ElGamalSecretKey {
/// Deterministically derives an ElGamal keypair from an Ed25519 signing key and a Solana
/// address.
pub fn new(signer: &dyn Signer, address: &Pubkey) -> Result<Self, SignerError> {
let message = Message::new(
&[Instruction::new_with_bytes(
@ -342,27 +339,42 @@ impl ElGamalSecretKey {
}
}
pub fn get_scalar(&self) -> Scalar {
self.0
/// Randomly samples an ElGamal secret key.
///
/// This function is randomized. It internally samples a scalar element using `OsRng`.
pub fn new_rand() -> Self {
ElGamalSecretKey(Scalar::random(&mut OsRng))
}
/// Utility method for code ergonomics.
pub fn decrypt(&self, ct: &ElGamalCiphertext) -> DiscreteLog {
ElGamal::decrypt(self, ct)
pub fn get_scalar(&self) -> &Scalar {
&self.0
}
/// Utility method for code ergonomics.
pub fn decrypt_u32(&self, ct: &ElGamalCiphertext) -> Option<u32> {
ElGamal::decrypt_u32(self, ct)
/// Decrypts a ciphertext using the ElGamal secret key.
///
/// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted
/// message, use `DiscreteLog::decode`.
pub fn decrypt(&self, ciphertext: &ElGamalCiphertext) -> DiscreteLog {
ElGamal::decrypt(self, ciphertext)
}
/// Utility method for code ergonomics.
/// Decrypts a ciphertext using the ElGamal secret key interpretting the message as type `u32`.
pub fn decrypt_u32(&self, ciphertext: &ElGamalCiphertext) -> Option<u32> {
ElGamal::decrypt_u32(self, ciphertext)
}
/// Decrypts a ciphertext using the ElGamal secret key and a pre-computed hashmap. It
/// interprets the decrypted message as type `u32`.
pub fn decrypt_u32_online(
&self,
ct: &ElGamalCiphertext,
ciphertext: &ElGamalCiphertext,
hashmap: &DecodeU32Precomputation,
) -> Option<u32> {
ElGamal::decrypt_u32_online(self, ct, hashmap)
ElGamal::decrypt_u32_online(self, ciphertext, hashmap)
}
pub fn as_bytes(&self) -> &[u8; 32] {
self.0.as_bytes()
}
pub fn to_bytes(&self) -> [u8; 32] {
@ -394,61 +406,64 @@ impl ConstantTimeEq for ElGamalSecretKey {
/// Ciphertext for the ElGamal encryption scheme.
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ElGamalCiphertext {
pub message_comm: PedersenCommitment,
pub decrypt_handle: PedersenDecryptHandle,
pub commitment: PedersenCommitment,
pub handle: DecryptHandle,
}
impl ElGamalCiphertext {
pub fn add_to_msg<T: Into<Scalar>>(&self, message: T) -> Self {
let diff_comm = Pedersen::with(message, &PedersenOpening::default());
pub fn add_amount<T: Into<Scalar>>(&self, amount: T) -> Self {
let commitment_to_add = PedersenCommitment(amount.into() * &(*G));
ElGamalCiphertext {
message_comm: self.message_comm + diff_comm,
decrypt_handle: self.decrypt_handle,
commitment: &self.commitment + &commitment_to_add,
handle: self.handle,
}
}
pub fn sub_to_msg<T: Into<Scalar>>(&self, message: T) -> Self {
let diff_comm = Pedersen::with(message, &PedersenOpening::default());
pub fn subtract_amount<T: Into<Scalar>>(&self, amount: T) -> Self {
let commitment_to_subtract = PedersenCommitment(amount.into() * &(*G));
ElGamalCiphertext {
message_comm: self.message_comm - diff_comm,
decrypt_handle: self.decrypt_handle,
commitment: &self.commitment - &commitment_to_subtract,
handle: self.handle,
}
}
#[allow(clippy::wrong_self_convention)]
pub fn to_bytes(&self) -> [u8; 64] {
let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(self.message_comm.get_point().compress().as_bytes());
bytes[32..].copy_from_slice(self.decrypt_handle.get_point().compress().as_bytes());
bytes[..32].copy_from_slice(&self.commitment.to_bytes());
bytes[32..].copy_from_slice(&self.handle.to_bytes());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalCiphertext> {
let bytes = array_ref![bytes, 0, 64];
let (message_comm, decrypt_handle) = array_refs![bytes, 32, 32];
let (commitment, handle) = array_refs![bytes, 32, 32];
let message_comm = CompressedRistretto::from_slice(message_comm).decompress()?;
let decrypt_handle = CompressedRistretto::from_slice(decrypt_handle).decompress()?;
let commitment = CompressedRistretto::from_slice(commitment).decompress()?;
let handle = CompressedRistretto::from_slice(handle).decompress()?;
Some(ElGamalCiphertext {
message_comm: PedersenCommitment(message_comm),
decrypt_handle: PedersenDecryptHandle(decrypt_handle),
commitment: PedersenCommitment(commitment),
handle: DecryptHandle(handle),
})
}
/// Utility method for code ergonomics.
/// Decrypts the ciphertext using an ElGamal secret key.
///
/// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted
/// message, use `DiscreteLog::decode`.
pub fn decrypt(&self, secret: &ElGamalSecretKey) -> DiscreteLog {
ElGamal::decrypt(secret, self)
}
/// Utility method for code ergonomics.
/// Decrypts the ciphertext using an ElGamal secret key interpretting the message as type `u32`.
pub fn decrypt_u32(&self, secret: &ElGamalSecretKey) -> Option<u32> {
ElGamal::decrypt_u32(secret, self)
}
/// Utility method for code ergonomics.
/// Decrypts the ciphertext using an ElGamal secret key and a pre-computed hashmap. It
/// interprets the decrypted message as type `u32`.
pub fn decrypt_u32_online(
&self,
secret: &ElGamalSecretKey,
@ -458,22 +473,13 @@ impl ElGamalCiphertext {
}
}
impl From<(PedersenCommitment, PedersenDecryptHandle)> for ElGamalCiphertext {
fn from((comm, handle): (PedersenCommitment, PedersenDecryptHandle)) -> Self {
ElGamalCiphertext {
message_comm: comm,
decrypt_handle: handle,
}
}
}
impl<'a, 'b> Add<&'b ElGamalCiphertext> for &'a ElGamalCiphertext {
type Output = ElGamalCiphertext;
fn add(self, other: &'b ElGamalCiphertext) -> ElGamalCiphertext {
ElGamalCiphertext {
message_comm: self.message_comm + other.message_comm,
decrypt_handle: self.decrypt_handle + other.decrypt_handle,
commitment: &self.commitment + &other.commitment,
handle: &self.handle + &other.handle,
}
}
}
@ -489,8 +495,8 @@ impl<'a, 'b> Sub<&'b ElGamalCiphertext> for &'a ElGamalCiphertext {
fn sub(self, other: &'b ElGamalCiphertext) -> ElGamalCiphertext {
ElGamalCiphertext {
message_comm: self.message_comm - other.message_comm,
decrypt_handle: self.decrypt_handle - other.decrypt_handle,
commitment: &self.commitment - &other.commitment,
handle: &self.handle - &other.handle,
}
}
}
@ -506,8 +512,8 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a ElGamalCiphertext {
fn mul(self, other: &'b Scalar) -> ElGamalCiphertext {
ElGamalCiphertext {
message_comm: self.message_comm * other,
decrypt_handle: self.decrypt_handle * other,
commitment: &self.commitment * other,
handle: &self.handle * other,
}
}
}
@ -518,178 +524,215 @@ define_mul_variants!(
Output = ElGamalCiphertext
);
impl<'a, 'b> Div<&'b Scalar> for &'a ElGamalCiphertext {
type Output = ElGamalCiphertext;
/// Decryption handle for Pedersen commitment.
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct DecryptHandle(RistrettoPoint);
impl DecryptHandle {
pub fn new(public: &ElGamalPubkey, opening: &PedersenOpening) -> Self {
Self(&public.0 * &opening.0)
}
fn div(self, other: &'b Scalar) -> ElGamalCiphertext {
ElGamalCiphertext {
message_comm: self.message_comm * other.invert(),
decrypt_handle: self.decrypt_handle * other.invert(),
}
pub fn get_point(&self) -> &RistrettoPoint {
&self.0
}
#[allow(clippy::wrong_self_convention)]
pub fn to_bytes(&self) -> [u8; 32] {
self.0.compress().to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Option<DecryptHandle> {
Some(DecryptHandle(
CompressedRistretto::from_slice(bytes).decompress()?,
))
}
}
define_div_variants!(
LHS = ElGamalCiphertext,
RHS = Scalar,
Output = ElGamalCiphertext
impl<'a, 'b> Add<&'b DecryptHandle> for &'a DecryptHandle {
type Output = DecryptHandle;
fn add(self, other: &'b DecryptHandle) -> DecryptHandle {
DecryptHandle(&self.0 + &other.0)
}
}
define_add_variants!(
LHS = DecryptHandle,
RHS = DecryptHandle,
Output = DecryptHandle
);
impl<'a, 'b> Sub<&'b DecryptHandle> for &'a DecryptHandle {
type Output = DecryptHandle;
fn sub(self, other: &'b DecryptHandle) -> DecryptHandle {
DecryptHandle(&self.0 - &other.0)
}
}
define_sub_variants!(
LHS = DecryptHandle,
RHS = DecryptHandle,
Output = DecryptHandle
);
impl<'a, 'b> Mul<&'b Scalar> for &'a DecryptHandle {
type Output = DecryptHandle;
fn mul(self, other: &'b Scalar) -> DecryptHandle {
DecryptHandle(&self.0 * other)
}
}
define_mul_variants!(LHS = DecryptHandle, RHS = Scalar, Output = DecryptHandle);
#[cfg(test)]
mod tests {
use {
super::*,
crate::encryption::pedersen::Pedersen,
crate::encryption::{discrete_log::DECODE_U32_PRECOMPUTATION_FOR_G, pedersen::Pedersen},
solana_sdk::{signature::Keypair, signer::null_signer::NullSigner},
};
#[test]
fn test_encrypt_decrypt_correctness() {
let ElGamalKeypair { public, secret } = ElGamalKeypair::default();
let msg: u32 = 57;
let ct = ElGamal::encrypt(&public, msg);
let ElGamalKeypair { public, secret } = ElGamalKeypair::new_rand();
let amount: u32 = 57;
let ciphertext = ElGamal::encrypt(&public, amount);
let expected_instance = DiscreteLog {
generator: PedersenBase::default().G,
target: Scalar::from(msg) * PedersenBase::default().G,
generator: *G,
target: Scalar::from(amount) * &(*G),
};
assert_eq!(expected_instance, ElGamal::decrypt(&secret, &ct));
// Commenting out for faster testing
// assert_eq!(msg, ElGamalKeypair::decrypt_u32(&secret, &ct).unwrap());
assert_eq!(expected_instance, ElGamal::decrypt(&secret, &ciphertext));
assert_eq!(
57_u32,
secret
.decrypt_u32_online(&ciphertext, &(*DECODE_U32_PRECOMPUTATION_FOR_G))
.unwrap()
);
}
#[test]
fn test_decrypt_handle() {
let ElGamalKeypair {
public: pk_1,
secret: sk_1,
} = ElGamalKeypair::default();
public: public_0,
secret: secret_0,
} = ElGamalKeypair::new_rand();
let ElGamalKeypair {
public: pk_2,
secret: sk_2,
} = ElGamalKeypair::default();
public: public_1,
secret: secret_1,
} = ElGamalKeypair::new_rand();
let msg: u32 = 77;
let (comm, open) = Pedersen::new(msg);
let amount: u32 = 77;
let (commitment, opening) = Pedersen::new(amount);
let decrypt_handle_1 = pk_1.decrypt_handle(&open);
let decrypt_handle_2 = pk_2.decrypt_handle(&open);
let handle_0 = public_0.decrypt_handle(&opening);
let handle_1 = public_1.decrypt_handle(&opening);
let ct_1: ElGamalCiphertext = (comm, decrypt_handle_1).into();
let ct_2: ElGamalCiphertext = (comm, decrypt_handle_2).into();
let expected_instance = DiscreteLog {
generator: PedersenBase::default().G,
target: Scalar::from(msg) * PedersenBase::default().G,
let ciphertext_0 = ElGamalCiphertext {
commitment,
handle: handle_0,
};
let ciphertext_1 = ElGamalCiphertext {
commitment,
handle: handle_1,
};
assert_eq!(expected_instance, sk_1.decrypt(&ct_1));
assert_eq!(expected_instance, sk_2.decrypt(&ct_2));
let expected_instance = DiscreteLog {
generator: *G,
target: Scalar::from(amount) * (*G),
};
// Commenting out for faster testing
// assert_eq!(msg, sk_1.decrypt_u32(&ct_1).unwrap());
// assert_eq!(msg, sk_2.decrypt_u32(&ct_2).unwrap());
assert_eq!(expected_instance, secret_0.decrypt(&ciphertext_0));
assert_eq!(expected_instance, secret_1.decrypt(&ciphertext_1));
}
#[test]
fn test_homomorphic_addition() {
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default();
let msg_0: u64 = 57;
let msg_1: u64 = 77;
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
let amount_0: u64 = 57;
let amount_1: u64 = 77;
// Add two ElGamal ciphertexts
let open_0 = PedersenOpening::random(&mut OsRng);
let open_1 = PedersenOpening::random(&mut OsRng);
let opening_0 = PedersenOpening::new_rand();
let opening_1 = PedersenOpening::new_rand();
let ct_0 = ElGamal::encrypt_with(&public, msg_0, &open_0);
let ct_1 = ElGamal::encrypt_with(&public, msg_1, &open_1);
let ciphertext_0 = ElGamal::encrypt_with(amount_0, &public, &opening_0);
let ciphertext_1 = ElGamal::encrypt_with(amount_1, &public, &opening_1);
let ct_sum = ElGamal::encrypt_with(&public, msg_0 + msg_1, &(open_0 + open_1));
let ciphertext_sum =
ElGamal::encrypt_with(amount_0 + amount_1, &public, &(&opening_0 + &opening_1));
assert_eq!(ct_sum, ct_0 + ct_1);
assert_eq!(ciphertext_sum, ciphertext_0 + ciphertext_1);
// Add to ElGamal ciphertext
let open = PedersenOpening::random(&mut OsRng);
let ct = ElGamal::encrypt_with(&public, msg_0, &open);
let ct_sum = ElGamal::encrypt_with(&public, msg_0 + msg_1, &open);
let opening = PedersenOpening::new_rand();
let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening);
let ciphertext_sum = ElGamal::encrypt_with(amount_0 + amount_1, &public, &opening);
assert_eq!(ct_sum, ct.add_to_msg(msg_1));
assert_eq!(ciphertext_sum, ciphertext.add_amount(amount_1));
}
#[test]
fn test_homomorphic_subtraction() {
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default();
let msg_0: u64 = 77;
let msg_1: u64 = 55;
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
let amount_0: u64 = 77;
let amount_1: u64 = 55;
// Subtract two ElGamal ciphertexts
let open_0 = PedersenOpening::random(&mut OsRng);
let open_1 = PedersenOpening::random(&mut OsRng);
let opening_0 = PedersenOpening::new_rand();
let opening_1 = PedersenOpening::new_rand();
let ct_0 = ElGamal::encrypt_with(&public, msg_0, &open_0);
let ct_1 = ElGamal::encrypt_with(&public, msg_1, &open_1);
let ciphertext_0 = ElGamal::encrypt_with(amount_0, &public, &opening_0);
let ciphertext_1 = ElGamal::encrypt_with(amount_1, &public, &opening_1);
let ct_sub = ElGamal::encrypt_with(&public, msg_0 - msg_1, &(open_0 - open_1));
let ciphertext_sub =
ElGamal::encrypt_with(amount_0 - amount_1, &public, &(&opening_0 - &opening_1));
assert_eq!(ct_sub, ct_0 - ct_1);
assert_eq!(ciphertext_sub, ciphertext_0 - ciphertext_1);
// Subtract to ElGamal ciphertext
let open = PedersenOpening::random(&mut OsRng);
let ct = ElGamal::encrypt_with(&public, msg_0, &open);
let ct_sub = ElGamal::encrypt_with(&public, msg_0 - msg_1, &open);
let opening = PedersenOpening::new_rand();
let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening);
let ciphertext_sub = ElGamal::encrypt_with(amount_0 - amount_1, &public, &opening);
assert_eq!(ct_sub, ct.sub_to_msg(msg_1));
assert_eq!(ciphertext_sub, ciphertext.subtract_amount(amount_1));
}
#[test]
fn test_homomorphic_multiplication() {
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default();
let msg_0: u64 = 57;
let msg_1: u64 = 77;
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
let amount_0: u64 = 57;
let amount_1: u64 = 77;
let open = PedersenOpening::random(&mut OsRng);
let opening = PedersenOpening::new_rand();
let ct = ElGamal::encrypt_with(&public, msg_0, &open);
let scalar = Scalar::from(msg_1);
let ciphertext = ElGamal::encrypt_with(amount_0, &public, &opening);
let scalar = Scalar::from(amount_1);
let ct_prod = ElGamal::encrypt_with(&public, msg_0 * msg_1, &(open * scalar));
let ciphertext_prod =
ElGamal::encrypt_with(amount_0 * amount_1, &public, &(&opening * scalar));
assert_eq!(ct_prod, ct * scalar);
}
#[test]
fn test_homomorphic_division() {
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default();
let msg_0: u64 = 55;
let msg_1: u64 = 5;
let open = PedersenOpening::random(&mut OsRng);
let ct = ElGamal::encrypt_with(&public, msg_0, &open);
let scalar = Scalar::from(msg_1);
let ct_div = ElGamal::encrypt_with(&public, msg_0 / msg_1, &(open / scalar));
assert_eq!(ct_div, ct / scalar);
assert_eq!(ciphertext_prod, ciphertext * scalar);
}
#[test]
fn test_serde_ciphertext() {
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default();
let msg: u64 = 77;
let ct = public.encrypt(msg);
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
let amount: u64 = 77;
let ciphertext = public.encrypt(amount);
let encoded = bincode::serialize(&ct).unwrap();
let encoded = bincode::serialize(&ciphertext).unwrap();
let decoded: ElGamalCiphertext = bincode::deserialize(&encoded).unwrap();
assert_eq!(ct, decoded);
assert_eq!(ciphertext, decoded);
}
#[test]
fn test_serde_pubkey() {
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::default();
let ElGamalKeypair { public, secret: _ } = ElGamalKeypair::new_rand();
let encoded = bincode::serialize(&public).unwrap();
let decoded: ElGamalPubkey = bincode::deserialize(&encoded).unwrap();
@ -699,7 +742,7 @@ mod tests {
#[test]
fn test_serde_secretkey() {
let ElGamalKeypair { public: _, secret } = ElGamalKeypair::default();
let ElGamalKeypair { public: _, secret } = ElGamalKeypair::new_rand();
let encoded = bincode::serialize(&secret).unwrap();
let decoded: ElGamalSecretKey = bincode::deserialize(&encoded).unwrap();
@ -710,14 +753,16 @@ mod tests {
fn tmp_file_path(name: &str) -> String {
use std::env;
let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let keypair = ElGamalKeypair::default();
let keypair = ElGamalKeypair::new_rand();
format!("{}/tmp/{}-{}", out_dir, name, keypair.public)
}
#[test]
fn test_write_keypair_file() {
let outfile = tmp_file_path("test_write_keypair_file.json");
let serialized_keypair = ElGamalKeypair::default().write_json_file(&outfile).unwrap();
let serialized_keypair = ElGamalKeypair::new_rand()
.write_json_file(&outfile)
.unwrap();
let keypair_vec: Vec<u8> = serde_json::from_str(&serialized_keypair).unwrap();
assert!(Path::new(&outfile).exists());
assert_eq!(
@ -749,15 +794,21 @@ mod tests {
fn test_write_keypair_file_overwrite_ok() {
let outfile = tmp_file_path("test_write_keypair_file_overwrite_ok.json");
ElGamalKeypair::default().write_json_file(&outfile).unwrap();
ElGamalKeypair::default().write_json_file(&outfile).unwrap();
ElGamalKeypair::new_rand()
.write_json_file(&outfile)
.unwrap();
ElGamalKeypair::new_rand()
.write_json_file(&outfile)
.unwrap();
}
#[test]
fn test_write_keypair_file_truncate() {
let outfile = tmp_file_path("test_write_keypair_file_truncate.json");
ElGamalKeypair::default().write_json_file(&outfile).unwrap();
ElGamalKeypair::new_rand()
.write_json_file(&outfile)
.unwrap();
ElGamalKeypair::read_json_file(&outfile).unwrap();
// Ensure outfile is truncated
@ -766,7 +817,9 @@ mod tests {
f.write_all(String::from_utf8([b'a'; 2048].to_vec()).unwrap().as_bytes())
.unwrap();
}
ElGamalKeypair::default().write_json_file(&outfile).unwrap();
ElGamalKeypair::new_rand()
.write_json_file(&outfile)
.unwrap();
ElGamalKeypair::read_json_file(&outfile).unwrap();
}
@ -787,4 +840,24 @@ mod tests {
let null_signer = NullSigner::new(&Pubkey::default());
assert!(ElGamalSecretKey::new(&null_signer, &Pubkey::default()).is_err());
}
#[test]
fn test_decrypt_handle_bytes() {
let handle = DecryptHandle(RistrettoPoint::default());
let encoded = handle.to_bytes();
let decoded = DecryptHandle::from_bytes(&encoded).unwrap();
assert_eq!(handle, decoded);
}
#[test]
fn test_serde_decrypt_handle() {
let handle = DecryptHandle(RistrettoPoint::default());
let encoded = bincode::serialize(&handle).unwrap();
let decoded: DecryptHandle = bincode::deserialize(&encoded).unwrap();
assert_eq!(handle, decoded);
}
}

View File

@ -1,3 +1,15 @@
//! Collection of encryption-related data structures and algorithms used in the Solana zk-token
//! protocol.
//!
//! The module contains implementations of the following cryptographic objects:
//! - Pedersen commitments that uses the prime-order Ristretto representation of Curve25519.
//! [curve25519-dalek](https://docs.rs/curve25519-dalek/latest/curve25519_dalek/ristretto/index.html)
//! is used for the Ristretto group implementation.
//! - The twisted ElGamal scheme, which converts Pedersen commitments into a public-key encryption
//! scheme.
//! - Basic type-wrapper around the AES-GCM-SIV symmetric authenticated encryption scheme
//! implemented by [aes-gcm-siv](https://docs.rs/aes-gcm-siv/latest/aes_gcm_siv/) crate.
pub mod auth_encryption;
pub mod discrete_log;
pub mod elgamal;

View File

@ -1,8 +1,9 @@
//! Pedersen commitment implementation using the Ristretto prime-order group.
#[cfg(not(target_arch = "bpf"))]
use rand::{rngs::OsRng, CryptoRng, RngCore};
use rand::rngs::OsRng;
use {
crate::encryption::elgamal::ElGamalPubkey,
core::ops::{Add, Div, Mul, Sub},
core::ops::{Add, Mul, Sub},
curve25519_dalek::{
constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT},
ristretto::{CompressedRistretto, RistrettoPoint},
@ -16,71 +17,62 @@ use {
zeroize::Zeroize,
};
/// Curve basepoints for which Pedersen commitment is defined over.
///
/// These points should be fixed for the entire system.
/// TODO: Consider setting these points as constants?
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
pub struct PedersenBase {
pub G: RistrettoPoint,
pub H: RistrettoPoint,
}
/// Default PedersenBase. This is set arbitrarily for now, but it should be fixed
/// for the entire system.
///
/// `G` is a constant point in the curve25519_dalek library
/// `H` is the Sha3 hash of `G` interpretted as a RistrettoPoint
impl Default for PedersenBase {
#[allow(non_snake_case)]
fn default() -> PedersenBase {
let G = RISTRETTO_BASEPOINT_POINT;
let H =
RistrettoPoint::hash_from_bytes::<Sha3_512>(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
PedersenBase { G, H }
}
lazy_static::lazy_static! {
/// Pedersen base point for encoding messages to be committed.
pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT;
/// Pedersen base point for encoding the commitment openings.
pub static ref H: RistrettoPoint =
RistrettoPoint::hash_from_bytes::<Sha3_512>(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
}
/// Handle for the Pedersen commitment scheme
/// Algorithm handle for the Pedersen commitment scheme.
pub struct Pedersen;
impl Pedersen {
/// Given a number as input, the function returns a Pedersen commitment of
/// the number and its corresponding opening.
/// On input a message, the function returns a Pedersen commitment of the message and the
/// corresponding opening.
///
/// This function is randomized. It internally samples a Pedersen opening using `OsRng`.
#[cfg(not(target_arch = "bpf"))]
#[allow(clippy::new_ret_no_self)]
pub fn new<T: Into<Scalar>>(amount: T) -> (PedersenCommitment, PedersenOpening) {
let open = PedersenOpening(Scalar::random(&mut OsRng));
let comm = Pedersen::with(amount, &open);
pub fn new<T: Into<Scalar>>(message: T) -> (PedersenCommitment, PedersenOpening) {
let opening = PedersenOpening::new_rand();
let commitment = Pedersen::with(message, &opening);
(comm, open)
(commitment, opening)
}
/// Given a number and an opening as inputs, the function returns their
/// Pedersen commitment.
/// On input a message and a Pedersen opening, the function returns the corresponding Pedersen
/// commitment.
///
/// This function is deterministic.
#[allow(non_snake_case)]
pub fn with<T: Into<Scalar>>(amount: T, open: &PedersenOpening) -> PedersenCommitment {
let G = PedersenBase::default().G;
let H = PedersenBase::default().H;
let x: Scalar = amount.into();
let r = open.get_scalar();
PedersenCommitment(RistrettoPoint::multiscalar_mul(&[x, r], &[G, H]))
PedersenCommitment(RistrettoPoint::multiscalar_mul(&[x, *r], &[*G, *H]))
}
}
#[derive(Serialize, Deserialize, Default, Clone, Debug, Zeroize)]
/// Pedersen opening type.
///
/// Instances of Pedersen openings are zeroized on drop.
#[derive(Clone, Debug, Default, Serialize, Deserialize, Zeroize)]
#[zeroize(drop)]
pub struct PedersenOpening(pub(crate) Scalar);
impl PedersenOpening {
pub fn get_scalar(&self) -> Scalar {
self.0
pub fn get_scalar(&self) -> &Scalar {
&self.0
}
#[cfg(not(target_arch = "bpf"))]
pub fn random<T: RngCore + CryptoRng>(rng: &mut T) -> Self {
PedersenOpening(Scalar::random(rng))
pub fn new_rand() -> Self {
PedersenOpening(Scalar::random(&mut OsRng))
}
#[allow(clippy::wrong_self_convention)]
pub fn as_bytes(&self) -> &[u8; 32] {
self.0.as_bytes()
}
#[allow(clippy::wrong_self_convention)]
@ -111,7 +103,7 @@ impl<'a, 'b> Add<&'b PedersenOpening> for &'a PedersenOpening {
type Output = PedersenOpening;
fn add(self, other: &'b PedersenOpening) -> PedersenOpening {
PedersenOpening(self.get_scalar() + other.get_scalar())
PedersenOpening(&self.0 + &other.0)
}
}
@ -125,7 +117,7 @@ impl<'a, 'b> Sub<&'b PedersenOpening> for &'a PedersenOpening {
type Output = PedersenOpening;
fn sub(self, other: &'b PedersenOpening) -> PedersenOpening {
PedersenOpening(self.get_scalar() - other.get_scalar())
PedersenOpening(&self.0 - &other.0)
}
}
@ -139,7 +131,7 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a PedersenOpening {
type Output = PedersenOpening;
fn mul(self, other: &'b Scalar) -> PedersenOpening {
PedersenOpening(self.get_scalar() * other)
PedersenOpening(&self.0 * other)
}
}
@ -149,26 +141,12 @@ define_mul_variants!(
Output = PedersenOpening
);
impl<'a, 'b> Div<&'b Scalar> for &'a PedersenOpening {
type Output = PedersenOpening;
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, other: &'b Scalar) -> PedersenOpening {
PedersenOpening(self.get_scalar() * other.invert())
}
}
define_div_variants!(
LHS = PedersenOpening,
RHS = Scalar,
Output = PedersenOpening
);
#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, Eq, PartialEq)]
/// Pedersen commitment type.
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct PedersenCommitment(pub(crate) RistrettoPoint);
impl PedersenCommitment {
pub fn get_point(&self) -> RistrettoPoint {
self.0
pub fn get_point(&self) -> &RistrettoPoint {
&self.0
}
#[allow(clippy::wrong_self_convention)]
@ -187,7 +165,7 @@ impl<'a, 'b> Add<&'b PedersenCommitment> for &'a PedersenCommitment {
type Output = PedersenCommitment;
fn add(self, other: &'b PedersenCommitment) -> PedersenCommitment {
PedersenCommitment(self.get_point() + other.get_point())
PedersenCommitment(&self.0 + &other.0)
}
}
@ -201,7 +179,7 @@ impl<'a, 'b> Sub<&'b PedersenCommitment> for &'a PedersenCommitment {
type Output = PedersenCommitment;
fn sub(self, other: &'b PedersenCommitment) -> PedersenCommitment {
PedersenCommitment(self.get_point() - other.get_point())
PedersenCommitment(&self.0 - &other.0)
}
}
@ -215,7 +193,7 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a PedersenCommitment {
type Output = PedersenCommitment;
fn mul(self, other: &'b Scalar) -> PedersenCommitment {
PedersenCommitment(self.get_point() * other)
PedersenCommitment(&self.0 * other)
}
}
@ -225,111 +203,12 @@ define_mul_variants!(
Output = PedersenCommitment
);
impl<'a, 'b> Div<&'b Scalar> for &'a PedersenCommitment {
type Output = PedersenCommitment;
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, other: &'b Scalar) -> PedersenCommitment {
PedersenCommitment(self.get_point() * other.invert())
}
}
define_div_variants!(
LHS = PedersenCommitment,
RHS = Scalar,
Output = PedersenCommitment
);
/// Decryption handle for Pedersen commitment.
///
/// A decryption handle can be combined with Pedersen commitments to form an
/// ElGamal ciphertext.
#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, Eq, PartialEq)]
pub struct PedersenDecryptHandle(pub(crate) RistrettoPoint);
impl PedersenDecryptHandle {
pub fn new(pk: &ElGamalPubkey, open: &PedersenOpening) -> Self {
Self(pk.get_point() * open.get_scalar())
}
pub fn get_point(&self) -> RistrettoPoint {
self.0
}
#[allow(clippy::wrong_self_convention)]
pub fn to_bytes(&self) -> [u8; 32] {
self.0.compress().to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Option<PedersenDecryptHandle> {
Some(PedersenDecryptHandle(
CompressedRistretto::from_slice(bytes).decompress()?,
))
}
}
impl<'a, 'b> Add<&'b PedersenDecryptHandle> for &'a PedersenDecryptHandle {
type Output = PedersenDecryptHandle;
fn add(self, other: &'b PedersenDecryptHandle) -> PedersenDecryptHandle {
PedersenDecryptHandle(self.get_point() + other.get_point())
}
}
define_add_variants!(
LHS = PedersenDecryptHandle,
RHS = PedersenDecryptHandle,
Output = PedersenDecryptHandle
);
impl<'a, 'b> Sub<&'b PedersenDecryptHandle> for &'a PedersenDecryptHandle {
type Output = PedersenDecryptHandle;
fn sub(self, other: &'b PedersenDecryptHandle) -> PedersenDecryptHandle {
PedersenDecryptHandle(self.get_point() - other.get_point())
}
}
define_sub_variants!(
LHS = PedersenDecryptHandle,
RHS = PedersenDecryptHandle,
Output = PedersenDecryptHandle
);
impl<'a, 'b> Mul<&'b Scalar> for &'a PedersenDecryptHandle {
type Output = PedersenDecryptHandle;
fn mul(self, other: &'b Scalar) -> PedersenDecryptHandle {
PedersenDecryptHandle(self.get_point() * other)
}
}
define_mul_variants!(
LHS = PedersenDecryptHandle,
RHS = Scalar,
Output = PedersenDecryptHandle
);
impl<'a, 'b> Div<&'b Scalar> for &'a PedersenDecryptHandle {
type Output = PedersenDecryptHandle;
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, other: &'b Scalar) -> PedersenDecryptHandle {
PedersenDecryptHandle(self.get_point() * other.invert())
}
}
define_div_variants!(
LHS = PedersenDecryptHandle,
RHS = Scalar,
Output = PedersenDecryptHandle
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_homomorphic_addition() {
fn test_pedersen_homomorphic_addition() {
let amt_0: u64 = 77;
let amt_1: u64 = 57;
@ -345,7 +224,7 @@ mod tests {
}
#[test]
fn test_homomorphic_subtraction() {
fn test_pedersen_homomorphic_subtraction() {
let amt_0: u64 = 77;
let amt_1: u64 = 57;
@ -361,7 +240,7 @@ mod tests {
}
#[test]
fn test_homomorphic_multiplication() {
fn test_pedersen_homomorphic_multiplication() {
let amt_0: u64 = 77;
let amt_1: u64 = 57;
@ -373,19 +252,7 @@ mod tests {
}
#[test]
fn test_homomorphic_division() {
let amt_0: u64 = 77;
let amt_1: u64 = 7;
let (comm, open) = Pedersen::new(amt_0);
let scalar = Scalar::from(amt_1);
let comm_addition = Pedersen::with(amt_0 / amt_1, &(open / scalar));
assert_eq!(comm_addition, comm / scalar);
}
#[test]
fn test_commitment_bytes() {
fn test_pedersen_commitment_bytes() {
let amt: u64 = 77;
let (comm, _) = Pedersen::new(amt);
@ -396,7 +263,7 @@ mod tests {
}
#[test]
fn test_opening_bytes() {
fn test_pedersen_opening_bytes() {
let open = PedersenOpening(Scalar::random(&mut OsRng));
let encoded = open.to_bytes();
@ -406,17 +273,7 @@ mod tests {
}
#[test]
fn test_decrypt_handle_bytes() {
let handle = PedersenDecryptHandle(RistrettoPoint::default());
let encoded = handle.to_bytes();
let decoded = PedersenDecryptHandle::from_bytes(&encoded).unwrap();
assert_eq!(handle, decoded);
}
#[test]
fn test_serde_commitment() {
fn test_serde_pedersen_commitment() {
let amt: u64 = 77;
let (comm, _) = Pedersen::new(amt);
@ -427,7 +284,7 @@ mod tests {
}
#[test]
fn test_serde_opening() {
fn test_serde_pedersen_opening() {
let open = PedersenOpening(Scalar::random(&mut OsRng));
let encoded = bincode::serialize(&open).unwrap();
@ -435,14 +292,4 @@ mod tests {
assert_eq!(open, decoded);
}
#[test]
fn test_serde_decrypt_handle() {
let handle = PedersenDecryptHandle(RistrettoPoint::default());
let encoded = bincode::serialize(&handle).unwrap();
let decoded: PedersenDecryptHandle = bincode::deserialize(&encoded).unwrap();
assert_eq!(handle, decoded);
}
}

View File

@ -109,14 +109,14 @@ mod test {
use {
super::*,
crate::encryption::{
elgamal::ElGamalKeypair,
pedersen::{Pedersen, PedersenDecryptHandle, PedersenOpening},
elgamal::{DecryptHandle, ElGamalKeypair},
pedersen::{Pedersen, PedersenOpening},
},
};
#[test]
fn test_close_account_correctness() {
let source_keypair = ElGamalKeypair::default();
let source_keypair = ElGamalKeypair::new_rand();
// general case: encryption of 0
let balance = source_keypair.public.encrypt(0_u64);
@ -135,11 +135,11 @@ mod test {
// edge cases: only C or D is zero - such ciphertext is always invalid
let zeroed_comm = Pedersen::with(0_u64, &PedersenOpening::default());
let handle = balance.decrypt_handle;
let handle = balance.handle;
let zeroed_comm_ciphertext = ElGamalCiphertext {
message_comm: zeroed_comm,
decrypt_handle: handle,
commitment: zeroed_comm,
handle,
};
let proof = CloseAccountProof::new(&source_keypair, &zeroed_comm_ciphertext);
@ -148,8 +148,8 @@ mod test {
.is_err());
let zeroed_handle_ciphertext = ElGamalCiphertext {
message_comm: balance.message_comm,
decrypt_handle: PedersenDecryptHandle::default(),
commitment: balance.commitment,
handle: DecryptHandle::default(),
};
let proof = CloseAccountProof::new(&source_keypair, &zeroed_handle_ciphertext);

View File

@ -7,8 +7,10 @@ use {
crate::{
encryption::{
discrete_log::*,
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey},
pedersen::{Pedersen, PedersenCommitment, PedersenDecryptHandle, PedersenOpening},
elgamal::{
DecryptHandle, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey,
},
pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
},
errors::ProofError,
instruction::{Role, Verifiable},
@ -128,8 +130,8 @@ impl TransferData {
};
// subtract transfer amount from the spendable ciphertext
let spendable_comm = spendable_balance_ciphertext.message_comm;
let spendable_handle = spendable_balance_ciphertext.decrypt_handle;
let spendable_comm = spendable_balance_ciphertext.commitment;
let spendable_handle = spendable_balance_ciphertext.handle;
let new_spendable_balance = spendable_balance - transfer_amount;
let new_spendable_comm = spendable_comm - combine_u32_comms(comm_lo, comm_hi);
@ -137,8 +139,8 @@ impl TransferData {
spendable_handle - combine_u32_handles(handle_source_lo, handle_source_hi);
let new_spendable_ct = ElGamalCiphertext {
message_comm: new_spendable_comm,
decrypt_handle: new_spendable_handle,
commitment: new_spendable_comm,
handle: new_spendable_handle,
};
// range_proof and validity_proof should be generated together
@ -172,7 +174,10 @@ impl TransferData {
}
.try_into()?;
Ok((transfer_comm_lo, decryption_handle_lo).into())
Ok(ElGamalCiphertext {
commitment: transfer_comm_lo,
handle: decryption_handle_lo,
})
}
/// Extracts the lo ciphertexts associated with a transfer data
@ -187,7 +192,10 @@ impl TransferData {
}
.try_into()?;
Ok((transfer_comm_hi, decryption_handle_hi).into())
Ok(ElGamalCiphertext {
commitment: transfer_comm_hi,
handle: decryption_handle_hi,
})
}
/// Decrypts transfer amount from transfer data
@ -274,8 +282,8 @@ impl TransferProof {
// extract the relevant scalar and Ristretto points from the inputs
let P_EG = source_keypair.public.get_point();
let C_EG = source_new_balance_ct.message_comm.get_point();
let D_EG = source_new_balance_ct.decrypt_handle.get_point();
let C_EG = source_new_balance_ct.commitment.get_point();
let D_EG = source_new_balance_ct.handle.get_point();
let C_Ped = source_commitment.get_point();
// append all current state to the transcript
@ -339,8 +347,8 @@ impl TransferProof {
let new_spendable_ct: ElGamalCiphertext = (*new_spendable_ct).try_into()?;
let P_EG = source_pk.get_point();
let C_EG = new_spendable_ct.message_comm.get_point();
let D_EG = new_spendable_ct.decrypt_handle.get_point();
let C_EG = new_spendable_ct.commitment.get_point();
let D_EG = new_spendable_ct.handle.get_point();
let C_Ped = commitment.get_point();
// append all current state to the transcript
@ -361,11 +369,11 @@ impl TransferProof {
let amount_comm_lo: PedersenCommitment = amount_comms.lo.try_into()?;
let amount_comm_hi: PedersenCommitment = amount_comms.hi.try_into()?;
let handle_lo_dest: PedersenDecryptHandle = decryption_handles_lo.dest.try_into()?;
let handle_hi_dest: PedersenDecryptHandle = decryption_handles_hi.dest.try_into()?;
let handle_lo_dest: DecryptHandle = decryption_handles_lo.dest.try_into()?;
let handle_hi_dest: DecryptHandle = decryption_handles_hi.dest.try_into()?;
let handle_lo_auditor: PedersenDecryptHandle = decryption_handles_lo.auditor.try_into()?;
let handle_hi_auditor: PedersenDecryptHandle = decryption_handles_hi.auditor.try_into()?;
let handle_lo_auditor: DecryptHandle = decryption_handles_lo.auditor.try_into()?;
let handle_hi_auditor: DecryptHandle = decryption_handles_hi.auditor.try_into()?;
// TODO: validity proof
validity_proof.verify(
@ -419,9 +427,9 @@ pub struct EncryptedTransferAmount {
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferDecryptHandles {
pub source: pod::PedersenDecryptHandle, // 32 bytes
pub dest: pod::PedersenDecryptHandle, // 32 bytes
pub auditor: pod::PedersenDecryptHandle, // 32 bytes
pub source: pod::DecryptHandle, // 32 bytes
pub dest: pod::DecryptHandle, // 32 bytes
pub auditor: pod::DecryptHandle, // 32 bytes
}
#[derive(Clone, Copy, Pod, Zeroable)]
@ -437,9 +445,9 @@ pub struct EncryptedTransferFee {
/// The transfer fee commitment
pub fee_comm: pod::PedersenCommitment,
/// The decryption handle for destination ElGamal pubkey
pub decrypt_handle_dest: pod::PedersenDecryptHandle,
pub decrypt_handle_dest: pod::DecryptHandle,
/// The decryption handle for fee collector ElGamal pubkey
pub decrypt_handle_fee_collector: pod::PedersenDecryptHandle,
pub decrypt_handle_fee_collector: pod::DecryptHandle,
}
/// Split u64 number into two u32 numbers
@ -464,10 +472,7 @@ pub fn combine_u32_comms(
}
#[cfg(not(target_arch = "bpf"))]
pub fn combine_u32_handles(
handle_lo: PedersenDecryptHandle,
handle_hi: PedersenDecryptHandle,
) -> PedersenDecryptHandle {
pub fn combine_u32_handles(handle_lo: DecryptHandle, handle_hi: DecryptHandle) -> DecryptHandle {
handle_lo + handle_hi * Scalar::from(TWO_32)
}
@ -483,9 +488,9 @@ mod test {
#[test]
fn test_transfer_correctness() {
// ElGamalKeypair keys for source, destination, and auditor accounts
let source_keypair = ElGamalKeypair::default();
let dest_pk = ElGamalKeypair::default().public;
let auditor_pk = ElGamalKeypair::default().public;
let source_keypair = ElGamalKeypair::new_rand();
let dest_pk = ElGamalKeypair::new_rand().public;
let auditor_pk = ElGamalKeypair::new_rand().public;
// create source account spendable ciphertext
let spendable_balance: u64 = 77;
@ -510,17 +515,17 @@ mod test {
#[test]
fn test_source_dest_ciphertext() {
// ElGamalKeypair keys for source, destination, and auditor accounts
let source_keypair = ElGamalKeypair::default();
let source_keypair = ElGamalKeypair::new_rand();
let ElGamalKeypair {
public: dest_pk,
secret: dest_sk,
} = ElGamalKeypair::default();
} = ElGamalKeypair::new_rand();
let ElGamalKeypair {
public: auditor_pk,
secret: auditor_sk,
} = ElGamalKeypair::default();
} = ElGamalKeypair::new_rand();
// create source account spendable ciphertext
let spendable_balance: u64 = 77;

View File

@ -117,8 +117,8 @@ impl WithdrawProof {
// extract the relevant scalar and Ristretto points from the inputs
let P_EG = source_keypair.public.get_point();
let C_EG = final_balance_ct.message_comm.get_point();
let D_EG = final_balance_ct.decrypt_handle.get_point();
let C_EG = final_balance_ct.commitment.get_point();
let D_EG = final_balance_ct.handle.get_point();
let C_Ped = commitment.get_point();
// append all current state to the transcript
@ -166,8 +166,8 @@ impl WithdrawProof {
// 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_EG = final_balance_ct.commitment.get_point();
let D_EG = final_balance_ct.handle.get_point();
let C_Ped = commitment.get_point();
// append all current state to the transcript
@ -201,7 +201,7 @@ mod test {
#[test]
fn test_withdraw_correctness() {
// generate and verify proof for the proper setting
let elgamal_keypair = ElGamalKeypair::default();
let elgamal_keypair = ElGamalKeypair::new_rand();
let current_balance: u64 = 77;
let current_balance_ct = elgamal_keypair.public.encrypt(current_balance);

View File

@ -1,4 +1,21 @@
#![allow(clippy::integer_arithmetic)]
#![allow(clippy::integer_arithmetic, clippy::op_ref)]
// The warning `clippy::op_ref` is disabled to allow efficient operator arithmetic of structs that
// implement the `Copy` trait.
//
// ```
// let opening_0: PedersenOpening = PedersenOpening::new_rand();
// let opening_1: PedersenOpening = PedersenOpening::new_rand();
//
// // since PedersenOpening implement `Copy`, `opening_0` and `opening_1` will be copied as
// // parameters before `opening_sum` is computed.
// let opening_sum = opening_0 + opening_1;
//
// // if passed in as references, the extra copies will not occur
// let opening_sum = &opening_0 + &opening_1;
// ```
//
// `clippy::op_ref` is turned off to prevent clippy from warning that this is not idiomatic code.
#[cfg(not(target_arch = "bpf"))]
#[macro_use]

View File

@ -74,28 +74,3 @@ macro_rules! define_mul_variants {
}
};
}
macro_rules! define_div_variants {
(LHS = $lhs:ty, RHS = $rhs:ty, Output = $out:ty) => {
impl<'b> Div<&'b $rhs> for $lhs {
type Output = $out;
fn div(self, rhs: &'b $rhs) -> $out {
&self / rhs
}
}
impl<'a> Div<$rhs> for &'a $lhs {
type Output = $out;
fn div(self, rhs: $rhs) -> $out {
self / &rhs
}
}
impl Div<$rhs> for $lhs {
type Output = $out;
fn div(self, rhs: $rhs) -> $out {
&self / &rhs
}
}
};
}

View File

@ -7,7 +7,7 @@ use {
};
use {
crate::{
encryption::pedersen::PedersenBase,
encryption::pedersen::{G, H},
range_proof::{
errors::RangeProofError, generators::BulletproofGens, inner_product::InnerProductProof,
},
@ -71,12 +71,10 @@ impl RangeProof {
// TODO: precompute generators
// TODO: double check Pedersen generators and range proof generators does not interfere
let bp_gens = BulletproofGens::new(nm);
let G = PedersenBase::default().G;
let H = PedersenBase::default().H;
// bit-decompose values and generate their Pedersen vector commitment
let a_blinding = Scalar::random(&mut OsRng);
let mut A = a_blinding * H;
let mut A = a_blinding * &(*H);
let mut gens_iter = bp_gens.G(nm).zip(bp_gens.H(nm));
for (amount_i, n_i) in amounts.iter().zip(bit_lengths.iter()) {
@ -100,7 +98,7 @@ impl RangeProof {
let S = RistrettoPoint::multiscalar_mul(
iter::once(&s_blinding).chain(s_L.iter()).chain(s_R.iter()),
iter::once(&H).chain(bp_gens.G(nm)).chain(bp_gens.H(nm)),
iter::once(&(*H)).chain(bp_gens.G(nm)).chain(bp_gens.H(nm)),
)
.compress();
@ -168,8 +166,8 @@ impl RangeProof {
let t_blinding_poly = util::Poly2(
agg_opening,
t_1_blinding.get_scalar(),
t_2_blinding.get_scalar(),
*t_1_blinding.get_scalar(),
*t_2_blinding.get_scalar(),
);
let t_x = t_poly.eval(x);
@ -188,7 +186,7 @@ impl RangeProof {
// compute the inner product argument on the commitment:
// P = <l(x), G> + <r(x), H'> + <l(x), r(x)>*Q
let w = transcript.challenge_scalar(b"w");
let Q = w * G;
let Q = w * &(*G);
let G_factors: Vec<Scalar> = iter::repeat(Scalar::one()).take(nm).collect();
let H_factors: Vec<Scalar> = util::exp_iter(y.invert()).take(nm).collect();
@ -229,9 +227,6 @@ impl RangeProof {
// commitments and bit-lengths must be same length vectors
assert_eq!(comms.len(), bit_lengths.len());
let G = PedersenBase::default().G;
let H = PedersenBase::default().H;
let m = bit_lengths.len();
let nm: usize = bit_lengths.iter().sum();
let bp_gens = BulletproofGens::new(nm);
@ -307,8 +302,8 @@ impl RangeProof {
.chain(iter::once(self.S.decompress()))
.chain(iter::once(self.T_1.decompress()))
.chain(iter::once(self.T_2.decompress()))
.chain(iter::once(Some(H)))
.chain(iter::once(Some(G)))
.chain(iter::once(Some(*H)))
.chain(iter::once(Some(*G)))
.chain(self.ipp_proof.L_vec.iter().map(|L| L.decompress()))
.chain(self.ipp_proof.R_vec.iter().map(|R| R.decompress()))
.chain(bp_gens.G(nm).map(|&x| Some(x)))

View File

@ -2,7 +2,7 @@
use {
crate::encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::{PedersenBase, PedersenCommitment, PedersenOpening},
pedersen::{PedersenCommitment, PedersenOpening, G, H},
},
curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng,
@ -40,11 +40,8 @@ impl EqualityProof {
transcript: &mut Transcript,
) -> Self {
// extract the relevant scalar and Ristretto points from the inputs
let G = PedersenBase::default().G;
let H = PedersenBase::default().H;
let P_EG = elgamal_keypair.public.get_point();
let D_EG = ciphertext.decrypt_handle.get_point();
let D_EG = ciphertext.handle.get_point();
let s = elgamal_keypair.secret.get_scalar();
let x = Scalar::from(message);
@ -56,8 +53,8 @@ impl EqualityProof {
let y_r = Scalar::random(&mut OsRng);
let Y_0 = (y_s * P_EG).compress();
let Y_1 = RistrettoPoint::multiscalar_mul(vec![y_x, y_s], vec![G, D_EG]).compress();
let Y_2 = RistrettoPoint::multiscalar_mul(vec![y_x, y_r], vec![G, H]).compress();
let Y_1 = RistrettoPoint::multiscalar_mul(vec![y_x, y_s], vec![&(*G), D_EG]).compress();
let Y_2 = RistrettoPoint::multiscalar_mul(vec![y_x, y_r], vec![&(*G), &(*H)]).compress();
// record masking factors in transcript
transcript.append_point(b"Y_0", &Y_0);
@ -90,12 +87,9 @@ impl EqualityProof {
transcript: &mut Transcript,
) -> Result<(), EqualityProofError> {
// extract the relevant scalar and Ristretto points from the inputs
let G = PedersenBase::default().G;
let H = PedersenBase::default().H;
let P_EG = elgamal_pubkey.get_point();
let C_EG = ciphertext.message_comm.get_point();
let D_EG = ciphertext.decrypt_handle.get_point();
let C_EG = ciphertext.commitment.get_point();
let D_EG = ciphertext.handle.get_point();
let C_Ped = commitment.get_point();
@ -127,7 +121,19 @@ impl EqualityProof {
-ww * c,
-ww,
],
vec![P_EG, H, Y_0, G, D_EG, C_EG, Y_1, G, H, C_Ped, Y_2],
vec![
P_EG,
&(*H),
&Y_0,
&(*G),
D_EG,
C_EG,
&Y_1,
&(*G),
&(*H),
C_Ped,
&Y_2,
],
);
if check.is_identity() {
@ -179,7 +185,7 @@ mod test {
#[test]
fn test_equality_proof() {
// success case
let elgamal_keypair = ElGamalKeypair::default();
let elgamal_keypair = ElGamalKeypair::new_rand();
let message: u64 = 55;
let ciphertext = elgamal_keypair.public.encrypt(message);
@ -206,7 +212,7 @@ mod test {
.is_ok());
// fail case: encrypted and committed messages are different
let elgamal_keypair = ElGamalKeypair::default();
let elgamal_keypair = ElGamalKeypair::new_rand();
let encrypted_message: u64 = 55;
let committed_message: u64 = 77;

View File

@ -1,6 +1,6 @@
#[cfg(not(target_arch = "bpf"))]
use {
crate::encryption::pedersen::{PedersenBase, PedersenCommitment, PedersenOpening},
crate::encryption::pedersen::{PedersenCommitment, PedersenOpening, G, H},
rand::rngs::OsRng,
};
use {
@ -36,9 +36,6 @@ impl FeeProof {
transcript: &mut Transcript,
) -> Self {
// extract the relevant scalar and Ristretto points from the input
let G = PedersenBase::default().G;
let H = PedersenBase::default().H;
let x = Scalar::from(delta_fee);
let m = Scalar::from(max_fee);
@ -65,9 +62,11 @@ impl FeeProof {
// simulate max proof
let z_max = Scalar::random(&mut OsRng);
let c_max = Scalar::random(&mut OsRng);
let Y_max =
RistrettoPoint::multiscalar_mul(vec![z_max, -c_max, c_max * m], vec![H, C_max, G])
.compress();
let Y_max = RistrettoPoint::multiscalar_mul(
vec![z_max, -c_max, c_max * m],
vec![&(*H), C_max, &(*G)],
)
.compress();
let fee_max_proof = FeeMaxProof {
Y_max,
@ -81,9 +80,11 @@ impl FeeProof {
let y_delta_claimed = Scalar::random(&mut OsRng);
let Y_delta_real =
RistrettoPoint::multiscalar_mul(vec![y_x, y_delta_real], vec![G, H]).compress();
RistrettoPoint::multiscalar_mul(vec![y_x, y_delta_real], vec![&(*G), &(*H)])
.compress();
let Y_delta_claimed =
RistrettoPoint::multiscalar_mul(vec![y_x, y_delta_claimed], vec![G, H]).compress();
RistrettoPoint::multiscalar_mul(vec![y_x, y_delta_claimed], vec![&(*G), &(*H)])
.compress();
transcript.append_point(b"Y_max", &Y_max);
transcript.append_point(b"Y_delta_real", &Y_delta_real);
@ -119,13 +120,13 @@ impl FeeProof {
let Y_delta_real = RistrettoPoint::multiscalar_mul(
vec![z_x, z_delta_real, -c_equality],
vec![G, H, C_delta_real],
vec![&(*G), &(*H), C_delta_real],
)
.compress();
let Y_delta_claimed = RistrettoPoint::multiscalar_mul(
vec![z_x, z_delta_claimed, -c_equality],
vec![G, H, C_delta_claimed],
vec![&(*G), &(*H), C_delta_claimed],
)
.compress();
@ -139,7 +140,7 @@ impl FeeProof {
// generate max proof
let y_max = Scalar::random(&mut OsRng);
let Y_max = (y_max * H).compress();
let Y_max = (y_max * &(*H)).compress();
transcript.append_point(b"Y_max", &Y_max);
transcript.append_point(b"Y_delta_real", &Y_delta_real);
@ -174,9 +175,6 @@ impl FeeProof {
transcript: &mut Transcript,
) -> Result<(), FeeProofError> {
// extract the relevant scalar and Ristretto points from the input
let G = PedersenBase::default().G;
let H = PedersenBase::default().H;
let m = Scalar::from(max_fee);
let C_max = commitment_fee.get_point();
@ -243,17 +241,17 @@ impl FeeProof {
],
vec![
C_max,
G,
H,
Y_max,
G,
H,
&(*G),
&(*H),
&Y_max,
&(*G),
&(*H),
C_delta_real,
Y_delta_real,
G,
H,
&Y_delta_real,
&(*G),
&(*H),
C_delta_claimed,
Y_delta_claimed,
&Y_delta_claimed,
],
);

View File

@ -1,8 +1,8 @@
#[cfg(not(target_arch = "bpf"))]
use {
crate::encryption::{
elgamal::ElGamalPubkey,
pedersen::{PedersenBase, PedersenCommitment, PedersenDecryptHandle, PedersenOpening},
elgamal::{DecryptHandle, ElGamalPubkey},
pedersen::{PedersenCommitment, PedersenOpening, G, H},
},
curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng,
@ -39,9 +39,6 @@ impl ValidityProof {
transcript: &mut Transcript,
) -> Self {
// extract the relevant scalar and Ristretto points from the inputs
let G = PedersenBase::default().G;
let H = PedersenBase::default().H;
let P_dest = elgamal_pubkey_dest.get_point();
let P_auditor = elgamal_pubkey_auditor.get_point();
@ -49,7 +46,7 @@ impl ValidityProof {
let y_r = Scalar::random(&mut OsRng);
let y_x = Scalar::random(&mut OsRng);
let Y_0 = RistrettoPoint::multiscalar_mul(vec![y_r, y_x], vec![H, G]).compress();
let Y_0 = RistrettoPoint::multiscalar_mul(vec![y_r, y_x], vec![&(*H), &(*G)]).compress();
let Y_1 = (y_r * P_dest).compress();
let Y_2 = (y_r * P_auditor).compress();
@ -84,14 +81,10 @@ impl ValidityProof {
elgamal_pubkey_dest: &ElGamalPubkey,
elgamal_pubkey_auditor: &ElGamalPubkey,
commitments: (&PedersenCommitment, &PedersenCommitment),
handle_dest: (&PedersenDecryptHandle, &PedersenDecryptHandle),
handle_auditor: (&PedersenDecryptHandle, &PedersenDecryptHandle),
handle_dest: (&DecryptHandle, &DecryptHandle),
handle_auditor: (&DecryptHandle, &DecryptHandle),
transcript: &mut Transcript,
) -> Result<(), ValidityProofError> {
// extract the relevant scalar and Ristretto points from the inputs
let G = PedersenBase::default().G;
let H = PedersenBase::default().H;
// 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)?;
@ -127,7 +120,18 @@ impl ValidityProof {
-ww * c,
-ww,
],
vec![H, G, C, Y_0, P_dest, D_dest, Y_1, P_auditor, D_auditor, Y_2],
vec![
&(*H),
&(*G),
&C,
&Y_0,
P_dest,
&D_dest,
&Y_1,
P_auditor,
&D_auditor,
&Y_2,
],
);
if check.is_identity() {
@ -175,8 +179,8 @@ mod test {
#[test]
fn test_validity_proof() {
let elgamal_pubkey_dest = ElGamalKeypair::default().public;
let elgamal_pubkey_auditor = ElGamalKeypair::default().public;
let elgamal_pubkey_dest = ElGamalKeypair::new_rand().public;
let elgamal_pubkey_auditor = ElGamalKeypair::new_rand().public;
let x_lo: u64 = 55;
let x_hi: u64 = 77;

View File

@ -2,7 +2,7 @@
use {
crate::encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::PedersenBase,
pedersen::H,
},
curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng,
@ -38,8 +38,8 @@ impl ZeroBalanceProof {
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();
let C = elgamal_ciphertext.commitment.get_point();
let D = elgamal_ciphertext.handle.get_point();
// record ElGamal pubkey and ciphertext in the transcript
transcript.append_point(b"P", &P.compress());
@ -71,10 +71,8 @@ impl ZeroBalanceProof {
) -> Result<(), ZeroBalanceProofError> {
// 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;
let C = elgamal_ciphertext.commitment.get_point();
let D = elgamal_ciphertext.handle.get_point();
// record ElGamal pubkey and ciphertext in the transcript
transcript.validate_and_append_point(b"P", &P.compress())?;
@ -96,7 +94,7 @@ impl ZeroBalanceProof {
// 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],
vec![P, &(*H), &Y_P, D, C, &Y_D],
);
if check.is_identity() {
@ -131,13 +129,13 @@ impl ZeroBalanceProof {
mod test {
use super::*;
use crate::encryption::{
elgamal::ElGamalKeypair,
pedersen::{Pedersen, PedersenDecryptHandle, PedersenOpening},
elgamal::{DecryptHandle, ElGamalKeypair},
pedersen::{Pedersen, PedersenOpening},
};
#[test]
fn test_zero_balance_proof() {
let source_keypair = ElGamalKeypair::default();
let source_keypair = ElGamalKeypair::new_rand();
let mut transcript_prover = Transcript::new(b"test");
let mut transcript_verifier = Transcript::new(b"test");
@ -175,11 +173,11 @@ mod test {
// 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 handle = elgamal_ciphertext.handle;
let zeroed_comm_ciphertext = ElGamalCiphertext {
message_comm: zeroed_comm,
decrypt_handle: handle,
commitment: zeroed_comm,
handle,
};
let proof = ZeroBalanceProof::new(
@ -197,8 +195,8 @@ mod test {
let (zero_comm, _) = Pedersen::new(0_u64);
let zeroed_handle_ciphertext = ElGamalCiphertext {
message_comm: zero_comm,
decrypt_handle: PedersenDecryptHandle::default(),
commitment: zero_comm,
handle: DecryptHandle::default(),
};
let proof = ZeroBalanceProof::new(

View File

@ -1,8 +1,8 @@
use super::pod;
pub use target_arch::*;
impl From<(pod::PedersenCommitment, pod::PedersenDecryptHandle)> for pod::ElGamalCiphertext {
fn from((comm, decrypt_handle): (pod::PedersenCommitment, pod::PedersenDecryptHandle)) -> Self {
impl From<(pod::PedersenCommitment, pod::DecryptHandle)> for pod::ElGamalCiphertext {
fn from((comm, decrypt_handle): (pod::PedersenCommitment, pod::DecryptHandle)) -> Self {
let mut buf = [0_u8; 64];
buf[..32].copy_from_slice(&comm.0);
buf[32..].copy_from_slice(&decrypt_handle.0);
@ -17,8 +17,8 @@ mod target_arch {
crate::{
encryption::{
auth_encryption::AeCiphertext,
elgamal::{ElGamalCiphertext, ElGamalPubkey},
pedersen::{PedersenCommitment, PedersenDecryptHandle},
elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey},
pedersen::PedersenCommitment,
},
errors::ProofError,
range_proof::{errors::RangeProofError, RangeProof},
@ -107,25 +107,25 @@ mod target_arch {
}
#[cfg(not(target_arch = "bpf"))]
impl From<PedersenDecryptHandle> for pod::PedersenDecryptHandle {
fn from(handle: PedersenDecryptHandle) -> Self {
impl From<DecryptHandle> for pod::DecryptHandle {
fn from(handle: DecryptHandle) -> Self {
Self(handle.to_bytes())
}
}
// For proof verification, interpret pod::PedersenDecHandle as CompressedRistretto
#[cfg(not(target_arch = "bpf"))]
impl From<pod::PedersenDecryptHandle> for CompressedRistretto {
fn from(pod: pod::PedersenDecryptHandle) -> Self {
impl From<pod::DecryptHandle> for CompressedRistretto {
fn from(pod: pod::DecryptHandle) -> Self {
Self(pod.0)
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<pod::PedersenDecryptHandle> for PedersenDecryptHandle {
impl TryFrom<pod::DecryptHandle> for DecryptHandle {
type Error = ProofError;
fn try_from(pod: pod::PedersenDecryptHandle) -> Result<Self, Self::Error> {
fn try_from(pod: pod::DecryptHandle) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0).ok_or(ProofError::InconsistentCTData)
}
}

View File

@ -241,7 +241,6 @@ mod tests {
},
bytemuck::Zeroable,
curve25519_dalek::scalar::Scalar,
rand::rngs::OsRng,
std::convert::TryInto,
};
@ -252,7 +251,7 @@ mod tests {
// spendable_ct should be an encryption of 0 for any public key when
// `PedersenOpen::default()` is used
let public = ElGamalKeypair::default().public;
let public = ElGamalKeypair::new_rand().public;
let balance: u64 = 0;
assert_eq!(
spendable_ct,
@ -260,7 +259,7 @@ mod tests {
);
// homomorphism should work like any other ciphertext
let open = PedersenOpening::random(&mut OsRng);
let open = PedersenOpening::new_rand();
let transfer_amount_ct = public.encrypt_with(55_u64, &open);
let transfer_amount_pod: pod::ElGamalCiphertext = transfer_amount_ct.into();
@ -276,7 +275,7 @@ mod tests {
let added_ct = ops::add_to(&spendable_balance, 55).unwrap();
let public = ElGamalKeypair::default().public;
let public = ElGamalKeypair::new_rand().public;
let expected: pod::ElGamalCiphertext = public
.encrypt_with(55_u64, &PedersenOpening::default())
.into();
@ -287,8 +286,8 @@ mod tests {
#[test]
fn test_subtract_from() {
let amount = 77_u64;
let public = ElGamalKeypair::default().public;
let open = PedersenOpening::random(&mut OsRng);
let public = ElGamalKeypair::new_rand().public;
let open = PedersenOpening::new_rand();
let encrypted_amount: pod::ElGamalCiphertext = public.encrypt_with(amount, &open).into();
let subtracted_ct = ops::subtract_from(&encrypted_amount, 55).unwrap();
@ -313,9 +312,9 @@ mod tests {
let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount);
// generate public keys
let source_pk = ElGamalKeypair::default().public;
let dest_pk = ElGamalKeypair::default().public;
let auditor_pk = ElGamalKeypair::default().public;
let source_pk = ElGamalKeypair::new_rand().public;
let dest_pk = ElGamalKeypair::new_rand().public;
let auditor_pk = ElGamalKeypair::new_rand().public;
// commitments associated with TransferRangeProof
let (comm_lo, open_lo) = Pedersen::new(amount_lo);
@ -325,21 +324,17 @@ mod tests {
let comm_hi: pod::PedersenCommitment = comm_hi.into();
// decryption handles associated with TransferValidityProof
let handle_source_lo: pod::PedersenDecryptHandle =
source_pk.decrypt_handle(&open_lo).into();
let handle_dest_lo: pod::PedersenDecryptHandle = dest_pk.decrypt_handle(&open_lo).into();
let _handle_auditor_lo: pod::PedersenDecryptHandle =
auditor_pk.decrypt_handle(&open_lo).into();
let handle_source_lo: pod::DecryptHandle = source_pk.decrypt_handle(&open_lo).into();
let handle_dest_lo: pod::DecryptHandle = dest_pk.decrypt_handle(&open_lo).into();
let _handle_auditor_lo: pod::DecryptHandle = auditor_pk.decrypt_handle(&open_lo).into();
let handle_source_hi: pod::PedersenDecryptHandle =
source_pk.decrypt_handle(&open_hi).into();
let handle_dest_hi: pod::PedersenDecryptHandle = dest_pk.decrypt_handle(&open_hi).into();
let _handle_auditor_hi: pod::PedersenDecryptHandle =
auditor_pk.decrypt_handle(&open_hi).into();
let handle_source_hi: pod::DecryptHandle = source_pk.decrypt_handle(&open_hi).into();
let handle_dest_hi: pod::DecryptHandle = dest_pk.decrypt_handle(&open_hi).into();
let _handle_auditor_hi: pod::DecryptHandle = auditor_pk.decrypt_handle(&open_hi).into();
// source spendable and recipient pending
let source_open = PedersenOpening::random(&mut OsRng);
let dest_open = PedersenOpening::random(&mut OsRng);
let source_open = PedersenOpening::new_rand();
let dest_open = PedersenOpening::new_rand();
let source_spendable_ct: pod::ElGamalCiphertext =
source_pk.encrypt_with(77_u64, &source_open).into();

View File

@ -47,9 +47,9 @@ impl fmt::Debug for PedersenCommitment {
#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct PedersenDecryptHandle(pub [u8; 32]);
pub struct DecryptHandle(pub [u8; 32]);
impl fmt::Debug for PedersenDecryptHandle {
impl fmt::Debug for DecryptHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}