#![allow(non_snake_case)]
use super::super::types::*;
use crate::{
bulletproofs::range_proof::{
prove_given_scalars as bulletprove, verify_efficient,
VerificationError as BulletproofVerificationError,
},
common::types::Amount,
curve_arithmetic::{Curve, Value},
elgamal::{Cipher, PublicKey, Randomness, SecretKey},
id::{id_proof_types::ProofVersion, types::GlobalContext},
pedersen_commitment::{Commitment, CommitmentKey, Randomness as PedersenRandomness},
random_oracle::*,
sigma_protocols::{com_eq::*, common::*, dlog::*, enc_trans::*},
};
use itertools::izip;
use rand::*;
use std::rc::Rc;
#[allow(clippy::many_single_char_names)]
fn gen_enc_exp_info<C: Curve>(
cmm_key: &CommitmentKey<C>,
pk: &PublicKey<C>,
ciphers: &[Cipher<C>],
) -> Vec<ComEq<C, C>> {
let pk_eg = pk.key;
let mut sigma_protocols = Vec::with_capacity(ciphers.len());
for cipher in ciphers {
let commitment = Commitment(cipher.1);
let y = cipher.0;
let cmm_key_comeq = CommitmentKey {
g: pk_eg,
h: cmm_key.h,
};
let comeq = ComEq {
commitment,
y,
cmm_key: cmm_key_comeq,
g: cmm_key.g,
};
sigma_protocols.push(comeq);
}
sigma_protocols
}
pub fn gen_enc_trans_proof_info<C: Curve>(
pk_sender: &PublicKey<C>,
pk_receiver: &PublicKey<C>,
S: &Cipher<C>,
A: &[Cipher<C>],
S_prime: &[Cipher<C>],
h: &C,
) -> EncTrans<C> {
let sigma_1 = Dlog {
public: pk_sender.key,
coeff: pk_sender.generator,
};
let (e1, e2) = (S.0, S.1);
let elg_dec = ElgDec {
public: e2,
coeff: [e1, *h],
};
let sigma_2 = elg_dec;
let cmm_key = CommitmentKey {
g: pk_sender.generator,
h: *h,
};
let sigma_3_protocols = gen_enc_exp_info(&cmm_key, pk_receiver, A);
let sigma_4_protocols = gen_enc_exp_info(&cmm_key, pk_sender, S_prime);
EncTrans {
dlog: sigma_1,
elg_dec: sigma_2,
encexp1: sigma_3_protocols,
encexp2: sigma_4_protocols,
}
}
#[allow(clippy::too_many_arguments)]
pub fn gen_enc_trans<C: Curve, R: Rng>(
context: &GlobalContext<C>,
ro: &mut RandomOracle,
pk_sender: &PublicKey<C>,
sk_sender: &SecretKey<C>,
pk_receiver: &PublicKey<C>,
index: EncryptedAmountAggIndex,
S: &Cipher<C>,
s: Amount,
a: Amount,
csprng: &mut R,
) -> Option<EncryptedAmountTransferData<C>> {
if s < a {
return None;
}
let gens = context.bulletproof_generators().take(64);
let generator = context.encryption_in_exponent_generator();
let s_prime = s.micro_ccd() - a.micro_ccd();
let s_prime_chunks = CHUNK_SIZE.u64_to_chunks(s_prime);
let a_chunks = CHUNK_SIZE.u64_to_chunks(a.micro_ccd());
let A_enc_randomness = a_chunks
.iter()
.map(|&x| {
pk_receiver.encrypt_exponent_rand_given_generator(
&Value::<C>::from(x),
generator,
csprng,
)
})
.collect::<Vec<_>>();
let (A, A_rand): (Vec<_>, Vec<_>) = A_enc_randomness.iter().cloned().unzip();
let S_prime_enc_randomness = s_prime_chunks
.iter()
.map(|&x| {
pk_sender.encrypt_exponent_rand_given_generator(&Value::<C>::from(x), generator, csprng)
})
.collect::<Vec<_>>();
let (S_prime, S_prime_rand): (Vec<_>, Vec<_>) = S_prime_enc_randomness.iter().cloned().unzip();
let a_secrets = izip!(a_chunks.iter(), A_rand.iter())
.map(|(a_i, r_i)| ComEqSecret::<C> {
r: PedersenRandomness::from_u64(*a_i),
a: Randomness::to_value(r_i),
})
.collect();
let s_prime_secrets = izip!(s_prime_chunks.iter(), S_prime_rand.iter())
.map(|(a_i, r_i)| ComEqSecret::<C> {
r: PedersenRandomness::from_u64(*a_i),
a: Randomness::to_value(r_i),
})
.collect();
let protocol = gen_enc_trans_proof_info(pk_sender, pk_receiver, S, &A, &S_prime, generator);
let secret = EncTransSecret {
dlog_secret: Rc::new(sk_sender.scalar),
encexp1_secrets: a_secrets,
encexp2_secrets: s_prime_secrets,
};
let sigma_proof = prove(ro, &protocol, secret, csprng)?;
let cmm_key_bulletproof_a = CommitmentKey {
g: *generator,
h: pk_receiver.key,
};
let cmm_key_bulletproof_s_prime = CommitmentKey {
g: *generator,
h: pk_sender.key,
};
let a_chunks_as_scalars: Vec<_> = a_chunks.iter().copied().map(C::scalar_from_u64).collect();
let A_rand_as_pedrand: Vec<PedersenRandomness<_>> = A_rand
.iter()
.map(|x| PedersenRandomness::from_value(&x.to_value()))
.collect();
let s_prime_chunks_as_scalars: Vec<_> = s_prime_chunks
.iter()
.copied()
.map(C::scalar_from_u64)
.collect();
let S_prime_rand_as_pedrand: Vec<PedersenRandomness<_>> = S_prime_rand
.iter()
.map(|x| PedersenRandomness::new(*(x.as_ref())))
.collect();
let bulletproof_a = bulletprove(
ProofVersion::Version1,
ro,
csprng,
u8::from(CHUNK_SIZE),
a_chunks.len() as u8,
&a_chunks_as_scalars,
&gens,
&cmm_key_bulletproof_a,
&A_rand_as_pedrand,
)?;
let bulletproof_s_prime = bulletprove(
ProofVersion::Version1,
ro,
csprng,
u8::from(CHUNK_SIZE),
s_prime_chunks.len() as u8,
&s_prime_chunks_as_scalars,
&gens,
&cmm_key_bulletproof_s_prime,
&S_prime_rand_as_pedrand,
)?;
let proof = EncryptedAmountTransferProof {
accounting: sigma_proof,
transfer_amount_correct_encryption: bulletproof_a,
remaining_amount_correct_encryption: bulletproof_s_prime,
};
let transfer_amount = EncryptedAmount {
encryptions: [A[0], A[1]],
};
let remaining_amount = EncryptedAmount {
encryptions: [S_prime[0], S_prime[1]],
};
Some(EncryptedAmountTransferData {
remaining_amount,
transfer_amount,
index,
proof,
})
}
#[allow(clippy::too_many_arguments)]
#[allow(non_snake_case)]
pub fn gen_sec_to_pub_trans<C: Curve, R: Rng>(
context: &GlobalContext<C>,
ro: &mut RandomOracle,
pk: &PublicKey<C>, sk: &SecretKey<C>,
index: EncryptedAmountAggIndex, S: &Cipher<C>,
s: Amount, a: Amount, csprng: &mut R,
) -> Option<SecToPubAmountTransferData<C>> {
if s < a {
return None;
}
let gens = context.bulletproof_generators();
let generator = context.encryption_in_exponent_generator();
let s_prime = s.micro_ccd() - a.micro_ccd();
let s_prime_chunks = CHUNK_SIZE.u64_to_chunks(s_prime);
let S_prime_enc_randomness = s_prime_chunks
.iter()
.map(|&x| pk.encrypt_exponent_rand_given_generator(&Value::<C>::from(x), generator, csprng))
.collect::<Vec<_>>();
let A_dummy_encryption = {
let ha = generator.mul_by_scalar(&C::scalar_from_u64(a.micro_ccd()));
Cipher(C::zero_point(), ha)
};
let A = [A_dummy_encryption];
let (S_prime, S_prime_rand): (Vec<_>, Vec<_>) = S_prime_enc_randomness.iter().cloned().unzip();
let protocol = gen_enc_trans_proof_info(pk, pk, S, &A, &S_prime, generator);
let s_prime_secrets = izip!(s_prime_chunks.iter(), S_prime_rand.iter())
.map(|(a_i, r_i)| ComEqSecret::<C> {
r: PedersenRandomness::from_u64(*a_i),
a: Randomness::to_value(r_i),
})
.collect();
let secret = EncTransSecret {
dlog_secret: Rc::new(sk.scalar),
encexp1_secrets: vec![ComEqSecret::<C> {
r: PedersenRandomness::from_u64(a.micro_ccd()),
a: Value::from(0u64),
}],
encexp2_secrets: s_prime_secrets,
};
let sigma_proof = prove(ro, &protocol, secret, csprng)?;
let cmm_key_bulletproof_s_prime = CommitmentKey {
g: *generator,
h: pk.key,
};
let s_prime_chunks_as_scalars: Vec<_> = s_prime_chunks
.iter()
.copied()
.map(C::scalar_from_u64)
.collect();
let S_prime_rand_as_pedrand: Vec<PedersenRandomness<_>> = S_prime_rand
.iter()
.map(|x| PedersenRandomness::new(*(x.as_ref())))
.collect();
let bulletproof_s_prime = bulletprove(
ProofVersion::Version1,
ro,
csprng,
u8::from(CHUNK_SIZE),
s_prime_chunks.len() as u8,
&s_prime_chunks_as_scalars,
gens,
&cmm_key_bulletproof_s_prime,
&S_prime_rand_as_pedrand,
)?;
let proof = SecToPubAmountTransferProof {
accounting: sigma_proof,
remaining_amount_correct_encryption: bulletproof_s_prime,
};
let transfer_amount = a;
let remaining_amount = EncryptedAmount {
encryptions: [S_prime[0], S_prime[1]],
};
Some(SecToPubAmountTransferData {
remaining_amount,
transfer_amount,
index,
proof,
})
}
#[derive(Debug, PartialEq)]
pub enum VerificationError {
SigmaProofError,
FirstBulletproofError(BulletproofVerificationError),
SecondBulletproofError(BulletproofVerificationError),
}
#[allow(clippy::too_many_arguments)]
pub fn verify_enc_trans<C: Curve>(
context: &GlobalContext<C>,
ro: &mut RandomOracle,
transaction: &EncryptedAmountTransferData<C>,
pk_sender: &PublicKey<C>,
pk_receiver: &PublicKey<C>,
S: &Cipher<C>,
) -> Result<(), VerificationError> {
let generator = context.encryption_in_exponent_generator();
let gens = context.bulletproof_generators();
let protocol = gen_enc_trans_proof_info(
pk_sender,
pk_receiver,
S,
transaction.transfer_amount.as_ref(),
transaction.remaining_amount.as_ref(),
generator,
);
if !verify(ro, &protocol, &transaction.proof.accounting) {
return Err(VerificationError::SigmaProofError);
}
let num_chunks = 64 / usize::from(u8::from(CHUNK_SIZE));
let commitments_a = {
let mut commitments_a = Vec::with_capacity(num_chunks);
let ta: &[Cipher<C>; 2] = transaction.transfer_amount.as_ref();
for cipher in ta {
commitments_a.push(Commitment(cipher.1));
}
commitments_a
};
let commitments_s_prime = {
let mut commitments_s_prime = Vec::with_capacity(num_chunks);
let ts_prime: &[Cipher<C>; 2] = transaction.remaining_amount.as_ref();
for cipher in ts_prime {
commitments_s_prime.push(Commitment(cipher.1));
}
commitments_s_prime
};
let cmm_key_bulletproof_a = CommitmentKey {
g: *generator,
h: pk_receiver.key,
};
let cmm_key_bulletproof_s_prime = CommitmentKey {
g: *generator,
h: pk_sender.key,
};
let first_bulletproof = verify_efficient(
ProofVersion::Version1,
ro,
u8::from(CHUNK_SIZE),
&commitments_a,
&transaction.proof.transfer_amount_correct_encryption,
gens,
&cmm_key_bulletproof_a,
);
if let Err(err) = first_bulletproof {
return Err(VerificationError::FirstBulletproofError(err));
}
let second_bulletproof = verify_efficient(
ProofVersion::Version1,
ro,
u8::from(CHUNK_SIZE),
&commitments_s_prime,
&transaction.proof.remaining_amount_correct_encryption,
gens,
&cmm_key_bulletproof_s_prime,
);
if let Err(err) = second_bulletproof {
return Err(VerificationError::SecondBulletproofError(err));
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn verify_sec_to_pub_trans<C: Curve>(
context: &GlobalContext<C>,
ro: &mut RandomOracle,
transaction: &SecToPubAmountTransferData<C>,
pk: &PublicKey<C>,
S: &Cipher<C>,
) -> Result<(), VerificationError> {
let generator = context.encryption_in_exponent_generator();
let gens = context.bulletproof_generators();
let a = transaction.transfer_amount;
let A_dummy_encryption = {
let ha = generator.mul_by_scalar(&C::scalar_from_u64(a.micro_ccd()));
Cipher(C::zero_point(), ha)
};
let A = [A_dummy_encryption];
let protocol = gen_enc_trans_proof_info(
pk,
pk,
S,
&A,
transaction.remaining_amount.as_ref(),
generator,
);
if !verify(ro, &protocol, &transaction.proof.accounting) {
return Err(VerificationError::SigmaProofError);
}
let num_chunks = 64 / usize::from(u8::from(CHUNK_SIZE));
let commitments_s_prime = {
let mut commitments_s_prime = Vec::with_capacity(num_chunks);
let ts_prime: &[Cipher<C>; 2] = transaction.remaining_amount.as_ref();
for cipher in ts_prime {
commitments_s_prime.push(Commitment(cipher.1));
}
commitments_s_prime
};
let cmm_key_bulletproof_s_prime = CommitmentKey {
g: *generator,
h: pk.key,
};
let num_bits_in_chunk = (64 / num_chunks) as u8;
let bulletproof = verify_efficient(
ProofVersion::Version1,
ro,
num_bits_in_chunk,
&commitments_s_prime,
&transaction.proof.remaining_amount_correct_encryption,
gens,
&cmm_key_bulletproof_s_prime,
);
if let Err(err) = bulletproof {
return Err(VerificationError::SecondBulletproofError(err));
}
Ok(())
}
#[cfg(test)]
mod test {
use ark_bls12_381::G1Projective;
use crate::{common, curve_arithmetic::arkworks_instances::ArkGroup};
use super::*;
type SomeCurve = ArkGroup<G1Projective>;
pub fn generate_challenge_prefix<R: rand::Rng>(csprng: &mut R) -> Vec<u8> {
let l = csprng.gen_range(0..1000);
let mut challenge_prefix = vec![0; l];
for v in challenge_prefix.iter_mut() {
*v = csprng.gen();
}
challenge_prefix
}
#[allow(non_snake_case)]
#[test]
fn test_enc_trans() {
let mut csprng = thread_rng();
let sk_sender: SecretKey<SomeCurve> = SecretKey::generate_all(&mut csprng);
let pk_sender = PublicKey::from(&sk_sender);
let sk_receiver: SecretKey<SomeCurve> =
SecretKey::generate(&pk_sender.generator, &mut csprng);
let pk_receiver = PublicKey::from(&sk_receiver);
let s = csprng.gen::<u64>();
let a = csprng.gen_range(0..s);
let m = 2; let n = 32;
let nm = n * m;
let context = GlobalContext::<SomeCurve>::generate_size(String::from("genesis_string"), nm);
let generator = context.encryption_in_exponent_generator(); let s_value = Value::from(s);
let S = pk_sender.encrypt_exponent_given_generator(&s_value, generator, &mut csprng);
let challenge_prefix = generate_challenge_prefix(&mut csprng);
let mut ro = RandomOracle::domain(challenge_prefix);
let index = csprng.gen::<u64>().into(); let transaction = gen_enc_trans(
&context,
&mut ro.split(),
&pk_sender,
&sk_sender,
&pk_receiver,
index,
&S,
Amount::from_micro_ccd(s),
Amount::from_micro_ccd(a),
&mut csprng,
)
.expect("Could not produce proof.");
assert_eq!(
verify_enc_trans(
&context,
&mut ro,
&transaction,
&pk_sender,
&pk_receiver,
&S,
),
Ok(())
)
}
fn seed0() -> rand::rngs::StdRng {
rand::rngs::StdRng::seed_from_u64(0)
}
#[test]
fn test_enc_trans_stable() {
let sk_sender: SecretKey<SomeCurve> = SecretKey::generate_all(&mut seed0());
let pk_sender = PublicKey::from(&sk_sender);
let sk_receiver: SecretKey<SomeCurve> =
SecretKey::generate(&pk_sender.generator, &mut seed0());
let pk_receiver = PublicKey::from(&sk_receiver);
let m = 2; let n = 32;
let nm = n * m;
let context = GlobalContext::<SomeCurve>::generate_size(String::from("genesis_string"), nm);
let generator = context.encryption_in_exponent_generator(); let s = seed0().gen::<u64>(); let s_value = Value::from(s);
let S = pk_sender.encrypt_exponent_given_generator(&s_value, generator, &mut seed0());
let challenge_prefix = generate_challenge_prefix(&mut seed0());
let mut ro = RandomOracle::domain(challenge_prefix);
let tx_stable_hex = include_str!("enc_trans_stable.hex");
let tx_stable_bytes = hex::decode(&tx_stable_hex).unwrap();
let tx_stable: EncryptedAmountTransferData<SomeCurve> =
common::from_bytes(&mut tx_stable_bytes.as_slice())
.expect("Could not deserialize stable pio");
assert_eq!(
verify_enc_trans(&context, &mut ro, &tx_stable, &pk_sender, &pk_receiver, &S,),
Ok(())
)
}
#[allow(non_snake_case)]
#[test]
fn test_sec_to_pub() {
let mut csprng = thread_rng();
let sk: SecretKey<SomeCurve> = SecretKey::generate_all(&mut csprng);
let pk = PublicKey::from(&sk);
let s = csprng.gen::<u64>();
let a = csprng.gen_range(0..s);
let m = 2; let n = 32;
let nm = n * m;
let context = GlobalContext::<SomeCurve>::generate_size(String::from("genesis_string"), nm);
let generator = context.encryption_in_exponent_generator(); let s_value = Value::from(s);
let S = pk.encrypt_exponent_given_generator(&s_value, generator, &mut csprng);
let challenge_prefix = generate_challenge_prefix(&mut csprng);
let mut ro = RandomOracle::domain(challenge_prefix);
let index = csprng.gen::<u64>().into(); let transaction = gen_sec_to_pub_trans(
&context,
&mut ro.split(),
&pk,
&sk,
index,
&S,
Amount::from_micro_ccd(s),
Amount::from_micro_ccd(a),
&mut csprng,
)
.expect("Proving failed, but that is extremely unlikely, which indicates a bug.");
assert_eq!(
verify_sec_to_pub_trans(&context, &mut ro, &transaction, &pk, &S,),
Ok(())
)
}
#[test]
fn test_sec_to_pub_stable() {
let sk: SecretKey<SomeCurve> = SecretKey::generate_all(&mut seed0());
let pk = PublicKey::from(&sk);
let s = seed0().gen::<u64>();
let m = 2; let n = 32;
let nm = n * m;
let context = GlobalContext::<SomeCurve>::generate_size(String::from("genesis_string"), nm);
let generator = context.encryption_in_exponent_generator(); let s_value = Value::from(s);
let S = pk.encrypt_exponent_given_generator(&s_value, generator, &mut seed0());
let challenge_prefix = generate_challenge_prefix(&mut seed0());
let mut ro = RandomOracle::domain(challenge_prefix);
let tx_stable_hex = include_str!("enc_to_pub_stable.hex");
let tx_stable_bytes = hex::decode(&tx_stable_hex).unwrap();
let tx_stable: SecToPubAmountTransferData<SomeCurve> =
common::from_bytes(&mut tx_stable_bytes.as_slice())
.expect("Could not deserialize stable pio");
assert_eq!(
verify_sec_to_pub_trans(&context, &mut ro, &tx_stable, &pk, &S,),
Ok(())
)
}
}