Add initial functions

This commit is contained in:
aniampio
2022-03-21 18:09:05 +00:00
committed by durch
parent 9558c3deb2
commit bea8e4a881
16 changed files with 462 additions and 17 deletions
Vendored
BIN
View File
Binary file not shown.
+1
View File
@@ -56,6 +56,7 @@ members = [
"common/node-tester-utils",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nym-compact-ecash",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
+13 -1
View File
@@ -8,6 +8,18 @@ edition = "2021"
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
nymcoconut = { path = "../nymcoconut" }
rand = "0.8"
thiserror = "1.0"
sha2 = "0.9"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
criterion = { version = "0.3", features = ["html_reports"] }
[dependencies.ff]
version = "0.10"
default-features = false
[dependencies.group]
version = "0.10"
default-features = false
+24
View File
@@ -0,0 +1,24 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, CompactEcashError>;
#[derive(Error, Debug)]
pub enum CompactEcashError {
#[error("Setup error: {0}")]
Setup(String),
#[error("Withdrawal Request Verification related error: {0}")]
WithdrawalVerification(String),
#[error("Deserialization error: {0}")]
Deserialization(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,2 +1,8 @@
pub use nymcoconut::*;
mod error;
mod proofs;
mod scheme;
mod scheme;
mod utils;
#[cfg(test)]
mod tests;
-3
View File
@@ -1,3 +0,0 @@
fn main() {
println!("Hello, world!");
}
@@ -0,0 +1,4 @@
/// Instances:
/// Statement:
/// Witnesses:
pub struct WithdrawalProof {}
@@ -0,0 +1,171 @@
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Projective, G2Projective, Scalar};
use crate::error::{CompactEcashError, Result};
use crate::scheme::setup::Parameters;
use crate::utils::{try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar, try_deserialize_scalar_vec};
pub struct SecretKeyAuth {
pub(crate) x: Scalar,
pub(crate) ys: Vec<Scalar>,
}
impl TryFrom<&[u8]> for SecretKeyAuth {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<SecretKeyAuth> {
// There should be x and at least one y
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8,
target: 32 * 2 + 8,
modulus: 32,
object: "secret key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
let actual_ys_len = (bytes.len() - 40) / 32;
if ys_len as usize != actual_ys_len {
return Err(CompactEcashError::Deserialization(format!(
"Tried to deserialize secret key with inconsistent ys len (expected {}, got {})",
ys_len, actual_ys_len
)));
}
let x = try_deserialize_scalar(
&x_bytes,
CompactEcashError::Deserialization("Failed to deserialize secret key scalar".to_string()),
)?;
let ys = try_deserialize_scalar_vec(
ys_len,
&bytes[40..],
CompactEcashError::Deserialization("Failed to deserialize secret key scalars".to_string()),
)?;
Ok(SecretKeyAuth { x, ys })
}
}
impl SecretKeyAuth {
pub fn verification_key(&self, params: &Parameters) -> VerificationKeyAuth {
let g1 = params.gen1();
let g2 = params.gen2();
VerificationKeyAuth {
alpha: g2 * self.x,
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len();
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
bytes.extend_from_slice(&self.x.to_bytes());
bytes.extend_from_slice(&ys_len.to_le_bytes());
for y in self.ys.iter() {
bytes.extend_from_slice(&y.to_bytes())
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<SecretKeyAuth> {
SecretKeyAuth::try_from(bytes)
}
}
pub struct VerificationKeyAuth {
pub(crate) alpha: G2Projective,
pub(crate) beta_g1: Vec<G1Projective>,
pub(crate) beta_g2: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for VerificationKeyAuth {
type Error = CompactEcashError;
fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
// There should be at least alpha, one betaG1 and one betaG2 and their length
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
return Err(CompactEcashError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8 - 96,
target: 96 * 2 + 48 + 8,
modulus: 96 + 48,
object: "verification key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
if betas_len as usize != actual_betas_len {
return Err(
CompactEcashError::Deserialization(
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
betas_len, actual_betas_len
)));
}
let alpha = try_deserialize_g2_projective(
&alpha_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G2 point (alpha)".to_string(),
),
)?;
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
let mut beta_g1_end: u64 = 0;
for i in 0..betas_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective(
&beta_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G1 point (beta)".to_string(),
),
)?;
beta_g1_end = end as u64;
beta_g1.push(beta_i)
}
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize;
let end = (start + 96) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
CompactEcashError::Deserialization(
"Failed to deserialize verification key G2 point (beta)".to_string(),
),
)?;
beta_g2.push(beta_i)
}
Ok(VerificationKeyAuth {
alpha,
beta_g1,
beta_g2,
})
}
}
pub struct SecretKeyUser {
pub(crate) sk: Scalar,
}
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
+7 -1
View File
@@ -1,5 +1,11 @@
use bls12_381::G1Projective;
pub mod aggregation;
pub mod withdrawal;
pub mod keygen;
pub mod setup;
pub mod spend;
pub mod spend;
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct BlindedSignature(G1Projective, G1Projective);
+48 -1
View File
@@ -1,4 +1,13 @@
use bls12_381::{G1Affine, G2Affine};
use bls12_381::{G1Affine, G2Affine, Scalar};
use ff::Field;
use group::Curve;
use rand::thread_rng;
use crate::error::Result;
use crate::utils::hash_g1;
const ATTRIBUTES_LEN: usize = 3;
const MAX_COIN_VALUE: usize = 32;
pub struct Parameters {
/// Generator of the G1 group
@@ -9,4 +18,42 @@ pub struct Parameters {
gammas: Vec<G1Affine>,
/// Value of wallet
L: usize,
}
impl Parameters {
pub fn new() -> Result<Parameters> {
let gammas = (1..=ATTRIBUTES_LEN)
.map(|i| hash_g1(format!("gamma{}", i)).to_affine())
.collect();
Ok(Parameters {
g1: G1Affine::generator(),
g2: G2Affine::generator(),
gammas,
L: MAX_COIN_VALUE,
})
}
pub(crate) fn gen1(&self) -> &G1Affine {
&self.g1
}
pub(crate) fn gen2(&self) -> &G2Affine {
&self.g2
}
pub(crate) fn gammas(&self) -> &Vec<G1Affine> { &self.gammas }
pub 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 fn n_random_scalars(&self, n: usize) -> Vec<Scalar> {
(0..n).map(|_| self.random_scalar()).collect()
}
}
pub fn setup() -> Result<Parameters> {
Parameters::new()
}
@@ -0,0 +1,88 @@
use bls12_381::{G1Projective, Scalar};
use group::{Curve, GroupEncoding};
use nymcoconut::utils::hash_g1;
use crate::error::{CompactEcashError, Result};
use crate::proofs::WithdrawalProof;
use crate::scheme::BlindedSignature;
use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser};
use crate::scheme::setup::Parameters;
pub struct WithdrawalRequest {
commitment_hash: G1Projective,
attrs_commitment: G1Projective,
pc_commitments: Vec<G1Projective>,
zk_proof: WithdrawalProof,
}
pub struct RequestInfo {
commitment_hash: G1Projective,
attrs_commitment_opening: Scalar,
pc_openings: Vec<Scalar>,
v: Scalar,
t: Scalar,
}
pub fn withdrawal_request(params: &Parameters, skUser: SecretKeyUser) {
let v = params.random_scalar();
let t = params.random_scalar();
let attributes = [skUser.sk, v, t];
let gammas = params.gammas();
let attrs_commitment_opening = params.random_scalar();
let attribute_commitment = attributes
.iter()
.zip(gammas).map(|(&m, gamma)| gamma * m)
.sum::<G1Projective>();
let commitment_hash = hash_g1(attribute_commitment.to_bytes());
let pc_openings = params.n_random_scalars(attributes.len());
// Compute Pedersen commitment for each attribute
let pc_attributes = pc_openings
.iter()
.zip(attributes.iter())
.map(|(o_j, m_j)| params.gen1() * o_j + commitment_hash * m_j)
.collect::<Vec<_>>();
// construct a zk proof of knowledge proving possession of m1, m2, m3, o, o1, o2, o3
let req_info = RequestInfo {
commitment_hash,
attrs_commitment_opening,
pc_openings,
v,
t,
};
}
pub fn issue(params: &Parameters, skAuth: SecretKeyAuth, pkUser: PublicKeyUser, withReq: &WithdrawalRequest) -> Result<BlindedSignature> {
let h = hash_g1(withReq.attrs_commitment.to_bytes());
if !(h == withReq.commitment_hash) {
return Err(CompactEcashError::WithdrawalVerification(
"Failed to verify the commitment hash".to_string(),
));
}
let sig = withReq.pc_commitments
.iter()
.zip(skAuth.ys.iter())
.map(|(pc, yi)| pc * yi)
.chain(std::iter::once(h * skAuth.x)).sum();
// verify zk proof
Ok(BlindedSignature(h, sig))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn withdrawal_request() {
let params = Parameters::new().unwrap();
}
}
@@ -0,0 +1 @@
mod e2e;
+77
View File
@@ -0,0 +1,77 @@
use std::convert::TryInto;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar};
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
use crate::error::{CompactEcashError, Result};
// 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.
// 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(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],
err: CompactEcashError,
) -> 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)
}
+12 -1
View File
@@ -27,8 +27,19 @@ pub use scheme::verification::prove_bandwidth_credential;
pub use scheme::verification::verify_credential;
pub use scheme::verification::Theta;
pub use scheme::BlindedSignature;
pub use scheme::issuance::blind_sign;
pub use scheme::issuance::BlindSignRequest;
pub use scheme::issuance::prepare_blind_sign;
pub use scheme::keygen::KeyPair;
pub use scheme::keygen::ttp_keygen;
pub use scheme::keygen::VerificationKey;
pub use scheme::setup::Parameters;
pub use scheme::setup::setup;
pub use scheme::Signature;
pub use scheme::SignatureShare;
pub use scheme::verification::prove_bandwidth_credential;
pub use scheme::verification::Theta;
pub use scheme::verification::verify_credential;
pub use traits::Base58;
pub use utils::hash_to_scalar;
@@ -39,7 +50,7 @@ mod proofs;
mod scheme;
pub mod tests;
mod traits;
mod utils;
pub mod utils;
pub type Attribute = Scalar;
pub type PrivateAttribute = Attribute;
+9 -9
View File
@@ -5,8 +5,8 @@ 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::{CoconutError, Result};
@@ -81,9 +81,9 @@ 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>,
where
T: Sum,
for<'a> &'a T: Mul<Scalar, Output=T>,
{
if points.is_empty() || values.is_empty() {
return Err(CoconutError::Interpolation(
@@ -122,7 +122,7 @@ const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_R
// 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(crate) fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
}
@@ -137,7 +137,7 @@ pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
output[0]
}
pub(crate) fn try_deserialize_scalar_vec(
pub fn try_deserialize_scalar_vec(
expected_len: u64,
bytes: &[u8],
err: CoconutError,
@@ -159,11 +159,11 @@ pub(crate) fn try_deserialize_scalar_vec(
Ok(out)
}
pub(crate) fn try_deserialize_scalar(bytes: &[u8; 32], err: CoconutError) -> Result<Scalar> {
pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CoconutError) -> Result<Scalar> {
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
}
pub(crate) fn try_deserialize_g1_projective(
pub fn try_deserialize_g1_projective(
bytes: &[u8; 48],
err: CoconutError,
) -> Result<G1Projective> {
@@ -172,7 +172,7 @@ pub(crate) fn try_deserialize_g1_projective(
.map(G1Projective::from)
}
pub(crate) fn try_deserialize_g2_projective(
pub fn try_deserialize_g2_projective(
bytes: &[u8; 96],
err: CoconutError,
) -> Result<G2Projective> {