Work in progress: structure preserving signature scheme

This commit is contained in:
aniampio
2022-04-04 15:24:16 +01:00
parent c902e8eaa5
commit 33c356b3ec
9 changed files with 740 additions and 7 deletions
Generated
+16
View File
@@ -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"
@@ -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<Scalar> for &'a VerificationKeyAuth {
}
impl<T> Sum<T> for VerificationKeyAuth
where
T: Borrow<VerificationKeyAuth>,
where
T: Borrow<VerificationKeyAuth>,
{
#[inline]
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = T>,
where
I: Iterator<Item=T>,
{
let mut peekable = iter.peekable();
let head_attributes = match peekable.peek() {
@@ -440,3 +440,4 @@ pub fn ttp_keygen(
Ok(keypairs)
}
+36
View File
@@ -0,0 +1,36 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, DivisibleEcashError>;
#[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,
},
}
+7 -1
View File
@@ -1,5 +1,11 @@
use bls12_381::Scalar;
mod proofs;
mod scheme;
#[cfg(test)]
mod tests;
mod error;
mod error;
mod utils;
mod traits;
pub type Attribute = Scalar;
+2 -1
View File
@@ -2,4 +2,5 @@ pub mod aggregation;
pub mod identify;
pub mod keygen;
pub mod setup;
pub mod withdrawal;
pub mod withdrawal;
pub mod structure_preserving_signature;
@@ -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<GroupParameters> {
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<Scalar> {
(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<G1Projective> = (1..=grp.getL()).map(|i| sigma * (y * Scalar::from(i))).collect();
let thetasUser: Vec<G1Projective> = (1..=grp.getL()).map(|i| theta * (y * Scalar::from(i))).collect();
let deltasAuth: Vec<G2Projective> = (0..=grp.getL() - 1).map(|i| g2 * (y * Scalar::from(i))).collect();
let etasUser: Vec<G1Projective> = vec_a.iter().map(|x| g1 * x).collect();
let mut etasAuth: Vec<G2Projective> = 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<G1Projective>,
sigmas: Vec<G1Projective>,
thetas: Vec<G1Projective>,
}
#[derive(Debug, Clone)]
pub struct ParametersAuthority {
deltas: Vec<G2Projective>,
etas: Vec<G2Projective>,
}
@@ -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<G1Projective>,
pub Ws: Vec<G2Projective>,
pub Y: G2Projective,
pub Z: G2Projective,
}
pub(crate) struct SPSSecretKey {
spsVK: SPSVerificationKey,
us: Vec<Scalar>,
ws: Vec<Scalar>,
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<Attribute>) -> SPSSignature {
let r = grparams.random_scalar();
let R = grparams.gen1() * r;
let prod: Vec<Scalar> = 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<G1Projective> = us.iter().map(|u| grparams.gen1() * u).collect();
let Y = grparams.gen2() * y;
let Ws: Vec<G2Projective> = 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 {}
+22
View File
@@ -0,0 +1,22 @@
use crate::error::DivisibleEcashError;
pub trait Bytable
where
Self: Sized,
{
fn to_byte_vec(&self) -> Vec<u8>;
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, DivisibleEcashError>;
}
pub trait Base58
where
Self: Bytable,
{
fn try_from_bs58<S: AsRef<str>>(x: S) -> Result<Self, DivisibleEcashError> {
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()
}
}
+432
View File
@@ -0,0 +1,432 @@
// 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::{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<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(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<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(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<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
}
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
let mut output = vec![Scalar::zero()];
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
msg.as_ref(),
SCALAR_HASH_DOMAIN,
&mut output,
);
output[0]
}
pub fn try_deserialize_scalar_vec(
expected_len: u64,
bytes: &[u8],
err: DivisibleEcashError,
) -> Result<Vec<Scalar>> {
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::<Option<Scalar>>::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<Scalar> {
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
}
pub fn try_deserialize_g1_projective(
bytes: &[u8; 48],
err: DivisibleEcashError,
) -> 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: DivisibleEcashError,
) -> Result<G2Projective> {
Into::<Option<G2Affine>>::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<Signature> {
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> {
Signature::try_from(bytes)
}
}
impl Bytable for Signature {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
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<Signature> {
// 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));
}
}