From 6ae0913aa18290e29d729562d056ab7f89e4e8a5 Mon Sep 17 00:00:00 2001 From: aniampio Date: Tue, 22 Mar 2022 18:14:13 +0000 Subject: [PATCH] Copy polynomial and ttpcode from Coconut; add first test; add keypair structures --- common/nym-compact-ecash/src/error.rs | 3 + common/nym-compact-ecash/src/scheme/keygen.rs | 108 +++++++ common/nym-compact-ecash/src/scheme/mod.rs | 2 + .../src/scheme/withdrawal.rs | 23 +- common/nym-compact-ecash/src/tests/e2e.rs | 17 ++ common/nym-compact-ecash/src/utils.rs | 278 ++++++++++++++++-- 6 files changed, 391 insertions(+), 40 deletions(-) diff --git a/common/nym-compact-ecash/src/error.rs b/common/nym-compact-ecash/src/error.rs index 5139984a92..66462ab8e2 100644 --- a/common/nym-compact-ecash/src/error.rs +++ b/common/nym-compact-ecash/src/error.rs @@ -13,6 +13,9 @@ pub enum CompactEcashError { #[error("Deserialization error: {0}")] Deserialization(String), + #[error("Interpolation error: {0}")] + Interpolation(String), + #[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")] DeserializationInvalidLength { actual: usize, diff --git a/common/nym-compact-ecash/src/scheme/keygen.rs b/common/nym-compact-ecash/src/scheme/keygen.rs index f38bb09f3c..267f4cc16f 100644 --- a/common/nym-compact-ecash/src/scheme/keygen.rs +++ b/common/nym-compact-ecash/src/scheme/keygen.rs @@ -5,11 +5,14 @@ use bls12_381::{G1Projective, G2Projective, Scalar}; use crate::error::{CompactEcashError, Result}; use crate::scheme::setup::Parameters; +use crate::scheme::SignerIndex; use crate::utils::{ try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar, try_deserialize_scalar_vec, }; +use crate::utils::Polynomial; +#[derive(Debug, PartialEq, Clone)] pub struct SecretKeyAuth { pub(crate) x: Scalar, pub(crate) ys: Vec, @@ -87,6 +90,7 @@ impl SecretKeyAuth { } } +#[derive(Debug, PartialEq, Clone)] pub struct VerificationKeyAuth { pub(crate) alpha: G2Projective, pub(crate) beta_g1: Vec, @@ -169,6 +173,7 @@ impl TryFrom<&[u8]> for VerificationKeyAuth { } } +#[derive(Debug, PartialEq, Clone)] pub struct SecretKeyUser { pub(crate) sk: Scalar, } @@ -181,6 +186,109 @@ impl SecretKeyUser { } } +#[derive(Debug, PartialEq, Clone)] pub struct PublicKeyUser { pub(crate) pk: G1Projective, } + +pub struct KeyPairAuth { + secret_key: SecretKeyAuth, + verification_key: VerificationKeyAuth, + /// Optional index value specifying polynomial point used during threshold key generation. + pub index: Option, +} + +impl KeyPairAuth { + pub fn secret_key(&self) -> SecretKeyAuth { + self.secret_key.clone() + } + + pub fn verification_key(&self) -> VerificationKeyAuth { self.verification_key.clone() } +} + +pub struct KeyPairUser { + secret_key: SecretKeyUser, + public_key: PublicKeyUser, +} + +impl KeyPairUser { + pub fn secret_key(&self) -> SecretKeyUser { + self.secret_key.clone() + } + + pub fn public_key(&self) -> PublicKeyUser { + self.public_key.clone() + } +} + +pub fn generate_keypair_user(params: &Parameters) -> KeyPairUser { + let sk_user = SecretKeyUser { + sk: params.random_scalar(), + }; + let pk_user = PublicKeyUser { + pk: params.gen1() * sk_user.sk, + }; + + KeyPairUser { + secret_key: sk_user, + public_key: pk_user, + } +} + +pub fn ttp_keygen( + params: &Parameters, + threshold: u64, + num_authorities: u64, +) -> Result> { + if threshold == 0 { + return Err(CompactEcashError::Setup( + "Tried to generate threshold keys with a 0 threshold value".to_string(), + )); + } + + if threshold > num_authorities { + return Err( + CompactEcashError::Setup( + "Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(), + )); + } + + let attributes = params.gammas().len(); + + // generate polynomials + let v = Polynomial::new_random(params, threshold - 1); + let ws = (0..attributes) + .map(|_| Polynomial::new_random(params, threshold - 1)) + .collect::>(); + + // TODO: potentially if we had some known authority identifier we could use that instead + // of the increasing (1,2,3,...) sequence + let polynomial_indices = (1..=num_authorities).collect::>(); + + // generate polynomial shares + let x = polynomial_indices + .iter() + .map(|&id| v.evaluate(&Scalar::from(id))); + let ys = polynomial_indices.iter().map(|&id| { + ws.iter() + .map(|w| w.evaluate(&Scalar::from(id))) + .collect::>() + }); + + // finally set the keys + let secret_keys = x.zip(ys).map(|(x, ys)| SecretKeyAuth { x, ys }); + + let keypairs = secret_keys + .zip(polynomial_indices.iter()) + .map(|(secret_key, index)| { + let verification_key = secret_key.verification_key(params); + KeyPairAuth { + secret_key, + verification_key, + index: Some(*index), + } + }) + .collect(); + + Ok(keypairs) +} \ No newline at end of file diff --git a/common/nym-compact-ecash/src/scheme/mod.rs b/common/nym-compact-ecash/src/scheme/mod.rs index 64cbba6977..165e340c9c 100644 --- a/common/nym-compact-ecash/src/scheme/mod.rs +++ b/common/nym-compact-ecash/src/scheme/mod.rs @@ -6,6 +6,8 @@ pub mod setup; pub mod spend; pub mod withdrawal; +pub type SignerIndex = u64; + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct BlindedSignature(G1Projective, G1Projective); diff --git a/common/nym-compact-ecash/src/scheme/withdrawal.rs b/common/nym-compact-ecash/src/scheme/withdrawal.rs index 08ea88b6ca..52564f90a7 100644 --- a/common/nym-compact-ecash/src/scheme/withdrawal.rs +++ b/common/nym-compact-ecash/src/scheme/withdrawal.rs @@ -1,13 +1,13 @@ use bls12_381::{G1Projective, Scalar}; use group::GroupEncoding; -use nymcoconut::utils::hash_g1; - use crate::error::{CompactEcashError, Result}; use crate::proofs::{WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness}; -use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser}; -use crate::scheme::setup::Parameters; use crate::scheme::BlindedSignature; +use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser}; +use crate::scheme::keygen::ttp_keygen; +use crate::scheme::setup::Parameters; +use crate::utils::hash_g1; pub struct WithdrawalRequest { commitment_hash: G1Projective, @@ -89,7 +89,7 @@ pub fn withdrawal_request( Ok((req, req_info)) } -pub fn issue( +pub fn issue_wallet( params: &Parameters, sk_auth: SecretKeyAuth, pk_user: PublicKeyUser, @@ -128,19 +128,8 @@ pub fn issue( Ok(BlindedSignature(h, sig)) } + #[cfg(test)] mod tests { use super::*; - - #[test] - fn withdrawal_request_test() { - let params = Parameters::new().unwrap(); - let sk_user = SecretKeyUser { - sk: params.random_scalar(), - }; - let pk_user = PublicKeyUser { - pk: params.gen1() * sk_user.sk, - }; - let (req, req_info) = withdrawal_request(¶ms, &sk_user).unwrap(); - } } diff --git a/common/nym-compact-ecash/src/tests/e2e.rs b/common/nym-compact-ecash/src/tests/e2e.rs index 8b13789179..b967f335f9 100644 --- a/common/nym-compact-ecash/src/tests/e2e.rs +++ b/common/nym-compact-ecash/src/tests/e2e.rs @@ -1 +1,18 @@ +use crate::error::CompactEcashError; +use crate::scheme::keygen::{generate_keypair_user, PublicKeyUser, SecretKeyUser, ttp_keygen}; +use crate::scheme::setup::Parameters; +use crate::scheme::withdrawal::{issue_wallet, withdrawal_request}; +#[test] +fn main() -> Result<(), CompactEcashError> { + let params = Parameters::new().unwrap(); + let user_keypair = generate_keypair_user(¶ms); + + let (req, req_info) = withdrawal_request(¶ms, &user_keypair.secret_key()).unwrap(); + let mut authorities_keypairs = ttp_keygen(¶ms, 1, 1).unwrap(); + for auth_keypair in authorities_keypairs { + let blind_signature = issue_wallet(¶ms, auth_keypair.secret_key(), user_keypair.public_key(), &req); + } + + Ok(()) +} diff --git a/common/nym-compact-ecash/src/utils.rs b/common/nym-compact-ecash/src/utils.rs index c1ab93f98e..2846f9db98 100644 --- a/common/nym-compact-ecash/src/utils.rs +++ b/common/nym-compact-ecash/src/utils.rs @@ -1,9 +1,111 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use core::iter::Sum; +use core::ops::Mul; use std::convert::TryInto; -use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar}; +use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; +use ff::Field; use crate::error::{CompactEcashError, Result}; +use crate::scheme::setup::Parameters; +use crate::scheme::SignerIndex; + +pub struct Polynomial { + coefficients: Vec, +} + +impl Polynomial { + // for polynomial of degree n, we generate n+1 values + // (for example for degree 1, like y = x + 2, we need [2,1]) + pub fn new_random(params: &Parameters, degree: u64) -> Self { + Polynomial { + coefficients: params.n_random_scalars((degree + 1) as usize), + } + } + + /// Evaluates the polynomial at point x. + pub fn evaluate(&self, x: &Scalar) -> Scalar { + if self.coefficients.is_empty() { + Scalar::zero() + // if x is zero then we can ignore most of the expensive computation and + // just return the last term of the polynomial + } else if x.is_zero() { + // we checked that coefficients are not empty so unwrap here is fine + *self.coefficients.first().unwrap() + } else { + self.coefficients + .iter() + .enumerate() + // coefficient[n] * x ^ n + .map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0])) + .sum() + } + } +} + +#[inline] +fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec { + let x = Scalar::zero(); + + points + .iter() + .enumerate() + .map(|(i, point_i)| { + let mut numerator = Scalar::one(); + let mut denominator = Scalar::one(); + let xi = Scalar::from(*point_i); + + for (j, point_j) in points.iter().enumerate() { + if j != i { + let xj = Scalar::from(*point_j); + + // numerator = (x - xs[0]) * ... * (x - xs[j]), j != i + numerator *= x - xj; + + // denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i + denominator *= xi - xj; + } + } + // numerator / denominator + numerator * denominator.invert().unwrap() + }) + .collect() +} + +/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`. +/// It can be used for Scalars, G1 and G2 points. +pub(crate) fn perform_lagrangian_interpolation_at_origin( + points: &[SignerIndex], + values: &[T], +) -> Result + where + T: Sum, + for<'a> &'a T: Mul, +{ + if points.is_empty() || values.is_empty() { + return Err(CompactEcashError::Interpolation( + "Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(), + )); + } + + if points.len() != values.len() { + return Err(CompactEcashError::Interpolation( + "Tried to perform lagrangian interpolation for an incomplete set of coordinates" + .to_string(), + )); + } + + let coefficients = generate_lagrangian_coefficients_at_origin(points); + + Ok(coefficients + .into_iter() + .zip(values.iter()) + .map(|(coeff, val)| val * coeff) + .sum()) +} // A temporary way of hashing particular message into G1. // Implementation idea was taken from `threshold_crypto`: @@ -11,6 +113,9 @@ use crate::error::{CompactEcashError, Result}; // Eventually it should get replaced by, most likely, the osswu map // method once ideally it's implemented inside the pairing crate. +// note: I have absolutely no idea what are the correct domains for those. I just used whatever +// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11` + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1 const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_"; @@ -32,28 +137,6 @@ pub fn hash_to_scalar>(msg: M) -> Scalar { output[0] } -pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CompactEcashError) -> Result { - Into::>::into(Scalar::from_bytes(bytes)).ok_or(err) -} - -pub fn try_deserialize_g1_projective( - bytes: &[u8; 48], - err: CompactEcashError, -) -> Result { - Into::>::into(G1Affine::from_compressed(bytes)) - .ok_or(err) - .map(G1Projective::from) -} - -pub fn try_deserialize_g2_projective( - bytes: &[u8; 96], - err: CompactEcashError, -) -> Result { - Into::>::into(G2Affine::from_compressed(bytes)) - .ok_or(err) - .map(G2Projective::from) -} - pub fn try_deserialize_scalar_vec( expected_len: u64, bytes: &[u8], @@ -75,3 +158,152 @@ pub fn try_deserialize_scalar_vec( Ok(out) } + +pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CompactEcashError) -> Result { + Into::>::into(Scalar::from_bytes(bytes)).ok_or(err) +} + +pub fn try_deserialize_g1_projective(bytes: &[u8; 48], err: CompactEcashError) -> Result { + Into::>::into(G1Affine::from_compressed(bytes)) + .ok_or(err) + .map(G1Projective::from) +} + +pub fn try_deserialize_g2_projective(bytes: &[u8; 96], err: CompactEcashError) -> Result { + Into::>::into(G2Affine::from_compressed(bytes)) + .ok_or(err) + .map(G2Projective::from) +} + + +#[cfg(test)] +mod tests { + use rand::RngCore; + + use super::*; + + #[test] + fn polynomial_evaluation() { + // y = 42 (it should be 42 regardless of x) + let poly = Polynomial { + coefficients: vec![Scalar::from(42)], + }; + + assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1))); + assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0))); + assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10))); + + // y = x + 10, at x = 2 (exp: 12) + let poly = Polynomial { + coefficients: vec![Scalar::from(10), Scalar::from(1)], + }; + + assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2))); + + // y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39) + let poly = Polynomial { + coefficients: vec![ + (-Scalar::from(3)), + Scalar::from(2), + (-Scalar::from(5)), + Scalar::zero(), + Scalar::from(1), + ], + }; + + assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3))); + + // empty polynomial + let poly = Polynomial { + coefficients: vec![], + }; + + // should always be 0 + assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1))); + assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0))); + assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10))); + } + + #[test] + fn performing_lagrangian_scalar_interpolation_at_origin() { + // x^2 + 3 + // x, f(x): + // 1, 4, + // 2, 7, + // 3, 12, + let points = vec![1, 2, 3]; + let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)]; + + assert_eq!( + Scalar::from(3), + perform_lagrangian_interpolation_at_origin(&points, &values).unwrap() + ); + + // x^3 + 3x^2 - 5x + 11 + // x, f(x): + // 1, 10 + // 2, 21 + // 3, 50 + // 4, 103 + let points = vec![1, 2, 3, 4]; + let values = vec![ + Scalar::from(10), + Scalar::from(21), + Scalar::from(50), + Scalar::from(103), + ]; + + assert_eq!( + Scalar::from(11), + perform_lagrangian_interpolation_at_origin(&points, &values).unwrap() + ); + + // more points than it is required + // x^2 + x + 10 + // x, f(x) + // 1, 12 + // 2, 16 + // 3, 22 + // 4, 30 + // 5, 40 + let points = vec![1, 2, 3, 4, 5]; + let values = vec![ + Scalar::from(12), + Scalar::from(16), + Scalar::from(22), + Scalar::from(30), + Scalar::from(40), + ]; + + assert_eq!( + Scalar::from(10), + perform_lagrangian_interpolation_at_origin(&points, &values).unwrap() + ); + } + + #[test] + fn hash_g1_sanity_check() { + let mut rng = rand::thread_rng(); + let mut msg1 = [0u8; 1024]; + rng.fill_bytes(&mut msg1); + let mut msg2 = [0u8; 1024]; + rng.fill_bytes(&mut msg2); + + assert_eq!(hash_g1(msg1), hash_g1(msg1)); + assert_eq!(hash_g1(msg2), hash_g1(msg2)); + assert_ne!(hash_g1(msg1), hash_g1(msg2)); + } + + #[test] + fn hash_scalar_sanity_check() { + let mut rng = rand::thread_rng(); + let mut msg1 = [0u8; 1024]; + rng.fill_bytes(&mut msg1); + let mut msg2 = [0u8; 1024]; + rng.fill_bytes(&mut msg2); + + assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1)); + assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2)); + assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2)); + } +}