ferveo_pre_release/
api.rs

1use std::{fmt, io};
2
3use ark_poly::{EvaluationDomain, GeneralEvaluationDomain};
4use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
5use ark_std::UniformRand;
6use bincode;
7use ferveo_common::serialization;
8use generic_array::{typenum::U48, GenericArray};
9use group_threshold_cryptography as tpke;
10use rand::RngCore;
11use serde::{Deserialize, Serialize};
12use serde_with::serde_as;
13pub use tpke::api::{
14    prepare_combine_simple, share_combine_precomputed, share_combine_simple,
15    Fr, G1Affine, G1Prepared, G2Affine, SecretBox, E,
16};
17
18pub type PublicKey = ferveo_common::PublicKey<E>;
19pub type Keypair = ferveo_common::Keypair<E>;
20pub type Validator = crate::Validator<E>;
21pub type Transcript = PubliclyVerifiableSS<E>;
22pub type ValidatorMessage = (Validator, Transcript);
23
24#[cfg(feature = "bindings-python")]
25use crate::bindings_python;
26#[cfg(feature = "bindings-wasm")]
27use crate::bindings_wasm;
28pub use crate::EthereumAddress;
29use crate::{
30    do_verify_aggregation, Error, PVSSMap, PubliclyVerifiableParams,
31    PubliclyVerifiableSS, Result,
32};
33
34pub type DecryptionSharePrecomputed = tpke::api::DecryptionSharePrecomputed;
35
36// Normally, we would use a custom trait for this, but we can't because
37// the arkworks will not let us create a blanket implementation for G1Affine
38// and Fr types. So instead, we're using this shared utility function:
39pub fn to_bytes<T: CanonicalSerialize>(item: &T) -> Result<Vec<u8>> {
40    let mut writer = Vec::new();
41    item.serialize_compressed(&mut writer)?;
42    Ok(writer)
43}
44
45pub fn from_bytes<T: CanonicalDeserialize>(bytes: &[u8]) -> Result<T> {
46    let mut reader = io::Cursor::new(bytes);
47    let item = T::deserialize_compressed(&mut reader)?;
48    Ok(item)
49}
50
51pub fn encrypt(
52    message: SecretBox<Vec<u8>>,
53    aad: &[u8],
54    pubkey: &DkgPublicKey,
55) -> Result<Ciphertext> {
56    let mut rng = rand::thread_rng();
57    let ciphertext = tpke::api::encrypt(message, aad, &pubkey.0, &mut rng)?;
58    Ok(Ciphertext(ciphertext))
59}
60
61pub fn decrypt_with_shared_secret(
62    ciphertext: &Ciphertext,
63    aad: &[u8],
64    shared_secret: &SharedSecret,
65) -> Result<Vec<u8>> {
66    let dkg_public_params = DkgPublicParameters::default();
67    tpke::api::decrypt_with_shared_secret(
68        &ciphertext.0,
69        aad,
70        &shared_secret.0,
71        &dkg_public_params.g1_inv,
72    )
73    .map_err(Error::from)
74}
75
76#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Eq)]
77pub struct Ciphertext(tpke::api::Ciphertext);
78
79impl Ciphertext {
80    pub fn header(&self) -> Result<CiphertextHeader> {
81        Ok(CiphertextHeader(self.0.header()?))
82    }
83
84    pub fn payload(&self) -> Vec<u8> {
85        self.0.payload()
86    }
87}
88
89#[serde_as]
90#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
91pub struct CiphertextHeader(tpke::api::CiphertextHeader);
92
93/// The ferveo variant to use for the decryption share derivation.
94#[derive(
95    PartialEq, Eq, Debug, Serialize, Deserialize, Copy, Clone, PartialOrd,
96)]
97pub enum FerveoVariant {
98    /// The simple variant requires m of n shares to decrypt
99    Simple,
100    /// The precomputed variant requires n of n shares to decrypt
101    Precomputed,
102}
103
104impl fmt::Display for FerveoVariant {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(f, "{}", self.as_str())
107    }
108}
109
110impl FerveoVariant {
111    pub fn as_str(&self) -> &'static str {
112        match self {
113            FerveoVariant::Simple => "FerveoVariant::Simple",
114            FerveoVariant::Precomputed => "FerveoVariant::Precomputed",
115        }
116    }
117
118    pub fn from_string(s: &str) -> Result<Self> {
119        match s {
120            "FerveoVariant::Simple" => Ok(FerveoVariant::Simple),
121            "FerveoVariant::Precomputed" => Ok(FerveoVariant::Precomputed),
122            _ => Err(Error::InvalidVariant(s.to_string())),
123        }
124    }
125}
126
127#[cfg(feature = "bindings-python")]
128impl From<bindings_python::FerveoVariant> for FerveoVariant {
129    fn from(variant: bindings_python::FerveoVariant) -> Self {
130        variant.0
131    }
132}
133
134#[cfg(feature = "bindings-wasm")]
135impl From<bindings_wasm::FerveoVariant> for FerveoVariant {
136    fn from(variant: bindings_wasm::FerveoVariant) -> Self {
137        variant.0
138    }
139}
140
141#[serde_as]
142#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
143pub struct DkgPublicKey(
144    #[serde_as(as = "serialization::SerdeAs")] pub(crate) G1Affine,
145);
146
147impl DkgPublicKey {
148    pub fn to_bytes(&self) -> Result<GenericArray<u8, U48>> {
149        let as_bytes = to_bytes(&self.0)?;
150        Ok(GenericArray::<u8, U48>::from_slice(&as_bytes).to_owned())
151    }
152
153    pub fn from_bytes(bytes: &[u8]) -> Result<DkgPublicKey> {
154        let bytes =
155            GenericArray::<u8, U48>::from_exact_iter(bytes.iter().cloned())
156                .ok_or_else(|| {
157                    Error::InvalidByteLength(
158                        Self::serialized_size(),
159                        bytes.len(),
160                    )
161                })?;
162        from_bytes(&bytes).map(DkgPublicKey)
163    }
164
165    pub fn serialized_size() -> usize {
166        48
167    }
168
169    /// Generate a random DKG public key.
170    /// Use this for testing only.
171    pub fn random() -> Self {
172        let mut rng = rand::thread_rng();
173        let g1 = G1Affine::rand(&mut rng);
174        Self(g1)
175    }
176}
177
178pub type UnblindingKey = FieldPoint;
179
180#[serde_as]
181#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
182pub struct FieldPoint(#[serde_as(as = "serialization::SerdeAs")] pub Fr);
183
184impl FieldPoint {
185    pub fn to_bytes(&self) -> Result<Vec<u8>> {
186        to_bytes(&self.0)
187    }
188
189    pub fn from_bytes(bytes: &[u8]) -> Result<FieldPoint> {
190        from_bytes(bytes).map(FieldPoint)
191    }
192}
193
194#[derive(Clone)]
195pub struct Dkg(crate::PubliclyVerifiableDkg<E>);
196
197impl Dkg {
198    pub fn new(
199        tau: u32,
200        shares_num: u32,
201        security_threshold: u32,
202        validators: &[Validator],
203        me: &Validator,
204    ) -> Result<Self> {
205        let dkg_params = crate::DkgParams {
206            tau,
207            security_threshold,
208            shares_num,
209        };
210        let dkg = crate::PubliclyVerifiableDkg::<E>::new(
211            validators,
212            &dkg_params,
213            me,
214        )?;
215        Ok(Self(dkg))
216    }
217
218    pub fn public_key(&self) -> DkgPublicKey {
219        DkgPublicKey(self.0.public_key())
220    }
221
222    pub fn generate_transcript<R: RngCore>(
223        &self,
224        rng: &mut R,
225    ) -> Result<Transcript> {
226        self.0.create_share(rng)
227    }
228
229    pub fn aggregate_transcripts(
230        &mut self,
231        messages: &[ValidatorMessage],
232    ) -> Result<AggregatedTranscript> {
233        // We must use `deal` here instead of to produce AggregatedTranscript instead of simply
234        // creating an AggregatedTranscript from the messages, because `deal` also updates the
235        // internal state of the DKG.
236        // If we didn't do that, that would cause the DKG to produce incorrect decryption shares
237        // in the future.
238        // TODO: Remove this dependency on DKG state
239        // TODO: Avoid mutating current state here
240        for (validator, transcript) in messages {
241            self.0.deal(validator, transcript)?;
242        }
243        Ok(AggregatedTranscript(crate::pvss::aggregate(&self.0.vss)))
244    }
245
246    pub fn public_params(&self) -> DkgPublicParameters {
247        DkgPublicParameters {
248            g1_inv: self.0.pvss_params.g_inv(),
249        }
250    }
251}
252
253fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap<E> {
254    let mut pvss_map: PVSSMap<E> = PVSSMap::new();
255    messages.iter().for_each(|(validator, transcript)| {
256        pvss_map.insert(validator.address.clone(), transcript.clone());
257    });
258    pvss_map
259}
260
261#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
262pub struct AggregatedTranscript(PubliclyVerifiableSS<E, crate::Aggregated>);
263
264impl AggregatedTranscript {
265    pub fn new(messages: &[ValidatorMessage]) -> Self {
266        let pvss_map = make_pvss_map(messages);
267        AggregatedTranscript(crate::pvss::aggregate(&pvss_map))
268    }
269
270    pub fn verify(
271        &self,
272        shares_num: u32,
273        messages: &[ValidatorMessage],
274    ) -> Result<bool> {
275        let pvss_params = PubliclyVerifiableParams::<E>::default();
276        let domain = GeneralEvaluationDomain::<Fr>::new(shares_num as usize)
277            .expect("Unable to construct an evaluation domain");
278
279        let is_valid_optimistic = self.0.verify_optimistic();
280        if !is_valid_optimistic {
281            return Err(Error::InvalidTranscriptAggregate);
282        }
283
284        let pvss_map = make_pvss_map(messages);
285        let validators: Vec<_> = messages
286            .iter()
287            .map(|(validator, _)| validator)
288            .cloned()
289            .collect();
290
291        // This check also includes `verify_full`. See impl. for details.
292        let is_valid = do_verify_aggregation(
293            &self.0.coeffs,
294            &self.0.shares,
295            &pvss_params,
296            &validators,
297            &domain,
298            &pvss_map,
299        )?;
300        Ok(is_valid)
301    }
302
303    pub fn create_decryption_share_precomputed(
304        &self,
305        dkg: &Dkg,
306        ciphertext_header: &CiphertextHeader,
307        aad: &[u8],
308        validator_keypair: &Keypair,
309    ) -> Result<DecryptionSharePrecomputed> {
310        let domain_points: Vec<_> = dkg
311            .0
312            .domain
313            .elements()
314            .take(dkg.0.dkg_params.shares_num as usize)
315            .collect();
316        self.0.make_decryption_share_simple_precomputed(
317            &ciphertext_header.0,
318            aad,
319            &validator_keypair.decryption_key,
320            dkg.0.me.share_index,
321            &domain_points,
322            &dkg.0.pvss_params.g_inv(),
323        )
324    }
325
326    pub fn create_decryption_share_simple(
327        &self,
328        dkg: &Dkg,
329        ciphertext_header: &CiphertextHeader,
330        aad: &[u8],
331        validator_keypair: &Keypair,
332    ) -> Result<DecryptionShareSimple> {
333        let share = self.0.make_decryption_share_simple(
334            &ciphertext_header.0,
335            aad,
336            &validator_keypair.decryption_key,
337            dkg.0.me.share_index,
338            &dkg.0.pvss_params.g_inv(),
339        )?;
340        Ok(DecryptionShareSimple {
341            share,
342            domain_point: dkg.0.domain.element(dkg.0.me.share_index),
343        })
344    }
345}
346
347#[serde_as]
348#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
349pub struct DecryptionShareSimple {
350    share: tpke::api::DecryptionShareSimple,
351    #[serde_as(as = "serialization::SerdeAs")]
352    domain_point: Fr,
353}
354
355#[serde_as]
356#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
357pub struct DkgPublicParameters {
358    #[serde_as(as = "serialization::SerdeAs")]
359    pub(crate) g1_inv: G1Prepared,
360}
361
362impl Default for DkgPublicParameters {
363    fn default() -> Self {
364        DkgPublicParameters {
365            g1_inv: PubliclyVerifiableParams::<E>::default().g_inv(),
366        }
367    }
368}
369
370impl DkgPublicParameters {
371    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
372        bincode::deserialize(bytes).map_err(|e| e.into())
373    }
374
375    pub fn to_bytes(&self) -> Result<Vec<u8>> {
376        bincode::serialize(self).map_err(|e| e.into())
377    }
378}
379
380pub fn combine_shares_simple(shares: &[DecryptionShareSimple]) -> SharedSecret {
381    // Pick domain points that are corresponding to the shares we have.
382    let domain_points: Vec<_> = shares.iter().map(|s| s.domain_point).collect();
383    let lagrange_coefficients = prepare_combine_simple::<E>(&domain_points);
384
385    let shares: Vec<_> = shares.iter().cloned().map(|s| s.share).collect();
386    let shared_secret =
387        share_combine_simple(&shares, &lagrange_coefficients[..]);
388    SharedSecret(shared_secret)
389}
390
391#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
392pub struct SharedSecret(pub tpke::api::SharedSecret<E>);
393
394#[cfg(test)]
395mod test_ferveo_api {
396    use itertools::izip;
397    use rand::{prelude::StdRng, SeedableRng};
398    use tpke::SecretBox;
399
400    use crate::{api::*, dkg::test_common::*};
401
402    type TestInputs = (Vec<ValidatorMessage>, Vec<Validator>, Vec<Keypair>);
403
404    fn make_test_inputs(
405        rng: &mut StdRng,
406        tau: u32,
407        security_threshold: u32,
408        shares_num: u32,
409    ) -> TestInputs {
410        let validator_keypairs = gen_keypairs(shares_num);
411        let validators = validator_keypairs
412            .iter()
413            .enumerate()
414            .map(|(i, keypair)| Validator {
415                address: gen_address(i),
416                public_key: keypair.public_key(),
417            })
418            .collect::<Vec<_>>();
419
420        // Each validator holds their own DKG instance and generates a transcript every
421        // every validator, including themselves
422        let messages: Vec<_> = validators
423            .iter()
424            .map(|sender| {
425                let dkg = Dkg::new(
426                    tau,
427                    shares_num,
428                    security_threshold,
429                    &validators,
430                    sender,
431                )
432                .unwrap();
433                (sender.clone(), dkg.generate_transcript(rng).unwrap())
434            })
435            .collect();
436        (messages, validators, validator_keypairs)
437    }
438
439    #[test]
440    fn test_dkg_pk_serialization() {
441        let dkg_pk = DkgPublicKey::random();
442        let serialized = dkg_pk.to_bytes().unwrap();
443        let deserialized = DkgPublicKey::from_bytes(&serialized).unwrap();
444        assert_eq!(dkg_pk, deserialized);
445    }
446
447    #[test]
448    fn test_server_api_tdec_precomputed() {
449        let rng = &mut StdRng::seed_from_u64(0);
450
451        // Works for both power of 2 and non-power of 2
452        for shares_num in [4, 7] {
453            let tau = 1;
454            // In precomputed variant, the security threshold is equal to the number of shares
455            // TODO: Refactor DKG constructor to not require security threshold or this case.
456            //  Or figure out a different way to simplify the precomputed variant API.
457            let security_threshold = shares_num;
458
459            let (messages, validators, validator_keypairs) =
460                make_test_inputs(rng, tau, security_threshold, shares_num);
461
462            // Now that every validator holds a dkg instance and a transcript for every other validator,
463            // every validator can aggregate the transcripts
464            let me = validators[0].clone();
465            let mut dkg =
466                Dkg::new(tau, shares_num, security_threshold, &validators, &me)
467                    .unwrap();
468
469            let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
470            assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
471
472            // At this point, any given validator should be able to provide a DKG public key
473            let dkg_public_key = dkg.public_key();
474
475            // In the meantime, the client creates a ciphertext and decryption request
476            let msg = "my-msg".as_bytes().to_vec();
477            let aad: &[u8] = "my-aad".as_bytes();
478            let ciphertext =
479                encrypt(SecretBox::new(msg.clone()), aad, &dkg_public_key)
480                    .unwrap();
481
482            // Having aggregated the transcripts, the validators can now create decryption shares
483            let decryption_shares: Vec<_> =
484                izip!(&validators, &validator_keypairs)
485                    .map(|(validator, validator_keypair)| {
486                        // Each validator holds their own instance of DKG and creates their own aggregate
487                        let mut dkg = Dkg::new(
488                            tau,
489                            shares_num,
490                            security_threshold,
491                            &validators,
492                            validator,
493                        )
494                        .unwrap();
495                        let aggregate =
496                            dkg.aggregate_transcripts(&messages).unwrap();
497                        assert!(pvss_aggregated
498                            .verify(shares_num, &messages)
499                            .unwrap());
500
501                        // And then each validator creates their own decryption share
502                        aggregate
503                            .create_decryption_share_precomputed(
504                                &dkg,
505                                &ciphertext.header().unwrap(),
506                                aad,
507                                validator_keypair,
508                            )
509                            .unwrap()
510                    })
511                    .collect();
512
513            // Now, the decryption share can be used to decrypt the ciphertext
514            // This part is part of the client API
515
516            let shared_secret = share_combine_precomputed(&decryption_shares);
517            let plaintext = decrypt_with_shared_secret(
518                &ciphertext,
519                aad,
520                &SharedSecret(shared_secret),
521            )
522            .unwrap();
523            assert_eq!(plaintext, msg);
524
525            // Since we're using a precomputed variant, we need all the shares to be able to decrypt
526            // So if we remove one share, we should not be able to decrypt
527            let decryption_shares =
528                decryption_shares[..shares_num as usize - 1].to_vec();
529
530            let shared_secret = share_combine_precomputed(&decryption_shares);
531            let result = decrypt_with_shared_secret(
532                &ciphertext,
533                aad,
534                &SharedSecret(shared_secret),
535            );
536            assert!(result.is_err());
537        }
538    }
539
540    #[test]
541    fn test_server_api_tdec_simple() {
542        let rng = &mut StdRng::seed_from_u64(0);
543
544        // Works for both power of 2 and non-power of 2
545        for shares_num in [4, 7] {
546            let tau = 1;
547            let security_threshold = shares_num / 2 + 1;
548
549            let (messages, validators, validator_keypairs) =
550                make_test_inputs(rng, tau, security_threshold, shares_num);
551
552            // Now that every validator holds a dkg instance and a transcript for every other validator,
553            // every validator can aggregate the transcripts
554            let mut dkg = Dkg::new(
555                tau,
556                shares_num,
557                security_threshold,
558                &validators,
559                &validators[0],
560            )
561            .unwrap();
562
563            let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
564            assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
565
566            // At this point, any given validator should be able to provide a DKG public key
567            let public_key = dkg.public_key();
568
569            // In the meantime, the client creates a ciphertext and decryption request
570            let msg = "my-msg".as_bytes().to_vec();
571            let aad: &[u8] = "my-aad".as_bytes();
572            let ciphertext =
573                encrypt(SecretBox::new(msg.clone()), aad, &public_key).unwrap();
574
575            // Having aggregated the transcripts, the validators can now create decryption shares
576            let decryption_shares: Vec<_> =
577                izip!(&validators, &validator_keypairs)
578                    .map(|(validator, validator_keypair)| {
579                        // Each validator holds their own instance of DKG and creates their own aggregate
580                        let mut dkg = Dkg::new(
581                            tau,
582                            shares_num,
583                            security_threshold,
584                            &validators,
585                            validator,
586                        )
587                        .unwrap();
588                        let aggregate =
589                            dkg.aggregate_transcripts(&messages).unwrap();
590                        assert!(aggregate
591                            .verify(shares_num, &messages)
592                            .unwrap());
593                        aggregate
594                            .create_decryption_share_simple(
595                                &dkg,
596                                &ciphertext.header().unwrap(),
597                                aad,
598                                validator_keypair,
599                            )
600                            .unwrap()
601                    })
602                    .collect();
603
604            // Now, the decryption share can be used to decrypt the ciphertext
605            // This part is part of the client API
606
607            // In simple variant, we only need `security_threshold` shares to be able to decrypt
608            let decryption_shares =
609                decryption_shares[..security_threshold as usize].to_vec();
610
611            let shared_secret = combine_shares_simple(&decryption_shares);
612            let plaintext =
613                decrypt_with_shared_secret(&ciphertext, aad, &shared_secret)
614                    .unwrap();
615            assert_eq!(plaintext, msg);
616
617            // Let's say that we've only received `security_threshold - 1` shares
618            // In this case, we should not be able to decrypt
619            let decryption_shares =
620                decryption_shares[..security_threshold as usize - 1].to_vec();
621
622            let shared_secret = combine_shares_simple(&decryption_shares);
623            let result =
624                decrypt_with_shared_secret(&ciphertext, aad, &shared_secret);
625            assert!(result.is_err());
626        }
627    }
628
629    #[test]
630    fn server_side_local_verification() {
631        let rng = &mut StdRng::seed_from_u64(0);
632
633        let tau = 1;
634        let security_threshold = 3;
635        let shares_num = 4;
636
637        let (messages, validators, _) =
638            make_test_inputs(rng, tau, security_threshold, shares_num);
639
640        // Now that every validator holds a dkg instance and a transcript for every other validator,
641        // every validator can aggregate the transcripts
642        let me = validators[0].clone();
643        let mut dkg =
644            Dkg::new(tau, shares_num, security_threshold, &validators, &me)
645                .unwrap();
646
647        let local_aggregate = dkg.aggregate_transcripts(&messages).unwrap();
648        assert!(local_aggregate
649            .verify(dkg.0.dkg_params.shares_num, &messages)
650            .is_ok());
651    }
652
653    #[test]
654    fn client_side_local_verification() {
655        let rng = &mut StdRng::seed_from_u64(0);
656
657        let tau = 1;
658        let security_threshold = 3;
659        let shares_num = 4;
660
661        let (messages, _, _) =
662            make_test_inputs(rng, tau, security_threshold, shares_num);
663
664        // We only need `security_threshold` transcripts to aggregate
665        let messages = &messages[..security_threshold as usize];
666
667        // Create an aggregated transcript on the client side
668        let aggregated_transcript = AggregatedTranscript::new(messages);
669
670        // We are separating the verification from the aggregation since the client may fetch
671        // the aggregate from a side-channel or decide to persist it and verify it later
672
673        // Now, the client can verify the aggregated transcript
674        let result = aggregated_transcript.verify(shares_num, messages);
675        assert!(result.is_ok());
676        assert!(result.unwrap());
677
678        // Test negative cases
679
680        // Not enough transcripts
681        let not_enough_messages = &messages[..2];
682        assert!(not_enough_messages.len() < security_threshold as usize);
683        let insufficient_aggregate =
684            AggregatedTranscript::new(not_enough_messages);
685        let result = insufficient_aggregate.verify(shares_num, messages);
686        assert!(result.is_err());
687
688        // Unexpected transcripts in the aggregate or transcripts from a different ritual
689        // Using same DKG parameters, but different DKG instances and validators
690        let (bad_messages, _, _) =
691            make_test_inputs(rng, tau, security_threshold, shares_num);
692        let mixed_messages = [&messages[..2], &bad_messages[..1]].concat();
693        let bad_aggregate = AggregatedTranscript::new(&mixed_messages);
694        let result = bad_aggregate.verify(shares_num, messages);
695        assert!(result.is_err());
696    }
697}