Skip to main content

nym_compact_ecash/scheme/
keygen.rs

1// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::error::{CompactEcashError, Result};
5use crate::scheme::aggregation::aggregate_verification_keys;
6use crate::scheme::SignerIndex;
7use crate::traits::Bytable;
8use crate::utils::{hash_to_scalar, Polynomial};
9use crate::utils::{
10    try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
11    try_deserialize_scalar_vec,
12};
13use crate::{ecash_group_parameters, Base58};
14use core::borrow::Borrow;
15use core::iter::Sum;
16use core::ops::{Add, Mul};
17use group::{Curve, GroupEncoding};
18use nym_bls12_381_fork::{G1Projective, G2Projective, Scalar};
19use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
20use serde::{Deserialize, Serialize};
21use zeroize::{Zeroize, ZeroizeOnDrop};
22
23#[derive(Debug, PartialEq, Clone, Zeroize, ZeroizeOnDrop)]
24pub struct SecretKeyAuth {
25    pub(crate) x: Scalar,
26    pub(crate) ys: Vec<Scalar>,
27}
28
29impl PemStorableKey for SecretKeyAuth {
30    type Error = CompactEcashError;
31
32    fn pem_type() -> &'static str {
33        "ECASH SECRET KEY"
34    }
35
36    fn to_bytes(&self) -> Vec<u8> {
37        self.to_bytes()
38    }
39
40    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Error> {
41        Self::from_bytes(bytes)
42    }
43}
44
45impl TryFrom<&[u8]> for SecretKeyAuth {
46    type Error = CompactEcashError;
47
48    fn try_from(bytes: &[u8]) -> Result<SecretKeyAuth> {
49        // There should be x and at least one y
50        if bytes.len() < 32 * 2 + 8 || !(bytes.len() - 8).is_multiple_of(32) {
51            return Err(CompactEcashError::DeserializationInvalidLength {
52                actual: bytes.len(),
53                modulus_target: bytes.len() - 8,
54                target: 32 * 2 + 8,
55                modulus: 32,
56                object: "secret key".to_string(),
57            });
58        }
59
60        //SAFETY : slice to array conversion after a length check
61        #[allow(clippy::unwrap_used)]
62        #[allow(clippy::indexing_slicing)]
63        let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap();
64
65        #[allow(clippy::unwrap_used)]
66        #[allow(clippy::indexing_slicing)]
67        let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
68        let actual_ys_len = (bytes.len() - 40) / 32;
69
70        if ys_len as usize != actual_ys_len {
71            return Err(CompactEcashError::DeserializationLengthMismatch {
72                type_name: "Secret_key ys".into(),
73                expected: ys_len as usize,
74                actual: actual_ys_len,
75            });
76        }
77
78        let x = try_deserialize_scalar(&x_bytes)?;
79        #[allow(clippy::indexing_slicing)]
80        let ys = try_deserialize_scalar_vec(ys_len, &bytes[40..])?;
81
82        Ok(SecretKeyAuth { x, ys })
83    }
84}
85
86impl SecretKeyAuth {
87    /// Following a (distributed) key generation process, scalar values can be obtained
88    /// outside of the normal key generation process.
89    pub fn create_from_raw(x: Scalar, ys: Vec<Scalar>) -> Self {
90        Self { x, ys }
91    }
92
93    /// Extract the Scalar copy of the underlying secrets.
94    /// The caller of this function must exercise extreme care to not misuse the data and ensuring it gets zeroized
95    pub fn hazmat_to_raw(&self) -> (Scalar, Vec<Scalar>) {
96        (self.x, self.ys.clone())
97    }
98
99    pub fn size(&self) -> usize {
100        self.ys.len()
101    }
102
103    pub fn verification_key(&self) -> VerificationKeyAuth {
104        let params = ecash_group_parameters();
105        let g1 = params.gen1();
106        let g2 = params.gen2();
107        VerificationKeyAuth {
108            alpha: g2 * self.x,
109            beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
110            beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
111        }
112    }
113
114    pub fn to_bytes(&self) -> Vec<u8> {
115        let ys_len = self.ys.len();
116        let mut bytes = Vec::with_capacity(8 + (ys_len + 1) * 32);
117        bytes.extend_from_slice(&self.x.to_bytes());
118        bytes.extend_from_slice(&(ys_len as u64).to_le_bytes());
119        for y in self.ys.iter() {
120            bytes.extend_from_slice(&y.to_bytes())
121        }
122        bytes
123    }
124
125    pub fn from_bytes(bytes: &[u8]) -> Result<SecretKeyAuth> {
126        SecretKeyAuth::try_from(bytes)
127    }
128}
129
130#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
131pub struct VerificationKeyAuth {
132    pub(crate) alpha: G2Projective,
133    pub(crate) beta_g1: Vec<G1Projective>,
134    pub(crate) beta_g2: Vec<G2Projective>,
135}
136
137impl PemStorableKey for VerificationKeyAuth {
138    type Error = CompactEcashError;
139
140    fn pem_type() -> &'static str {
141        "ECASH VERIFICATION KEY"
142    }
143
144    fn to_bytes(&self) -> Vec<u8> {
145        self.to_bytes()
146    }
147
148    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Error> {
149        Self::from_bytes(bytes)
150    }
151}
152
153impl TryFrom<&[u8]> for VerificationKeyAuth {
154    type Error = CompactEcashError;
155
156    fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
157        // There should be at least alpha, one betaG1 and one betaG2 and their length
158        if bytes.len() < 96 * 2 + 48 + 8 || !(bytes.len() - 8 - 96).is_multiple_of(96 + 48) {
159            return Err(CompactEcashError::DeserializationInvalidLength {
160                actual: bytes.len(),
161                modulus_target: bytes.len() - 8 - 96,
162                target: 96 * 2 + 48 + 8,
163                modulus: 96 + 48,
164                object: "verification key".to_string(),
165            });
166        }
167
168        //SAFETY : slice to array conversion after a length check
169        #[allow(clippy::unwrap_used)]
170        #[allow(clippy::indexing_slicing)]
171        let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
172        #[allow(clippy::unwrap_used)]
173        #[allow(clippy::indexing_slicing)]
174        let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
175
176        let actual_betas_len = (bytes.len() - 104) / (96 + 48);
177
178        if betas_len as usize != actual_betas_len {
179            return Err(CompactEcashError::DeserializationLengthMismatch {
180                type_name: "Verification_key betas".into(),
181                expected: betas_len as usize,
182                actual: actual_betas_len,
183            });
184        }
185
186        let alpha = try_deserialize_g2_projective(&alpha_bytes)?;
187
188        let mut beta_g1 = Vec::with_capacity(betas_len as usize);
189        let mut beta_g1_end: u64 = 0;
190        for i in 0..betas_len {
191            let start = (104 + i * 48) as usize;
192            let end = start + 48;
193            //SAFETY : slice to array conversion after a length check
194            #[allow(clippy::unwrap_used)]
195            #[allow(clippy::indexing_slicing)]
196            let beta_i_bytes = bytes[start..end].try_into().unwrap();
197            let beta_i = try_deserialize_g1_projective(&beta_i_bytes)?;
198
199            beta_g1_end = end as u64;
200            beta_g1.push(beta_i)
201        }
202
203        let mut beta_g2 = Vec::with_capacity(betas_len as usize);
204        for i in 0..betas_len {
205            let start = (beta_g1_end + i * 96) as usize;
206            let end = start + 96;
207            //SAFETY : slice to array conversion after a length check
208            #[allow(clippy::unwrap_used)]
209            #[allow(clippy::indexing_slicing)]
210            let beta_i_bytes = bytes[start..end].try_into().unwrap();
211            let beta_i = try_deserialize_g2_projective(&beta_i_bytes)?;
212
213            beta_g2.push(beta_i)
214        }
215
216        Ok(VerificationKeyAuth {
217            alpha,
218            beta_g1,
219            beta_g2,
220        })
221    }
222}
223
224impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
225    type Output = VerificationKeyAuth;
226
227    #[inline]
228    fn add(self, rhs: &'b VerificationKeyAuth) -> VerificationKeyAuth {
229        // If you're trying to add two keys together that were created
230        // for different number of attributes, just panic as it's a
231        // nonsense operation.
232        assert_eq!(
233            self.beta_g1.len(),
234            rhs.beta_g1.len(),
235            "trying to add verification keys generated for different number of attributes [G1]"
236        );
237
238        assert_eq!(
239            self.beta_g2.len(),
240            rhs.beta_g2.len(),
241            "trying to add verification keys generated for different number of attributes [G2]"
242        );
243
244        assert_eq!(
245            self.beta_g1.len(),
246            self.beta_g2.len(),
247            "this key is incorrect - the number of elements G1 and G2 does not match"
248        );
249
250        assert_eq!(
251            rhs.beta_g1.len(),
252            rhs.beta_g2.len(),
253            "they key you want to add is incorrect - the number of elements G1 and G2 does not match"
254        );
255
256        VerificationKeyAuth {
257            alpha: self.alpha + rhs.alpha,
258            beta_g1: self
259                .beta_g1
260                .iter()
261                .zip(rhs.beta_g1.iter())
262                .map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
263                .collect(),
264            beta_g2: self
265                .beta_g2
266                .iter()
267                .zip(rhs.beta_g2.iter())
268                .map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
269                .collect(),
270        }
271    }
272}
273
274impl Mul<Scalar> for &VerificationKeyAuth {
275    type Output = VerificationKeyAuth;
276
277    #[inline]
278    fn mul(self, rhs: Scalar) -> Self::Output {
279        VerificationKeyAuth {
280            alpha: self.alpha * rhs,
281            beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
282            beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
283        }
284    }
285}
286
287impl<T> Sum<T> for VerificationKeyAuth
288where
289    T: Borrow<VerificationKeyAuth>,
290{
291    #[inline]
292    fn sum<I>(iter: I) -> Self
293    where
294        I: Iterator<Item = T>,
295    {
296        let mut peekable = iter.peekable();
297        let head_attributes = match peekable.peek() {
298            Some(head) => head.borrow().beta_g2.len(),
299            None => {
300                // TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
301                // of VerificationKey. So should it panic here or just return some nonsense value?
302                return VerificationKeyAuth::identity(0);
303            }
304        };
305
306        peekable.fold(
307            VerificationKeyAuth::identity(head_attributes),
308            |acc, item| acc + item.borrow(),
309        )
310    }
311}
312
313impl VerificationKeyAuth {
314    /// Create a (kinda) identity verification key using specified
315    /// number of 'beta' elements
316    pub(crate) fn identity(beta_size: usize) -> Self {
317        VerificationKeyAuth {
318            alpha: G2Projective::identity(),
319            beta_g1: vec![G1Projective::identity(); beta_size],
320            beta_g2: vec![G2Projective::identity(); beta_size],
321        }
322    }
323
324    pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self> {
325        aggregate_verification_keys(sigs, indices)
326    }
327
328    pub fn alpha(&self) -> &G2Projective {
329        &self.alpha
330    }
331
332    pub fn beta_g1(&self) -> &Vec<G1Projective> {
333        &self.beta_g1
334    }
335
336    pub fn beta_g2(&self) -> &Vec<G2Projective> {
337        &self.beta_g2
338    }
339
340    pub fn to_bytes(&self) -> Vec<u8> {
341        let beta_g1_len = self.beta_g1.len();
342        let beta_g2_len = self.beta_g2.len();
343        let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
344
345        bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
346
347        bytes.extend_from_slice(&(beta_g1_len as u64).to_le_bytes());
348
349        for beta_g1 in self.beta_g1.iter() {
350            bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
351        }
352
353        for beta_g2 in self.beta_g2.iter() {
354            bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
355        }
356
357        bytes
358    }
359
360    pub fn from_bytes(bytes: &[u8]) -> Result<VerificationKeyAuth> {
361        VerificationKeyAuth::try_from(bytes)
362    }
363}
364
365impl Bytable for VerificationKeyAuth {
366    fn to_byte_vec(&self) -> Vec<u8> {
367        self.to_bytes().to_vec()
368    }
369
370    fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
371        Self::from_bytes(slice)
372    }
373}
374
375impl Base58 for VerificationKeyAuth {}
376
377#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
378pub struct SecretKeyUser {
379    pub(crate) sk: Scalar,
380}
381
382impl SecretKeyUser {
383    pub fn public_key(&self) -> PublicKeyUser {
384        PublicKeyUser {
385            pk: ecash_group_parameters().gen1() * self.sk,
386        }
387    }
388
389    pub fn to_bytes(&self) -> Vec<u8> {
390        self.sk.to_bytes().to_vec()
391    }
392
393    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
394        let sk = Scalar::try_from_byte_slice(bytes)?;
395        Ok(SecretKeyUser { sk })
396    }
397}
398
399impl Bytable for SecretKeyUser {
400    fn to_byte_vec(&self) -> Vec<u8> {
401        self.to_bytes().to_vec()
402    }
403
404    fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
405        Self::from_bytes(slice)
406    }
407}
408
409impl Base58 for SecretKeyUser {}
410
411#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
412pub struct PublicKeyUser {
413    pub(crate) pk: G1Projective,
414}
415
416impl PublicKeyUser {
417    pub fn to_base58_string(&self) -> String {
418        bs58::encode(&self.pk.to_bytes()).into_string()
419    }
420
421    pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self> {
422        let bytes = bs58::decode(val).into_vec()?;
423        Self::from_bytes(&bytes)
424    }
425
426    pub fn to_bytes(&self) -> Vec<u8> {
427        self.pk.to_affine().to_compressed().to_vec()
428    }
429
430    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
431        if bytes.len() != 48 {
432            return Err(CompactEcashError::DeserializationLengthMismatch {
433                type_name: "PublicKeyUser".into(),
434                expected: 48,
435                actual: bytes.len(),
436            });
437        }
438        //SAFETY : slice to array conversion after a length check
439        #[allow(clippy::unwrap_used)]
440        #[allow(clippy::indexing_slicing)]
441        let pk_bytes: &[u8; 48] = bytes[..48].try_into().unwrap();
442        let pk = try_deserialize_g1_projective(pk_bytes)?;
443        Ok(PublicKeyUser { pk })
444    }
445}
446
447impl Bytable for PublicKeyUser {
448    fn to_byte_vec(&self) -> Vec<u8> {
449        self.to_bytes().to_vec()
450    }
451
452    fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
453        Self::from_bytes(slice)
454    }
455}
456
457impl Base58 for PublicKeyUser {}
458
459#[derive(Debug, Zeroize, ZeroizeOnDrop)]
460pub struct KeyPairAuth {
461    secret_key: SecretKeyAuth,
462    #[zeroize(skip)]
463    verification_key: VerificationKeyAuth,
464    /// Optional index value specifying polynomial point used during threshold key generation.
465    pub index: Option<SignerIndex>,
466}
467
468impl From<SecretKeyAuth> for KeyPairAuth {
469    fn from(secret_key: SecretKeyAuth) -> Self {
470        KeyPairAuth {
471            verification_key: secret_key.verification_key(),
472            secret_key,
473            index: None,
474        }
475    }
476}
477
478impl PemStorableKeyPair for KeyPairAuth {
479    type PrivatePemKey = SecretKeyAuth;
480    type PublicPemKey = VerificationKeyAuth;
481
482    fn private_key(&self) -> &Self::PrivatePemKey {
483        &self.secret_key
484    }
485
486    fn public_key(&self) -> &Self::PublicPemKey {
487        &self.verification_key
488    }
489
490    fn from_keys(secret_key: Self::PrivatePemKey, verification_key: Self::PublicPemKey) -> Self {
491        Self::from_keys(secret_key, verification_key)
492    }
493}
494
495impl KeyPairAuth {
496    pub fn new(
497        sk: SecretKeyAuth,
498        vk: VerificationKeyAuth,
499        index: Option<SignerIndex>,
500    ) -> KeyPairAuth {
501        KeyPairAuth {
502            secret_key: sk,
503            verification_key: vk,
504            index,
505        }
506    }
507
508    pub fn from_keys(secret_key: SecretKeyAuth, verification_key: VerificationKeyAuth) -> Self {
509        Self {
510            secret_key,
511            verification_key,
512            index: None,
513        }
514    }
515
516    pub fn secret_key(&self) -> &SecretKeyAuth {
517        &self.secret_key
518    }
519
520    pub fn verification_key(&self) -> VerificationKeyAuth {
521        self.verification_key.clone()
522    }
523
524    pub fn verification_key_ref(&self) -> &VerificationKeyAuth {
525        &self.verification_key
526    }
527}
528
529#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
530pub struct KeyPairUser {
531    secret_key: SecretKeyUser,
532    public_key: PublicKeyUser,
533}
534
535impl From<KeyPairUser> for SecretKeyUser {
536    fn from(value: KeyPairUser) -> Self {
537        value.secret_key
538    }
539}
540
541impl From<SecretKeyUser> for KeyPairUser {
542    fn from(value: SecretKeyUser) -> Self {
543        KeyPairUser {
544            public_key: value.public_key(),
545            secret_key: value,
546        }
547    }
548}
549
550impl KeyPairUser {
551    #[allow(clippy::new_without_default)]
552    pub fn new() -> Self {
553        generate_keypair_user()
554    }
555
556    pub fn new_seeded<M: AsRef<[u8]>>(seed: M) -> Self {
557        generate_keypair_user_from_seed(seed)
558    }
559
560    pub fn secret_key(&self) -> &SecretKeyUser {
561        &self.secret_key
562    }
563
564    pub fn public_key(&self) -> PublicKeyUser {
565        self.public_key
566    }
567
568    pub fn to_bytes(&self) -> Vec<u8> {
569        [self.secret_key.to_bytes(), self.public_key.to_bytes()].concat()
570    }
571
572    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
573        if bytes.len() != 32 + 48 {
574            return Err(CompactEcashError::DeserializationLengthMismatch {
575                type_name: "KeyPairUser".into(),
576                expected: 80,
577                actual: bytes.len(),
578            });
579        }
580        #[allow(clippy::indexing_slicing)]
581        let sk = SecretKeyUser::from_bytes(&bytes[..32])?;
582        #[allow(clippy::indexing_slicing)]
583        let pk = PublicKeyUser::from_bytes(&bytes[32..32 + 48])?;
584        Ok(KeyPairUser {
585            secret_key: sk,
586            public_key: pk,
587        })
588    }
589}
590
591pub fn generate_keypair_user() -> KeyPairUser {
592    let params = ecash_group_parameters();
593    let sk_user = SecretKeyUser {
594        sk: params.random_scalar(),
595    };
596    let pk_user = PublicKeyUser {
597        pk: params.gen1() * sk_user.sk,
598    };
599
600    KeyPairUser {
601        secret_key: sk_user,
602        public_key: pk_user,
603    }
604}
605
606pub fn generate_keypair_user_from_seed<M: AsRef<[u8]>>(seed: M) -> KeyPairUser {
607    let params = ecash_group_parameters();
608    let sk_user = SecretKeyUser {
609        sk: hash_to_scalar(seed),
610    };
611    let pk_user = PublicKeyUser {
612        pk: params.gen1() * sk_user.sk,
613    };
614
615    KeyPairUser {
616        secret_key: sk_user,
617        public_key: pk_user,
618    }
619}
620
621pub fn ttp_keygen(threshold: u64, num_authorities: u64) -> Result<Vec<KeyPairAuth>> {
622    let params = ecash_group_parameters();
623    if threshold == 0 {
624        return Err(CompactEcashError::KeygenParameters);
625    }
626
627    if threshold > num_authorities {
628        return Err(CompactEcashError::KeygenParameters);
629    }
630
631    let attributes = params.gammas().len();
632
633    // generate polynomials
634    let v = Polynomial::new_random(params, threshold - 1);
635    let ws = (0..attributes + 1)
636        .map(|_| Polynomial::new_random(params, threshold - 1))
637        .collect::<Vec<_>>();
638
639    // TODO: potentially if we had some known authority identifier we could use that instead
640    // of the increasing (1,2,3,...) sequence
641    let polynomial_indices = (1..=num_authorities).collect::<Vec<_>>();
642
643    // generate polynomial shares
644    let x = polynomial_indices
645        .iter()
646        .map(|&id| v.evaluate(&Scalar::from(id)));
647    let ys = polynomial_indices.iter().map(|&id| {
648        ws.iter()
649            .map(|w| w.evaluate(&Scalar::from(id)))
650            .collect::<Vec<_>>()
651    });
652
653    // finally set the keys
654    let secret_keys = x.zip(ys).map(|(x, ys)| SecretKeyAuth { x, ys });
655
656    let keypairs = secret_keys
657        .zip(polynomial_indices.iter())
658        .map(|(secret_key, index)| {
659            let verification_key = secret_key.verification_key();
660            KeyPairAuth {
661                secret_key,
662                verification_key,
663                index: Some(*index),
664            }
665        })
666        .collect();
667
668    Ok(keypairs)
669}
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674
675    fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
676
677    fn assert_zeroize<T: Zeroize>() {}
678
679    #[test]
680    fn secret_key_is_zeroized() {
681        assert_zeroize::<SecretKeyAuth>();
682        assert_zeroize_on_drop::<SecretKeyAuth>();
683
684        assert_zeroize::<SecretKeyUser>();
685        assert_zeroize_on_drop::<SecretKeyUser>();
686    }
687}