From 33c356b3ecef7ee17dd416cc5740430f95d5ec9c Mon Sep 17 00:00:00 2001 From: aniampio Date: Mon, 4 Apr 2022 15:24:16 +0100 Subject: [PATCH] Work in progress: structure preserving signature scheme --- Cargo.lock | 16 + common/nym-compact-ecash/src/scheme/keygen.rs | 11 +- common/nym-divisible-ecash/src/error.rs | 36 ++ common/nym-divisible-ecash/src/lib.rs | 8 +- common/nym-divisible-ecash/src/scheme/mod.rs | 3 +- .../nym-divisible-ecash/src/scheme/setup.rs | 140 ++++++ .../scheme/structure_preserving_signature.rs | 79 ++++ common/nym-divisible-ecash/src/traits.rs | 22 + common/nym-divisible-ecash/src/utils.rs | 432 ++++++++++++++++++ 9 files changed, 740 insertions(+), 7 deletions(-) create mode 100644 common/nym-divisible-ecash/src/scheme/structure_preserving_signature.rs create mode 100644 common/nym-divisible-ecash/src/traits.rs create mode 100644 common/nym-divisible-ecash/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index a2e07a074e..c748fdd173 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2950,6 +2950,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nym-divisible-ecash" +version = "0.1.0" +dependencies = [ + "bls12_381", + "bs58", + "criterion", + "digest 0.9.0", + "ff 0.10.1", + "group 0.10.0", + "itertools", + "rand 0.8.5", + "sha2", + "thiserror", +] + [[package]] name = "nym-gateway" version = "0.12.1" diff --git a/common/nym-compact-ecash/src/scheme/keygen.rs b/common/nym-compact-ecash/src/scheme/keygen.rs index de9b77008a..a14e08d993 100644 --- a/common/nym-compact-ecash/src/scheme/keygen.rs +++ b/common/nym-compact-ecash/src/scheme/keygen.rs @@ -11,11 +11,11 @@ use crate::error::{CompactEcashError, Result}; use crate::scheme::aggregation::aggregate_verification_keys; use crate::scheme::setup::Parameters; use crate::scheme::SignerIndex; -use crate::utils::Polynomial; 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 { @@ -242,13 +242,13 @@ impl<'a> Mul for &'a VerificationKeyAuth { } impl Sum for VerificationKeyAuth -where - T: Borrow, + where + T: Borrow, { #[inline] fn sum(iter: I) -> Self - where - I: Iterator, + where + I: Iterator, { let mut peekable = iter.peekable(); let head_attributes = match peekable.peek() { @@ -440,3 +440,4 @@ pub fn ttp_keygen( Ok(keypairs) } + diff --git a/common/nym-divisible-ecash/src/error.rs b/common/nym-divisible-ecash/src/error.rs index e69de29bb2..43b3f6ae68 100644 --- a/common/nym-divisible-ecash/src/error.rs +++ b/common/nym-divisible-ecash/src/error.rs @@ -0,0 +1,36 @@ +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum DivisibleEcashError { + #[error("Setup error: {0}")] + Setup(String), + + #[error("Aggregation error: {0}")] + Aggregation(String), + + #[error("Withdrawal Request Verification related error: {0}")] + WithdrawalRequestVerification(String), + + #[error("Deserialization error: {0}")] + Deserialization(String), + + #[error("Interpolation error: {0}")] + Interpolation(String), + + #[error("Issuance Verification related error: {0}")] + IssuanceVfy(String), + + #[error("Spend Verification related error: {0}")] + Spend(String), + + #[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")] + DeserializationInvalidLength { + actual: usize, + target: usize, + modulus_target: usize, + modulus: usize, + object: String, + }, +} diff --git a/common/nym-divisible-ecash/src/lib.rs b/common/nym-divisible-ecash/src/lib.rs index 4195485e46..e024c444b8 100644 --- a/common/nym-divisible-ecash/src/lib.rs +++ b/common/nym-divisible-ecash/src/lib.rs @@ -1,5 +1,11 @@ +use bls12_381::Scalar; + mod proofs; mod scheme; #[cfg(test)] mod tests; -mod error; \ No newline at end of file +mod error; +mod utils; +mod traits; + +pub type Attribute = Scalar; \ No newline at end of file diff --git a/common/nym-divisible-ecash/src/scheme/mod.rs b/common/nym-divisible-ecash/src/scheme/mod.rs index b93a145d29..3eb6c0d793 100644 --- a/common/nym-divisible-ecash/src/scheme/mod.rs +++ b/common/nym-divisible-ecash/src/scheme/mod.rs @@ -2,4 +2,5 @@ pub mod aggregation; pub mod identify; pub mod keygen; pub mod setup; -pub mod withdrawal; \ No newline at end of file +pub mod withdrawal; +pub mod structure_preserving_signature; \ No newline at end of file diff --git a/common/nym-divisible-ecash/src/scheme/setup.rs b/common/nym-divisible-ecash/src/scheme/setup.rs index e69de29bb2..0a85070ca4 100644 --- a/common/nym-divisible-ecash/src/scheme/setup.rs +++ b/common/nym-divisible-ecash/src/scheme/setup.rs @@ -0,0 +1,140 @@ +use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar}; +use ff::Field; +use rand::thread_rng; + +use crate::error::Result; +use crate::utils::hash_g1; + +const MAX_COIN_VALUE: u64 = 32; + +#[derive(Debug, Clone)] +pub struct GroupParameters { + /// Generator of the G1 group + g1: G1Affine, + /// Generator of the G2 group + g2: G2Affine, + /// Precomputed G2 generator used for the miller loop + _g2_prepared_miller: G2Prepared, + /// Value of wallet + L: u64, +} + +impl GroupParameters { + pub fn new() -> Result { + Ok(GroupParameters { + g1: G1Affine::generator(), + g2: G2Affine::generator(), + _g2_prepared_miller: G2Prepared::from(G2Affine::generator()), + L: MAX_COIN_VALUE, + }) + } + + pub(crate) fn gen1(&self) -> &G1Affine { + &self.g1 + } + + pub(crate) fn gen2(&self) -> &G2Affine { + &self.g2 + } + + pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared { + &self._g2_prepared_miller + } + + pub(crate) fn getL(&self) -> u64 { self.L } + + pub(crate) fn random_scalar(&self) -> Scalar { + // lazily-initialized thread-local random number generator, seeded by the system + let mut rng = thread_rng(); + Scalar::random(&mut rng) + } + + pub(crate) fn n_random_scalars(&self, n: usize) -> Vec { + (0..n).map(|_| self.random_scalar()).collect() + } +} + +#[derive(Debug, Clone)] +pub struct Parameters { + paramsUser: ParametersUser, + paramsAuth: ParametersAuthority, +} + + +impl Parameters { + pub fn new(grp: GroupParameters) -> Parameters { + let g1 = grp.gen1(); + let g2 = grp.gen2(); + let gamma1 = hash_g1("gamma1"); + let gamma2 = hash_g1("gamma2"); + let eta = hash_g1("eta"); + let omega = hash_g1("omega"); + + let z = grp.random_scalar(); + let y = grp.random_scalar(); + + let vec_a = grp.n_random_scalars(grp.getL() as usize); + + let sigma = g1 * z; + let theta = eta * z; + + let sigmasUser: Vec = (1..=grp.getL()).map(|i| sigma * (y * Scalar::from(i))).collect(); + let thetasUser: Vec = (1..=grp.getL()).map(|i| theta * (y * Scalar::from(i))).collect(); + + let deltasAuth: Vec = (0..=grp.getL() - 1).map(|i| g2 * (y * Scalar::from(i))).collect(); + let etasUser: Vec = vec_a.iter().map(|x| g1 * x).collect(); + + let mut etasAuth: Vec = Default::default(); + for l in 1..=grp.getL() { + for k in 0..=l - 1 { + etasAuth.push(g2 * (vec_a[l as usize].neg() * (y * Scalar::from(k)))); + } + } + + // TODO: Run the key generation algorithm of the Structure-Preserving Signature scheme + + // Compute signature for each pair sigma, theta + let paramsUser = ParametersUser { + g1: *g1, + g2: *g2, + gamma1, + gamma2, + eta, + omega, + etas: etasUser, + sigmas: sigmasUser, + thetas: thetasUser, + }; + + let paramsAuth = ParametersAuthority { + deltas: deltasAuth, + etas: etasAuth, + }; + + return Parameters { + paramsUser, + paramsAuth, + }; + } +} + +#[derive(Debug, Clone)] +pub struct ParametersUser { + /// Generator of the G1 group + g1: G1Affine, + /// Generator of the G2 group + g2: G2Affine, + gamma1: G1Projective, + gamma2: G1Projective, + eta: G1Projective, + omega: G1Projective, + etas: Vec, + sigmas: Vec, + thetas: Vec, +} + +#[derive(Debug, Clone)] +pub struct ParametersAuthority { + deltas: Vec, + etas: Vec, +} \ No newline at end of file diff --git a/common/nym-divisible-ecash/src/scheme/structure_preserving_signature.rs b/common/nym-divisible-ecash/src/scheme/structure_preserving_signature.rs new file mode 100644 index 0000000000..4207aa4c3b --- /dev/null +++ b/common/nym-divisible-ecash/src/scheme/structure_preserving_signature.rs @@ -0,0 +1,79 @@ +use std::convert::TryFrom; + +use bls12_381::{G1Projective, G2Projective, Scalar}; +use group::Curve; + +use crate::Attribute; +use crate::scheme::setup::GroupParameters; + +#[derive(Debug, Clone)] +pub(crate) struct SPSVerificationKey { + pub grparams: GroupParameters, + pub Us: Vec, + pub Ws: Vec, + pub Y: G2Projective, + pub Z: G2Projective, +} + +pub(crate) struct SPSSecretKey { + spsVK: SPSVerificationKey, + us: Vec, + ws: Vec, + y: Scalar, + z: Scalar, +} + +impl SPSSecretKey { + pub fn z(&self) -> Scalar { self.z } + pub fn y(&self) -> Scalar { self.y } + pub fn sign(&self, grparams: GroupParameters, attributes: Vec) -> SPSSignature { + let r = grparams.random_scalar(); + let R = grparams.gen1() * r; + let prod: Vec = attributes.iter().zip(self.ws.iter()).map(|(w_i, m_i)| m_i * w_i.neg()).collect(); + let Z = grparams.gen1() * (self.z() - r * self.y()) + prod.iter().fold(1 | acc, x | acc * x); + // let sum = a.iter().fold(0, |acc, x| acc + x); + // let Z: G1Projective = grparams.gen1() * (self.z() - r * self.y()) + // + attributes + // .iter() + // .zip(self.ws.iter()) + // .map(|(w_i, m_i)| m_i * w_i.neg()).product(); + SPSSignature {} + } +} + +pub struct SPSKeyPair { + spsSK: SPSSecretKey, + spsVK: SPSVerificationKey, +} + +impl SPSKeyPair { + pub fn new(grparams: GroupParameters, a: usize, b: usize) -> SPSKeyPair { + let us = grparams.n_random_scalars(b); + let ws = grparams.n_random_scalars(a); + let y = grparams.random_scalar(); + let z = grparams.random_scalar(); + let Us: Vec = us.iter().map(|u| grparams.gen1() * u).collect(); + let Y = grparams.gen2() * y; + let Ws: Vec = ws.iter().map(|w| grparams.gen2() * w).collect(); + let Z = grparams.gen2() * z; + + let spsVK = SPSVerificationKey { + grparams, + Us, + Ws, + Y, + Z, + }; + let spsSK = SPSSecretKey { + spsVK: spsVK.clone(), + us, + ws, + y, + z, + }; + SPSKeyPair { spsSK, spsVK } + } +} + +pub struct SPSSignature {} + diff --git a/common/nym-divisible-ecash/src/traits.rs b/common/nym-divisible-ecash/src/traits.rs new file mode 100644 index 0000000000..425c9dd366 --- /dev/null +++ b/common/nym-divisible-ecash/src/traits.rs @@ -0,0 +1,22 @@ +use crate::error::DivisibleEcashError; + +pub trait Bytable + where + Self: Sized, +{ + fn to_byte_vec(&self) -> Vec; + + fn try_from_byte_slice(slice: &[u8]) -> Result; +} + +pub trait Base58 + where + Self: Bytable, +{ + fn try_from_bs58>(x: S) -> Result { + Self::try_from_byte_slice(&bs58::decode(x.as_ref()).into_vec().unwrap()) + } + fn to_bs58(&self) -> String { + bs58::encode(self.to_byte_vec()).into_string() + } +} diff --git a/common/nym-divisible-ecash/src/utils.rs b/common/nym-divisible-ecash/src/utils.rs new file mode 100644 index 0000000000..3eeee13db2 --- /dev/null +++ b/common/nym-divisible-ecash/src/utils.rs @@ -0,0 +1,432 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use core::iter::Sum; +use core::ops::Mul; +use std::convert::{TryFrom, TryInto}; +use std::ops::Neg; + +use bls12_381::{ + G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, multi_miller_loop, Scalar, +}; +use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; +use ff::Field; +use group::{Curve, Group}; + +use crate::error::{DivisibleEcashError, Result}; +use crate::scheme::setup::GroupParameters; +use crate::traits::Bytable; + +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(grp: &GroupParameters, degree: u64) -> Self { + Polynomial { + coefficients: grp.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(DivisibleEcashError::Interpolation( + "Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(), + )); + } + + if points.len() != values.len() { + return Err(DivisibleEcashError::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`: +// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691 +// 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_"; + +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1 +const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander"; + +pub fn hash_g1>(msg: M) -> G1Projective { + >>::hash_to_curve(msg, G1_HASH_DOMAIN) +} + +pub fn hash_to_scalar>(msg: M) -> Scalar { + let mut output = vec![Scalar::zero()]; + + Scalar::hash_to_field::>( + msg.as_ref(), + SCALAR_HASH_DOMAIN, + &mut output, + ); + output[0] +} + +pub fn try_deserialize_scalar_vec( + expected_len: u64, + bytes: &[u8], + err: DivisibleEcashError, +) -> Result> { + if bytes.len() != expected_len as usize * 32 { + return Err(err); + } + + let mut out = Vec::with_capacity(expected_len as usize); + for i in 0..expected_len as usize { + let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap(); + let s = match Into::>::into(Scalar::from_bytes(&s_bytes)) { + None => return Err(err), + Some(scalar) => scalar, + }; + out.push(s) + } + + Ok(out) +} + +pub fn try_deserialize_scalar(bytes: &[u8; 32], err: DivisibleEcashError) -> Result { + Into::>::into(Scalar::from_bytes(bytes)).ok_or(err) +} + +pub fn try_deserialize_g1_projective( + bytes: &[u8; 48], + err: DivisibleEcashError, +) -> Result { + Into::>::into(G1Affine::from_compressed(bytes)) + .ok_or(err) + .map(G1Projective::from) +} + +pub fn try_deserialize_g2_projective( + bytes: &[u8; 96], + err: DivisibleEcashError, +) -> Result { + Into::>::into(G2Affine::from_compressed(bytes)) + .ok_or(err) + .map(G2Projective::from) +} + +/// Checks whether e(P, Q) * e(-R, S) == id +pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool { + // checking e(P, Q) * e(-R, S) == id + // is equivalent to checking e(P, Q) == e(R, S) + // but requires only a single final exponentiation rather than two of them + // and therefore, as seen via benchmarks.rs, is almost 50% faster + // (1.47ms vs 2.45ms, tested on R9 5900X) + + let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]); + multi_miller.final_exponentiation().is_identity().into() +} + +pub type SignerIndex = u64; + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(test, derive(PartialEq))] +pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective); + +pub type PartialSignature = Signature; + +impl TryFrom<&[u8]> for Signature { + type Error = DivisibleEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != 96 { + return Err(DivisibleEcashError::Deserialization(format!( + "Signature must be exactly 96 bytes, got {}", + bytes.len() + ))); + } + + let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48"); + let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48"); + + let sig1 = try_deserialize_g1_projective( + sig1_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize compressed sig1".to_string()), + )?; + + let sig2 = try_deserialize_g1_projective( + sig2_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize compressed sig2".to_string()), + )?; + + Ok(Signature(sig1, sig2)) + } +} + +impl Signature { + pub(crate) fn sig1(&self) -> &G1Projective { + &self.0 + } + + pub(crate) fn sig2(&self) -> &G1Projective { + &self.1 + } + + pub fn randomise(&self, grp: &GroupParameters) -> (Signature, Scalar) { + let r = grp.random_scalar(); + let r_prime = grp.random_scalar(); + let h_prime = self.0 * r_prime; + let s_prime = (self.1 * r_prime) + (h_prime * r); + (Signature(h_prime, s_prime), r) + } + + pub fn to_bytes(self) -> [u8; 96] { + let mut bytes = [0u8; 96]; + bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed()); + bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed()); + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + Signature::try_from(bytes) + } +} + +impl Bytable for Signature { + fn to_byte_vec(&self) -> Vec { + self.to_bytes().to_vec() + } + + fn try_from_byte_slice(slice: &[u8]) -> Result { + Signature::from_bytes(slice) + } +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub struct BlindedSignature(pub(crate) G1Projective, pub(crate) G1Projective); + +pub struct SignatureShare { + signature: Signature, + index: SignerIndex, +} + +impl SignatureShare { + pub fn new(signature: Signature, index: SignerIndex) -> Self { + SignatureShare { signature, index } + } + + pub fn signature(&self) -> &Signature { + &self.signature + } + + pub fn index(&self) -> SignerIndex { + self.index + } + + // pub fn aggregate(shares: &[Self]) -> Result { + // aggregate_signature_shares(shares) + // } +} + +#[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)); + } +}