chore: removed all old coconut code (#5500)
This commit is contained in:
committed by
GitHub
parent
8ba5322997
commit
69b2448500
Generated
+1
-27
@@ -4775,7 +4775,6 @@ dependencies = [
|
||||
"nym-api-requests",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-bin-common",
|
||||
"nym-coconut",
|
||||
"nym-coconut-dkg-common",
|
||||
"nym-compact-ecash",
|
||||
"nym-config",
|
||||
@@ -5258,30 +5257,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-coconut"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bls12_381",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"doc-comment",
|
||||
"ff",
|
||||
"getrandom 0.2.15",
|
||||
"group",
|
||||
"itertools 0.13.0",
|
||||
"nym-dkg",
|
||||
"nym-pemstore",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"sha2 0.9.9",
|
||||
"thiserror 2.0.11",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-coconut-bandwidth-contract-common"
|
||||
version = "0.1.0"
|
||||
@@ -5888,7 +5863,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"subtle 2.6.1",
|
||||
"subtle 2.5.0",
|
||||
"tower 0.5.2",
|
||||
"tracing",
|
||||
"utoipa",
|
||||
@@ -12202,7 +12177,6 @@ dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"js-sys",
|
||||
"nym-bin-common",
|
||||
"nym-coconut",
|
||||
"nym-compact-ecash",
|
||||
"nym-credentials",
|
||||
"nym-crypto",
|
||||
|
||||
@@ -66,7 +66,6 @@ members = [
|
||||
"common/nym-id",
|
||||
"common/nym-metrics",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nymcoconut",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
[package]
|
||||
name = "nym-coconut"
|
||||
version = "0.5.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>", "Ania Piotrowska <ania@nymtech.net>"]
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
itertools = { workspace = true }
|
||||
digest = "0.9"
|
||||
rand = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
zeroize = { workspace = true, optional = true }
|
||||
|
||||
nym-dkg = { path = "../dkg" }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
|
||||
[dependencies.ff]
|
||||
workspace = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.group]
|
||||
workspace = true
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true, features = ["html_reports"] }
|
||||
doc-comment = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
key-zeroize = ["zeroize", "bls12_381/zeroize"]
|
||||
default = []
|
||||
|
||||
|
||||
[target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies]
|
||||
getrandom = { version="0.2", features=["js"] }
|
||||
@@ -1 +0,0 @@
|
||||
This project was partially funded through the NGI0 PET Fund, a fund established by NL.net with financial support from the European Commission's NGI programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310.
|
||||
@@ -1,360 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Scalar};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group};
|
||||
use nym_coconut::{
|
||||
aggregate_signature_shares_and_verify, aggregate_verification_keys, blind_sign,
|
||||
prepare_blind_sign, prove_bandwidth_credential, random_scalars_refs, setup, ttp_keygen,
|
||||
verify_credential, verify_partial_blind_signature, Attribute, BlindedSignature, Parameters,
|
||||
Signature, SignatureShare, VerificationKey,
|
||||
};
|
||||
use rand::seq::SliceRandom;
|
||||
use std::ops::Neg;
|
||||
use std::time::Duration;
|
||||
|
||||
#[allow(unused)]
|
||||
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let gt1 = bls12_381::pairing(g11, g21);
|
||||
let gt2 = bls12_381::pairing(g12, g22);
|
||||
assert_eq!(gt1, gt2)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
|
||||
let miller_loop_result = multi_miller_loop(&[
|
||||
(g11, &G2Prepared::from(*g21)),
|
||||
(&g12.neg(), &G2Prepared::from(*g22)),
|
||||
]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn bench_pairings(c: &mut Criterion) {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let g1 = G1Affine::generator();
|
||||
let g2 = G2Affine::generator();
|
||||
let r = Scalar::random(&mut rng);
|
||||
let s = Scalar::random(&mut rng);
|
||||
|
||||
let g11 = (g1 * r).to_affine();
|
||||
let g21 = (g2 * s).to_affine();
|
||||
let g21_prep = G2Prepared::from(g21);
|
||||
|
||||
let g12 = (g1 * s).to_affine();
|
||||
let g22 = (g2 * r).to_affine();
|
||||
let g22_prep = G2Prepared::from(g22);
|
||||
|
||||
c.bench_function("double pairing", |b| {
|
||||
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller in affine", |b| {
|
||||
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller with prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
|
||||
});
|
||||
|
||||
c.bench_function("multi miller with semi-prepared g2", |b| {
|
||||
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Prepared,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
// the case of being able to prepare G2 generator
|
||||
#[allow(unused)]
|
||||
fn multi_miller_pairing_with_semi_prepared(
|
||||
g11: &G1Affine,
|
||||
g21: &G2Affine,
|
||||
g12: &G1Affine,
|
||||
g22: &G2Prepared,
|
||||
) {
|
||||
let miller_loop_result =
|
||||
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
|
||||
assert!(bool::from(
|
||||
miller_loop_result.final_exponentiation().is_identity()
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn unblind_and_aggregate(
|
||||
params: &Parameters,
|
||||
blinded_signatures: &[BlindedSignature],
|
||||
partial_verification_keys: &[VerificationKey],
|
||||
private_attributes: &[&Attribute],
|
||||
public_attributes: &[&Attribute],
|
||||
commitment_hash: &G1Projective,
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
verification_key: &VerificationKey,
|
||||
) -> Signature {
|
||||
// Unblind all partial signatures
|
||||
let unblinded_signatures: Vec<Signature> = blinded_signatures
|
||||
.iter()
|
||||
.zip(partial_verification_keys.iter())
|
||||
.map(|(signature, partial_verification_key)| {
|
||||
signature
|
||||
.unblind_and_verify(
|
||||
params,
|
||||
partial_verification_key,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
commitment_hash,
|
||||
pedersen_commitments_openings,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let unblinded_signature_shares: Vec<SignatureShare> = unblinded_signatures
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
|
||||
.collect();
|
||||
|
||||
let mut attributes = vec![];
|
||||
attributes.extend_from_slice(private_attributes);
|
||||
attributes.extend_from_slice(public_attributes);
|
||||
aggregate_signature_shares_and_verify(
|
||||
params,
|
||||
verification_key,
|
||||
&attributes,
|
||||
&unblinded_signature_shares,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
struct BenchCase {
|
||||
num_authorities: u64,
|
||||
threshold_p: f32,
|
||||
num_public_attrs: u32,
|
||||
num_private_attrs: u32,
|
||||
}
|
||||
|
||||
impl BenchCase {
|
||||
fn threshold(&self) -> u64 {
|
||||
(self.num_authorities as f32 * self.threshold_p).round() as u64
|
||||
}
|
||||
|
||||
fn num_attrs(&self) -> u32 {
|
||||
self.num_public_attrs + self.num_private_attrs
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_coconut(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("benchmark-coconut");
|
||||
group.measurement_time(Duration::from_secs(1000));
|
||||
let case = BenchCase {
|
||||
num_authorities: 100,
|
||||
threshold_p: 0.7,
|
||||
num_public_attrs: 2,
|
||||
num_private_attrs: 2,
|
||||
};
|
||||
|
||||
let params = setup(case.num_public_attrs + case.num_private_attrs).unwrap();
|
||||
|
||||
random_scalars_refs!(public_attributes, params, case.num_public_attrs as usize);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![&serial_number, &binding_number];
|
||||
|
||||
// The prepare blind sign is performed by the user
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
// CLIENT BENCHMARK: Data needed to ask for a credential
|
||||
// Let's benchmark the operations the client has to perform
|
||||
// to ask for a credential
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Client] prepare_blind_sign_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap())
|
||||
},
|
||||
);
|
||||
|
||||
// keys for the validators
|
||||
let coconut_keypairs = ttp_keygen(¶ms, case.threshold(), case.num_authorities).unwrap();
|
||||
|
||||
// VALIDATOR BENCHMARK: Issue partial credential
|
||||
// we pick only one key pair, as we want to validate how much does it
|
||||
// take for a single validator to issue a partial credential
|
||||
let mut rng = rand::thread_rng();
|
||||
let keypair = coconut_keypairs.choose(&mut rng).unwrap();
|
||||
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Validator] compute_single_blind_sign_for_credential_with_{}_attributes",
|
||||
case.num_attrs(),
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
blind_sign(
|
||||
¶ms,
|
||||
keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// computing all partial credentials
|
||||
// NOTE: in reality, each validator computes only single signature
|
||||
let mut blinded_signatures = Vec::new();
|
||||
for keypair in coconut_keypairs.iter() {
|
||||
let blinded_signature = blind_sign(
|
||||
¶ms,
|
||||
keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
blinded_signatures.push(blinded_signature)
|
||||
}
|
||||
|
||||
let verification_keys: Vec<VerificationKey> = coconut_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key().clone())
|
||||
.collect();
|
||||
|
||||
// verify a random partial blind signature
|
||||
let rand_idx = 1;
|
||||
let random_blind_signature = blinded_signatures.get(rand_idx).unwrap();
|
||||
let partial_verification_key = verification_keys.get(rand_idx).unwrap();
|
||||
|
||||
group.bench_function(
|
||||
format!(
|
||||
"verify_partial_blind_signature_{}_private_attributes_{}_public_attributes",
|
||||
case.num_private_attrs, case.num_public_attrs
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
verify_partial_blind_signature(
|
||||
¶ms,
|
||||
blind_sign_request.get_private_attributes_pedersen_commitments(),
|
||||
&public_attributes,
|
||||
random_blind_signature,
|
||||
partial_verification_key,
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// Lets bench worse case, ie aggregating all
|
||||
let indices: Vec<u64> = (1..=case.num_authorities).collect();
|
||||
// aggregate verification keys
|
||||
let aggr_verification_key =
|
||||
aggregate_verification_keys(&verification_keys, Some(&indices)).unwrap();
|
||||
|
||||
// CLIENT OPERATION: Unblind partial singatures and aggregate into single signature
|
||||
let aggregated_signature = unblind_and_aggregate(
|
||||
¶ms,
|
||||
&blinded_signatures,
|
||||
&verification_keys,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&pedersen_commitments_openings,
|
||||
&aggr_verification_key,
|
||||
);
|
||||
|
||||
// CLIENT BENCHMARK: aggregate all partial credentials
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Client] unblind_and_aggregate_partial_credentials_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
unblind_and_aggregate(
|
||||
¶ms,
|
||||
&blinded_signatures,
|
||||
&verification_keys,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&pedersen_commitments_openings,
|
||||
&aggr_verification_key)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// CLIENT OPERATION: Randomize credentials and generate any cryptographic material to verify them
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_verification_key,
|
||||
&aggregated_signature,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// CLIENT BENCHMARK
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Client] randomize_and_prove_credential_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_verification_key,
|
||||
&aggregated_signature,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// VERIFIER OPERATION
|
||||
// Verify credentials
|
||||
verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes);
|
||||
|
||||
// VERIFICATION BENCHMARK
|
||||
group.bench_function(
|
||||
format!(
|
||||
"[Verifier] verify_credentials_{}_authorities_{}_attributes_{}_threshold",
|
||||
case.num_authorities,
|
||||
case.num_attrs(),
|
||||
case.threshold_p,
|
||||
),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes)
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
criterion_group!(benches, bench_coconut);
|
||||
criterion_main!(benches);
|
||||
@@ -1,354 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::ops::{Deref, Mul};
|
||||
|
||||
use bls12_381::{G1Projective, Scalar};
|
||||
use group::Curve;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar};
|
||||
use crate::Attribute;
|
||||
|
||||
/// Type alias for the ephemeral key generated during ElGamal encryption
|
||||
pub type EphemeralKey = Scalar;
|
||||
|
||||
/// Two G1 points representing ElGamal ciphertext
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct Ciphertext(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
impl TryFrom<&[u8]> for Ciphertext {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Ciphertext> {
|
||||
if bytes.len() != 96 {
|
||||
return Err(CoconutError::Deserialization(format!(
|
||||
"Ciphertext must be exactly 96 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// safety: we just checked for the length so the unwraps are fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let c1_bytes: &[u8; 48] = &bytes[..48].try_into().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let c2_bytes: &[u8; 48] = &bytes[48..].try_into().unwrap();
|
||||
|
||||
let c1 = try_deserialize_g1_projective(
|
||||
c1_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize compressed c1".to_string()),
|
||||
)?;
|
||||
let c2 = try_deserialize_g1_projective(
|
||||
c2_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize compressed c2".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(Ciphertext(c1, c2))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ciphertext {
|
||||
pub fn c1(&self) -> &G1Projective {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn c2(&self) -> &G1Projective {
|
||||
&self.1
|
||||
}
|
||||
|
||||
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<Ciphertext> {
|
||||
Ciphertext::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// PrivateKey used in the ElGamal encryption scheme to recover the plaintext
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct PrivateKey(pub(crate) Scalar);
|
||||
|
||||
impl PrivateKey {
|
||||
/// Decrypt takes the ElGamal encryption of a message and returns a point on the G1 curve
|
||||
/// that represents original h^m.
|
||||
pub fn decrypt(&self, ciphertext: &Ciphertext) -> G1Projective {
|
||||
let (c1, c2) = &(ciphertext.0, ciphertext.1);
|
||||
|
||||
// (gamma^k * h^m) / (g1^{d * k}) | note: gamma = g1^d
|
||||
c2 - c1 * self.0
|
||||
}
|
||||
|
||||
pub fn public_key(&self, params: &Parameters) -> PublicKey {
|
||||
PublicKey(params.gen1() * self.0)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8; 32]) -> Result<PrivateKey> {
|
||||
try_deserialize_scalar(
|
||||
bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize ElGamal private key - it was not in the canonical form"
|
||||
.to_string(),
|
||||
),
|
||||
)
|
||||
.map(PrivateKey)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for PrivateKey {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
let received = slice.len();
|
||||
let Ok(arr) = slice.try_into() else {
|
||||
return Err(CoconutError::UnexpectedArrayLength {
|
||||
typ: "elgamal::PrivateKey".to_string(),
|
||||
received,
|
||||
expected: 32,
|
||||
});
|
||||
};
|
||||
|
||||
PrivateKey::from_bytes(arr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for PrivateKey {}
|
||||
|
||||
// TODO: perhaps be more explicit and apart from gamma also store generator and group order?
|
||||
/// PublicKey used in the ElGamal encryption scheme to produce the ciphertext
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct PublicKey(G1Projective);
|
||||
|
||||
impl PublicKey {
|
||||
/// Encrypt encrypts the given message in the form of h^m,
|
||||
/// where h is a point on the G1 curve using the given public key.
|
||||
/// The random k is returned alongside the encryption
|
||||
/// as it is required by the Coconut Scheme to create proofs of knowledge.
|
||||
pub fn encrypt(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
h: &G1Projective,
|
||||
msg: &Scalar,
|
||||
) -> (Ciphertext, EphemeralKey) {
|
||||
let k = params.random_scalar();
|
||||
// c1 = g1^k
|
||||
let c1 = params.gen1() * k;
|
||||
// c2 = gamma^k * h^m
|
||||
let c2 = self.0 * k + h * msg;
|
||||
|
||||
(Ciphertext(c1, c2), k)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 48] {
|
||||
self.0.to_affine().to_compressed()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8; 48]) -> Result<PublicKey> {
|
||||
try_deserialize_g1_projective(
|
||||
bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize compressed ElGamal public key".to_string(),
|
||||
),
|
||||
)
|
||||
.map(PublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for PublicKey {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().into()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
let received = slice.len();
|
||||
let Ok(arr) = slice.try_into() else {
|
||||
return Err(CoconutError::UnexpectedArrayLength {
|
||||
typ: "elgamal::PublicKey".to_string(),
|
||||
received,
|
||||
expected: 48,
|
||||
});
|
||||
};
|
||||
|
||||
PublicKey::from_bytes(arr)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for PublicKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(slice: &[u8]) -> Result<PublicKey> {
|
||||
PublicKey::try_from_byte_slice(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for PublicKey {}
|
||||
|
||||
impl Deref for PublicKey {
|
||||
type Target = G1Projective;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<&'a Scalar> for &PublicKey {
|
||||
type Output = G1Projective;
|
||||
|
||||
fn mul(self, rhs: &'a Scalar) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
/// A convenient wrapper for both keys of the ElGamal keypair
|
||||
pub struct ElGamalKeyPair {
|
||||
private_key: PrivateKey,
|
||||
public_key: PublicKey,
|
||||
}
|
||||
|
||||
impl ElGamalKeyPair {
|
||||
pub fn public_key(&self) -> &PublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
|
||||
pub fn private_key(&self) -> &PrivateKey {
|
||||
&self.private_key
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a fresh ElGamal keypair using the group generator specified by the provided [Parameters]
|
||||
pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair {
|
||||
let private_key = params.random_scalar();
|
||||
let gamma = params.gen1() * private_key;
|
||||
|
||||
ElGamalKeyPair {
|
||||
private_key: PrivateKey(private_key),
|
||||
public_key: PublicKey(gamma),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_attribute_encryption(
|
||||
params: &Parameters,
|
||||
private_attributes: &[&Attribute],
|
||||
pub_key: &PublicKey,
|
||||
commitment_hash: G1Projective,
|
||||
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
|
||||
private_attributes
|
||||
.iter()
|
||||
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
|
||||
.unzip()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn keygen() {
|
||||
let params = Parameters::default();
|
||||
let keypair = super::elgamal_keygen(¶ms);
|
||||
|
||||
let expected = params.gen1() * keypair.private_key.0;
|
||||
let gamma = keypair.public_key.0;
|
||||
assert_eq!(
|
||||
expected, gamma,
|
||||
"Public key, gamma, should be equal to g1^d, where d is the private key"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encryption() {
|
||||
let params = Parameters::default();
|
||||
let keypair = super::elgamal_keygen(¶ms);
|
||||
|
||||
let r = params.random_scalar();
|
||||
let h = params.gen1() * r;
|
||||
let m = params.random_scalar();
|
||||
|
||||
let (ciphertext, ephemeral_key) = keypair.public_key.encrypt(¶ms, &h, &m);
|
||||
|
||||
let expected_c1 = params.gen1() * ephemeral_key;
|
||||
assert_eq!(expected_c1, ciphertext.0, "c1 should be equal to g1^k");
|
||||
|
||||
let expected_c2 = keypair.public_key.0 * ephemeral_key + h * m;
|
||||
assert_eq!(
|
||||
expected_c2, ciphertext.1,
|
||||
"c2 should be equal to gamma^k * h^m"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decryption() {
|
||||
let params = Parameters::default();
|
||||
let keypair = super::elgamal_keygen(¶ms);
|
||||
|
||||
let r = params.random_scalar();
|
||||
let h = params.gen1() * r;
|
||||
let m = params.random_scalar();
|
||||
|
||||
let (ciphertext, _) = keypair.public_key.encrypt(¶ms, &h, &m);
|
||||
let dec = keypair.private_key.decrypt(&ciphertext);
|
||||
|
||||
let expected = h * m;
|
||||
assert_eq!(
|
||||
expected, dec,
|
||||
"after ElGamal decryption, original h^m should be obtained"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_key_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let private_key = PrivateKey(params.random_scalar());
|
||||
let bytes = private_key.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal scalar's bytes
|
||||
assert_eq!(private_key.0.to_bytes(), bytes);
|
||||
assert_eq!(private_key, PrivateKey::from_bytes(&bytes).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_key_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let r = params.random_scalar();
|
||||
let public_key = PublicKey(params.gen1() * r);
|
||||
let bytes = public_key.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal g1 compressed bytes
|
||||
assert_eq!(public_key.0.to_affine().to_compressed(), bytes);
|
||||
assert_eq!(public_key, PublicKey::from_bytes(&bytes).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ciphertext_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let r = params.random_scalar();
|
||||
let s = params.random_scalar();
|
||||
let ciphertext = Ciphertext(params.gen1() * r, params.gen1() * s);
|
||||
let bytes = ciphertext.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
|
||||
let expected_bytes = [
|
||||
ciphertext.0.to_affine().to_compressed(),
|
||||
ciphertext.1.to_affine().to_compressed(),
|
||||
]
|
||||
.concat();
|
||||
assert_eq!(expected_bytes, bytes);
|
||||
assert_eq!(ciphertext, Ciphertext::try_from(&bytes[..]).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// A `Result` alias where the `Err` case is `coconut_rs::Error`.
|
||||
pub type Result<T> = std::result::Result<T, CoconutError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CoconutError {
|
||||
#[error("Setup error: {0}")]
|
||||
Setup(String),
|
||||
|
||||
#[error("encountered error during keygen")]
|
||||
Keygen,
|
||||
|
||||
#[error("Issuance related error: {0}")]
|
||||
Issuance(String),
|
||||
|
||||
#[error("Tried to prepare blind sign request for higher than specified number of attributes (max: {}, requested: {})", max, requested)]
|
||||
IssuanceMaxAttributes { max: usize, requested: usize },
|
||||
|
||||
#[error("Interpolation error: {0}")]
|
||||
Interpolation(String),
|
||||
|
||||
#[error("Aggregation error: {0}")]
|
||||
Aggregation(String),
|
||||
|
||||
#[error("Unblind error: {0}")]
|
||||
Unblind(String),
|
||||
|
||||
#[error("Verification error: {0}")]
|
||||
Verification(String),
|
||||
|
||||
#[error("Deserialization error: {0}")]
|
||||
Deserialization(String),
|
||||
|
||||
#[error(
|
||||
"Deserailization error, expected at least {} bytes, got {}",
|
||||
min,
|
||||
actual
|
||||
)]
|
||||
DeserializationMinLength { min: usize, actual: usize },
|
||||
|
||||
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {object} or {modulus_target} % {modulus} == 0")]
|
||||
DeserializationInvalidLength {
|
||||
actual: usize,
|
||||
target: usize,
|
||||
modulus_target: usize,
|
||||
modulus: usize,
|
||||
object: String,
|
||||
},
|
||||
|
||||
#[error("received an array of unexpected size for deserialization of {typ}. got {received} but expected {expected}")]
|
||||
UnexpectedArrayLength {
|
||||
typ: String,
|
||||
received: usize,
|
||||
expected: usize,
|
||||
},
|
||||
|
||||
#[error("failed to decode the base58 representation: {0}")]
|
||||
Base58DecodingFailure(#[from] bs58::decode::Error),
|
||||
|
||||
#[error("failed to deserialize scalar from the received bytes - it might not have been canonically encoded")]
|
||||
ScalarDeserializationFailure,
|
||||
|
||||
#[error("failed to deserialize G1Projective point from the received bytes - it might not have been canonically encoded")]
|
||||
G1ProjectiveDeserializationFailure,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use crate::{BlindSignRequest, BlindedSignature, Bytable, VerifyCredentialRequest};
|
||||
|
||||
macro_rules! impl_clone {
|
||||
($struct:ident) => {
|
||||
impl Clone for $struct {
|
||||
fn clone(&self) -> Self {
|
||||
Self::try_from_byte_slice(&self.to_byte_vec()).unwrap()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_clone!(BlindSignRequest);
|
||||
impl_clone!(BlindedSignature);
|
||||
impl_clone!(VerifyCredentialRequest);
|
||||
@@ -1,2 +0,0 @@
|
||||
mod clone;
|
||||
mod serde;
|
||||
@@ -1,57 +0,0 @@
|
||||
use crate::elgamal::PrivateKey;
|
||||
use crate::scheme::SecretKey;
|
||||
use crate::{
|
||||
Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, VerificationKey,
|
||||
VerifyCredentialRequest,
|
||||
};
|
||||
use serde::de::Unexpected;
|
||||
use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
|
||||
macro_rules! impl_serde {
|
||||
($struct:ident, $visitor:ident) => {
|
||||
pub struct $visitor {}
|
||||
|
||||
impl Serialize for $struct {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_bs58())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Visitor<'de> for $visitor {
|
||||
type Value = $struct;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(formatter, "A base58 encoded struct")
|
||||
}
|
||||
|
||||
fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
|
||||
match $struct::try_from_bs58(s) {
|
||||
Ok(x) => Ok(x),
|
||||
Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &self)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for $struct {
|
||||
fn deserialize<D>(deserializer: D) -> Result<$struct, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str($visitor {})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_serde!(SecretKey, V1);
|
||||
impl_serde!(VerificationKey, V2);
|
||||
impl_serde!(PublicKey, V3);
|
||||
impl_serde!(PrivateKey, V4);
|
||||
impl_serde!(BlindSignRequest, V5);
|
||||
impl_serde!(BlindedSignature, V6);
|
||||
impl_serde!(Signature, V7);
|
||||
impl_serde!(VerifyCredentialRequest, V8);
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub use bls12_381::Scalar;
|
||||
pub use elgamal::elgamal_keygen;
|
||||
pub use elgamal::ElGamalKeyPair;
|
||||
pub use elgamal::PublicKey;
|
||||
pub use error::CoconutError;
|
||||
pub use scheme::aggregation::aggregate_key_shares;
|
||||
pub use scheme::aggregation::aggregate_signature_shares;
|
||||
pub use scheme::aggregation::aggregate_signature_shares_and_verify;
|
||||
pub use scheme::aggregation::aggregate_verification_keys;
|
||||
pub use scheme::issuance::blind_sign;
|
||||
pub use scheme::issuance::prepare_blind_sign;
|
||||
pub use scheme::issuance::sign;
|
||||
pub use scheme::issuance::verify_partial_blind_signature;
|
||||
pub use scheme::issuance::BlindSignRequest;
|
||||
pub use scheme::keygen::keygen;
|
||||
pub use scheme::keygen::ttp_keygen;
|
||||
pub use scheme::keygen::KeyPair;
|
||||
pub use scheme::keygen::SecretKey;
|
||||
pub use scheme::keygen::VerificationKey;
|
||||
pub use scheme::keygen::VerificationKeyShare;
|
||||
pub use scheme::setup::setup;
|
||||
pub use scheme::setup::Parameters;
|
||||
pub use scheme::verification::check_vk_pairing;
|
||||
pub use scheme::verification::prove_bandwidth_credential;
|
||||
pub use scheme::verification::verify;
|
||||
pub use scheme::verification::verify_credential;
|
||||
pub use scheme::verification::BlindedSerialNumber;
|
||||
pub use scheme::verification::VerifyCredentialRequest;
|
||||
pub use scheme::BlindedSignature;
|
||||
pub use scheme::Signature;
|
||||
pub use scheme::SignatureShare;
|
||||
pub use scheme::SignerIndex;
|
||||
pub use traits::Base58;
|
||||
pub use traits::Bytable;
|
||||
pub use utils::hash_to_scalar;
|
||||
|
||||
pub mod elgamal;
|
||||
mod error;
|
||||
mod impls;
|
||||
mod proofs;
|
||||
mod scheme;
|
||||
pub mod tests;
|
||||
mod traits;
|
||||
pub mod utils;
|
||||
|
||||
pub type Attribute = bls12_381::Scalar;
|
||||
pub type PrivateAttribute = Attribute;
|
||||
pub type PublicAttribute = Attribute;
|
||||
|
||||
pub use bls12_381::G1Projective;
|
||||
@@ -1,619 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TODO: look at https://crates.io/crates/merlin to perhaps use it instead?
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use digest::generic_array::typenum::Unsigned;
|
||||
use digest::Digest;
|
||||
use group::GroupEncoding;
|
||||
use itertools::izip;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::issuance::compute_hash;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::VerificationKey;
|
||||
use crate::utils::{try_deserialize_scalar, try_deserialize_scalar_vec};
|
||||
use crate::Attribute;
|
||||
|
||||
// as per the reference python implementation
|
||||
type ChallengeDigest = Sha256;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct ProofCmCs {
|
||||
challenge: Scalar,
|
||||
response_opening: Scalar,
|
||||
response_openings: Vec<Scalar>,
|
||||
response_attributes: Vec<Scalar>,
|
||||
}
|
||||
|
||||
// note: this is slightly different from the reference python implementation
|
||||
// as we omit the unnecessary string conversion. Instead we concatenate byte
|
||||
// representations together and hash that.
|
||||
// note2: G1 and G2 elements are using their compressed representations
|
||||
// and as per the bls12-381 library all elements are using big-endian form
|
||||
/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points.
|
||||
fn compute_challenge<D, I, B>(iter: I) -> Scalar
|
||||
where
|
||||
D: Digest,
|
||||
I: Iterator<Item = B>,
|
||||
B: AsRef<[u8]>,
|
||||
{
|
||||
let mut h = D::new();
|
||||
for point_representation in iter {
|
||||
h.update(point_representation);
|
||||
}
|
||||
let digest = h.finalize();
|
||||
|
||||
// TODO: I don't like the 0 padding here (though it's what we've been using before,
|
||||
// but we never had a security audit anyway...)
|
||||
// instead we could maybe use the `from_bytes` variant and adding some suffix
|
||||
// when computing the digest until we produce a valid scalar.
|
||||
let mut bytes = [0u8; 64];
|
||||
let pad_size = 64usize
|
||||
.checked_sub(D::OutputSize::to_usize())
|
||||
.unwrap_or_default();
|
||||
|
||||
bytes[pad_size..].copy_from_slice(&digest);
|
||||
|
||||
Scalar::from_bytes_wide(&bytes)
|
||||
}
|
||||
|
||||
fn produce_response(witness: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar {
|
||||
witness - challenge * secret
|
||||
}
|
||||
|
||||
// note: it's caller's responsibility to ensure witnesses.len() = secrets.len()
|
||||
fn produce_responses<S>(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec<Scalar>
|
||||
where
|
||||
S: Borrow<Scalar>,
|
||||
{
|
||||
debug_assert_eq!(witnesses.len(), secrets.len());
|
||||
|
||||
witnesses
|
||||
.iter()
|
||||
.zip(secrets.iter())
|
||||
.map(|(w, x)| produce_response(w, challenge, x.borrow()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl ProofCmCs {
|
||||
/// Construct non-interactive zero-knowledge proof of correctness of the ciphertexts and the commitment
|
||||
/// using the Fiat-Shamir heuristic.
|
||||
pub(crate) fn construct(
|
||||
params: &Parameters,
|
||||
commitment: &G1Projective,
|
||||
commitment_opening: &Scalar,
|
||||
commitments: &[G1Projective],
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
private_attributes: &[&Attribute],
|
||||
public_attributes: &[&Attribute],
|
||||
) -> Self {
|
||||
// note: this is only called from `prepare_blind_sign` that already checks
|
||||
// whether private attributes are non-empty and whether we don't have too many
|
||||
// attributes in total to sign.
|
||||
// we also know, due to the single call place, that ephemeral_keys.len() == private_attributes.len()
|
||||
|
||||
// witness creation
|
||||
let witness_commitment_opening = params.random_scalar();
|
||||
let witness_pedersen_commitments_openings =
|
||||
params.n_random_scalars(pedersen_commitments_openings.len());
|
||||
let witness_attributes = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
// recompute h
|
||||
let h = compute_hash(*commitment, public_attributes);
|
||||
let hs_bytes = params
|
||||
.gen_hs()
|
||||
.iter()
|
||||
.map(|h| h.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let g1 = params.gen1();
|
||||
|
||||
// compute commitments
|
||||
|
||||
// zkp commitment for the attributes commitment cm
|
||||
// Ccm = (wr * g1) + (wm[0] * hs[0]) + ... + (wm[i] * hs[i])
|
||||
let commitment_attributes = g1 * witness_commitment_opening
|
||||
+ witness_attributes
|
||||
.iter()
|
||||
.zip(params.gen_hs().iter())
|
||||
.map(|(wm_i, hs_i)| hs_i * wm_i)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
// zkp commitments for the individual attributes
|
||||
let commitments_attributes = witness_pedersen_commitments_openings
|
||||
.iter()
|
||||
.zip(witness_attributes.iter())
|
||||
.map(|(o_j, m_j)| g1 * o_j + h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_bytes = commitments
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_attributes_bytes = commitments_attributes
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// compute challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(h.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment.to_bytes().as_ref()))
|
||||
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
|
||||
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
|
||||
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
|
||||
);
|
||||
|
||||
// Responses
|
||||
let response_opening =
|
||||
produce_response(&witness_commitment_opening, &challenge, commitment_opening);
|
||||
let response_openings = produce_responses(
|
||||
&witness_pedersen_commitments_openings,
|
||||
&challenge,
|
||||
&pedersen_commitments_openings.iter().collect::<Vec<_>>(),
|
||||
);
|
||||
let response_attributes =
|
||||
produce_responses(&witness_attributes, &challenge, private_attributes);
|
||||
|
||||
ProofCmCs {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
commitment: &G1Projective,
|
||||
commitments: &[G1Projective],
|
||||
public_attributes: &[&Attribute],
|
||||
) -> bool {
|
||||
if self.response_attributes.len() != commitments.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// recompute h
|
||||
let h = compute_hash(*commitment, public_attributes);
|
||||
let g1 = params.gen1();
|
||||
|
||||
let hs_bytes = params
|
||||
.gen_hs()
|
||||
.iter()
|
||||
.map(|h| h.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// recompute witnesses commitments
|
||||
// Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n])
|
||||
let commitment_attributes = (commitment
|
||||
- public_attributes
|
||||
.iter()
|
||||
.zip(params.gen_hs().iter().skip(self.response_attributes.len()))
|
||||
.map(|(&pub_attr, hs)| hs * pub_attr)
|
||||
.sum::<G1Projective>())
|
||||
* self.challenge
|
||||
+ g1 * self.response_opening
|
||||
+ self
|
||||
.response_attributes
|
||||
.iter()
|
||||
.zip(params.gen_hs().iter())
|
||||
.map(|(res_attr, hs)| hs * res_attr)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let commitments_attributes = izip!(
|
||||
commitments.iter(),
|
||||
self.response_openings.iter(),
|
||||
self.response_attributes.iter()
|
||||
)
|
||||
.map(|(cm_j, r_o_j, r_m_j)| cm_j * self.challenge + g1 * r_o_j + h * r_m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_bytes = commitments
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments_attributes_bytes = commitments_attributes
|
||||
.iter()
|
||||
.map(|cm| cm.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// re-compute the challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen1().to_bytes().as_ref())
|
||||
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
|
||||
.chain(std::iter::once(h.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment.to_bytes().as_ref()))
|
||||
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
|
||||
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
|
||||
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
// challenge || response opening || openings len || response openings || attributes len ||
|
||||
// response attributes
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let openings_len = self.response_openings.len() as u64;
|
||||
let attributes_len = self.response_attributes.len() as u64;
|
||||
|
||||
let mut bytes = Vec::with_capacity(16 + (2 + openings_len + attributes_len) as usize * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.challenge.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_opening.to_bytes());
|
||||
|
||||
bytes.extend_from_slice(&openings_len.to_le_bytes());
|
||||
for ro in &self.response_openings {
|
||||
bytes.extend_from_slice(&ro.to_bytes());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&attributes_len.to_le_bytes());
|
||||
for rm in &self.response_attributes {
|
||||
bytes.extend_from_slice(&rm.to_bytes());
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
// at the very minimum there must be a single attribute being proven
|
||||
if bytes.len() < 32 * 4 + 16 || (bytes.len() - 16) % 32 != 0 {
|
||||
return Err(CoconutError::Deserialization(
|
||||
"tried to deserialize proof of commitments with bytes of invalid length"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap();
|
||||
idx += 32;
|
||||
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
let response_opening = try_deserialize_scalar(
|
||||
&response_opening_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize the response to the random".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
|
||||
idx += 8;
|
||||
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
"tried to deserialize proof of ciphertexts and commitment with insufficient number of bytes provided".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
let ro_end = idx + ro_len as usize * 32;
|
||||
let response_openings = try_deserialize_scalar_vec(
|
||||
ro_len,
|
||||
&bytes[idx..ro_end],
|
||||
CoconutError::Deserialization("Failed to deserialize openings response".to_string()),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let rm_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
|
||||
let response_attributes = try_deserialize_scalar_vec(
|
||||
rm_len,
|
||||
&bytes[ro_end + 8..],
|
||||
CoconutError::Deserialization("Failed to deserialize attributes response".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(ProofCmCs {
|
||||
challenge,
|
||||
response_opening,
|
||||
response_openings,
|
||||
response_attributes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ProofKappaZeta {
|
||||
// c
|
||||
challenge: Scalar,
|
||||
|
||||
// responses
|
||||
response_serial_number: Scalar,
|
||||
response_binding_number: Scalar,
|
||||
response_blinder: Scalar,
|
||||
}
|
||||
|
||||
impl ProofKappaZeta {
|
||||
pub(crate) fn construct(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
serial_number: &Attribute,
|
||||
binding_number: &Attribute,
|
||||
blinding_factor: &Scalar,
|
||||
blinded_message: &G2Projective,
|
||||
blinded_serial_number: &G2Projective,
|
||||
) -> Self {
|
||||
// create the witnesses
|
||||
let witness_blinder = params.random_scalar();
|
||||
let witness_serial_number = params.random_scalar();
|
||||
let witness_binding_number = params.random_scalar();
|
||||
let witness_attributes = [witness_serial_number, witness_binding_number];
|
||||
|
||||
let beta_bytes = verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// witnesses commitments
|
||||
// Aw = g2 * wt + alpha + beta[0] * wm[0] + ... + beta[i] * wm[i]
|
||||
let commitment_kappa = params.gen2() * witness_blinder
|
||||
+ verification_key.alpha
|
||||
+ witness_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(wm_i, beta_i)| beta_i * wm_i)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// zeta is the public value associated with the serial number
|
||||
let commitment_zeta = params.gen2() * witness_serial_number;
|
||||
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen2().to_bytes().as_ref())
|
||||
.chain(std::iter::once(blinded_message.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(blinded_serial_number.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
|
||||
.chain(beta_bytes.iter().map(|b| b.as_ref()))
|
||||
.chain(std::iter::once(commitment_kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment_zeta.to_bytes().as_ref())),
|
||||
);
|
||||
|
||||
// responses
|
||||
let response_blinder = produce_response(&witness_blinder, &challenge, blinding_factor);
|
||||
let response_serial_number =
|
||||
produce_response(&witness_serial_number, &challenge, serial_number);
|
||||
let response_binding_number =
|
||||
produce_response(&witness_binding_number, &challenge, binding_number);
|
||||
|
||||
ProofKappaZeta {
|
||||
challenge,
|
||||
response_serial_number,
|
||||
response_binding_number,
|
||||
response_blinder,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn private_attributes_len(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
pub(crate) fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
kappa: &G2Projective,
|
||||
zeta: &G2Projective,
|
||||
) -> bool {
|
||||
let beta_bytes = verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.map(|beta_i| beta_i.to_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let response_attributes = [self.response_serial_number, self.response_binding_number];
|
||||
// re-compute witnesses commitments
|
||||
// Aw = (c * kappa) + (rt * g2) + ((1 - c) * alpha) + (rm[0] * beta[0]) + ... + (rm[i] * beta[i])
|
||||
let commitment_kappa = kappa * self.challenge
|
||||
+ params.gen2() * self.response_blinder
|
||||
+ verification_key.alpha * (Scalar::one() - self.challenge)
|
||||
+ response_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// zeta is the public value associated with the serial number
|
||||
let commitment_zeta = zeta * self.challenge + params.gen2() * self.response_serial_number;
|
||||
|
||||
// compute the challenge
|
||||
let challenge = compute_challenge::<ChallengeDigest, _, _>(
|
||||
std::iter::once(params.gen2().to_bytes().as_ref())
|
||||
.chain(std::iter::once(kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(zeta.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(verification_key.alpha.to_bytes().as_ref()))
|
||||
.chain(beta_bytes.iter().map(|b| b.as_ref()))
|
||||
.chain(std::iter::once(commitment_kappa.to_bytes().as_ref()))
|
||||
.chain(std::iter::once(commitment_zeta.to_bytes().as_ref())),
|
||||
);
|
||||
|
||||
challenge == self.challenge
|
||||
}
|
||||
|
||||
// challenge || response serial number || response binding number || repose blinder
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let attributes_len = 2; // because we have serial number and the binding number
|
||||
let mut bytes = Vec::with_capacity((1 + attributes_len + 1) as usize * 32);
|
||||
|
||||
bytes.extend_from_slice(&self.challenge.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_serial_number.to_bytes());
|
||||
bytes.extend_from_slice(&self.response_binding_number.to_bytes());
|
||||
|
||||
bytes.extend_from_slice(&self.response_blinder.to_bytes());
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
// at the very minimum there must be a single attribute being proven
|
||||
if bytes.len() != 128 {
|
||||
return Err(CoconutError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len(),
|
||||
modulus: 32,
|
||||
object: "kappa and zeta".to_string(),
|
||||
target: 32 * 4,
|
||||
});
|
||||
}
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let challenge_bytes = bytes[..32].try_into().unwrap();
|
||||
let challenge = try_deserialize_scalar(
|
||||
&challenge_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let serial_number_bytes = &bytes[32..64].try_into().unwrap();
|
||||
let response_serial_number = try_deserialize_scalar(
|
||||
serial_number_bytes,
|
||||
CoconutError::Deserialization("failed to deserialize the serial number".to_string()),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let binding_number_bytes = &bytes[64..96].try_into().unwrap();
|
||||
let response_binding_number = try_deserialize_scalar(
|
||||
binding_number_bytes,
|
||||
CoconutError::Deserialization("failed to deserialize the binding number".to_string()),
|
||||
)?;
|
||||
|
||||
// safety: bound checked + constant offset
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let blinder_bytes = bytes[96..].try_into().unwrap();
|
||||
let response_blinder = try_deserialize_scalar(
|
||||
&blinder_bytes,
|
||||
CoconutError::Deserialization("failed to deserialize the blinder".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(ProofKappaZeta {
|
||||
challenge,
|
||||
response_serial_number,
|
||||
response_binding_number,
|
||||
response_blinder,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// proof builder:
|
||||
// - commitment
|
||||
// - challenge
|
||||
// - responses
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::scheme::keygen::keygen;
|
||||
use crate::scheme::setup::setup;
|
||||
use crate::scheme::verification::{compute_kappa, compute_zeta};
|
||||
use crate::tests::helpers::random_scalars_refs;
|
||||
use group::Group;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn proof_cm_cs_bytes_roundtrip() {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let params = setup(1).unwrap();
|
||||
let cm = G1Projective::random(&mut rng);
|
||||
let r = params.random_scalar();
|
||||
let cms: [G1Projective; 1] = [G1Projective::random(&mut rng)];
|
||||
let rs = params.n_random_scalars(1);
|
||||
random_scalars_refs!(private_attributes, params, 1);
|
||||
|
||||
// 0 public 1 private
|
||||
let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]);
|
||||
|
||||
let bytes = pi_s.to_bytes();
|
||||
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
|
||||
|
||||
let params = setup(2).unwrap();
|
||||
let cm = G1Projective::random(&mut rng);
|
||||
let r = params.random_scalar();
|
||||
let cms: [G1Projective; 2] = [
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
];
|
||||
let rs = params.n_random_scalars(2);
|
||||
random_scalars_refs!(private_attributes, params, 2);
|
||||
|
||||
// 0 public 2 privates
|
||||
let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]);
|
||||
|
||||
let bytes = pi_s.to_bytes();
|
||||
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_kappa_zeta_bytes_roundtrip() {
|
||||
let params = setup(4).unwrap();
|
||||
|
||||
let keypair = keygen(¶ms);
|
||||
|
||||
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
|
||||
let serial_number = ¶ms.random_scalar();
|
||||
let binding_number = ¶ms.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
|
||||
let r = params.random_scalar();
|
||||
let kappa = compute_kappa(¶ms, keypair.verification_key(), &private_attributes, r);
|
||||
let zeta = compute_zeta(¶ms, serial_number);
|
||||
|
||||
// 0 public 2 private
|
||||
let pi_v = ProofKappaZeta::construct(
|
||||
¶ms,
|
||||
keypair.verification_key(),
|
||||
serial_number,
|
||||
binding_number,
|
||||
&r,
|
||||
&kappa,
|
||||
&zeta,
|
||||
);
|
||||
|
||||
let proof_bytes = pi_v.to_bytes();
|
||||
|
||||
let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap();
|
||||
assert_eq!(proof_from_bytes, pi_v);
|
||||
|
||||
// 2 public 2 private
|
||||
let params = setup(4).unwrap();
|
||||
let keypair = keygen(¶ms);
|
||||
|
||||
let pi_v = ProofKappaZeta::construct(
|
||||
¶ms,
|
||||
keypair.verification_key(),
|
||||
serial_number,
|
||||
binding_number,
|
||||
&r,
|
||||
&kappa,
|
||||
&zeta,
|
||||
);
|
||||
|
||||
let proof_bytes = pi_v.to_bytes();
|
||||
|
||||
let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap();
|
||||
assert_eq!(proof_from_bytes, pi_v);
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
|
||||
use bls12_381::{G2Prepared, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::verification::check_bilinear_pairing;
|
||||
use crate::scheme::{PartialSignature, Signature, SignatureShare, SignerIndex, VerificationKey};
|
||||
use crate::utils::perform_lagrangian_interpolation_at_origin;
|
||||
use crate::{Attribute, Parameters, VerificationKeyShare};
|
||||
|
||||
pub(crate) trait Aggregatable: Sized {
|
||||
fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
|
||||
|
||||
fn check_unique_indices(indices: &[SignerIndex]) -> bool {
|
||||
// if aggregation is a threshold one, all indices should be unique
|
||||
indices.iter().unique_by(|&index| index).count() == indices.len()
|
||||
}
|
||||
}
|
||||
|
||||
// includes `VerificationKey`
|
||||
impl<T> Aggregatable for T
|
||||
where
|
||||
T: Sum,
|
||||
for<'a> T: Sum<&'a T>,
|
||||
for<'a> &'a T: Mul<Scalar, Output = T>,
|
||||
{
|
||||
fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
|
||||
if aggregatable.is_empty() {
|
||||
return Err(CoconutError::Aggregation("Empty set of values".to_string()));
|
||||
}
|
||||
|
||||
if let Some(indices) = indices {
|
||||
if !Self::check_unique_indices(indices) {
|
||||
return Err(CoconutError::Aggregation("Non-unique indices".to_string()));
|
||||
}
|
||||
perform_lagrangian_interpolation_at_origin(indices, aggregatable)
|
||||
} else {
|
||||
// non-threshold
|
||||
Ok(aggregatable.iter().sum())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aggregatable for PartialSignature {
|
||||
fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
|
||||
let h = sigs
|
||||
.first()
|
||||
.ok_or_else(|| CoconutError::Aggregation("Empty set of signatures".to_string()))?
|
||||
.sig1();
|
||||
|
||||
// TODO: is it possible to avoid this allocation?
|
||||
let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
|
||||
let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
|
||||
|
||||
Ok(Signature(*h, aggr_sigma))
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures all provided verification keys were generated to verify the same number of attributes.
|
||||
fn check_same_key_size(keys: &[VerificationKey]) -> bool {
|
||||
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
|
||||
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
|
||||
}
|
||||
|
||||
pub fn aggregate_verification_keys(
|
||||
keys: &[VerificationKey],
|
||||
indices: Option<&[SignerIndex]>,
|
||||
) -> Result<VerificationKey> {
|
||||
if !check_same_key_size(keys) {
|
||||
return Err(CoconutError::Aggregation(
|
||||
"Verification keys are of different sizes".to_string(),
|
||||
));
|
||||
}
|
||||
Aggregatable::aggregate(keys, indices)
|
||||
}
|
||||
|
||||
pub fn aggregate_key_shares(shares: &[VerificationKeyShare]) -> Result<VerificationKey> {
|
||||
let (keys, indices): (Vec<_>, Vec<_>) = shares
|
||||
.iter()
|
||||
.map(|share| (share.key.clone(), share.index))
|
||||
.unzip();
|
||||
|
||||
aggregate_verification_keys(&keys, Some(&indices))
|
||||
}
|
||||
|
||||
pub fn aggregate_signatures(
|
||||
signatures: &[PartialSignature],
|
||||
indices: Option<&[SignerIndex]>,
|
||||
) -> Result<Signature> {
|
||||
Aggregatable::aggregate(signatures, indices)
|
||||
}
|
||||
|
||||
pub fn aggregate_signatures_and_verify(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
attributes: &[&Attribute],
|
||||
signatures: &[PartialSignature],
|
||||
indices: Option<&[SignerIndex]>,
|
||||
) -> Result<Signature> {
|
||||
// aggregate the signature
|
||||
let signature = aggregate_signatures(signatures, indices)?;
|
||||
|
||||
// Verify the signature
|
||||
let alpha = verification_key.alpha;
|
||||
|
||||
let tmp = attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(&attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
if bool::from(signature.0.is_identity()) {
|
||||
return Err(CoconutError::Aggregation(
|
||||
"Verification of the aggregated signature failed - h is an identity point".to_string(),
|
||||
));
|
||||
}
|
||||
if !check_bilinear_pairing(
|
||||
&signature.0.to_affine(),
|
||||
&G2Prepared::from((alpha + tmp).to_affine()),
|
||||
&signature.1.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CoconutError::Aggregation(
|
||||
"Verification of the aggregated signature failed".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub fn aggregate_signature_shares(shares: &[SignatureShare]) -> Result<Signature> {
|
||||
let (signatures, indices): (Vec<_>, Vec<_>) = shares
|
||||
.iter()
|
||||
.map(|share| (*share.signature(), share.index()))
|
||||
.unzip();
|
||||
|
||||
aggregate_signatures(&signatures, Some(&indices))
|
||||
}
|
||||
|
||||
pub fn aggregate_signature_shares_and_verify(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
attributes: &[&Attribute],
|
||||
shares: &[SignatureShare],
|
||||
) -> Result<Signature> {
|
||||
let (signatures, indices): (Vec<_>, Vec<_>) = shares
|
||||
.iter()
|
||||
.map(|share| (*share.signature(), share.index()))
|
||||
.unzip();
|
||||
|
||||
aggregate_signatures_and_verify(
|
||||
params,
|
||||
verification_key,
|
||||
attributes,
|
||||
&signatures,
|
||||
Some(&indices),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::scheme::issuance::sign;
|
||||
use crate::scheme::keygen::ttp_keygen;
|
||||
use crate::scheme::verification::verify;
|
||||
use crate::tests::helpers::random_scalars_refs;
|
||||
use bls12_381::G1Projective;
|
||||
use group::Group;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_works_for_any_subset_of_keys() {
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
|
||||
let vks = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| keypair.verification_key().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let aggr_vk1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap();
|
||||
let aggr_vk2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap();
|
||||
|
||||
assert_eq!(aggr_vk1, aggr_vk2);
|
||||
|
||||
// TODO: should those two actually work or not?
|
||||
// aggregating threshold+1
|
||||
let aggr_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap();
|
||||
assert_eq!(aggr_vk1, aggr_more);
|
||||
|
||||
// aggregating all
|
||||
let aggr_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap();
|
||||
assert_eq!(aggr_all, aggr_vk1);
|
||||
|
||||
// not taking enough points (threshold was 3)
|
||||
let aggr_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap();
|
||||
assert_ne!(aggr_not_enough, aggr_vk1);
|
||||
|
||||
// taking wrong index
|
||||
let aggr_bad = aggregate_verification_keys(&vks[2..], Some(&[42, 123, 100])).unwrap();
|
||||
assert_ne!(aggr_vk1, aggr_bad);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_doesnt_work_for_empty_set_of_keys() {
|
||||
let keys: Vec<VerificationKey> = vec![];
|
||||
assert!(aggregate_verification_keys(&keys, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_doesnt_work_if_indices_have_invalid_length() {
|
||||
let keys = vec![VerificationKey::identity(3)];
|
||||
|
||||
assert!(aggregate_verification_keys(&keys, Some(&[])).is_err());
|
||||
assert!(aggregate_verification_keys(&keys, Some(&[1, 2])).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_doesnt_work_for_non_unique_indices() {
|
||||
let keys = vec![VerificationKey::identity(3), VerificationKey::identity(3)];
|
||||
|
||||
assert!(aggregate_verification_keys(&keys, Some(&[1, 1])).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_aggregation_doesnt_work_for_keys_of_different_size() {
|
||||
let keys = vec![VerificationKey::identity(3), VerificationKey::identity(1)];
|
||||
|
||||
assert!(aggregate_verification_keys(&keys, None).is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_works_for_any_subset_of_signatures() {
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(attributes, params, 2);
|
||||
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
|
||||
let (sks, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| {
|
||||
(
|
||||
keypair.secret_key().clone(),
|
||||
keypair.verification_key().clone(),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
let sigs = sks
|
||||
.iter()
|
||||
.map(|sk| sign(sk, &attributes).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// aggregating (any) threshold works
|
||||
let aggr_vk_1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap();
|
||||
let aggr_sig1 = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_1,
|
||||
&attributes,
|
||||
&sigs[..3],
|
||||
Some(&[1, 2, 3]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let aggr_vk_2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap();
|
||||
let aggr_sig2 = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_1,
|
||||
&attributes,
|
||||
&sigs[2..],
|
||||
Some(&[3, 4, 5]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(aggr_sig1, aggr_sig2);
|
||||
|
||||
// verify credential for good measure
|
||||
assert!(verify(¶ms, &aggr_vk_1, &attributes, &aggr_sig1));
|
||||
assert!(verify(¶ms, &aggr_vk_2, &attributes, &aggr_sig2));
|
||||
|
||||
// aggregating threshold+1 works
|
||||
let aggr_vk_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap();
|
||||
let aggr_more = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_more,
|
||||
&attributes,
|
||||
&sigs[1..],
|
||||
Some(&[2, 3, 4, 5]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(aggr_sig1, aggr_more);
|
||||
|
||||
// aggregating all
|
||||
let aggr_vk_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap();
|
||||
let aggr_all = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&sigs,
|
||||
Some(&[1, 2, 3, 4, 5]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(aggr_all, aggr_sig1);
|
||||
|
||||
// not taking enough points (threshold was 3) should fail
|
||||
let aggr_vk_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap();
|
||||
let aggr_not_enough = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_not_enough,
|
||||
&attributes,
|
||||
&sigs[..2],
|
||||
Some(&[1, 2]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_ne!(aggr_not_enough, aggr_sig1);
|
||||
|
||||
// taking wrong index should fail
|
||||
let aggr_vk_bad = aggregate_verification_keys(&vks[2..], Some(&[1, 2, 3])).unwrap();
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_bad,
|
||||
&attributes,
|
||||
&sigs[2..],
|
||||
Some(&[42, 123, 100]),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
fn random_signature() -> Signature {
|
||||
let mut rng = rand::thread_rng();
|
||||
Signature(
|
||||
G1Projective::random(&mut rng),
|
||||
G1Projective::random(&mut rng),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() {
|
||||
let signatures: Vec<Signature> = vec![];
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(attributes, params, 2);
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| {
|
||||
(
|
||||
keypair.secret_key().clone(),
|
||||
keypair.verification_key().clone(),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap();
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
None
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() {
|
||||
let signatures = vec![random_signature()];
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(attributes, params, 2);
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| {
|
||||
(
|
||||
keypair.secret_key().clone(),
|
||||
keypair.verification_key().clone(),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap();
|
||||
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
Some(&[])
|
||||
)
|
||||
.is_err());
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
Some(&[1, 2]),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_aggregation_doesnt_work_for_non_unique_indices() {
|
||||
let signatures = vec![random_signature(), random_signature()];
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(attributes, params, 2);
|
||||
let keypairs = ttp_keygen(¶ms, 3, 5).unwrap();
|
||||
let (_, vks): (Vec<_>, Vec<_>) = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| {
|
||||
(
|
||||
keypair.secret_key().clone(),
|
||||
keypair.verification_key().clone(),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap();
|
||||
|
||||
assert!(aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk_all,
|
||||
&attributes,
|
||||
&signatures,
|
||||
Some(&[1, 1]),
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// TODO: test for aggregating non-threshold keys
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::try_deserialize_g2_projective;
|
||||
use bls12_381::{G2Affine, G2Projective};
|
||||
use group::Curve;
|
||||
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub struct BlindedSerialNumber(G2Projective);
|
||||
|
||||
// use custom Debug implementation to show base58 encoding (rather than raw curve elements)
|
||||
impl Debug for BlindedSerialNumber {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("BlindedSerialNumber")
|
||||
.field(&self.to_bs58())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<G2Projective> for BlindedSerialNumber {
|
||||
fn from(value: G2Projective) -> Self {
|
||||
BlindedSerialNumber(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<G2Affine> for BlindedSerialNumber {
|
||||
fn from(value: G2Affine) -> Self {
|
||||
BlindedSerialNumber(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BlindedSerialNumber {
|
||||
type Target = G2Projective;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for BlindedSerialNumber {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self> {
|
||||
if bytes.len() != 96 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
format!("Tried to deserialize blinded serial number with incorrect number of bytes, expected 96, got {}", bytes.len()),
|
||||
));
|
||||
}
|
||||
|
||||
// safety: we've just made a check for 96 bytes
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let inner = try_deserialize_g2_projective(
|
||||
&bytes.try_into().unwrap(),
|
||||
CoconutError::Deserialization(
|
||||
"failed to deserialize the blinded serial number (zeta)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(BlindedSerialNumber(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for BlindedSerialNumber {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.0.to_affine().to_compressed().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
Self::try_from(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for BlindedSerialNumber {}
|
||||
@@ -1,660 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::ops::Neg;
|
||||
|
||||
use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Prepared, Scalar};
|
||||
use group::{Curve, Group, GroupEncoding};
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::proofs::ProofCmCs;
|
||||
use crate::scheme::keygen::VerificationKey;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::BlindedSignature;
|
||||
use crate::scheme::SecretKey;
|
||||
use crate::Attribute;
|
||||
use crate::Signature;
|
||||
|
||||
// TODO: possibly completely remove those two functions.
|
||||
// They only exist to have a simpler and smaller code snippets to test
|
||||
// basic functionalities.
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::{hash_g1, try_deserialize_g1_projective};
|
||||
|
||||
// TODO NAMING: double check this one
|
||||
// Lambda
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct BlindSignRequest {
|
||||
// cm
|
||||
commitment: G1Projective,
|
||||
// h
|
||||
commitment_hash: G1Projective,
|
||||
// c
|
||||
private_attributes_commitments: Vec<G1Projective>,
|
||||
// pi_s
|
||||
pi_s: ProofCmCs,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for BlindSignRequest {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<BlindSignRequest> {
|
||||
if bytes.len() < 48 + 48 + 8 + 48 {
|
||||
return Err(CoconutError::DeserializationMinLength {
|
||||
min: 48 + 48 + 8 + 48,
|
||||
actual: bytes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut j = 0;
|
||||
let commitment_bytes_len = 48;
|
||||
let commitment_hash_bytes_len = 48;
|
||||
|
||||
// safety: we made bound check and we're using constant offest
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let cm_bytes = bytes[..j + commitment_bytes_len].try_into().unwrap();
|
||||
let commitment = try_deserialize_g1_projective(
|
||||
&cm_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize compressed commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
j += commitment_bytes_len;
|
||||
|
||||
// safety: we made bound check and we're using constant offest
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let cm_hash_bytes = bytes[j..j + commitment_hash_bytes_len].try_into().unwrap();
|
||||
let commitment_hash = try_deserialize_g1_projective(
|
||||
&cm_hash_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize compressed commitment hash".to_string(),
|
||||
),
|
||||
)?;
|
||||
j += commitment_hash_bytes_len;
|
||||
|
||||
// safety: we made bound check and we're using constant offest
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let c_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
|
||||
j += 8;
|
||||
if bytes[j..].len() < c_len as usize * 48 {
|
||||
return Err(CoconutError::DeserializationMinLength {
|
||||
min: c_len as usize * 48,
|
||||
actual: bytes[56..].len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut private_attributes_commitments = Vec::with_capacity(c_len as usize);
|
||||
for i in 0..c_len as usize {
|
||||
let start = j + i * 48;
|
||||
let end = start + 48;
|
||||
|
||||
if bytes.len() < end {
|
||||
return Err(CoconutError::Deserialization(
|
||||
"Failed to deserialize compressed commitment".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// safety: we made bound check and we're using constant offest
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let private_attributes_commitment_bytes = bytes[start..end].try_into().unwrap();
|
||||
let private_attributes_commitment = try_deserialize_g1_projective(
|
||||
&private_attributes_commitment_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize compressed commitment".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
private_attributes_commitments.push(private_attributes_commitment)
|
||||
}
|
||||
|
||||
let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 48..])?;
|
||||
|
||||
Ok(BlindSignRequest {
|
||||
commitment,
|
||||
commitment_hash,
|
||||
private_attributes_commitments,
|
||||
pi_s,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for BlindSignRequest {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
let cm_bytes = self.commitment.to_affine().to_compressed();
|
||||
let cm_hash_bytes = self.commitment_hash.to_affine().to_compressed();
|
||||
let c_len = self.private_attributes_commitments.len() as u64;
|
||||
let proof_bytes = self.pi_s.to_bytes();
|
||||
|
||||
let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 48 + proof_bytes.len());
|
||||
|
||||
bytes.extend_from_slice(&cm_bytes);
|
||||
bytes.extend_from_slice(&cm_hash_bytes);
|
||||
bytes.extend_from_slice(&c_len.to_le_bytes());
|
||||
for c in &self.private_attributes_commitments {
|
||||
bytes.extend_from_slice(&c.to_affine().to_compressed());
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&proof_bytes);
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
BlindSignRequest::from_bytes(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for BlindSignRequest {}
|
||||
|
||||
impl BlindSignRequest {
|
||||
fn verify_proof(&self, params: &Parameters, public_attributes: &[&Attribute]) -> bool {
|
||||
self.pi_s.verify(
|
||||
params,
|
||||
&self.commitment,
|
||||
&self.private_attributes_commitments,
|
||||
public_attributes,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn verify_commitment_hash(&self, public_attributes: &[&Attribute]) -> bool {
|
||||
self.commitment_hash == compute_hash(self.commitment, public_attributes)
|
||||
}
|
||||
|
||||
pub fn get_commitment_hash(&self) -> G1Projective {
|
||||
self.commitment_hash
|
||||
}
|
||||
|
||||
pub fn get_private_attributes_pedersen_commitments(&self) -> &[G1Projective] {
|
||||
&self.private_attributes_commitments
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.to_byte_vec()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<BlindSignRequest> {
|
||||
BlindSignRequest::try_from(bytes)
|
||||
}
|
||||
|
||||
pub fn num_private_attributes(&self) -> usize {
|
||||
self.private_attributes_commitments.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_attributes_commitment(
|
||||
params: &Parameters,
|
||||
private_attributes: &[&Attribute],
|
||||
public_attributes: &[&Attribute],
|
||||
hs: &[G1Affine],
|
||||
) -> (Scalar, G1Projective) {
|
||||
let commitment_opening = params.random_scalar();
|
||||
|
||||
// Produces h0 ^ m0 * h1^m1 * .... * hn^mn
|
||||
// where m0, m1, ...., mn are attributes
|
||||
let attr_cm = private_attributes
|
||||
.iter()
|
||||
.chain(public_attributes.iter())
|
||||
.zip(hs)
|
||||
.map(|(&m, h)| h * m)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
// Produces g1^r * h0 ^ m0 * h1^m1 * .... * hn^mn
|
||||
let commitment = params.gen1() * commitment_opening + attr_cm;
|
||||
(commitment_opening, commitment)
|
||||
}
|
||||
|
||||
pub fn compute_pedersen_commitments_for_private_attributes(
|
||||
params: &Parameters,
|
||||
private_attributes: &[&Attribute],
|
||||
h: &G1Projective,
|
||||
) -> (Vec<Scalar>, Vec<G1Projective>) {
|
||||
// Generate openings for Pedersen commitment for each private attribute
|
||||
let commitments_openings = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
// Compute Pedersen commitment for each private attribute
|
||||
let pedersen_commitments = commitments_openings
|
||||
.iter()
|
||||
.zip(private_attributes.iter())
|
||||
.map(|(o_j, &m_j)| params.gen1() * o_j + h * m_j)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(commitments_openings, pedersen_commitments)
|
||||
}
|
||||
|
||||
pub fn compute_hash(commitment: G1Projective, public_attributes: &[&Attribute]) -> G1Projective {
|
||||
let mut buff = Vec::new();
|
||||
buff.extend_from_slice(commitment.to_bytes().as_ref());
|
||||
for attr in public_attributes {
|
||||
buff.extend_from_slice(attr.to_bytes().as_ref());
|
||||
}
|
||||
hash_g1(buff)
|
||||
}
|
||||
|
||||
/// Builds cryptographic material required for blind sign.
|
||||
pub fn prepare_blind_sign(
|
||||
params: &Parameters,
|
||||
private_attributes: &[&Attribute],
|
||||
public_attributes: &[&Attribute],
|
||||
) -> Result<(Vec<Scalar>, BlindSignRequest)> {
|
||||
if private_attributes.is_empty() {
|
||||
return Err(CoconutError::Issuance(
|
||||
"Tried to prepare blind sign request for an empty set of private attributes"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let hs = params.gen_hs();
|
||||
if private_attributes.len() + public_attributes.len() > hs.len() {
|
||||
return Err(CoconutError::IssuanceMaxAttributes {
|
||||
max: hs.len(),
|
||||
requested: private_attributes.len() + public_attributes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut commitment_hash;
|
||||
let mut commitment;
|
||||
let mut commitment_opening;
|
||||
|
||||
loop {
|
||||
// Compute the attributes commitment
|
||||
let (c_opening, c) =
|
||||
compute_attributes_commitment(params, private_attributes, public_attributes, hs);
|
||||
commitment_opening = c_opening;
|
||||
commitment = c;
|
||||
|
||||
// Compute the commitment hash
|
||||
commitment_hash = compute_hash(commitment, public_attributes);
|
||||
|
||||
// Check if the commitment hash is not the identity point
|
||||
if !bool::from(commitment_hash.is_identity()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let (pedersen_commitments_openings, pedersen_commitments) =
|
||||
compute_pedersen_commitments_for_private_attributes(
|
||||
params,
|
||||
private_attributes,
|
||||
&commitment_hash,
|
||||
);
|
||||
|
||||
let pi_s = ProofCmCs::construct(
|
||||
params,
|
||||
&commitment,
|
||||
&commitment_opening,
|
||||
&pedersen_commitments,
|
||||
&pedersen_commitments_openings,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
);
|
||||
|
||||
Ok((
|
||||
pedersen_commitments_openings,
|
||||
BlindSignRequest {
|
||||
commitment,
|
||||
commitment_hash,
|
||||
private_attributes_commitments: pedersen_commitments,
|
||||
pi_s,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn blind_sign(
|
||||
params: &Parameters,
|
||||
signing_secret_key: &SecretKey,
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
public_attributes: &[&Attribute],
|
||||
) -> Result<BlindedSignature> {
|
||||
let num_private = blind_sign_request.private_attributes_commitments.len();
|
||||
let hs = params.gen_hs();
|
||||
|
||||
if num_private + public_attributes.len() > hs.len() {
|
||||
return Err(CoconutError::IssuanceMaxAttributes {
|
||||
max: hs.len(),
|
||||
requested: num_private + public_attributes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
// Verify the commitment hash
|
||||
let h = compute_hash(blind_sign_request.commitment, public_attributes);
|
||||
if bool::from(blind_sign_request.commitment_hash.is_identity()) {
|
||||
return Err(CoconutError::Issuance(
|
||||
"Commitment hash should not be an identity point".to_string(),
|
||||
));
|
||||
}
|
||||
if !(h == blind_sign_request.commitment_hash) {
|
||||
return Err(CoconutError::Issuance(
|
||||
"Failed to verify the commitment hash".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Verify the ZK proof
|
||||
if !blind_sign_request.verify_proof(params, public_attributes) {
|
||||
return Err(CoconutError::Issuance(
|
||||
"Failed to verify the proof of knowledge".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// in python implementation there are n^2 G1 multiplications, let's do it with a single one instead.
|
||||
// i.e. compute h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n]) directly (where m is number of PRIVATE attributes)
|
||||
// rather than ((h ^ pub_m[0]) ^ y[m + 1] , (h ^ pub_m[1]) ^ y[m + 2] , ...).sum() separately
|
||||
let signed_public = h * public_attributes
|
||||
.iter()
|
||||
.zip(signing_secret_key.ys.iter().skip(num_private))
|
||||
.map(|(&attr, yi)| attr * yi)
|
||||
.sum::<Scalar>();
|
||||
|
||||
// h ^ x + c[0] ^ y[0] + ... c[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n])
|
||||
let sig = blind_sign_request
|
||||
.private_attributes_commitments
|
||||
.iter()
|
||||
.zip(signing_secret_key.ys.iter())
|
||||
.map(|(c, yi)| c * yi)
|
||||
.chain(std::iter::once(h * signing_secret_key.x))
|
||||
.chain(std::iter::once(signed_public))
|
||||
.sum();
|
||||
|
||||
Ok(BlindedSignature(h, sig))
|
||||
}
|
||||
|
||||
/// Verifies a partial blind signature using the provided parameters and validator's verification key.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `params` - A reference to the cryptographic parameters.
|
||||
/// * `blind_sign_request` - A reference to the blind signature request signed by the client.
|
||||
/// * `public_attributes` - A reference to the public attributes included in the client's request.
|
||||
/// * `blind_sig` - A reference to the issued partial blinded signature to be verified.
|
||||
/// * `partial_verification_key` - A reference to the validator's partial verification key.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A boolean indicating whether the partial blind signature is valid (`true`) or not (`false`).
|
||||
///
|
||||
/// # Remarks
|
||||
///
|
||||
/// This function verifies the correctness and validity of a partial blind signature using
|
||||
/// the provided cryptographic parameters, blind signature request, blinded signature,
|
||||
/// and partial verification key.
|
||||
/// It calculates pairings based on the provided values and checks whether the partial blind signature
|
||||
/// is consistent with the verification key and commitments in the blind signature request.
|
||||
/// The function returns `true` if the partial blind signature is valid, and `false` otherwise.
|
||||
pub fn verify_partial_blind_signature(
|
||||
params: &Parameters,
|
||||
private_attribute_commitments: &[G1Projective],
|
||||
public_attributes: &[&Attribute],
|
||||
blind_sig: &BlindedSignature,
|
||||
partial_verification_key: &VerificationKey,
|
||||
) -> bool {
|
||||
let num_private_attributes = private_attribute_commitments.len();
|
||||
if num_private_attributes + public_attributes.len() > partial_verification_key.beta_g2.len() {
|
||||
return false;
|
||||
}
|
||||
if bool::from(blind_sig.0.is_identity()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: we're losing some memory here due to extra allocation,
|
||||
// but worst-case scenario (given SANE amount of attributes), it's just few kb at most
|
||||
let c_neg = blind_sig.1.to_affine().neg();
|
||||
let g2_prep = params.prepared_miller_g2();
|
||||
|
||||
let mut terms = vec![
|
||||
// (c^{-1}, g2)
|
||||
(c_neg, g2_prep.clone()),
|
||||
// (s, alpha)
|
||||
(
|
||||
blind_sig.0.to_affine(),
|
||||
G2Prepared::from(partial_verification_key.alpha.to_affine()),
|
||||
),
|
||||
];
|
||||
|
||||
// for each private attribute, add (cm_i, beta_i) to the miller terms
|
||||
for (private_attr_commit, beta_g2) in private_attribute_commitments
|
||||
.iter()
|
||||
.zip(&partial_verification_key.beta_g2)
|
||||
{
|
||||
// (cm_i, beta_i)
|
||||
terms.push((
|
||||
private_attr_commit.to_affine(),
|
||||
G2Prepared::from(beta_g2.to_affine()),
|
||||
))
|
||||
}
|
||||
|
||||
// for each public attribute, add (s^pub_j, beta_{priv + j}) to the miller terms
|
||||
for (&pub_attr, beta_g2) in public_attributes.iter().zip(
|
||||
partial_verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.skip(num_private_attributes),
|
||||
) {
|
||||
// (s^pub_j, beta_j)
|
||||
terms.push((
|
||||
(blind_sig.0 * pub_attr).to_affine(),
|
||||
G2Prepared::from(beta_g2.to_affine()),
|
||||
))
|
||||
}
|
||||
|
||||
// get the references to all the terms to get the arguments the miller loop expects
|
||||
#[allow(clippy::map_identity)]
|
||||
let terms_refs = terms.iter().map(|(g1, g2)| (g1, g2)).collect::<Vec<_>>();
|
||||
|
||||
// since checking whether e(a, b) == e(c, d)
|
||||
// is equivalent to checking e(a, b) • e(c, d)^{-1} == id
|
||||
// and thus to e(a, b) • e(c^{-1}, d) == id
|
||||
//
|
||||
// compute e(c^{-1}, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j})
|
||||
multi_miller_loop(&terms_refs)
|
||||
.final_exponentiation()
|
||||
.is_identity()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Creates a Coconut Signature under a given secret key on a set of public attributes only.
|
||||
pub fn sign(secret_key: &SecretKey, public_attributes: &[&Attribute]) -> Result<Signature> {
|
||||
if public_attributes.len() > secret_key.ys.len() {
|
||||
return Err(CoconutError::IssuanceMaxAttributes {
|
||||
max: secret_key.ys.len(),
|
||||
requested: public_attributes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
//Serialize the array structure of the public attributes into a byte array
|
||||
let mut serialized_attributes = Vec::new();
|
||||
//Prepend the length of the entire array (in bytes)
|
||||
let array_len = public_attributes.len() as u64;
|
||||
serialized_attributes.extend_from_slice(&array_len.to_le_bytes());
|
||||
//Serialize each attribute with its length
|
||||
for &attribute in public_attributes.iter() {
|
||||
let attr_bytes = attribute.to_bytes();
|
||||
let attr_len = attr_bytes.len() as u64;
|
||||
|
||||
// Prefix the attribute with its length
|
||||
serialized_attributes.extend_from_slice(&attr_len.to_le_bytes());
|
||||
serialized_attributes.extend_from_slice(&attr_bytes);
|
||||
}
|
||||
|
||||
//Hash the resulting byte array to derive the point H
|
||||
let h = hash_g1(serialized_attributes);
|
||||
|
||||
// x + m0 * y0 + m1 * y1 + ... mn * yn
|
||||
let exponent = secret_key.x
|
||||
+ public_attributes
|
||||
.iter()
|
||||
.zip(secret_key.ys.iter())
|
||||
.map(|(&m_i, y_i)| m_i * y_i)
|
||||
.sum::<Scalar>();
|
||||
|
||||
let sig2 = h * exponent;
|
||||
Ok(Signature(h, sig2))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::scheme::keygen::keygen;
|
||||
use crate::tests::helpers::random_scalars_refs;
|
||||
|
||||
#[test]
|
||||
fn blind_sign_request_bytes_roundtrip() {
|
||||
// 0 public and 1 private attribute
|
||||
let params = Parameters::new(1).unwrap();
|
||||
random_scalars_refs!(private_attributes, params, 1);
|
||||
random_scalars_refs!(public_attributes, params, 0);
|
||||
|
||||
let (_commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let bytes = lambda.to_bytes();
|
||||
assert_eq!(
|
||||
BlindSignRequest::try_from(bytes.as_slice()).unwrap(),
|
||||
lambda
|
||||
);
|
||||
|
||||
// 2 public and 2 private attributes
|
||||
let params = Parameters::new(4).unwrap();
|
||||
random_scalars_refs!(private_attributes, params, 2);
|
||||
random_scalars_refs!(public_attributes, params, 2);
|
||||
|
||||
let (_commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let bytes = lambda.to_bytes();
|
||||
assert_eq!(
|
||||
BlindSignRequest::try_from(bytes.as_slice()).unwrap(),
|
||||
lambda
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prepare_blind_sign_non_identity_commitment_hash() {
|
||||
let params = Parameters::new(1).unwrap();
|
||||
random_scalars_refs!(private_attributes, params, 1);
|
||||
random_scalars_refs!(public_attributes, params, 0);
|
||||
|
||||
// Call the function to prepare the blind sign
|
||||
let result = prepare_blind_sign(¶ms, &private_attributes, &public_attributes);
|
||||
|
||||
// Ensure the result is Ok
|
||||
assert!(result.is_ok(), "prepare_blind_sign should succeed");
|
||||
|
||||
let (_, blind_sign_request) = result.unwrap();
|
||||
|
||||
// Ensure the commitment_hash is not the identity point
|
||||
assert!(
|
||||
!bool::from(blind_sign_request.commitment_hash.is_identity()),
|
||||
"commitment_hash should not be the identity point"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blind_sign_with_identity_commitment_hash() {
|
||||
let params = Parameters::new(1).unwrap();
|
||||
random_scalars_refs!(private_attributes, params, 1);
|
||||
random_scalars_refs!(public_attributes, params, 0);
|
||||
|
||||
// Call the function to prepare the blind sign
|
||||
let (_commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
let blind_sign_request = BlindSignRequest {
|
||||
commitment_hash: G1Projective::identity(),
|
||||
..blind_sign_request // This copies the other fields from the existing instance
|
||||
};
|
||||
|
||||
let signing_secret_key = SecretKey {
|
||||
x: params.random_scalar(),
|
||||
ys: vec![params.random_scalar()],
|
||||
};
|
||||
|
||||
// Call blind_sign and ensure it returns an error due to identity commitment hash
|
||||
let result = blind_sign(
|
||||
¶ms,
|
||||
&signing_secret_key,
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
);
|
||||
|
||||
// The result should be an error
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"blind_sign should return an error when commitment_hash is the identity point"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_verify_partial_blind_signature() {
|
||||
let params = Parameters::new(4).unwrap();
|
||||
random_scalars_refs!(private_attributes, params, 2);
|
||||
random_scalars_refs!(public_attributes, params, 2);
|
||||
|
||||
let (_commitments_openings, request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let validator_keypair = keygen(¶ms);
|
||||
let blind_sig = blind_sign(
|
||||
¶ms,
|
||||
validator_keypair.secret_key(),
|
||||
&request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(verify_partial_blind_signature(
|
||||
¶ms,
|
||||
&request.private_attributes_commitments,
|
||||
&public_attributes,
|
||||
&blind_sig,
|
||||
validator_keypair.verification_key()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_verify_partial_blind_signature_no_public_attributes() {
|
||||
let params = Parameters::new(4).unwrap();
|
||||
random_scalars_refs!(private_attributes, params, 2);
|
||||
|
||||
let (_commitments_openings, request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap();
|
||||
|
||||
let validator_keypair = keygen(¶ms);
|
||||
let blind_sig = blind_sign(¶ms, validator_keypair.secret_key(), &request, &[]).unwrap();
|
||||
|
||||
assert!(verify_partial_blind_signature(
|
||||
¶ms,
|
||||
&request.private_attributes_commitments,
|
||||
&[],
|
||||
&blind_sig,
|
||||
validator_keypair.verification_key()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_verify_partial_blind_signature_with_wrong_key() {
|
||||
let params = Parameters::new(4).unwrap();
|
||||
random_scalars_refs!(private_attributes, params, 2);
|
||||
random_scalars_refs!(public_attributes, params, 2);
|
||||
|
||||
let (_commitments_openings, request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let validator_keypair = keygen(¶ms);
|
||||
let validator2_keypair = keygen(¶ms);
|
||||
let blind_sig = blind_sign(
|
||||
¶ms,
|
||||
validator_keypair.secret_key(),
|
||||
&request,
|
||||
&public_attributes,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// this assertion should fail, as we try to verify with a wrong validator key
|
||||
assert!(!verify_partial_blind_signature(
|
||||
¶ms,
|
||||
&request.private_attributes_commitments,
|
||||
&public_attributes,
|
||||
&blind_sig,
|
||||
validator2_keypair.verification_key()
|
||||
),);
|
||||
}
|
||||
}
|
||||
@@ -1,722 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::borrow::Borrow;
|
||||
use core::iter::Sum;
|
||||
use core::ops::{Add, Mul};
|
||||
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::aggregation::aggregate_verification_keys;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::SignerIndex;
|
||||
use crate::traits::Bytable;
|
||||
use crate::utils::{
|
||||
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
|
||||
try_deserialize_scalar_vec, Polynomial,
|
||||
};
|
||||
use crate::Base58;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Clone))]
|
||||
#[cfg_attr(
|
||||
feature = "key-zeroize",
|
||||
derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop)
|
||||
)]
|
||||
pub struct SecretKey {
|
||||
pub(crate) x: Scalar,
|
||||
pub(crate) ys: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl PemStorableKey for SecretKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn pem_type() -> &'static str {
|
||||
"COCONUT SECRET KEY"
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.to_bytes()
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Error> {
|
||||
Self::from_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SecretKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SecretKey> {
|
||||
// There should be x and at least one y
|
||||
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
|
||||
return Err(CoconutError::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
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
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(CoconutError::Deserialization(format!(
|
||||
"Tried to deserialize secret key with inconsistent ys len (expected {ys_len}, got {actual_ys_len})"
|
||||
)));
|
||||
}
|
||||
|
||||
let x = try_deserialize_scalar(
|
||||
&x_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize secret key scalar".to_string()),
|
||||
)?;
|
||||
let ys = try_deserialize_scalar_vec(
|
||||
ys_len,
|
||||
&bytes[40..],
|
||||
CoconutError::Deserialization("Failed to deserialize secret key scalars".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(SecretKey { x, ys })
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretKey {
|
||||
/// Following a (distributed) key generation process, scalar values can be obtained
|
||||
/// outside of the normal key generation process.
|
||||
pub fn create_from_raw(x: Scalar, ys: Vec<Scalar>) -> Self {
|
||||
Self { x, ys }
|
||||
}
|
||||
|
||||
/// Extract the Scalar copy of the underlying secrets.
|
||||
/// The caller of this function must exercise extreme care to not misuse the data and ensuring it gets zeroized
|
||||
pub fn hazmat_to_raw(&self) -> (Scalar, Vec<Scalar>) {
|
||||
(self.x, self.ys.clone())
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.ys.len()
|
||||
}
|
||||
|
||||
/// Derive verification key using this secret key.
|
||||
pub fn verification_key(&self, params: &Parameters) -> VerificationKey {
|
||||
let g1 = params.gen1();
|
||||
let g2 = params.gen2();
|
||||
VerificationKey {
|
||||
alpha: g2 * self.x,
|
||||
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
|
||||
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
// x || ys.len() || ys
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ys_len = self.ys.len();
|
||||
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) * 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<SecretKey> {
|
||||
SecretKey::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for SecretKey {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
SecretKey::try_from(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for SecretKey {}
|
||||
|
||||
// TODO: perhaps change points to affine representation
|
||||
// to make verification slightly more efficient?
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct VerificationKey {
|
||||
// TODO add gen2 as per the paper or imply it from the fact library is using bls381?
|
||||
pub(crate) alpha: G2Projective,
|
||||
pub(crate) beta_g1: Vec<G1Projective>,
|
||||
pub(crate) beta_g2: Vec<G2Projective>,
|
||||
}
|
||||
|
||||
impl PemStorableKey for VerificationKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn pem_type() -> &'static str {
|
||||
"COCONUT VERIFICATION KEY"
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.to_bytes()
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Error> {
|
||||
Self::from_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for VerificationKey {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<VerificationKey> {
|
||||
// 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(CoconutError::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
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
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(
|
||||
CoconutError::Deserialization(
|
||||
format!("Tried to deserialize verification key with inconsistent betas len (expected {betas_len}, got {actual_betas_len})"
|
||||
)));
|
||||
}
|
||||
|
||||
let alpha = try_deserialize_g2_projective(
|
||||
&alpha_bytes,
|
||||
CoconutError::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;
|
||||
// we're using a constant 48 byte offset (which is the size of G1 compressed) so unwrap is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g1_projective(
|
||||
&beta_i_bytes,
|
||||
CoconutError::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;
|
||||
// we're using a constant 96 byte offset (which is the size of G2 compressed) so unwrap is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let beta_i_bytes = bytes[start..end].try_into().unwrap();
|
||||
let beta_i = try_deserialize_g2_projective(
|
||||
&beta_i_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"Failed to deserialize verification key G2 point (beta)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
beta_g2.push(beta_i)
|
||||
}
|
||||
|
||||
Ok(VerificationKey {
|
||||
alpha,
|
||||
beta_g1,
|
||||
beta_g2,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Add<&'b VerificationKey> for VerificationKey {
|
||||
type Output = VerificationKey;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: &'b VerificationKey) -> VerificationKey {
|
||||
// If you're trying to add two keys together that were created
|
||||
// for different number of attributes, just panic as it's a
|
||||
// nonsense operation.
|
||||
assert_eq!(
|
||||
self.beta_g1.len(),
|
||||
rhs.beta_g1.len(),
|
||||
"trying to add verification keys generated for different number of attributes [G1]"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self.beta_g2.len(),
|
||||
rhs.beta_g2.len(),
|
||||
"trying to add verification keys generated for different number of attributes [G2]"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self.beta_g1.len(),
|
||||
self.beta_g2.len(),
|
||||
"this key is incorrect - the number of elements G1 and G2 does not match"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rhs.beta_g1.len(),
|
||||
rhs.beta_g2.len(),
|
||||
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
|
||||
);
|
||||
|
||||
VerificationKey {
|
||||
alpha: self.alpha + rhs.alpha,
|
||||
beta_g1: self
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(rhs.beta_g1.iter())
|
||||
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
|
||||
.collect(),
|
||||
beta_g2: self
|
||||
.beta_g2
|
||||
.iter()
|
||||
.zip(rhs.beta_g2.iter())
|
||||
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for &VerificationKey {
|
||||
type Output = VerificationKey;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: Scalar) -> Self::Output {
|
||||
VerificationKey {
|
||||
alpha: self.alpha * rhs,
|
||||
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
|
||||
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sum<T> for VerificationKey
|
||||
where
|
||||
T: Borrow<VerificationKey>,
|
||||
{
|
||||
#[inline]
|
||||
fn sum<I>(iter: I) -> Self
|
||||
where
|
||||
I: Iterator<Item = T>,
|
||||
{
|
||||
let mut peekable = iter.peekable();
|
||||
let head_attributes = match peekable.peek() {
|
||||
Some(head) => head.borrow().beta_g2.len(),
|
||||
None => {
|
||||
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
|
||||
// of VerificationKey. So should it panic here or just return some nonsense value?
|
||||
return VerificationKey::identity(0);
|
||||
}
|
||||
};
|
||||
|
||||
peekable.fold(VerificationKey::identity(head_attributes), |acc, item| {
|
||||
acc + item.borrow()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl VerificationKey {
|
||||
/// Create a (kinda) identity verification key using specified
|
||||
/// number of 'beta' elements
|
||||
pub(crate) fn identity(beta_size: usize) -> Self {
|
||||
VerificationKey {
|
||||
alpha: G2Projective::identity(),
|
||||
beta_g1: vec![G1Projective::identity(); beta_size],
|
||||
beta_g2: vec![G2Projective::identity(); beta_size],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self> {
|
||||
aggregate_verification_keys(sigs, indices)
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> &G2Projective {
|
||||
&self.alpha
|
||||
}
|
||||
|
||||
pub fn beta_g1(&self) -> &Vec<G1Projective> {
|
||||
&self.beta_g1
|
||||
}
|
||||
|
||||
pub fn beta_g2(&self) -> &Vec<G2Projective> {
|
||||
&self.beta_g2
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let beta_g1_len = self.beta_g1.len();
|
||||
let beta_g2_len = self.beta_g2.len();
|
||||
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
|
||||
|
||||
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
|
||||
|
||||
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
|
||||
|
||||
for beta_g1 in self.beta_g1.iter() {
|
||||
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
for beta_g2 in self.beta_g2.iter() {
|
||||
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKey> {
|
||||
VerificationKey::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for VerificationKey {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
VerificationKey::try_from(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for VerificationKey {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VerificationKeyShare {
|
||||
pub key: VerificationKey,
|
||||
pub index: SignerIndex,
|
||||
}
|
||||
|
||||
impl From<(VerificationKey, SignerIndex)> for VerificationKeyShare {
|
||||
fn from(value: (VerificationKey, SignerIndex)) -> Self {
|
||||
VerificationKeyShare {
|
||||
key: value.0,
|
||||
index: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Clone))]
|
||||
pub struct KeyPair {
|
||||
secret_key: SecretKey,
|
||||
verification_key: VerificationKey,
|
||||
|
||||
/// Optional index value specifying polynomial point used during threshold key generation.
|
||||
pub index: Option<SignerIndex>,
|
||||
}
|
||||
|
||||
impl From<KeyPair> for (SecretKey, VerificationKey) {
|
||||
fn from(value: KeyPair) -> Self {
|
||||
(value.secret_key, value.verification_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl PemStorableKeyPair for KeyPair {
|
||||
type PrivatePemKey = SecretKey;
|
||||
type PublicPemKey = VerificationKey;
|
||||
|
||||
fn private_key(&self) -> &Self::PrivatePemKey {
|
||||
&self.secret_key
|
||||
}
|
||||
|
||||
fn public_key(&self) -> &Self::PublicPemKey {
|
||||
&self.verification_key
|
||||
}
|
||||
|
||||
fn from_keys(secret_key: Self::PrivatePemKey, verification_key: Self::PublicPemKey) -> Self {
|
||||
Self::from_keys(secret_key, verification_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
const MARKER_BYTES: &'static [u8] = b"coconutkeypair";
|
||||
|
||||
pub fn from_keys(secret_key: SecretKey, verification_key: VerificationKey) -> Self {
|
||||
Self {
|
||||
secret_key,
|
||||
verification_key,
|
||||
index: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn secret_key(&self) -> &SecretKey {
|
||||
&self.secret_key
|
||||
}
|
||||
|
||||
pub fn verification_key(&self) -> &VerificationKey {
|
||||
&self.verification_key
|
||||
}
|
||||
|
||||
pub fn to_verification_key_share(&self) -> Option<VerificationKeyShare> {
|
||||
self.index.map(|index| VerificationKeyShare {
|
||||
key: self.verification_key.clone(),
|
||||
index,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
// Schema is coconutkeypair[14]|secret_key_len[8]|secret_key[secret_key_len]|verification_key_len[8]|verification_key[verification_key_len]|signer_index[8] - optional
|
||||
self.to_byte_vec()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
KeyPair::try_from_byte_slice(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for KeyPair {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
// Schema is coconutkeypair[14]|secret_key_len[8]|secret_key[secret_key_len]|verification_key_len[8]|verification_key[verification_key_len]|signer_index[8] - optional
|
||||
let mut byts = vec![];
|
||||
let secret_key_bytes = self.secret_key.to_bytes();
|
||||
let secret_key_len = (secret_key_bytes.len() as u64).to_le_bytes();
|
||||
let verification_key_bytes = self.verification_key.to_bytes();
|
||||
let verification_key_len = (verification_key_bytes.len() as u64).to_le_bytes();
|
||||
byts.extend_from_slice(Self::MARKER_BYTES);
|
||||
byts.extend_from_slice(&secret_key_len);
|
||||
byts.extend_from_slice(&secret_key_bytes);
|
||||
byts.extend_from_slice(&verification_key_len);
|
||||
byts.extend_from_slice(&verification_key_bytes);
|
||||
if let Some(index) = self.index {
|
||||
byts.extend_from_slice(&index.to_le_bytes())
|
||||
}
|
||||
byts
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
KeyPair::try_from(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for KeyPair {}
|
||||
|
||||
impl TryFrom<&[u8]> for KeyPair {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<KeyPair> {
|
||||
let header_len = Self::MARKER_BYTES.len();
|
||||
|
||||
// we must be able to at the very least read the length of secret key which is past the header
|
||||
// and is 8 bytes long
|
||||
if bytes.len() < header_len + 8 {
|
||||
return Err(CoconutError::DeserializationMinLength {
|
||||
min: header_len + 8,
|
||||
actual: bytes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
// safety: we made bound check and we're using constant offest
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let secret_key_len =
|
||||
u64::from_le_bytes(bytes[header_len..header_len + 8].try_into().unwrap()) as usize;
|
||||
let secret_key_start = header_len + 8;
|
||||
|
||||
let secret_key =
|
||||
SecretKey::try_from(&bytes[secret_key_start..secret_key_start + secret_key_len])?;
|
||||
|
||||
// we must be able to read the length of verification key
|
||||
if bytes.len() < secret_key_start + secret_key_len + 8 {
|
||||
return Err(CoconutError::DeserializationMinLength {
|
||||
min: secret_key_start + secret_key_len + 8,
|
||||
actual: bytes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
// safety: we made bound check
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let verification_key_len = u64::from_le_bytes(
|
||||
bytes[secret_key_start + secret_key_len..secret_key_start + secret_key_len + 8]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
) as usize;
|
||||
let verification_key_start = secret_key_start + secret_key_len + 8;
|
||||
|
||||
let verification_key = VerificationKey::try_from(
|
||||
&bytes[verification_key_start..verification_key_start + verification_key_len],
|
||||
)?;
|
||||
let consumed_bytes = verification_key_start + verification_key_len;
|
||||
let index = if consumed_bytes < bytes.len() && [consumed_bytes..].len() == 8 {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Some(u64::from_le_bytes(
|
||||
bytes[consumed_bytes..consumed_bytes + 8]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(KeyPair {
|
||||
secret_key,
|
||||
verification_key,
|
||||
index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a single Coconut keypair ((x, y0, y1...), (g2^x, g2^y0, ...)).
|
||||
///
|
||||
/// It is not suitable for threshold credentials as all subsequent calls to `keygen` generate keys
|
||||
/// that are independent of each other.
|
||||
pub fn keygen(params: &Parameters) -> KeyPair {
|
||||
let attributes = params.gen_hs().len();
|
||||
|
||||
let x = params.random_scalar();
|
||||
let ys = params.n_random_scalars(attributes);
|
||||
|
||||
let secret_key = SecretKey { x, ys };
|
||||
let verification_key = secret_key.verification_key(params);
|
||||
|
||||
KeyPair {
|
||||
secret_key,
|
||||
verification_key,
|
||||
index: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate Coconut keypairs.
|
||||
///
|
||||
/// Generate a set of n Coconut keypairs [((x, y0, y1...), (g2^x, g2^y0, ...)), ...],
|
||||
/// such that they support threshold aggregation by `threshold` number of parties.
|
||||
/// It is expected that this procedure is executed by a Trusted Third Party.
|
||||
pub fn ttp_keygen(
|
||||
params: &Parameters,
|
||||
threshold: u64,
|
||||
num_authorities: u64,
|
||||
) -> Result<Vec<KeyPair>> {
|
||||
if threshold == 0 {
|
||||
return Err(CoconutError::Setup(
|
||||
"Tried to generate threshold keys with a 0 threshold value".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if threshold > num_authorities {
|
||||
return Err(
|
||||
CoconutError::Setup(
|
||||
"Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let attributes = params.gen_hs().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)| SecretKey { x, ys });
|
||||
|
||||
let keypairs = secret_keys
|
||||
.zip(polynomial_indices.iter())
|
||||
.map(|(secret_key, index)| {
|
||||
let verification_key = secret_key.verification_key(params);
|
||||
KeyPair {
|
||||
secret_key,
|
||||
verification_key,
|
||||
index: Some(*index),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(keypairs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::scheme::setup::setup;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn keypair_bytes_roundtrip() {
|
||||
let params1 = setup(1).unwrap();
|
||||
let params5 = setup(5).unwrap();
|
||||
|
||||
let keypair1 = keygen(¶ms1);
|
||||
let keypair5 = keygen(¶ms5);
|
||||
|
||||
let bytes1 = keypair1.to_bytes();
|
||||
let bytes5 = keypair5.to_bytes();
|
||||
|
||||
assert_eq!(KeyPair::from_bytes(&bytes1).unwrap(), keypair1);
|
||||
assert_eq!(KeyPair::from_bytes(&bytes5).unwrap(), keypair5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_key_bytes_roundtrip() {
|
||||
let params1 = setup(1).unwrap();
|
||||
let params5 = setup(5).unwrap();
|
||||
|
||||
let keypair1 = keygen(¶ms1);
|
||||
let keypair5 = keygen(¶ms5);
|
||||
|
||||
let bytes1 = keypair1.secret_key.to_bytes();
|
||||
let bytes5 = keypair5.secret_key.to_bytes();
|
||||
|
||||
assert_eq!(SecretKey::from_bytes(&bytes1).unwrap(), keypair1.secret_key);
|
||||
assert_eq!(SecretKey::from_bytes(&bytes5).unwrap(), keypair5.secret_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_key_bytes_roundtrip() {
|
||||
let params1 = setup(1).unwrap();
|
||||
let params5 = setup(5).unwrap();
|
||||
|
||||
let keypair1 = &keygen(¶ms1);
|
||||
let keypair5 = &keygen(¶ms5);
|
||||
|
||||
let bytes1: Vec<u8> = keypair1.verification_key.to_bytes();
|
||||
let bytes5: Vec<u8> = keypair5.verification_key.to_bytes();
|
||||
|
||||
assert_eq!(
|
||||
VerificationKey::try_from(bytes1.as_slice()).unwrap(),
|
||||
keypair1.verification_key
|
||||
);
|
||||
assert_eq!(
|
||||
VerificationKey::try_from(bytes5.as_slice()).unwrap(),
|
||||
keypair5.verification_key
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,672 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TODO: implement https://crates.io/crates/signature traits?
|
||||
|
||||
use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar};
|
||||
use group::Curve;
|
||||
|
||||
pub use keygen::{SecretKey, VerificationKey};
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::verification::check_bilinear_pairing;
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::try_deserialize_g1_projective;
|
||||
use crate::Attribute;
|
||||
|
||||
pub mod aggregation;
|
||||
pub mod double_use;
|
||||
pub mod issuance;
|
||||
pub mod keygen;
|
||||
pub mod setup;
|
||||
pub mod verification;
|
||||
|
||||
pub type SignerIndex = u64;
|
||||
|
||||
// (h, s)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
pub type PartialSignature = Signature;
|
||||
|
||||
impl TryFrom<&[u8]> for Signature {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Signature> {
|
||||
if bytes.len() != 96 {
|
||||
return Err(CoconutError::Deserialization(format!(
|
||||
"Signature must be exactly 96 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// safety: we just checked for the length so the unwraps are fine
|
||||
#[allow(clippy::expect_used)]
|
||||
let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
|
||||
#[allow(clippy::expect_used)]
|
||||
let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
|
||||
|
||||
let sig1 = try_deserialize_g1_projective(
|
||||
sig1_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize compressed sig1".to_string()),
|
||||
)?;
|
||||
|
||||
let sig2 = try_deserialize_g1_projective(
|
||||
sig2_bytes,
|
||||
CoconutError::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_simple(&self, params: &Parameters) -> Signature {
|
||||
let r = params.random_scalar();
|
||||
Signature(self.0 * r, self.1 * r)
|
||||
}
|
||||
|
||||
pub fn randomise(&self, params: &Parameters) -> (Signature, Scalar) {
|
||||
let r = params.random_scalar();
|
||||
let r_prime = params.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)
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
partial_verification_key: &VerificationKey,
|
||||
private_attributes: &[&Attribute],
|
||||
public_attributes: &[&Attribute],
|
||||
commitment_hash: &G1Projective,
|
||||
) -> Result<()> {
|
||||
// Verify the commitment hash
|
||||
if bool::from(self.0.is_identity()) {
|
||||
return Err(CoconutError::Verification(
|
||||
"Commitment hash should not be an identity point".to_string(),
|
||||
));
|
||||
}
|
||||
if !(commitment_hash == &self.0) {
|
||||
return Err(CoconutError::Verification(
|
||||
"Verification of commitment hash from signature failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let alpha = partial_verification_key.alpha;
|
||||
|
||||
let signed_attributes = private_attributes
|
||||
.iter()
|
||||
.chain(public_attributes.iter())
|
||||
.zip(partial_verification_key.beta_g2.iter())
|
||||
.map(|(&attr, beta_i)| beta_i * attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
// Verify the signature share
|
||||
if !check_bilinear_pairing(
|
||||
&self.0.to_affine(),
|
||||
&G2Prepared::from((alpha + signed_attributes).to_affine()),
|
||||
&self.1.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) {
|
||||
return Err(CoconutError::Unblind(
|
||||
"Verification of signature share failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for Signature {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct BlindedSignature(G1Projective, G1Projective);
|
||||
|
||||
impl Bytable for BlindedSignature {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
Self::from_bytes(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for BlindedSignature {}
|
||||
|
||||
impl TryFrom<&[u8]> for BlindedSignature {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<BlindedSignature> {
|
||||
if bytes.len() != 96 {
|
||||
return Err(CoconutError::Deserialization(format!(
|
||||
"BlindedSignature must be exactly 96 bytes, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// safety: we just checked for the length so the unwraps are fine
|
||||
#[allow(clippy::expect_used)]
|
||||
let h_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
|
||||
#[allow(clippy::expect_used)]
|
||||
let sig_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
|
||||
|
||||
let h = try_deserialize_g1_projective(
|
||||
h_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize compressed h".to_string()),
|
||||
)?;
|
||||
let sig = try_deserialize_g1_projective(
|
||||
sig_bytes,
|
||||
CoconutError::Deserialization("Failed to deserialize compressed sig".to_string()),
|
||||
)?;
|
||||
|
||||
Ok(BlindedSignature(h, sig))
|
||||
}
|
||||
}
|
||||
|
||||
impl BlindedSignature {
|
||||
pub fn unblind(
|
||||
&self,
|
||||
partial_verification_key: &VerificationKey,
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
) -> Signature {
|
||||
// parse the signature
|
||||
let h = &self.0;
|
||||
let c = &self.1;
|
||||
let blinding_removers = partial_verification_key
|
||||
.beta_g1
|
||||
.iter()
|
||||
.zip(pedersen_commitments_openings.iter())
|
||||
.map(|(beta, opening)| beta * opening)
|
||||
.sum::<G1Projective>();
|
||||
|
||||
let unblinded_c = c - blinding_removers;
|
||||
|
||||
Signature(*h, unblinded_c)
|
||||
}
|
||||
|
||||
pub fn unblind_and_verify(
|
||||
&self,
|
||||
params: &Parameters,
|
||||
partial_verification_key: &VerificationKey,
|
||||
private_attributes: &[&Attribute],
|
||||
public_attributes: &[&Attribute],
|
||||
commitment_hash: &G1Projective,
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
) -> Result<Signature> {
|
||||
let unblinded = self.unblind(partial_verification_key, pedersen_commitments_openings);
|
||||
unblinded.verify(
|
||||
params,
|
||||
partial_verification_key,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
commitment_hash,
|
||||
)?;
|
||||
Ok(unblinded)
|
||||
}
|
||||
|
||||
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<BlindedSignature> {
|
||||
BlindedSignature::try_from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// perhaps this should take signature by reference? we'll see how it goes
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SignatureShare {
|
||||
signature: Signature,
|
||||
index: SignerIndex,
|
||||
}
|
||||
|
||||
impl From<(Signature, SignerIndex)> for SignatureShare {
|
||||
fn from(value: (Signature, SignerIndex)) -> Self {
|
||||
SignatureShare {
|
||||
signature: value.0,
|
||||
index: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 super::*;
|
||||
use crate::hash_to_scalar;
|
||||
use crate::scheme::aggregation::{
|
||||
aggregate_signatures_and_verify, aggregate_verification_keys,
|
||||
};
|
||||
use crate::scheme::issuance::{blind_sign, compute_hash, prepare_blind_sign, sign};
|
||||
use crate::scheme::keygen::{keygen, ttp_keygen};
|
||||
use crate::scheme::verification::{prove_bandwidth_credential, verify, verify_credential};
|
||||
use crate::tests::helpers::random_scalars_refs;
|
||||
|
||||
#[test]
|
||||
fn unblind_returns_error_if_integrity_check_on_commitment_hash_fails() {
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(private_attributes, params, 2);
|
||||
|
||||
let (_commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap();
|
||||
|
||||
let keypair1 = keygen(¶ms);
|
||||
|
||||
let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]).unwrap();
|
||||
|
||||
let wrong_commitment_opening = params.random_scalar();
|
||||
let wrong_commitment = params.gen1() * wrong_commitment_opening;
|
||||
let fake_commitment_hash = compute_hash(wrong_commitment, &[]);
|
||||
let wrong_commitments_openings = params.n_random_scalars(private_attributes.len());
|
||||
|
||||
assert!(sig1
|
||||
.unblind_and_verify(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&private_attributes,
|
||||
&[],
|
||||
&fake_commitment_hash,
|
||||
&wrong_commitments_openings,
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unblind_returns_error_if_signature_verification_fails() {
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let p = [hash_to_scalar("Attribute1"), hash_to_scalar("Attribute2")];
|
||||
let private_attributes = vec![&p[0], &p[1]];
|
||||
|
||||
let p2 = [hash_to_scalar("Attribute3"), hash_to_scalar("Attribute4")];
|
||||
let private_attributes2 = vec![&p2[0], &p2[1]];
|
||||
|
||||
let (commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap();
|
||||
|
||||
let keypair1 = keygen(¶ms);
|
||||
|
||||
let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]).unwrap();
|
||||
|
||||
assert!(sig1
|
||||
.unblind_and_verify(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&private_attributes2,
|
||||
&[],
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_on_two_private_attributes() {
|
||||
let params = Parameters::new(2).unwrap();
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![&serial_number, &binding_number];
|
||||
|
||||
let keypair1 = keygen(¶ms);
|
||||
let keypair2 = keygen(¶ms);
|
||||
|
||||
let (commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap();
|
||||
|
||||
let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[])
|
||||
.unwrap()
|
||||
.unblind_and_verify(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&private_attributes,
|
||||
&[],
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sig2 = blind_sign(¶ms, keypair2.secret_key(), &lambda, &[])
|
||||
.unwrap()
|
||||
.unblind_and_verify(
|
||||
¶ms,
|
||||
keypair2.verification_key(),
|
||||
&private_attributes,
|
||||
&[],
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let theta1 = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&sig1,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let theta2 = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
keypair2.verification_key(),
|
||||
&sig2,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&theta1,
|
||||
&[],
|
||||
));
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
keypair2.verification_key(),
|
||||
&theta2,
|
||||
&[],
|
||||
));
|
||||
|
||||
assert!(!verify_credential(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&theta2,
|
||||
&[],
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_on_two_public_attributes() {
|
||||
let params = Parameters::new(2).unwrap();
|
||||
random_scalars_refs!(attributes, params, 2);
|
||||
|
||||
let keypair1 = keygen(¶ms);
|
||||
let keypair2 = keygen(¶ms);
|
||||
let sig1 = sign(keypair1.secret_key(), &attributes).unwrap();
|
||||
let sig2 = sign(keypair2.secret_key(), &attributes).unwrap();
|
||||
|
||||
assert!(verify(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&attributes,
|
||||
&sig1,
|
||||
));
|
||||
|
||||
assert!(!verify(
|
||||
¶ms,
|
||||
keypair2.verification_key(),
|
||||
&attributes,
|
||||
&sig1,
|
||||
));
|
||||
|
||||
assert!(!verify(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&attributes,
|
||||
&sig2,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_on_two_public_and_two_private_attributes() {
|
||||
let params = Parameters::new(4).unwrap();
|
||||
random_scalars_refs!(public_attributes, params, 2);
|
||||
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![&serial_number, &binding_number];
|
||||
|
||||
let keypair1 = keygen(¶ms);
|
||||
let keypair2 = keygen(¶ms);
|
||||
|
||||
let (commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &public_attributes)
|
||||
.unwrap()
|
||||
.unblind_and_verify(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sig2 = blind_sign(¶ms, keypair2.secret_key(), &lambda, &public_attributes)
|
||||
.unwrap()
|
||||
.unblind_and_verify(
|
||||
¶ms,
|
||||
keypair2.verification_key(),
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let theta1 = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&sig1,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let theta2 = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
keypair2.verification_key(),
|
||||
&sig2,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&theta1,
|
||||
&public_attributes,
|
||||
));
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
keypair2.verification_key(),
|
||||
&theta2,
|
||||
&public_attributes,
|
||||
));
|
||||
|
||||
assert!(!verify_credential(
|
||||
¶ms,
|
||||
keypair1.verification_key(),
|
||||
&theta2,
|
||||
&public_attributes,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_on_two_public_and_two_private_attributes_from_two_signers() {
|
||||
let params = Parameters::new(4).unwrap();
|
||||
random_scalars_refs!(public_attributes, params, 2);
|
||||
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![&serial_number, &binding_number];
|
||||
|
||||
let keypairs = ttp_keygen(¶ms, 2, 3).unwrap();
|
||||
|
||||
let (commitments_openings, lambda) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap();
|
||||
|
||||
let sigs = keypairs
|
||||
.iter()
|
||||
.map(|keypair| {
|
||||
blind_sign(¶ms, keypair.secret_key(), &lambda, &public_attributes)
|
||||
.unwrap()
|
||||
.unblind_and_verify(
|
||||
¶ms,
|
||||
keypair.verification_key(),
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&lambda.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let vks = keypairs
|
||||
.into_iter()
|
||||
.map(|keypair| keypair.verification_key().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
|
||||
attributes.extend_from_slice(&private_attributes);
|
||||
attributes.extend_from_slice(&public_attributes);
|
||||
|
||||
let aggr_vk = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap();
|
||||
let aggr_sig = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk,
|
||||
&attributes,
|
||||
&sigs[..2],
|
||||
Some(&[1, 2]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_vk,
|
||||
&aggr_sig,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
&aggr_vk,
|
||||
&theta,
|
||||
&public_attributes,
|
||||
));
|
||||
|
||||
// taking different subset of keys and credentials
|
||||
let aggr_vk = aggregate_verification_keys(&vks[1..], Some(&[2, 3])).unwrap();
|
||||
let aggr_sig = aggregate_signatures_and_verify(
|
||||
¶ms,
|
||||
&aggr_vk,
|
||||
&attributes,
|
||||
&sigs[1..],
|
||||
Some(&[2, 3]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&aggr_vk,
|
||||
&aggr_sig,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
&aggr_vk,
|
||||
&theta,
|
||||
&public_attributes,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let r = params.random_scalar();
|
||||
let s = params.random_scalar();
|
||||
let signature = Signature(params.gen1() * r, params.gen1() * s);
|
||||
let bytes = signature.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
|
||||
let expected_bytes = [
|
||||
signature.0.to_affine().to_compressed(),
|
||||
signature.1.to_affine().to_compressed(),
|
||||
]
|
||||
.concat();
|
||||
assert_eq!(expected_bytes, bytes);
|
||||
assert_eq!(signature, Signature::try_from(&bytes[..]).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blinded_signature_bytes_roundtrip() {
|
||||
let params = Parameters::default();
|
||||
let r = params.random_scalar();
|
||||
let s = params.random_scalar();
|
||||
let blinded_sig = BlindedSignature(params.gen1() * r, params.gen1() * s);
|
||||
let bytes = blinded_sig.to_bytes();
|
||||
|
||||
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
|
||||
let expected_bytes = [
|
||||
blinded_sig.0.to_affine().to_compressed(),
|
||||
blinded_sig.1.to_affine().to_compressed(),
|
||||
]
|
||||
.concat();
|
||||
assert_eq!(expected_bytes, bytes);
|
||||
assert_eq!(blinded_sig, BlindedSignature::try_from(&bytes[..]).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::{G1Affine, G2Affine, G2Prepared, Scalar};
|
||||
use ff::Field;
|
||||
use group::Curve;
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::utils::hash_g1;
|
||||
|
||||
/// System-wide parameters used for the protocol
|
||||
#[derive(Clone)]
|
||||
pub struct Parameters {
|
||||
/// Generator of the G1 group
|
||||
g1: G1Affine,
|
||||
|
||||
/// Additional generators of the G1 group
|
||||
hs: Vec<G1Affine>,
|
||||
|
||||
/// Generator of the G2 group
|
||||
g2: G2Affine,
|
||||
|
||||
/// Precomputed G2 generator used for the miller loop
|
||||
_g2_prepared_miller: G2Prepared,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
pub fn new(num_attributes: u32) -> Result<Parameters> {
|
||||
if num_attributes == 0 {
|
||||
return Err(CoconutError::Setup(
|
||||
"Tried to setup the scheme for 0 attributes".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let hs = (1..=num_attributes)
|
||||
.map(|i| hash_g1(format!("h{i}")).to_affine())
|
||||
.collect();
|
||||
|
||||
Ok(Parameters {
|
||||
g1: G1Affine::generator(),
|
||||
hs,
|
||||
g2: G2Affine::generator(),
|
||||
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn gen1(&self) -> &G1Affine {
|
||||
&self.g1
|
||||
}
|
||||
|
||||
pub fn gen2(&self) -> &G2Affine {
|
||||
&self.g2
|
||||
}
|
||||
|
||||
pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared {
|
||||
&self._g2_prepared_miller
|
||||
}
|
||||
|
||||
pub fn gen_hs(&self) -> &[G1Affine] {
|
||||
&self.hs
|
||||
}
|
||||
|
||||
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(num_attributes: u32) -> Result<Parameters> {
|
||||
Parameters::new(num_attributes)
|
||||
}
|
||||
|
||||
// for ease of use in tests requiring params
|
||||
// TODO: not sure if this will have to go away when tests require some specific number of generators
|
||||
#[cfg(test)]
|
||||
impl Default for Parameters {
|
||||
fn default() -> Self {
|
||||
Parameters {
|
||||
g1: G1Affine::generator(),
|
||||
hs: Vec::new(),
|
||||
g2: G2Affine::generator(),
|
||||
_g2_prepared_miller: G2Prepared::from(G2Affine::generator()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::proofs::ProofKappaZeta;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::Signature;
|
||||
use crate::scheme::VerificationKey;
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::try_deserialize_g2_projective;
|
||||
use crate::Attribute;
|
||||
use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar};
|
||||
use core::ops::Neg;
|
||||
use group::{Curve, Group};
|
||||
|
||||
pub use crate::scheme::double_use::BlindedSerialNumber;
|
||||
|
||||
// TODO NAMING: this whole thing
|
||||
// Theta
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct VerifyCredentialRequest {
|
||||
// blinded_message (kappa)
|
||||
pub blinded_message: G2Projective,
|
||||
// blinded serial number (zeta)
|
||||
pub blinded_serial_number: BlindedSerialNumber,
|
||||
// sigma
|
||||
pub credential: Signature,
|
||||
// pi_v
|
||||
pub pi_v: ProofKappaZeta,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for VerifyCredentialRequest {
|
||||
type Error = CoconutError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<VerifyCredentialRequest> {
|
||||
if bytes.len() < 288 {
|
||||
return Err(
|
||||
CoconutError::Deserialization(
|
||||
format!("Tried to deserialize theta with insufficient number of bytes, expected >= 288, got {}", bytes.len()),
|
||||
));
|
||||
}
|
||||
|
||||
// safety: we just checked for the length so the unwraps are fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let blinded_message_bytes = bytes[..96].try_into().unwrap();
|
||||
let blinded_message = try_deserialize_g2_projective(
|
||||
&blinded_message_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"failed to deserialize the blinded message (kappa)".to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
let blinded_serial_number_bytes = &bytes[96..192];
|
||||
let blinded_serial_number =
|
||||
BlindedSerialNumber::try_from_byte_slice(blinded_serial_number_bytes)?;
|
||||
|
||||
let credential = Signature::try_from(&bytes[192..288])?;
|
||||
|
||||
let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?;
|
||||
|
||||
Ok(VerifyCredentialRequest {
|
||||
blinded_message,
|
||||
blinded_serial_number,
|
||||
credential,
|
||||
pi_v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl VerifyCredentialRequest {
|
||||
fn verify_proof(&self, params: &Parameters, verification_key: &VerificationKey) -> bool {
|
||||
self.pi_v.verify(
|
||||
params,
|
||||
verification_key,
|
||||
&self.blinded_message,
|
||||
&self.blinded_serial_number,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result<bool> {
|
||||
let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?;
|
||||
let ret = self.blinded_serial_number.eq(&blinded_serial_number);
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
// blinded message (kappa) || blinded serial number (zeta) || credential || pi_v
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let blinded_message_bytes = self.blinded_message.to_affine().to_compressed();
|
||||
let blinded_serial_number_bytes = self.blinded_serial_number.to_affine().to_compressed();
|
||||
let credential_bytes = self.credential.to_bytes();
|
||||
let proof_bytes = self.pi_v.to_bytes();
|
||||
|
||||
let mut bytes = Vec::with_capacity(288 + proof_bytes.len());
|
||||
bytes.extend_from_slice(&blinded_message_bytes);
|
||||
bytes.extend_from_slice(&blinded_serial_number_bytes);
|
||||
bytes.extend_from_slice(&credential_bytes);
|
||||
bytes.extend_from_slice(&proof_bytes);
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<VerifyCredentialRequest> {
|
||||
VerifyCredentialRequest::try_from(bytes)
|
||||
}
|
||||
|
||||
pub fn blinded_serial_number(&self) -> BlindedSerialNumber {
|
||||
self.blinded_serial_number
|
||||
}
|
||||
|
||||
pub fn blinded_serial_number_bs58(&self) -> String {
|
||||
self.blinded_serial_number.to_bs58()
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for VerifyCredentialRequest {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
VerifyCredentialRequest::try_from(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for VerifyCredentialRequest {}
|
||||
|
||||
pub fn compute_kappa(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
private_attributes: &[&Attribute],
|
||||
blinding_factor: Scalar,
|
||||
) -> G2Projective {
|
||||
params.gen2() * blinding_factor
|
||||
+ verification_key.alpha
|
||||
+ private_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(&priv_attr, beta_i)| beta_i * priv_attr)
|
||||
.sum::<G2Projective>()
|
||||
}
|
||||
|
||||
pub fn compute_zeta(params: &Parameters, serial_number: &Attribute) -> G2Projective {
|
||||
params.gen2() * serial_number
|
||||
}
|
||||
|
||||
pub fn prove_bandwidth_credential(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
signature: &Signature,
|
||||
serial_number: &Attribute,
|
||||
binding_number: &Attribute,
|
||||
) -> Result<VerifyCredentialRequest> {
|
||||
if verification_key.beta_g2.len() < 2 {
|
||||
return Err(
|
||||
CoconutError::Verification(
|
||||
format!("Tried to prove a credential for higher than supported by the provided verification key number of attributes (max: {}, requested: 2)",
|
||||
verification_key.beta_g2.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// Randomize the signature
|
||||
let (signature_prime, sign_blinding_factor) = signature.randomise(params);
|
||||
|
||||
// blinded_message : kappa in the paper.
|
||||
// Value kappa is needed since we want to show a signature sigma'.
|
||||
// In order to verify sigma' we need both the verification key vk and the message m.
|
||||
// However, we do not want to reveal m to whomever we are showing the signature.
|
||||
// Thus, we need kappa which allows us to verify sigma'. In particular,
|
||||
// kappa is computed on m as input, but thanks to the use or random value r,
|
||||
// it does not reveal any information about m.
|
||||
let private_attributes = [serial_number, binding_number];
|
||||
let blinded_message = compute_kappa(
|
||||
params,
|
||||
verification_key,
|
||||
&private_attributes,
|
||||
sign_blinding_factor,
|
||||
);
|
||||
|
||||
// zeta is a commitment to the serial number (i.e., a public value associated with the serial number)
|
||||
let blinded_serial_number = compute_zeta(params, serial_number);
|
||||
|
||||
let pi_v = ProofKappaZeta::construct(
|
||||
params,
|
||||
verification_key,
|
||||
serial_number,
|
||||
binding_number,
|
||||
&sign_blinding_factor,
|
||||
&blinded_message,
|
||||
&blinded_serial_number,
|
||||
);
|
||||
|
||||
Ok(VerifyCredentialRequest {
|
||||
blinded_message,
|
||||
blinded_serial_number: blinded_serial_number.into(),
|
||||
credential: signature_prime,
|
||||
pi_v,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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 fn check_vk_pairing(
|
||||
params: &Parameters,
|
||||
dkg_values: &[G2Projective],
|
||||
vk: &VerificationKey,
|
||||
) -> bool {
|
||||
let values_len = dkg_values.len();
|
||||
if values_len == 0 || values_len - 1 != vk.beta_g1.len() || values_len - 1 != vk.beta_g2.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// safety: we made an explicit check for if the length of the slice is 0, thus unwrap here is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
if &vk.alpha != *dkg_values.first().as_ref().unwrap() {
|
||||
return false;
|
||||
}
|
||||
let dkg_betas = &dkg_values[1..];
|
||||
if dkg_betas
|
||||
.iter()
|
||||
.zip(vk.beta_g2.iter())
|
||||
.any(|(dkg_beta, vk_beta)| dkg_beta != vk_beta)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if vk.beta_g1.iter().zip(vk.beta_g2.iter()).any(|(g1, g2)| {
|
||||
!check_bilinear_pairing(
|
||||
params.gen1(),
|
||||
&G2Prepared::from(g2.to_affine()),
|
||||
&g1.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
)
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn verify_credential(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
theta: &VerifyCredentialRequest,
|
||||
public_attributes: &[&Attribute],
|
||||
) -> bool {
|
||||
if public_attributes.len() + theta.pi_v.private_attributes_len()
|
||||
> verification_key.beta_g2.len()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if !theta.verify_proof(params, verification_key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let kappa = if public_attributes.is_empty() {
|
||||
theta.blinded_message
|
||||
} else {
|
||||
let signed_public_attributes = public_attributes
|
||||
.iter()
|
||||
.zip(
|
||||
verification_key
|
||||
.beta_g2
|
||||
.iter()
|
||||
.skip(theta.pi_v.private_attributes_len()),
|
||||
)
|
||||
.map(|(&pub_attr, beta_i)| beta_i * pub_attr)
|
||||
.sum::<G2Projective>();
|
||||
|
||||
theta.blinded_message + signed_public_attributes
|
||||
};
|
||||
|
||||
check_bilinear_pairing(
|
||||
&theta.credential.0.to_affine(),
|
||||
&G2Prepared::from(kappa.to_affine()),
|
||||
&(theta.credential.1).to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) && !bool::from(theta.credential.0.is_identity())
|
||||
}
|
||||
|
||||
// Used in tests only
|
||||
pub fn verify(
|
||||
params: &Parameters,
|
||||
verification_key: &VerificationKey,
|
||||
public_attributes: &[&Attribute],
|
||||
sig: &Signature,
|
||||
) -> bool {
|
||||
let kappa = (verification_key.alpha
|
||||
+ public_attributes
|
||||
.iter()
|
||||
.zip(verification_key.beta_g2.iter())
|
||||
.map(|(&m_i, b_i)| b_i * m_i)
|
||||
.sum::<G2Projective>())
|
||||
.to_affine();
|
||||
|
||||
check_bilinear_pairing(
|
||||
&sig.0.to_affine(),
|
||||
&G2Prepared::from(kappa),
|
||||
&sig.1.to_affine(),
|
||||
params.prepared_miller_g2(),
|
||||
) && !bool::from(sig.0.is_identity())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::scheme::issuance::sign;
|
||||
use crate::scheme::keygen::keygen;
|
||||
use crate::scheme::setup::setup;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn vk_pairing() {
|
||||
let params = setup(2).unwrap();
|
||||
let keypair = keygen(¶ms);
|
||||
let vk = keypair.verification_key();
|
||||
|
||||
let mut dkg_values = vec![vk.alpha];
|
||||
dkg_values.append(&mut vk.beta_g2.clone());
|
||||
assert!(check_vk_pairing(¶ms, &dkg_values, vk));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn theta_bytes_roundtrip() {
|
||||
let params = setup(2).unwrap();
|
||||
|
||||
let keypair = keygen(¶ms);
|
||||
let r = params.random_scalar();
|
||||
let s = params.random_scalar();
|
||||
|
||||
let signature = Signature(params.gen1() * r, params.gen1() * s);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
keypair.verification_key(),
|
||||
&signature,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bytes = theta.to_bytes();
|
||||
assert_eq!(
|
||||
VerifyCredentialRequest::try_from(bytes.as_slice()).unwrap(),
|
||||
theta
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_forged_signature_via_linear_combination() {
|
||||
// This test checks if the protocol correctly rejects forged signatures created
|
||||
// by linear combinations of valid signatures. The verification for forged
|
||||
// signatures should fail.
|
||||
let params = Parameters::new(4).unwrap();
|
||||
|
||||
let scalar_2 = Scalar::one() + Scalar::one();
|
||||
let scalar_2_inv = Scalar::invert(&scalar_2).unwrap();
|
||||
|
||||
//#1
|
||||
let a = params.random_scalar();
|
||||
let zero = Scalar::zero();
|
||||
let a_zero = vec![&a, &zero];
|
||||
let zero_a = vec![&zero, &a];
|
||||
|
||||
let validator_keypair = keygen(¶ms);
|
||||
|
||||
//#2
|
||||
let sig_a_zero = sign(validator_keypair.secret_key(), &a_zero).unwrap();
|
||||
let sig_zero_a = sign(validator_keypair.secret_key(), &zero_a).unwrap();
|
||||
|
||||
assert!(verify(
|
||||
¶ms,
|
||||
validator_keypair.verification_key(),
|
||||
&a_zero,
|
||||
&sig_a_zero
|
||||
));
|
||||
assert!(verify(
|
||||
¶ms,
|
||||
validator_keypair.verification_key(),
|
||||
&zero_a,
|
||||
&sig_zero_a
|
||||
));
|
||||
|
||||
//#3
|
||||
let h0 = sig_a_zero.0;
|
||||
// Removed unnecessary references
|
||||
let h1 = scalar_2_inv * sig_a_zero.1 + scalar_2_inv * sig_zero_a.1;
|
||||
let forged_signature = Signature(h0, h1);
|
||||
let a_half = a * scalar_2_inv;
|
||||
let new_plaintext = vec![&a_half, &a_half];
|
||||
|
||||
// The forged signature should not pass verification
|
||||
assert!(!verify(
|
||||
¶ms,
|
||||
validator_keypair.verification_key(),
|
||||
&new_plaintext,
|
||||
&forged_signature
|
||||
));
|
||||
|
||||
//#4
|
||||
let scalar_3 = Scalar::one() + Scalar::one() + Scalar::one();
|
||||
let scalar_4 = Scalar::one() + Scalar::one() + Scalar::one() + Scalar::one();
|
||||
let scalar_4_inv = Scalar::invert(&scalar_4).unwrap();
|
||||
let scalar_3_over_4 = scalar_3 * scalar_4_inv;
|
||||
|
||||
// Removed unnecessary references
|
||||
let h1_2 = scalar_4_inv * sig_a_zero.1 + scalar_3_over_4 * sig_zero_a.1;
|
||||
let forged_signature_2 = Signature(h0, h1_2);
|
||||
let a_quarter = a * scalar_4_inv;
|
||||
let a_3_over_4 = a * scalar_3_over_4;
|
||||
let new_plaintext_2 = vec![&a_quarter, &a_3_over_4];
|
||||
|
||||
// The second forged signature should also not pass verification
|
||||
assert!(!verify(
|
||||
¶ms,
|
||||
validator_keypair.verification_key(),
|
||||
&new_plaintext_2,
|
||||
&forged_signature_2
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::random_scalars_refs;
|
||||
use crate::tests::helpers::tests::generate_dkg_keys;
|
||||
use crate::{
|
||||
aggregate_verification_keys, setup, tests::helpers::*, ttp_keygen, verify_credential,
|
||||
CoconutError, VerificationKey,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn keygen() -> Result<(), CoconutError> {
|
||||
let params = setup(5)?;
|
||||
let node_indices = vec![15u64, 248, 33521];
|
||||
|
||||
random_scalars_refs!(public_attributes, params, 2);
|
||||
|
||||
// generate_keys
|
||||
let coconut_keypairs = ttp_keygen(¶ms, 2, 3)?;
|
||||
|
||||
let verification_keys: Vec<VerificationKey> = coconut_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key().clone())
|
||||
.collect();
|
||||
|
||||
// aggregate verification keys
|
||||
let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?;
|
||||
|
||||
// Generate cryptographic material to verify them
|
||||
let theta = theta_from_keys_and_attributes(
|
||||
¶ms,
|
||||
&coconut_keypairs,
|
||||
&node_indices,
|
||||
&public_attributes,
|
||||
)?;
|
||||
|
||||
// Verify credentials
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&theta,
|
||||
&public_attributes,
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // expensive test
|
||||
fn dkg() -> Result<(), CoconutError> {
|
||||
let params = setup(5)?;
|
||||
let node_indices = vec![15u64, 248, 33521];
|
||||
|
||||
random_scalars_refs!(public_attributes, params, 2);
|
||||
|
||||
// generate_keys
|
||||
let coconut_keypairs = generate_dkg_keys(5, &node_indices);
|
||||
|
||||
let verification_keys: Vec<VerificationKey> = coconut_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key().clone())
|
||||
.collect();
|
||||
|
||||
// aggregate verification keys
|
||||
let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?;
|
||||
|
||||
// Generate cryptographic material to verify them
|
||||
let theta = theta_from_keys_and_attributes(
|
||||
¶ms,
|
||||
&coconut_keypairs,
|
||||
&node_indices,
|
||||
&public_attributes,
|
||||
)?;
|
||||
|
||||
// Verify credentials
|
||||
assert!(verify_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&theta,
|
||||
&public_attributes,
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::*;
|
||||
use itertools::izip;
|
||||
use std::fmt::Debug;
|
||||
|
||||
// unwraps are fine in the test code
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn theta_from_keys_and_attributes(
|
||||
params: &Parameters,
|
||||
coconut_keypairs: &Vec<KeyPair>,
|
||||
indices: &[scheme::SignerIndex],
|
||||
public_attributes: &[&PublicAttribute],
|
||||
) -> Result<VerifyCredentialRequest, CoconutError> {
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![&serial_number, &binding_number];
|
||||
|
||||
// generate commitment
|
||||
let (commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(params, &private_attributes, public_attributes)?;
|
||||
|
||||
let verification_keys: Vec<VerificationKey> = coconut_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key().clone())
|
||||
.collect();
|
||||
|
||||
// aggregate verification keys
|
||||
let verification_key = aggregate_verification_keys(&verification_keys, Some(indices))?;
|
||||
|
||||
// generate blinded signatures
|
||||
let mut blinded_signatures = Vec::new();
|
||||
|
||||
for keypair in coconut_keypairs {
|
||||
let blinded_signature = blind_sign(
|
||||
params,
|
||||
keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
public_attributes,
|
||||
)?;
|
||||
blinded_signatures.push(blinded_signature)
|
||||
}
|
||||
|
||||
// Unblind
|
||||
let unblinded_signatures: Vec<(scheme::SignerIndex, Signature)> = izip!(
|
||||
indices.iter(),
|
||||
blinded_signatures.iter(),
|
||||
verification_keys.iter()
|
||||
)
|
||||
.map(|(idx, s, vk)| {
|
||||
(
|
||||
*idx,
|
||||
s.unblind_and_verify(
|
||||
params,
|
||||
vk,
|
||||
&private_attributes,
|
||||
public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Aggregate signatures
|
||||
let signature_shares: Vec<SignatureShare> = unblinded_signatures
|
||||
.iter()
|
||||
.map(|(idx, signature)| SignatureShare::new(*signature, *idx))
|
||||
.collect();
|
||||
|
||||
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
|
||||
attributes.extend_from_slice(&private_attributes);
|
||||
attributes.extend_from_slice(public_attributes);
|
||||
|
||||
// Randomize credentials and generate any cryptographic material to verify them
|
||||
let signature = aggregate_signature_shares_and_verify(
|
||||
params,
|
||||
&verification_key,
|
||||
&attributes,
|
||||
&signature_shares,
|
||||
)?;
|
||||
|
||||
// Generate cryptographic material to verify them
|
||||
let theta = prove_bandwidth_credential(
|
||||
params,
|
||||
&verification_key,
|
||||
&signature,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)?;
|
||||
|
||||
Ok(theta)
|
||||
}
|
||||
|
||||
// unwraps are fine in the test code
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn transpose_matrix<T: Debug>(matrix: Vec<Vec<T>>) -> Vec<Vec<T>> {
|
||||
if matrix.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
let len = matrix[0].len();
|
||||
let mut iters: Vec<_> = matrix.into_iter().map(|d| d.into_iter()).collect();
|
||||
(0..len)
|
||||
.map(|_| {
|
||||
iters
|
||||
.iter_mut()
|
||||
.map(|it| it.next().unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! random_scalars_refs {
|
||||
( $x: ident, $params: expr, $n: expr ) => {
|
||||
let _vec = $params.n_random_scalars($n);
|
||||
#[allow(clippy::map_identity)]
|
||||
let $x = _vec.iter().collect::<Vec<_>>();
|
||||
};
|
||||
}
|
||||
|
||||
pub use random_scalars_refs;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use bls12_381::Scalar;
|
||||
use nym_dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex};
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
|
||||
pub fn generate_dkg_secrets(node_indices: &[NodeIndex]) -> Vec<Scalar> {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = nym_dkg::bte::setup();
|
||||
|
||||
// the simplest possible case
|
||||
let threshold = 2;
|
||||
|
||||
let mut receivers = std::collections::BTreeMap::new();
|
||||
let mut full_keys = Vec::new();
|
||||
for index in node_indices {
|
||||
let (dk, pk) = nym_dkg::bte::keygen(¶ms, &mut rng);
|
||||
receivers.insert(*index, *pk.public_key());
|
||||
full_keys.push((dk, pk))
|
||||
}
|
||||
let dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut derived_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
let shares = dealings
|
||||
.iter()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
|
||||
.collect();
|
||||
|
||||
let recovered_secret =
|
||||
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap();
|
||||
|
||||
derived_secrets.push(recovered_secret)
|
||||
}
|
||||
derived_secrets
|
||||
}
|
||||
pub fn generate_dkg_keys(num_attributes: u32, node_indices: &[NodeIndex]) -> Vec<KeyPair> {
|
||||
let params = Parameters::new(num_attributes).unwrap();
|
||||
let mut all_secrets = vec![];
|
||||
for _ in 0..num_attributes {
|
||||
let secrets = generate_dkg_secrets(node_indices);
|
||||
all_secrets.push(secrets);
|
||||
}
|
||||
let signers = transpose_matrix(all_secrets);
|
||||
signers
|
||||
.into_iter()
|
||||
.map(|mut secrets| {
|
||||
let x = secrets.pop().unwrap();
|
||||
let sk = SecretKey::create_from_raw(x, secrets);
|
||||
let vk = sk.verification_key(¶ms);
|
||||
KeyPair::from_keys(sk, vk)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod e2e;
|
||||
pub mod helpers;
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
use crate::CoconutError;
|
||||
use bls12_381::{G1Affine, G1Projective, Scalar};
|
||||
use group::GroupEncoding;
|
||||
|
||||
pub trait Bytable
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn to_byte_vec(&self) -> Vec<u8>;
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CoconutError>;
|
||||
}
|
||||
|
||||
pub trait Base58
|
||||
where
|
||||
Self: Bytable,
|
||||
{
|
||||
fn try_from_bs58<S: AsRef<str>>(x: S) -> Result<Self, CoconutError> {
|
||||
let bs58_decoded = &bs58::decode(x.as_ref()).into_vec()?;
|
||||
Self::try_from_byte_slice(bs58_decoded)
|
||||
}
|
||||
fn to_bs58(&self) -> String {
|
||||
bs58::encode(self.to_byte_vec()).into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for Scalar {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CoconutError> {
|
||||
let received = slice.len();
|
||||
let Ok(arr) = slice.try_into() else {
|
||||
return Err(CoconutError::UnexpectedArrayLength {
|
||||
typ: "Scalar".to_string(),
|
||||
received,
|
||||
expected: 32,
|
||||
});
|
||||
};
|
||||
|
||||
let maybe_scalar = Scalar::from_bytes(arr);
|
||||
if maybe_scalar.is_none().into() {
|
||||
Err(CoconutError::ScalarDeserializationFailure)
|
||||
} else {
|
||||
// safety: this unwrap is fine as we've just checked the element is not none
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(maybe_scalar.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for Scalar {}
|
||||
|
||||
impl Bytable for G1Projective {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.to_bytes().as_ref().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CoconutError> {
|
||||
let received = slice.len();
|
||||
let arr: Result<[u8; 48], _> = slice.try_into();
|
||||
let Ok(bytes) = arr else {
|
||||
return Err(CoconutError::UnexpectedArrayLength {
|
||||
typ: "G1Projective".to_string(),
|
||||
received,
|
||||
expected: 48,
|
||||
});
|
||||
};
|
||||
|
||||
let maybe_g1 = G1Affine::from_compressed(&bytes);
|
||||
if maybe_g1.is_none().into() {
|
||||
Err(CoconutError::G1ProjectiveDeserializationFailure)
|
||||
} else {
|
||||
// safety: this unwrap is fine as we've just checked the element is not none
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(maybe_g1.unwrap().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Base58 for G1Projective {}
|
||||
@@ -1,382 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
|
||||
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
|
||||
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar};
|
||||
use ff::Field;
|
||||
|
||||
use crate::error::{CoconutError, 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().into() {
|
||||
// we checked that coefficients are not empty so unwrap here is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
*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(CoconutError::Interpolation(
|
||||
"Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if points.len() != values.len() {
|
||||
return Err(CoconutError::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: CoconutError,
|
||||
) -> 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 {
|
||||
// we just checked we have exactly the amount of bytes we need and thus the unwrap is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
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: CoconutError) -> Result<Scalar> {
|
||||
Into::<Option<Scalar>>::into(Scalar::from_bytes(bytes)).ok_or(err)
|
||||
}
|
||||
|
||||
pub fn try_deserialize_g1_projective(bytes: &[u8; 48], err: CoconutError) -> 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: CoconutError) -> Result<G2Projective> {
|
||||
Into::<Option<G2Affine>>::into(G2Affine::from_compressed(bytes))
|
||||
.ok_or(err)
|
||||
.map(G2Projective::from)
|
||||
}
|
||||
|
||||
// use core::fmt;
|
||||
// #[cfg(feature = "serde")]
|
||||
// use serde::de::Visitor;
|
||||
// #[cfg(feature = "serde")]
|
||||
// use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
|
||||
//
|
||||
// // #[cfg(feature = "serde")]
|
||||
// #[serde(remote = "Scalar")]
|
||||
// pub(crate) struct ScalarDef(pub Scalar);
|
||||
//
|
||||
// // #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
//
|
||||
// impl Serialize for ScalarDef {
|
||||
// fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
|
||||
// where
|
||||
// S: Serializer,
|
||||
// {
|
||||
// use serde::ser::SerializeTuple;
|
||||
// let mut tup = serializer.serialize_tuple(32)?;
|
||||
// for byte in self.0.to_bytes().iter() {
|
||||
// tup.serialize_element(byte)?;
|
||||
// }
|
||||
// tup.end()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<'de> Deserialize<'de> for ScalarDef {
|
||||
// fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
|
||||
// where
|
||||
// D: Deserializer<'de>,
|
||||
// {
|
||||
// struct ScalarVisitor;
|
||||
//
|
||||
// impl<'de> Visitor<'de> for ScalarVisitor {
|
||||
// type Value = ScalarDef;
|
||||
//
|
||||
// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
// formatter.write_str("a 32-byte canonical bls12_381 scalar")
|
||||
// }
|
||||
//
|
||||
// fn visit_seq<A>(self, mut seq: A) -> core::result::Result<ScalarDef, A::Error>
|
||||
// where
|
||||
// A: serde::de::SeqAccess<'de>,
|
||||
// {
|
||||
// let mut bytes = [0u8; 32];
|
||||
// for i in 0..32 {
|
||||
// bytes[i] = seq
|
||||
// .next_element()?
|
||||
// .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?;
|
||||
// }
|
||||
//
|
||||
// let res = Scalar::from_bytes(&bytes);
|
||||
// if res.is_some().into() {
|
||||
// Ok(ScalarDef(res.unwrap()))
|
||||
// } else {
|
||||
// Err(serde::de::Error::custom(
|
||||
// &"scalar was not canonically encoded",
|
||||
// ))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// deserializer.deserialize_tuple(32, ScalarVisitor)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[cfg(feature = "serde")]
|
||||
// pub(crate) struct G1ProjectiveSerdeHelper(Scalar);
|
||||
//
|
||||
// #[cfg(feature = "serde")]
|
||||
// pub(crate) struct G2ProjectiveSerdeHelper(Scalar);
|
||||
|
||||
#[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));
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,6 @@ nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet
|
||||
nym-vesting-contract-common = { path = "../common/cosmwasm-smart-contracts/vesting-contract" }
|
||||
nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float", "utoipa"] }
|
||||
nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-coconut = { path = "../common/nymcoconut", features = ["key-zeroize"] }
|
||||
nym-sphinx = { path = "../common/nymsphinx" }
|
||||
nym-pemstore = { path = "../common/pemstore" }
|
||||
nym-task = { path = "../common/task" }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::ecash::client::Client;
|
||||
use crate::ecash::keys::{KeyPairWithEpoch, LegacyCoconutKeyWithEpoch};
|
||||
use crate::ecash::keys::KeyPairWithEpoch;
|
||||
use crate::support::{config, nyxd};
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use nym_coconut_dkg_common::types::{EpochId, EpochState};
|
||||
@@ -43,24 +43,12 @@ pub(crate) fn load_ecash_keypair_if_exists(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// first attempt to load ecash keys directly,
|
||||
// if that fails fallback to coconut keys and perform migration
|
||||
if let Ok(ecash_key) =
|
||||
nym_pemstore::load_key::<KeyPairWithEpoch, _>(&config.storage_paths.ecash_key_path)
|
||||
{
|
||||
return Ok(Some(ecash_key));
|
||||
}
|
||||
|
||||
if let Ok(legacy_coconut_key) =
|
||||
nym_pemstore::load_key::<LegacyCoconutKeyWithEpoch, _>(&config.storage_paths.ecash_key_path)
|
||||
{
|
||||
let migrated_key: KeyPairWithEpoch = legacy_coconut_key.into();
|
||||
nym_pemstore::store_key(&migrated_key, &config.storage_paths.ecash_key_path)
|
||||
.context("migrated key storage failure")?;
|
||||
|
||||
return Ok(Some(migrated_key));
|
||||
}
|
||||
|
||||
bail!("ecash key load failure")
|
||||
}
|
||||
|
||||
|
||||
@@ -24,23 +24,6 @@ pub struct KeyPairWithEpoch {
|
||||
pub(crate) issued_for_epoch: EpochId,
|
||||
}
|
||||
|
||||
impl From<LegacyCoconutKeyWithEpoch> for KeyPairWithEpoch {
|
||||
fn from(value: LegacyCoconutKeyWithEpoch) -> Self {
|
||||
let (x, ys) = value.secret_key.hazmat_to_raw();
|
||||
let sk = nym_compact_ecash::SecretKeyAuth::create_from_raw(x, ys);
|
||||
|
||||
KeyPairWithEpoch {
|
||||
keys: sk.into(),
|
||||
issued_for_epoch: value.issued_for_epoch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LegacyCoconutKeyWithEpoch {
|
||||
pub(crate) secret_key: nym_coconut::SecretKey,
|
||||
pub(crate) issued_for_epoch: EpochId,
|
||||
}
|
||||
|
||||
impl KeyPairWithEpoch {
|
||||
pub(crate) fn new(keys: nym_compact_ecash::KeyPairAuth, issued_for_epoch: EpochId) -> Self {
|
||||
KeyPairWithEpoch {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::ecash::keys::{KeyPairWithEpoch, LegacyCoconutKeyWithEpoch};
|
||||
use crate::ecash::keys::KeyPairWithEpoch;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_compact_ecash::{error::CompactEcashError, scheme::keygen::SecretKeyAuth, KeyPairAuth};
|
||||
use nym_pemstore::traits::PemStorableKey;
|
||||
@@ -41,37 +41,3 @@ impl PemStorableKey for KeyPairWithEpoch {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PemStorableKey for LegacyCoconutKeyWithEpoch {
|
||||
// that's not the best error for this, but it felt like an overkill to define a dedicated struct just for this purpose
|
||||
type Error = nym_coconut::CoconutError;
|
||||
|
||||
fn pem_type() -> &'static str {
|
||||
"COCONUT KEY WITH EPOCH"
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = self.issued_for_epoch.to_be_bytes().to_vec();
|
||||
bytes.append(&mut self.secret_key.to_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
if bytes.len() <= mem::size_of::<EpochId>() {
|
||||
return Err(nym_coconut::CoconutError::DeserializationMinLength {
|
||||
min: mem::size_of::<EpochId>(),
|
||||
actual: bytes.len(),
|
||||
});
|
||||
}
|
||||
let epoch_id = EpochId::from_be_bytes([
|
||||
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
|
||||
]);
|
||||
|
||||
let sk = nym_coconut::SecretKey::from_bytes(&bytes[mem::size_of::<EpochId>()..])?;
|
||||
|
||||
Ok(LegacyCoconutKeyWithEpoch {
|
||||
secret_key: sk,
|
||||
issued_for_epoch: epoch_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,30 +145,4 @@ impl NymVpnApiClient for VpnApiClient {
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
// async fn get_bandwidth_voucher_blinded_shares(
|
||||
// &self,
|
||||
// blind_sign_request: BlindSignRequest,
|
||||
// ) -> Result<BandwidthVoucherResponse, VpnApiClientError> {
|
||||
// let req = self.inner.create_post_request(
|
||||
// &["/api", "/v1", "/bandwidth-voucher", "/obtain"],
|
||||
// NO_PARAMS,
|
||||
// &BandwidthVoucherRequest { blind_sign_request },
|
||||
// );
|
||||
//
|
||||
// let fut = req.bearer_auth(&self.bearer_token).send();
|
||||
//
|
||||
// // the only reason for that target lock is so that I could call this method from an ephemeral test
|
||||
// // running in non-wasm mode (since I wanted to use tokio)
|
||||
//
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
// let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), fut)
|
||||
// .await
|
||||
// .map_err(|_timeout| HttpClientError::RequestTimeout)??;
|
||||
//
|
||||
// #[cfg(not(target_arch = "wasm32"))]
|
||||
// let res = fut.await?;
|
||||
//
|
||||
// parse_response(res, false).await
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -9,48 +9,6 @@ use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme};
|
||||
use utoipa::{Modify, OpenApi};
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
/*
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
info(title = "Nym VPN Api"),
|
||||
paths(
|
||||
api::v1::freepass::generate_freepass,
|
||||
api::v1::bandwidth_voucher::obtain_bandwidth_voucher_shares,
|
||||
api::v1::bandwidth_voucher::obtain_async_bandwidth_voucher_shares,
|
||||
api::v1::bandwidth_voucher::current_deposit,
|
||||
api::v1::bandwidth_voucher::prehashed_public_attributes,
|
||||
api::v1::bandwidth_voucher::partial_verification_keys,
|
||||
api::v1::bandwidth_voucher::master_verification_key,
|
||||
api::v1::bandwidth_voucher::current_epoch,
|
||||
api::v1::bandwidth_voucher::shares::query_for_shares_by_id,
|
||||
),
|
||||
components(
|
||||
schemas(
|
||||
api::Output,
|
||||
api::OutputParams,
|
||||
api_requests::v1::ErrorResponse,
|
||||
api_requests::v1::freepass::models::FreepassCredentialResponse,
|
||||
api_requests::v1::freepass::models::FreepassQueryParams,
|
||||
api_requests::v1::bandwidth_voucher::models::DepositResponse,
|
||||
api_requests::v1::bandwidth_voucher::models::AttributesResponse,
|
||||
api_requests::v1::bandwidth_voucher::models::BandwidthVoucherResponse,
|
||||
api_requests::v1::bandwidth_voucher::models::BandwidthVoucherAsyncResponse,
|
||||
api_requests::v1::bandwidth_voucher::models::PartialVerificationKeysResponse,
|
||||
api_requests::v1::bandwidth_voucher::models::CurrentEpochResponse,
|
||||
api_requests::v1::bandwidth_voucher::models::CredentialShare,
|
||||
api_requests::v1::bandwidth_voucher::models::PartialVerificationKey,
|
||||
api_requests::v1::bandwidth_voucher::models::MasterVerificationKeyResponse,
|
||||
api_requests::v1::bandwidth_voucher::models::BandwidthVoucherAsyncRequest,
|
||||
api_requests::v1::bandwidth_voucher::models::BandwidthVoucherRequest,
|
||||
api_requests::v1::bandwidth_voucher::models::BlindSignRequestJsonSchemaWrapper
|
||||
),
|
||||
responses(RequestError),
|
||||
),
|
||||
modifiers(&SecurityAddon),
|
||||
)]
|
||||
pub(crate) struct ApiDoc;
|
||||
*/
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
info(title = "Nym Credential Proxy Api"),
|
||||
|
||||
@@ -31,7 +31,6 @@ rand = { workspace = true }
|
||||
|
||||
|
||||
nym-bin-common = { path = "../../common/bin-common" }
|
||||
nym-coconut = { path = "../../common/nymcoconut", features = ["key-zeroize"] }
|
||||
nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
|
||||
@@ -7,12 +7,6 @@ use wasm_utils::wasm_error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZkNymError {
|
||||
#[error("[coconut] cryptographic failure: {source}")]
|
||||
CoconutFailure {
|
||||
#[from]
|
||||
source: nym_coconut::CoconutError,
|
||||
},
|
||||
|
||||
#[error("[ecash] cryptographic failure: {source}")]
|
||||
EcashFailure {
|
||||
#[from]
|
||||
|
||||
@@ -1,338 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::coconut::types::{
|
||||
BlindSignRequestData, BlindSignRequestWrapper, BlindedCredentialWrapper,
|
||||
CredentialShareWrapper, CredentialWrapper, KeyPairWrapper, ParametersWrapper, ScalarsWrapper,
|
||||
VerificationKeyShareWrapper, VerificationKeyWrapper, VerifyCredentialRequestWrapper,
|
||||
};
|
||||
use crate::error::ZkNymError;
|
||||
use crate::GLOBAL_COCONUT_PARAMS;
|
||||
use nym_coconut::{hash_to_scalar, Parameters, SignerIndex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::OnceLock;
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
pub mod types;
|
||||
|
||||
// works under the assumption of having 4 attributes in the underlying credential(s)
|
||||
const DEFAULT_ATTRIBUTES: u32 = 4;
|
||||
|
||||
pub fn default_bandwidth_credential_params() -> &'static Parameters {
|
||||
static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock<Parameters> = OnceLock::new();
|
||||
BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(|| Parameters::new(DEFAULT_ATTRIBUTES).unwrap())
|
||||
}
|
||||
|
||||
// attempt to extract appropriate system parameters in the following order:
|
||||
// 1. attempt to get explicit provided value
|
||||
// 2. then try a globally set value
|
||||
// 3. finally fallback to sane default: the bandwidth credential params
|
||||
pub(crate) fn get_params(explicit_params: &Option<ParametersWrapper>) -> &Parameters {
|
||||
if let Some(explicit) = explicit_params.as_ref() {
|
||||
return explicit;
|
||||
}
|
||||
|
||||
if let Some(global) = GLOBAL_COCONUT_PARAMS.get() {
|
||||
return global;
|
||||
}
|
||||
|
||||
default_bandwidth_credential_params()
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize, Default)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetupOpts {
|
||||
#[tsify(optional)]
|
||||
pub num_attributes: Option<u32>,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub set_global: Option<bool>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutSetup")]
|
||||
pub fn setup(opts: SetupOpts) -> Result<ParametersWrapper, ZkNymError> {
|
||||
let num_attributes = opts.num_attributes.unwrap_or(DEFAULT_ATTRIBUTES);
|
||||
|
||||
let params = nym_coconut::setup(num_attributes)?;
|
||||
|
||||
if let Some(true) = opts.set_global {
|
||||
GLOBAL_COCONUT_PARAMS
|
||||
.set(params.clone())
|
||||
.map_err(|_| ZkNymError::GlobalParamsAlreadySet)?;
|
||||
}
|
||||
|
||||
Ok(params.into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutKeygen")]
|
||||
pub fn keygen(parameters: Option<ParametersWrapper>) -> KeyPairWrapper {
|
||||
let params = get_params(¶meters);
|
||||
nym_coconut::keygen(params).into()
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TtpKeygenOpts {
|
||||
pub threshold: u64,
|
||||
|
||||
pub authorities: u64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutTtpKeygen")]
|
||||
pub fn ttp_keygen(
|
||||
opts: TtpKeygenOpts,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<Vec<KeyPairWrapper>, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let keys = nym_coconut::ttp_keygen(params, opts.threshold, opts.authorities)?;
|
||||
Ok(keys.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutSignSimple")]
|
||||
pub fn sign_simple(
|
||||
attributes: Vec<String>,
|
||||
keys: &KeyPairWrapper,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let public_attributes = attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::sign(keys.secret_key(), &attributes_ref)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutPrepareBlindSign")]
|
||||
pub fn prepare_blind_sign(
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<BlindSignRequestData, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let public_attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
let private_attributes = private_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let private_attributes_ref = private_attributes.iter().collect::<Vec<_>>();
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
nym_coconut::prepare_blind_sign(params, &private_attributes_ref, &public_attributes_ref)?;
|
||||
|
||||
Ok(BlindSignRequestData {
|
||||
blind_sign_request,
|
||||
pedersen_commitments_openings,
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutBlindSign")]
|
||||
pub fn blind_sign(
|
||||
keys: &KeyPairWrapper,
|
||||
blind_sign_request: &BlindSignRequestWrapper,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<BlindedCredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let public_attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::blind_sign(
|
||||
params,
|
||||
keys.secret_key(),
|
||||
blind_sign_request,
|
||||
&public_attributes_ref,
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutUnblindSignatureShare")]
|
||||
pub fn unblind_signature_share(
|
||||
blinded_signature: &BlindedCredentialWrapper,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
pedersen_commitments_openings: &ScalarsWrapper,
|
||||
) -> CredentialWrapper {
|
||||
BlindedCredentialWrapper::unblind(
|
||||
blinded_signature,
|
||||
partial_verification_key,
|
||||
pedersen_commitments_openings,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutUnblindAndVerifySignatureShare")]
|
||||
pub fn unblind_and_verify_signature_share(
|
||||
blinded_signature: &BlindedCredentialWrapper,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
request: &BlindSignRequestData,
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
BlindedCredentialWrapper::unblind_and_verify(
|
||||
blinded_signature,
|
||||
partial_verification_key,
|
||||
request,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
parameters,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutAggregateSignatureShares")]
|
||||
pub fn aggregate_signature_shares(
|
||||
shares: Vec<CredentialShareWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let shares = shares.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_signature_shares(&shares)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutAggregateSignatureSharesAndVerify")]
|
||||
pub fn aggregate_signature_shares_and_verify(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
shares: Vec<CredentialShareWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let private_attributes = private_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let attributes = private_attributes
|
||||
.iter()
|
||||
.chain(public_attributes.iter())
|
||||
.collect::<Vec<_>>();
|
||||
let shares = shares.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_signature_shares_and_verify(
|
||||
params,
|
||||
verification_key,
|
||||
&attributes,
|
||||
&shares,
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutAggregateVerificationKeyShares")]
|
||||
pub fn aggregate_verification_key_shares(
|
||||
shares: Vec<VerificationKeyShareWrapper>,
|
||||
) -> Result<VerificationKeyWrapper, ZkNymError> {
|
||||
let shares = shares.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_key_shares(&shares)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutAggregateVerificationKeys")]
|
||||
pub fn aggregate_verification_keys(
|
||||
keys: Vec<VerificationKeyWrapper>,
|
||||
indices: Vec<SignerIndex>,
|
||||
) -> Result<VerificationKeyWrapper, ZkNymError> {
|
||||
let keys = keys.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::aggregate_verification_keys(&keys, Some(&indices))
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutProveBandwidthCredential")]
|
||||
pub fn prove_bandwidth_credential(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
credential: &CredentialWrapper,
|
||||
serial_number: String,
|
||||
binding_number: String,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<VerifyCredentialRequestWrapper, ZkNymError> {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
nym_coconut::prove_bandwidth_credential(
|
||||
params,
|
||||
verification_key,
|
||||
credential,
|
||||
&hash_to_scalar(serial_number),
|
||||
&hash_to_scalar(binding_number),
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutVerifyCredential")]
|
||||
pub fn verify_credential(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
verification_request: &VerifyCredentialRequestWrapper,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> bool {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::verify_credential(
|
||||
params,
|
||||
verification_key,
|
||||
verification_request,
|
||||
&attributes_ref,
|
||||
)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutVerifySimple")]
|
||||
pub fn verify_simple(
|
||||
verification_key: &VerificationKeyWrapper,
|
||||
attributes: Vec<String>,
|
||||
credential: &CredentialWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> bool {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
let public_attributes = attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
nym_coconut::verify(params, verification_key, &attributes_ref, credential)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "coconutSimpleRandomiseCredential")]
|
||||
pub fn simple_randomise_credential(
|
||||
credential: &CredentialWrapper,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> CredentialWrapper {
|
||||
let params = get_params(¶meters);
|
||||
|
||||
CredentialWrapper {
|
||||
inner: credential.inner.randomise_simple(params),
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ZkNymError;
|
||||
use crate::{data_pointer_clone, wasm_wrapper, wasm_wrapper_bs58};
|
||||
use nym_coconut::{
|
||||
hash_to_scalar, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters, Scalar,
|
||||
SecretKey, Signature, SignatureShare, SignerIndex, VerificationKey, VerificationKeyShare,
|
||||
VerifyCredentialRequest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
wasm_wrapper!(Parameters, ParametersWrapper);
|
||||
wasm_wrapper_bs58!(Signature, CredentialWrapper);
|
||||
wasm_wrapper_bs58!(BlindedSignature, BlindedCredentialWrapper);
|
||||
wasm_wrapper!(SignatureShare, CredentialShareWrapper);
|
||||
wasm_wrapper_bs58!(Scalar, ScalarWrapper);
|
||||
|
||||
wasm_wrapper!(KeyPair, KeyPairWrapper);
|
||||
wasm_wrapper!(SecretKey, SecretKeyWrapper);
|
||||
wasm_wrapper!(BlindSignRequest, BlindSignRequestWrapper);
|
||||
wasm_wrapper_bs58!(VerificationKey, VerificationKeyWrapper);
|
||||
wasm_wrapper_bs58!(VerifyCredentialRequest, VerifyCredentialRequestWrapper);
|
||||
wasm_wrapper!(VerificationKeyShare, VerificationKeyShareWrapper);
|
||||
|
||||
data_pointer_clone!(VerificationKeyShareWrapper);
|
||||
data_pointer_clone!(CredentialShareWrapper);
|
||||
data_pointer_clone!(BlindSignRequestWrapper);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl BlindedCredentialWrapper {
|
||||
pub fn unblind(
|
||||
&self,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
pedersen_commitments_openings: &ScalarsWrapper,
|
||||
) -> CredentialWrapper {
|
||||
self.inner
|
||||
.unblind(partial_verification_key, pedersen_commitments_openings)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "unblindAndVerify")]
|
||||
pub fn unblind_and_verify(
|
||||
&self,
|
||||
partial_verification_key: &VerificationKeyWrapper,
|
||||
request: &BlindSignRequestData,
|
||||
private_attributes: Vec<String>,
|
||||
public_attributes: Vec<String>,
|
||||
parameters: Option<ParametersWrapper>,
|
||||
) -> Result<CredentialWrapper, ZkNymError> {
|
||||
let params = super::get_params(¶meters);
|
||||
|
||||
let public_attributes = public_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let public_attributes_ref = public_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
let private_attributes = private_attributes
|
||||
.into_iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<_>>();
|
||||
let private_attributes_ref = private_attributes.iter().collect::<Vec<_>>();
|
||||
|
||||
let unblinded_signature = self.inner.unblind_and_verify(
|
||||
params,
|
||||
partial_verification_key,
|
||||
&private_attributes_ref,
|
||||
&public_attributes_ref,
|
||||
&request.blind_sign_request.get_commitment_hash(),
|
||||
&request.pedersen_commitments_openings,
|
||||
)?;
|
||||
|
||||
Ok(unblinded_signature.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl CredentialWrapper {
|
||||
#[wasm_bindgen(js_name = "intoShare")]
|
||||
pub fn into_share(self, index: SignerIndex) -> CredentialShareWrapper {
|
||||
CredentialShareWrapper {
|
||||
inner: SignatureShare::new(self.inner, index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeyPairWrapper {
|
||||
#[wasm_bindgen(js_name = "verificationKey")]
|
||||
pub fn verification_key(&self) -> VerificationKeyWrapper {
|
||||
self.inner.verification_key().clone().into()
|
||||
}
|
||||
|
||||
pub fn index(&self) -> Option<SignerIndex> {
|
||||
self.inner.index
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "verificationKeyShare")]
|
||||
pub fn verification_key_share(&self) -> Option<VerificationKeyShareWrapper> {
|
||||
self.inner.to_verification_key_share().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct BlindSignRequestData {
|
||||
pub(crate) blind_sign_request: BlindSignRequest,
|
||||
pub(crate) pedersen_commitments_openings: Vec<Scalar>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl BlindSignRequestData {
|
||||
#[wasm_bindgen(js_name = "blindSignRequest")]
|
||||
pub fn blind_sign_request(&self) -> BlindSignRequestWrapper {
|
||||
self.blind_sign_request.clone().into()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "pedersenCommitmentsOpenings")]
|
||||
pub fn pedersen_commitments_openings(&self) -> ScalarsWrapper {
|
||||
ScalarsWrapper(self.pedersen_commitments_openings.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct ScalarsWrapper(pub(crate) Vec<Scalar>);
|
||||
|
||||
impl Deref for ScalarsWrapper {
|
||||
type Target = Vec<Scalar>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop,
|
||||
)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KeypairWrapper {
|
||||
pub private_key: String,
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UnblindableShare {
|
||||
pub issuer_index: u64,
|
||||
pub issuer_key_bs58: String,
|
||||
pub blinded_share_bs58: String,
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod coconut;
|
||||
pub mod ecash;
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
// due to the code generated by Tsify
|
||||
#![allow(clippy::empty_docs)]
|
||||
|
||||
use nym_coconut::Parameters;
|
||||
use std::sync::OnceLock;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod bandwidth_voucher;
|
||||
@@ -22,8 +20,6 @@ pub mod types;
|
||||
// I mostly got it, so I could test the whole thing end to end
|
||||
pub(crate) mod vpn_api_client;
|
||||
|
||||
pub(crate) static GLOBAL_COCONUT_PARAMS: OnceLock<Parameters> = OnceLock::new();
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
pub fn main() {
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
use super::NymVpnApiClientError;
|
||||
use crate::error::ZkNymError;
|
||||
use crate::vpn_api_client::types::{
|
||||
AttributesResponse, BandwidthVoucherRequest, BandwidthVoucherResponse,
|
||||
MasterVerificationKeyResponse, PartialVerificationKeysResponse,
|
||||
AttributesResponse, MasterVerificationKeyResponse, PartialVerificationKeysResponse,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use nym_coconut::BlindSignRequest;
|
||||
pub use nym_http_api_client::Client;
|
||||
use nym_http_api_client::{parse_response, ApiClient, PathSegments, NO_PARAMS};
|
||||
use reqwest::IntoUrl;
|
||||
@@ -76,11 +74,6 @@ pub trait NymVpnApiClient {
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_bandwidth_voucher_blinded_shares(
|
||||
&self,
|
||||
blind_sign_request: BlindSignRequest,
|
||||
) -> Result<BandwidthVoucherResponse, NymVpnApiClientError>;
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
@@ -108,30 +101,4 @@ impl NymVpnApiClient for VpnApiClient {
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
async fn get_bandwidth_voucher_blinded_shares(
|
||||
&self,
|
||||
blind_sign_request: BlindSignRequest,
|
||||
) -> Result<BandwidthVoucherResponse, NymVpnApiClientError> {
|
||||
let req = self.inner.create_post_request(
|
||||
&["/api", "/v1", "/bandwidth-voucher", "/obtain"],
|
||||
NO_PARAMS,
|
||||
&BandwidthVoucherRequest { blind_sign_request },
|
||||
);
|
||||
|
||||
let fut = req.bearer_auth(&self.bearer_token).send();
|
||||
|
||||
// the only reason for that target lock is so that I could call this method from an ephemeral test
|
||||
// running in non-wasm mode (since I wanted to use tokio)
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), fut)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = fut.await?;
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,11 @@
|
||||
|
||||
// just copied over from dot com repo
|
||||
|
||||
use nym_coconut::BlindSignRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use tsify::Tsify;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BandwidthVoucherRequest {
|
||||
/// base58 encoded blind sign request
|
||||
pub blind_sign_request: BlindSignRequest,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BandwidthVoucherResponse {
|
||||
pub epoch_id: u64,
|
||||
pub shares: Vec<CredentialShare>,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
Reference in New Issue
Block a user