use crate::ec::curve::{pairing, polynomial_from_roots, Gt};
use crate::ec::univarpoly::UnivarPolynomial;
use crate::ec::{G1Projective, G2Projective, Scalar};
use crate::entry::entry_to_scalar;
use crate::entry::Entry;
use crate::error;
use crate::keypair::MaxCardinality;
use bls12_381_plus::elliptic_curve::bigint;
use bls12_381_plus::elliptic_curve::ops::MulByGenerator;
use bls12_381_plus::ff::Field;
use bls12_381_plus::group::{Curve, Group};
use bls12_381_plus::{G1Affine, G2Affine};
use rand::rngs::ThreadRng;
use secrecy::ExposeSecret;
use secrecy::Secret;
use sha2::{Digest, Sha256};
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct ParamSetCommitment {
pub pp_commit_g1: Vec<G1Projective>,
pub pp_commit_g2: Vec<G2Projective>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ParamSetCommitmentCompressed {
pub pp_commit_g1: Vec<Vec<u8>>,
pub pp_commit_g2: Vec<Vec<u8>>,
}
impl From<ParamSetCommitment> for ParamSetCommitmentCompressed {
fn from(param_sc: ParamSetCommitment) -> Self {
let pp_commit_g1_compressed = param_sc
.pp_commit_g1
.iter()
.map(|g1| g1.to_compressed().to_vec())
.collect::<Vec<_>>();
let pp_commit_g2_compressed = param_sc
.pp_commit_g2
.iter()
.map(|g2| g2.to_compressed().to_vec())
.collect::<Vec<_>>();
ParamSetCommitmentCompressed {
pp_commit_g1: pp_commit_g1_compressed,
pp_commit_g2: pp_commit_g2_compressed,
}
}
}
impl std::convert::TryFrom<ParamSetCommitmentCompressed> for ParamSetCommitment {
type Error = error::Error;
fn try_from(param_sc: ParamSetCommitmentCompressed) -> Result<Self, Self::Error> {
let pp_commit_g1 = param_sc
.pp_commit_g1
.iter()
.map(|g1| {
let mut bytes = [0u8; G1Affine::COMPRESSED_BYTES];
bytes.copy_from_slice(g1);
let g1_maybe = G1Affine::from_compressed(&bytes);
if g1_maybe.is_none().into() {
return Err(Self::Error::InvalidG1Point);
}
Ok(g1_maybe.expect("it'll be fine, it passed the check"))
})
.map(|item| item.unwrap().into())
.collect::<Vec<_>>();
let pp_commit_g2 = param_sc
.pp_commit_g2
.iter()
.map(|g2| {
let mut bytes = [0u8; G2Affine::COMPRESSED_BYTES];
bytes.copy_from_slice(g2);
let g2_maybe = G2Affine::from_compressed(&bytes);
if g2_maybe.is_none().into() {
return Err(Self::Error::InvalidG2Point);
}
Ok(g2_maybe.expect("it'll be fine, it passed the check"))
})
.map(|item| item.unwrap().into())
.collect::<Vec<_>>();
Ok(ParamSetCommitment {
pp_commit_g1,
pp_commit_g2,
})
}
}
#[cfg(feature = "serde_json")]
impl ToString for ParamSetCommitmentCompressed {
fn to_string(&self) -> String {
serde_json::to_string(self).expect("compressed should be well formed")
}
}
impl ToString for ParamSetCommitment {
fn to_string(&self) -> String {
ParamSetCommitmentCompressed::from(self.clone()).to_string()
}
}
impl ParamSetCommitment {
pub fn new(t: &usize) -> ParamSetCommitment {
let base: Secret<Scalar> = Secret::new(Scalar::random(ThreadRng::default()));
let pp_commit_g1 = (0..=*t)
.map(|i| {
G1Projective::mul_by_generator(&base.expose_secret().pow(&[i as u64, 0, 0, 0]))
})
.collect::<Vec<G1Projective>>();
let pp_commit_g2 = (0..=*t)
.map(|i| {
G2Projective::mul_by_generator(&base.expose_secret().pow(&[i as u64, 0, 0, 0]))
})
.collect::<Vec<G2Projective>>();
ParamSetCommitment {
pp_commit_g2,
pp_commit_g1,
}
}
}
pub trait Commitment {
#[allow(dead_code)]
fn new(t: MaxCardinality) -> Self;
#[allow(dead_code)]
fn public_parameters(self) -> ParamSetCommitment;
fn commit_set(param_sc: &ParamSetCommitment, mess_set_str: &Entry) -> (G1Projective, Scalar) {
let mess_set: Vec<Scalar> = entry_to_scalar(mess_set_str);
let monypol_coeff = polynomial_from_roots(&mess_set);
let pre_commit = generate_pre_commit(monypol_coeff, param_sc);
let open_info = Scalar::random(ThreadRng::default());
let commitment = pre_commit * open_info;
(commitment, open_info)
}
#[allow(dead_code)]
fn open_set(
param_sc: &ParamSetCommitment,
commitment: &G1Projective,
open_info: &Scalar,
mess_set_str: &Entry,
) -> bool {
let mess_set: Vec<Scalar> = entry_to_scalar(mess_set_str);
let monypol_coeff = polynomial_from_roots(&mess_set);
let pre_commit = generate_pre_commit(monypol_coeff, param_sc);
let commitment_check = pre_commit * open_info;
*commitment == commitment_check
}
fn open_subset(
param_sc: &ParamSetCommitment,
all_messages: &Entry,
open_info: &Scalar,
subset: &Entry,
) -> Option<G1Projective> {
if open_info.is_zero().into() {
return None;
}
let mess_set: Vec<Scalar> = entry_to_scalar(all_messages);
let mess_subset_t = entry_to_scalar(subset);
if mess_subset_t.len() > mess_set.len() {
return None;
}
if !mess_subset_t.iter().all(|item| mess_set.contains(item)) {
return None;
}
let create_witn_elements: Vec<Scalar> = mess_set
.into_iter()
.filter(|itm| !mess_subset_t.contains(itm))
.collect::<Vec<Scalar>>();
let coeff_witn = polynomial_from_roots(&create_witn_elements);
let witn_sum = generate_pre_commit(coeff_witn, param_sc);
let witness = witn_sum * open_info;
Some(witness)
}
#[allow(dead_code)]
fn verify_subset(
param_sc: &ParamSetCommitment,
commitment: &G1Projective,
subset_str: &Entry,
witness: &G1Projective,
) -> bool {
let mess_subset_t: Vec<Scalar> = entry_to_scalar(subset_str);
let coeff_t = polynomial_from_roots(&mess_subset_t);
let subset_group_elements = param_sc
.pp_commit_g2
.iter()
.zip(coeff_t.coefficients().iter())
.map(|(g2, coeff)| g2 * coeff)
.collect::<Vec<G2Projective>>();
let subset_elements_sum = subset_group_elements
.iter()
.fold(G2Projective::IDENTITY, |acc, x| acc + x);
pairing(witness, &subset_elements_sum) == pairing(commitment, &G2Projective::GENERATOR)
}
}
pub struct SetCommitment {
param_sc: ParamSetCommitment,
}
impl Commitment for SetCommitment {
fn new(t: MaxCardinality) -> Self {
Self {
param_sc: ParamSetCommitment::new(&t),
}
}
fn public_parameters(self) -> ParamSetCommitment {
self.param_sc
}
}
pub struct CrossSetCommitment {
pub param_sc: ParamSetCommitment,
}
impl Commitment for CrossSetCommitment {
fn new(t: MaxCardinality) -> Self {
CrossSetCommitment {
param_sc: ParamSetCommitment::new(&t),
}
}
fn public_parameters(self) -> ParamSetCommitment {
self.param_sc
}
}
impl CrossSetCommitment {
pub fn aggregate_cross(
witness_vector: &[G1Projective],
commit_vector: &[G1Projective],
) -> G1Projective {
witness_vector.iter().zip(commit_vector.iter()).fold(
G1Projective::identity(),
|acc, (witness, commit)| {
let hash_i = hash_to_scalar(commit);
acc + witness * hash_i
},
)
}
pub fn verify_cross(
param_sc: &ParamSetCommitment,
commit_vector: &[G1Projective],
selected_entry_subset_vector: &[Entry],
proof: &G1Projective,
) -> bool {
let subsets_vector: Vec<Vec<Scalar>> = selected_entry_subset_vector
.iter()
.enumerate()
.filter(|(_, entry)| !entry.is_empty())
.map(|(_, entry)| entry_to_scalar(entry))
.collect();
let set_s = subsets_vector
.iter()
.fold(Vec::new(), |mut acc, x| {
acc.extend(x.clone());
acc
})
.into_iter()
.collect::<Vec<Scalar>>();
let coeff_set_s = polynomial_from_roots(&set_s);
let set_s_group_element = param_sc
.pp_commit_g2
.iter()
.zip(coeff_set_s.coefficients().iter())
.map(|(g2, coeff)| g2 * coeff)
.collect::<Vec<G2Projective>>();
let set_s_elements_sum = set_s_group_element
.iter()
.fold(G2Projective::IDENTITY, |acc, x| acc + x);
let right_side = pairing(proof, &set_s_elements_sum);
let set_s_not_t = subsets_vector
.into_iter()
.map(|x| not_intersection(&set_s, x))
.collect::<Vec<Vec<Scalar>>>();
let vector_gt = commit_vector
.iter()
.zip(set_s_not_t.iter())
.map(|(commit, set_s_not_t)| {
let coeff_s_not_t = polynomial_from_roots(set_s_not_t);
let listpoints_s_not_t = param_sc
.pp_commit_g2
.iter()
.zip(coeff_s_not_t.coefficients().iter())
.map(|(g2, coeff)| g2 * coeff)
.collect::<Vec<G2Projective>>();
let temp_sum = listpoints_s_not_t
.iter()
.fold(G2Projective::IDENTITY, |acc, x| acc + x);
let hash_i = hash_to_scalar(commit);
pairing(commit, &(hash_i * temp_sum))
})
.collect::<Vec<Gt>>();
let left_side = vector_gt.iter().fold(Gt::IDENTITY, |acc, x| acc * *x);
left_side == right_side
}
}
fn hash_to_scalar(commit: &G1Projective) -> Scalar {
let chash = Sha256::digest(commit.to_affine().to_uncompressed().as_ref());
bigint::U256::from_be_slice(&chash).into()
}
pub fn generate_pre_commit(
monypol_coeff: UnivarPolynomial,
param_sc: &ParamSetCommitment,
) -> G1Projective {
let coef_points = param_sc
.pp_commit_g1
.iter()
.zip(monypol_coeff.coefficients().iter())
.map(|(g1, coeff)| g1 * coeff)
.collect::<Vec<G1Projective>>();
coef_points
.iter()
.fold(G1Projective::IDENTITY, |acc, x| acc + x)
}
pub fn not_intersection(list_s: &[Scalar], list_t: Vec<Scalar>) -> Vec<Scalar> {
list_s
.iter()
.filter(|value| !list_t.contains(value))
.cloned()
.collect::<Vec<Scalar>>()
}
#[cfg(target_arch = "wasm32")]
pub fn test_aggregate_verify_cross() {
use super::*;
let age = "age = 30";
let name = "name = Alice";
let drivers = "driver license = 12";
let gender = "Gender = male";
let company = "company = ACME Inc.";
let alt_drivers = "driver license type = B";
let set_str: Entry = Entry(vec![
Attribute::new(age),
Attribute::new(name),
Attribute::new(drivers),
]);
let set_str2: Entry = Entry(vec![
Attribute::new(gender),
Attribute::new(company),
Attribute::new(alt_drivers),
]);
let max_cardinal = 5;
let csc = CrossSetCommitment::new(MaxCardinality(max_cardinal));
let (commitment_1, opening_info_1) = CrossSetCommitment::commit_set(&csc.param_sc, &set_str);
let (commitment_2, opening_info_2) = CrossSetCommitment::commit_set(&csc.param_sc, &set_str2);
let commit_vector = &vec![commitment_1, commitment_2];
let subset_str_1 = Entry(vec![
Attribute::new(age),
Attribute::new(name),
Attribute::new(drivers),
]);
let subset_str_2 = Entry(vec![Attribute::new(gender), Attribute::new(company)]);
let witness_1 =
CrossSetCommitment::open_subset(&csc.param_sc, &set_str, &opening_info_1, &subset_str_1)
.expect("Some Witness");
let witness_2 =
CrossSetCommitment::open_subset(&csc.param_sc, &set_str2, &opening_info_2, &subset_str_2)
.expect("Some Witness");
let proof = CrossSetCommitment::aggregate_cross(&vec![witness_1, witness_2], commit_vector);
assert!(CrossSetCommitment::verify_cross(
&csc.param_sc,
commit_vector,
&[subset_str_1, subset_str_2],
&proof
));
}
#[cfg(test)]
mod test {
use crate::attributes::Attribute;
use super::*;
#[test]
fn test_commit_and_open() {
let max_cardinal = 5;
let age = "age = 30";
let name = "name = Alice";
let drivers = "driver license = 12";
let set_str: Entry = Entry(vec![
Attribute::new(age),
Attribute::new(name),
Attribute::new(drivers),
]);
let sc = SetCommitment::new(MaxCardinality(max_cardinal));
let (commitment, witness) = SetCommitment::commit_set(&sc.param_sc, &set_str);
assert!(SetCommitment::open_set(
&sc.param_sc,
&commitment,
&witness,
&set_str
));
}
#[test]
fn test_open_verify_subset() {
let max_cardinal = 5;
let age = "age = 30";
let name = "name = Alice";
let drivers = "driver license = 12";
let set_str = Entry(vec![
Attribute::new(age),
Attribute::new(name),
Attribute::new(drivers),
]);
let subset_str_1 = Entry(vec![Attribute::new(age), Attribute::new(name)]);
let sc = SetCommitment::new(MaxCardinality(max_cardinal));
let (commitment, opening_info) = SetCommitment::commit_set(&sc.param_sc, &set_str);
let witness_subset =
SetCommitment::open_subset(&sc.param_sc, &set_str, &opening_info, &subset_str_1);
assert!(witness_subset.is_some());
let witness_subset = witness_subset.expect("Some witness");
assert!(SetCommitment::verify_subset(
&sc.param_sc,
&commitment,
&subset_str_1,
&witness_subset
));
}
#[test]
fn test_aggregate_verify_cross() {
let age = "age = 30";
let name = "name = Alice";
let drivers = "driver license = 12";
let gender = "Gender = male";
let company = "company = ACME Inc.";
let alt_drivers = "driver license type = B";
let set_str: Entry = Entry(vec![
Attribute::new(age),
Attribute::new(name),
Attribute::new(drivers),
]);
let set_str2: Entry = Entry(vec![
Attribute::new(gender),
Attribute::new(company),
Attribute::new(alt_drivers),
]);
let max_cardinal = 5;
let csc = CrossSetCommitment::new(MaxCardinality(max_cardinal));
let (commitment_1, opening_info_1) =
CrossSetCommitment::commit_set(&csc.param_sc, &set_str);
let (commitment_2, opening_info_2) =
CrossSetCommitment::commit_set(&csc.param_sc, &set_str2);
let commit_vector = &vec![commitment_1, commitment_2];
let subset_str_1 = Entry(vec![
Attribute::new(age),
Attribute::new(name),
Attribute::new(drivers),
]);
let subset_str_2 = Entry(vec![Attribute::new(gender), Attribute::new(company)]);
let witness_1 = CrossSetCommitment::open_subset(
&csc.param_sc,
&set_str,
&opening_info_1,
&subset_str_1,
)
.expect("Some Witness");
let witness_2 = CrossSetCommitment::open_subset(
&csc.param_sc,
&set_str2,
&opening_info_2,
&subset_str_2,
)
.expect("Some Witness");
let proof = CrossSetCommitment::aggregate_cross(&vec![witness_1, witness_2], commit_vector);
assert!(CrossSetCommitment::verify_cross(
&csc.param_sc,
commit_vector,
&[subset_str_1, subset_str_2],
&proof
));
}
#[test]
fn test_little_endien_power() {
let base: Scalar = Scalar::ONE + Scalar::ONE;
let result = &base.pow(&[2u64, 0, 0, 0]);
let expected = Scalar::from(4u64);
assert_eq!(result, &expected);
}
#[test]
fn test_param_set_commitment_roundtrip() {
let max_cardinal = 5;
let param_sc = ParamSetCommitment::new(&max_cardinal);
let param_sc_compressed = ParamSetCommitmentCompressed::from(param_sc.clone());
let param_sc_decompressed = ParamSetCommitment::try_from(param_sc_compressed).unwrap();
assert_eq!(param_sc, param_sc_decompressed);
}
}