// Copyright 2021 - Nym Technologies SA // 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, } impl PemStorableKey for SecretKey { type Error = CoconutError; fn pem_type() -> &'static str { "COCONUT SECRET KEY" } fn to_bytes(&self) -> Vec { self.to_bytes() } fn from_bytes(bytes: &[u8]) -> std::result::Result { Self::from_bytes(bytes) } } impl TryFrom<&[u8]> for SecretKey { type Error = CoconutError; fn try_from(bytes: &[u8]) -> Result { // 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) -> 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) { (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 { 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::try_from(bytes) } } impl Bytable for SecretKey { fn to_byte_vec(&self) -> Vec { self.to_bytes() } fn try_from_byte_slice(slice: &[u8]) -> Result { 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, pub(crate) beta_g2: Vec, } impl PemStorableKey for VerificationKey { type Error = CoconutError; fn pem_type() -> &'static str { "COCONUT VERIFICATION KEY" } fn to_bytes(&self) -> Vec { self.to_bytes() } fn from_bytes(bytes: &[u8]) -> std::result::Result { Self::from_bytes(bytes) } } impl TryFrom<&[u8]> for VerificationKey { type Error = CoconutError; fn try_from(bytes: &[u8]) -> Result { // 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<'a> Mul for &'a 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 Sum for VerificationKey where T: Borrow, { #[inline] fn sum(iter: I) -> Self where I: Iterator, { 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 { aggregate_verification_keys(sigs, indices) } pub fn alpha(&self) -> &G2Projective { &self.alpha } pub fn beta_g1(&self) -> &Vec { &self.beta_g1 } pub fn beta_g2(&self) -> &Vec { &self.beta_g2 } pub fn to_bytes(&self) -> Vec { 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::try_from(bytes) } } impl Bytable for VerificationKey { fn to_byte_vec(&self) -> Vec { self.to_bytes() } fn try_from_byte_slice(slice: &[u8]) -> Result { 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, } impl From 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 { self.index.map(|index| VerificationKeyShare { key: self.verification_key.clone(), index, }) } pub fn to_bytes(&self) -> Vec { // 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 { KeyPair::try_from_byte_slice(bytes) } } impl Bytable for KeyPair { fn to_byte_vec(&self) -> Vec { // 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 { KeyPair::try_from(slice) } } impl Base58 for KeyPair {} impl TryFrom<&[u8]> for KeyPair { type Error = CoconutError; fn try_from(bytes: &[u8]) -> Result { 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> { 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::>(); // 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::>(); // 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::>() }); // 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 = keypair1.verification_key.to_bytes(); let bytes5: Vec = 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 ); } }