Copy polynomial and ttpcode from Coconut; add first test; add keypair structures
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<Scalar>,
|
||||
@@ -87,6 +90,7 @@ impl SecretKeyAuth {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct VerificationKeyAuth {
|
||||
pub(crate) alpha: G2Projective,
|
||||
pub(crate) beta_g1: Vec<G1Projective>,
|
||||
@@ -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<SignerIndex>,
|
||||
}
|
||||
|
||||
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<Vec<KeyPairAuth>> {
|
||||
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::<Vec<_>>();
|
||||
|
||||
// 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::<Vec<_>>();
|
||||
|
||||
// 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::<Vec<_>>()
|
||||
});
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -1,9 +1,111 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<Scalar>,
|
||||
}
|
||||
|
||||
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<Scalar> {
|
||||
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<T>(
|
||||
points: &[SignerIndex],
|
||||
values: &[T],
|
||||
) -> Result<T>
|
||||
where
|
||||
T: Sum,
|
||||
for<'a> &'a T: Mul<Scalar, Output=T>,
|
||||
{
|
||||
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<M: AsRef<[u8]>>(msg: M) -> Scalar {
|
||||
output[0]
|
||||
}
|
||||
|
||||
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CompactEcashError) -> Result<Scalar> {
|
||||
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g1_projective(
|
||||
bytes: &[u8; 48],
|
||||
err: CompactEcashError,
|
||||
) -> Result<G1Projective> {
|
||||
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
|
||||
.ok_or(err)
|
||||
.map(G1Projective::from)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g2_projective(
|
||||
bytes: &[u8; 96],
|
||||
err: CompactEcashError,
|
||||
) -> Result<G2Projective> {
|
||||
Into::<Option<G2Affine>>::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<Scalar> {
|
||||
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g1_projective(bytes: &[u8; 48], err: CompactEcashError) -> Result<G1Projective> {
|
||||
Into::<Option<G1Affine>>::into(G1Affine::from_compressed(bytes))
|
||||
.ok_or(err)
|
||||
.map(G1Projective::from)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g2_projective(bytes: &[u8; 96], err: CompactEcashError) -> Result<G2Projective> {
|
||||
Into::<Option<G2Affine>>::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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user