commonware_consensus/threshold_simplex/
prover.rs

1use super::{
2    encoder::{
3        finalize_namespace, notarize_namespace, nullify_message, nullify_namespace,
4        proposal_message, seed_message, seed_namespace,
5    },
6    wire, View,
7};
8use crate::Proof;
9use bytes::{Buf, BufMut};
10use commonware_cryptography::bls12381::primitives::{
11    group::{self, Element},
12    ops,
13    poly::{self, Eval},
14};
15use commonware_utils::{Array, SizedSerialize};
16use std::marker::PhantomData;
17
18type Callback = Box<dyn Fn(&poly::Poly<group::Public>) -> Option<u32>>;
19
20pub struct Verifier {
21    callback: Callback,
22}
23
24impl Verifier {
25    fn new<F>(callback: F) -> Self
26    where
27        F: Fn(&poly::Poly<group::Public>) -> Option<u32> + 'static,
28    {
29        Self {
30            callback: Box::new(callback),
31        }
32    }
33
34    pub fn verify(self, identity: &poly::Poly<group::Public>) -> Option<u32> {
35        (self.callback)(identity)
36    }
37}
38
39/// Encode and decode proofs of activity.
40///
41/// We don't use protobuf for proof encoding because we expect external parties
42/// to decode proofs in constrained environments where protobuf may not be implemented.
43#[derive(Clone)]
44pub struct Prover<D: Array> {
45    public: group::Public,
46
47    seed_namespace: Vec<u8>,
48    notarize_namespace: Vec<u8>,
49    nullify_namespace: Vec<u8>,
50    finalize_namespace: Vec<u8>,
51
52    _digest: PhantomData<D>,
53}
54
55/// If we expose partial signatures of proofs, can be used to construct a partial signature
56/// over pre-aggregated data (where the public key of each index can be derived from the group
57/// polynomial). This can be very useful for distributing rewards without including all partial signatures
58/// in a block.
59impl<D: Array> Prover<D> {
60    /// Create a new prover with the given signing `namespace`.
61    pub fn new(public: group::Public, namespace: &[u8]) -> Self {
62        Self {
63            public,
64
65            seed_namespace: seed_namespace(namespace),
66            notarize_namespace: notarize_namespace(namespace),
67            nullify_namespace: nullify_namespace(namespace),
68            finalize_namespace: finalize_namespace(namespace),
69
70            _digest: PhantomData,
71        }
72    }
73
74    /// Serialize a proposal proof.
75    pub fn serialize_proposal(proposal: &wire::Proposal, partial_signature: &[u8]) -> Proof {
76        // Setup proof
77        let len = u64::SERIALIZED_LEN
78            + u64::SERIALIZED_LEN
79            + D::SERIALIZED_LEN
80            + poly::PARTIAL_SIGNATURE_LENGTH;
81
82        // Encode proof
83        let mut proof = Vec::with_capacity(len);
84        proof.put_u64(proposal.view);
85        proof.put_u64(proposal.parent);
86        proof.extend_from_slice(&proposal.payload);
87        proof.extend_from_slice(partial_signature);
88        proof.into()
89    }
90
91    /// Deserialize a proposal proof.
92    fn deserialize_proposal(
93        &self,
94        mut proof: Proof,
95        namespace: &[u8],
96    ) -> Option<(View, View, D, Verifier)> {
97        // Ensure proof is big enough
98        let expected_len = u64::SERIALIZED_LEN
99            + u64::SERIALIZED_LEN
100            + D::SERIALIZED_LEN
101            + poly::PARTIAL_SIGNATURE_LENGTH;
102        if proof.len() != expected_len {
103            return None;
104        }
105
106        // Decode proof
107        let view = proof.get_u64();
108        let parent = proof.get_u64();
109        let payload = D::read_from(&mut proof).ok()?;
110        let signature = proof.copy_to_bytes(poly::PARTIAL_SIGNATURE_LENGTH);
111        let signature = poly::Eval::deserialize(&signature)?;
112
113        // Create callback
114        let proposal_message = proposal_message(view, parent, &payload);
115        let namespace = namespace.to_vec();
116        let callback = move |identity: &poly::Poly<group::Public>| -> Option<u32> {
117            if ops::partial_verify_message(
118                identity,
119                Some(&namespace),
120                &proposal_message,
121                &signature,
122            )
123            .is_err()
124            {
125                return None;
126            }
127            Some(signature.index)
128        };
129        Some((view, parent, payload, Verifier::new(callback)))
130    }
131
132    /// Serialize an aggregation proof.
133    pub fn serialize_threshold(proposal: &wire::Proposal, signature: &[u8], seed: &[u8]) -> Proof {
134        // Setup proof
135        let len = u64::SERIALIZED_LEN
136            + u64::SERIALIZED_LEN
137            + D::SERIALIZED_LEN
138            + group::SIGNATURE_LENGTH
139            + group::SIGNATURE_LENGTH;
140
141        // Encode proof
142        let mut proof = Vec::with_capacity(len);
143        proof.put_u64(proposal.view);
144        proof.put_u64(proposal.parent);
145        proof.extend_from_slice(&proposal.payload);
146        proof.extend_from_slice(signature);
147        proof.extend_from_slice(seed);
148        proof.into()
149    }
150
151    /// Deserialize an aggregation proof.
152    fn deserialize_threshold(
153        &self,
154        mut proof: Proof,
155        namespace: &[u8],
156    ) -> Option<(View, View, D, group::Signature, group::Signature)> {
157        // Ensure proof prefix is big enough
158        let expected_len = u64::SERIALIZED_LEN
159            + u64::SERIALIZED_LEN
160            + D::SERIALIZED_LEN
161            + group::SIGNATURE_LENGTH
162            + group::SIGNATURE_LENGTH;
163        if proof.len() != expected_len {
164            return None;
165        }
166
167        // Verify signature
168        let view = proof.get_u64();
169        let parent = proof.get_u64();
170        let payload = D::read_from(&mut proof).ok()?;
171        let message = proposal_message(view, parent, &payload);
172        let signature = proof.copy_to_bytes(group::SIGNATURE_LENGTH);
173        let signature = group::Signature::deserialize(&signature)?;
174        if ops::verify_message(&self.public, Some(namespace), &message, &signature).is_err() {
175            return None;
176        }
177
178        // Verify seed
179        let message = seed_message(view);
180        let seed = proof.copy_to_bytes(group::SIGNATURE_LENGTH);
181        let seed = group::Signature::deserialize(&seed)?;
182        if ops::verify_message(&self.public, Some(&self.seed_namespace), &message, &seed).is_err() {
183            return None;
184        }
185        Some((view, parent, payload, signature, seed))
186    }
187
188    /// Deserialize a notarize proof.
189    pub fn deserialize_notarize(&self, proof: Proof) -> Option<(View, View, D, Verifier)> {
190        Self::deserialize_proposal(self, proof, &self.notarize_namespace)
191    }
192
193    /// Deserialize a notarization proof.
194    pub fn deserialize_notarization(
195        &self,
196        proof: Proof,
197    ) -> Option<(View, View, D, group::Signature, group::Signature)> {
198        self.deserialize_threshold(proof, &self.notarize_namespace)
199    }
200
201    /// Deserialize a finalize proof.
202    pub fn deserialize_finalize(&self, proof: Proof) -> Option<(View, View, D, Verifier)> {
203        self.deserialize_proposal(proof, &self.finalize_namespace)
204    }
205
206    /// Deserialize a finalization proof.
207    pub fn deserialize_finalization(
208        &self,
209        proof: Proof,
210    ) -> Option<(View, View, D, group::Signature, group::Signature)> {
211        self.deserialize_threshold(proof, &self.finalize_namespace)
212    }
213
214    #[allow(clippy::too_many_arguments)]
215    pub fn serialize_conflicting_proposal(
216        view: View,
217        parent_1: View,
218        payload_1: &D,
219        signature_1: &[u8],
220        parent_2: View,
221        payload_2: &D,
222        signature_2: &[u8],
223    ) -> Proof {
224        // Setup proof
225        let len = u64::SERIALIZED_LEN
226            + u64::SERIALIZED_LEN
227            + D::SERIALIZED_LEN
228            + poly::PARTIAL_SIGNATURE_LENGTH
229            + u64::SERIALIZED_LEN
230            + D::SERIALIZED_LEN
231            + poly::PARTIAL_SIGNATURE_LENGTH;
232
233        // Encode proof
234        let mut proof = Vec::with_capacity(len);
235        proof.put_u64(view);
236        proof.put_u64(parent_1);
237        proof.extend_from_slice(payload_1);
238        proof.extend_from_slice(signature_1);
239        proof.put_u64(parent_2);
240        proof.extend_from_slice(payload_2);
241        proof.extend_from_slice(signature_2);
242        proof.into()
243    }
244
245    fn deserialize_conflicting_proposal(
246        &self,
247        mut proof: Proof,
248        namespace: &[u8],
249    ) -> Option<(View, Verifier)> {
250        // Ensure proof is big enough
251        let expected_len = u64::SERIALIZED_LEN
252            + u64::SERIALIZED_LEN
253            + D::SERIALIZED_LEN
254            + poly::PARTIAL_SIGNATURE_LENGTH
255            + u64::SERIALIZED_LEN
256            + D::SERIALIZED_LEN
257            + poly::PARTIAL_SIGNATURE_LENGTH;
258        if proof.len() != expected_len {
259            return None;
260        }
261
262        // Decode proof
263        let view = proof.get_u64();
264        let parent_1 = proof.get_u64();
265        let payload_1 = D::read_from(&mut proof).ok()?;
266        let signature_1 = proof.copy_to_bytes(poly::PARTIAL_SIGNATURE_LENGTH);
267        let signature_1 = Eval::deserialize(&signature_1)?;
268        let parent_2 = proof.get_u64();
269        let payload_2 = D::read_from(&mut proof).ok()?;
270        let signature_2 = proof.copy_to_bytes(poly::PARTIAL_SIGNATURE_LENGTH);
271        let signature_2 = Eval::deserialize(&signature_2)?;
272        if signature_1.index != signature_2.index {
273            return None;
274        }
275
276        // Create callback
277        let namespace = namespace.to_vec();
278        let callback = move |identity: &poly::Poly<group::Public>| -> Option<u32> {
279            if ops::partial_verify_message(
280                identity,
281                Some(&namespace),
282                &proposal_message(view, parent_1, &payload_1),
283                &signature_1,
284            )
285            .is_err()
286            {
287                return None;
288            }
289            if ops::partial_verify_message(
290                identity,
291                Some(&namespace),
292                &proposal_message(view, parent_2, &payload_2),
293                &signature_2,
294            )
295            .is_err()
296            {
297                return None;
298            }
299            Some(signature_1.index)
300        };
301        Some((view, Verifier::new(callback)))
302    }
303
304    /// Serialize a conflicting notarize proof.
305    #[allow(clippy::too_many_arguments)]
306    pub fn serialize_conflicting_notarize(
307        view: View,
308        parent_1: View,
309        payload_1: &D,
310        signature_1: &[u8],
311        parent_2: View,
312        payload_2: &D,
313        signature_2: &[u8],
314    ) -> Proof {
315        Self::serialize_conflicting_proposal(
316            view,
317            parent_1,
318            payload_1,
319            signature_1,
320            parent_2,
321            payload_2,
322            signature_2,
323        )
324    }
325
326    /// Deserialize a conflicting notarization proof.
327    pub fn deserialize_conflicting_notarize(&self, proof: Proof) -> Option<(View, Verifier)> {
328        self.deserialize_conflicting_proposal(proof, &self.notarize_namespace)
329    }
330
331    /// Serialize a conflicting finalize proof.
332    #[allow(clippy::too_many_arguments)]
333    pub fn serialize_conflicting_finalize(
334        view: View,
335        parent_1: View,
336        payload_1: &D,
337        signature_1: &[u8],
338        parent_2: View,
339        payload_2: &D,
340        signature_2: &[u8],
341    ) -> Proof {
342        Self::serialize_conflicting_proposal(
343            view,
344            parent_1,
345            payload_1,
346            signature_1,
347            parent_2,
348            payload_2,
349            signature_2,
350        )
351    }
352
353    /// Deserialize a conflicting finalization proof.
354    pub fn deserialize_conflicting_finalize(&self, proof: Proof) -> Option<(View, Verifier)> {
355        self.deserialize_conflicting_proposal(proof, &self.finalize_namespace)
356    }
357
358    /// Serialize a conflicting nullify and finalize proof.
359    pub fn serialize_nullify_finalize(
360        view: View,
361        parent: View,
362        payload: &D,
363        signature_finalize: &[u8],
364        signature_null: &[u8],
365    ) -> Proof {
366        // Setup proof
367        let len = u64::SERIALIZED_LEN
368            + u64::SERIALIZED_LEN
369            + D::SERIALIZED_LEN
370            + poly::PARTIAL_SIGNATURE_LENGTH
371            + poly::PARTIAL_SIGNATURE_LENGTH;
372
373        // Encode proof
374        let mut proof = Vec::with_capacity(len);
375        proof.put_u64(view);
376        proof.put_u64(parent);
377        proof.extend_from_slice(payload);
378        proof.extend_from_slice(signature_finalize);
379        proof.extend_from_slice(signature_null);
380        proof.into()
381    }
382
383    /// Deserialize a conflicting nullify and finalize proof.
384    pub fn deserialize_nullify_finalize(&self, mut proof: Proof) -> Option<(View, Verifier)> {
385        // Ensure proof is big enough
386        let expected_len = u64::SERIALIZED_LEN
387            + u64::SERIALIZED_LEN
388            + D::SERIALIZED_LEN
389            + poly::PARTIAL_SIGNATURE_LENGTH
390            + poly::PARTIAL_SIGNATURE_LENGTH;
391        if proof.len() != expected_len {
392            return None;
393        }
394
395        // Decode proof
396        let view = proof.get_u64();
397        let parent = proof.get_u64();
398        let payload = D::read_from(&mut proof).ok()?;
399        let signature_finalize = proof.copy_to_bytes(poly::PARTIAL_SIGNATURE_LENGTH);
400        let signature_finalize = Eval::deserialize(&signature_finalize)?;
401        let signature_null = proof.copy_to_bytes(poly::PARTIAL_SIGNATURE_LENGTH);
402        let signature_null = Eval::deserialize(&signature_null)?;
403        if signature_finalize.index != signature_null.index {
404            return None;
405        }
406
407        // Create callback
408        let finalize_namespace = self.finalize_namespace.clone();
409        let nullify_namespace = self.nullify_namespace.clone();
410        let callback = move |identity: &poly::Poly<group::Public>| -> Option<u32> {
411            if ops::partial_verify_message(
412                identity,
413                Some(&finalize_namespace),
414                &proposal_message(view, parent, &payload),
415                &signature_finalize,
416            )
417            .is_err()
418            {
419                return None;
420            }
421            if ops::partial_verify_message(
422                identity,
423                Some(&nullify_namespace),
424                &nullify_message(view),
425                &signature_null,
426            )
427            .is_err()
428            {
429                return None;
430            }
431            Some(signature_finalize.index)
432        };
433        Some((view, Verifier::new(callback)))
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440    use commonware_cryptography::{
441        bls12381::{
442            dkg::ops::generate_shares,
443            primitives::group::{self, Share},
444        },
445        sha256::Digest as Sha256Digest,
446        Hasher, Sha256,
447    };
448    use ops::{keypair, partial_sign_message, sign_message};
449    use rand::{rngs::StdRng, SeedableRng};
450
451    fn generate_threshold() -> (group::Public, poly::Public, Vec<Share>) {
452        let mut sampler = StdRng::seed_from_u64(0);
453        let (public, shares) = generate_shares(&mut sampler, None, 4, 3);
454        (poly::public(&public), public, shares)
455    }
456
457    fn generate_keypair() -> (group::Private, group::Public) {
458        let mut sampler = StdRng::seed_from_u64(0);
459        keypair(&mut sampler)
460    }
461
462    fn test_digest(value: u8) -> Sha256Digest {
463        let mut hasher = Sha256::new();
464        hasher.update(&[value]);
465        hasher.finalize()
466    }
467
468    #[test]
469    fn test_deserialize_proposal() {
470        // Create valid signature
471        let (public, poly, shares) = generate_threshold();
472        let prover = Prover::<Sha256Digest>::new(public, b"test");
473        let payload = test_digest(0);
474        let signature = partial_sign_message(
475            &shares[0],
476            Some(&prover.seed_namespace),
477            &proposal_message(1, 0, &payload),
478        )
479        .serialize();
480
481        // Create a proof with a length that would cause overflow
482        let mut proof = Vec::new();
483        proof.put_u64(1); // view
484        proof.put_u64(0); // parent
485        proof.extend_from_slice(&payload); // payload
486        proof.extend_from_slice(&signature); // signature
487
488        // Verify correct proof
489        let (_, _, _, verifier) = prover
490            .deserialize_proposal(proof.into(), &prover.notarize_namespace)
491            .unwrap();
492        assert!(verifier.verify(&poly).is_none());
493    }
494
495    #[test]
496    fn test_deserialize_proposal_invalid() {
497        // Create valid signature
498        let (public, poly, shares) = generate_threshold();
499        let prover = Prover::<Sha256Digest>::new(public, b"test");
500        let payload = test_digest(0);
501        let signature = partial_sign_message(
502            &shares[0],
503            Some(&prover.seed_namespace),
504            &proposal_message(1, 1, &payload),
505        )
506        .serialize();
507
508        // Create a proof with a length that would cause overflow
509        let mut proof = Vec::new();
510        proof.put_u64(1); // view
511        proof.put_u64(0); // parent
512        proof.extend_from_slice(&payload); // payload
513        proof.extend_from_slice(&signature); // invalid signature
514
515        // Verify bad signature
516        let (_, _, _, verifier) = prover
517            .deserialize_proposal(proof.into(), &prover.notarize_namespace)
518            .unwrap();
519        assert!(verifier.verify(&poly).is_none());
520    }
521
522    #[test]
523    fn test_deserialize_proposal_underflow() {
524        // Create valid signature
525        let (public, _, shares) = generate_threshold();
526        let prover = Prover::<Sha256Digest>::new(public, b"test");
527        let payload = test_digest(0);
528        let signature = partial_sign_message(
529            &shares[0],
530            Some(&prover.seed_namespace),
531            &proposal_message(1, 0, &payload),
532        )
533        .serialize();
534
535        // Shorten signature
536        let signature = signature[0..group::SIGNATURE_LENGTH - 1].to_vec();
537
538        // Create a proof with a length that would cause overflow
539        let mut proof = Vec::new();
540        proof.put_u64(1); // view
541        proof.put_u64(0); // parent
542        proof.extend_from_slice(&payload); // payload
543        proof.extend_from_slice(&signature); // undersized signature
544
545        // Verify bad proof
546        let result = prover.deserialize_proposal(proof.into(), &prover.notarize_namespace);
547        assert!(result.is_none());
548    }
549
550    #[test]
551    fn test_deserialize_proposal_overflow() {
552        // Create valid signature
553        let (public, _, shares) = generate_threshold();
554        let prover = Prover::<Sha256Digest>::new(public, b"test");
555        let payload = test_digest(0);
556        let signature = partial_sign_message(
557            &shares[0],
558            Some(&prover.seed_namespace),
559            &proposal_message(1, 0, &payload),
560        )
561        .serialize();
562
563        // Extend signature
564        let signature = [signature, vec![0; 1]].concat();
565
566        // Create a proof with a length that would cause overflow
567        let mut proof = Vec::new();
568        proof.put_u64(1); // view
569        proof.put_u64(0); // parent
570        proof.extend_from_slice(&payload); // payload
571        proof.extend_from_slice(&signature); // oversized signature
572
573        // Verify bad proof
574        let result = prover.deserialize_proposal(proof.into(), &prover.notarize_namespace);
575        assert!(result.is_none());
576    }
577
578    #[test]
579    fn test_deserialize_threshold() {
580        // Create valid signature
581        let (private, public) = generate_keypair();
582        let prover = Prover::<Sha256Digest>::new(public, b"test");
583
584        // Generate a valid signature
585        let payload = test_digest(0);
586        let proposal_signature = sign_message(
587            &private,
588            Some(&prover.notarize_namespace),
589            &proposal_message(1, 0, &payload),
590        )
591        .serialize();
592        let seed_signature =
593            sign_message(&private, Some(&prover.seed_namespace), &seed_message(1)).serialize();
594
595        // Create a proof with a length that would cause overflow
596        let mut proof = Vec::new();
597        proof.put_u64(1); // view
598        proof.put_u64(0); // parent
599        proof.extend_from_slice(&payload); // payload
600        proof.extend_from_slice(&proposal_signature); // proposal signature
601        proof.extend_from_slice(&seed_signature); // seed signature
602
603        // Verify correct proof
604        let result = prover.deserialize_threshold(proof.into(), &prover.notarize_namespace);
605        assert!(result.is_some());
606    }
607
608    #[test]
609    fn test_deserialize_threshold_invalid() {
610        // Create valid signature
611        let (private, public) = generate_keypair();
612        let prover = Prover::<Sha256Digest>::new(public, b"test");
613
614        // Generate a valid signature
615        let payload = test_digest(0);
616        let proposal_signature = sign_message(
617            &private,
618            Some(&prover.notarize_namespace),
619            &proposal_message(1, 0, &payload),
620        )
621        .serialize();
622        let seed_signature =
623            sign_message(&private, Some(&prover.seed_namespace), &seed_message(2)).serialize();
624
625        // Create a proof with a length that would cause overflow
626        let mut proof = Vec::new();
627        proof.put_u64(1); // view
628        proof.put_u64(0); // parent
629        proof.extend_from_slice(&payload); // payload
630        proof.extend_from_slice(&proposal_signature); // proposal signature
631        proof.extend_from_slice(&seed_signature); // invalid signature
632
633        // Verify correct proof
634        let result = prover.deserialize_threshold(proof.into(), &prover.notarize_namespace);
635        assert!(result.is_none());
636    }
637
638    #[test]
639    fn test_deserialize_threshold_underflow() {
640        // Create valid signature
641        let (private, public) = generate_keypair();
642        let prover = Prover::<Sha256Digest>::new(public, b"test");
643
644        // Generate a valid signature
645        let payload = test_digest(0);
646        let proposal_signature = sign_message(
647            &private,
648            Some(&prover.notarize_namespace),
649            &proposal_message(1, 0, &payload),
650        )
651        .serialize();
652        let seed_signature =
653            sign_message(&private, Some(&prover.seed_namespace), &seed_message(1)).serialize();
654
655        // Shorten seed signature
656        let seed_signature = seed_signature[0..group::SIGNATURE_LENGTH - 1].to_vec();
657
658        // Create a proof with a length that would cause overflow
659        let mut proof = Vec::new();
660        proof.put_u64(1); // view
661        proof.put_u64(0); // parent
662        proof.extend_from_slice(&payload); // payload
663        proof.extend_from_slice(&proposal_signature); // proposal signature
664        proof.extend_from_slice(&seed_signature); // undersized signature
665
666        // Verify correct proof
667        let result = prover.deserialize_threshold(proof.into(), &prover.notarize_namespace);
668        assert!(result.is_none());
669    }
670
671    #[test]
672    fn test_deserialize_threshold_overflow() {
673        // Create valid signature
674        let (private, public) = generate_keypair();
675        let prover = Prover::<Sha256Digest>::new(public, b"test");
676
677        // Generate a valid signature
678        let payload = test_digest(0);
679        let proposal_signature = sign_message(
680            &private,
681            Some(&prover.notarize_namespace),
682            &proposal_message(1, 0, &payload),
683        )
684        .serialize();
685        let seed_signature =
686            sign_message(&private, Some(&prover.seed_namespace), &seed_message(1)).serialize();
687
688        // Extend seed signature
689        let seed_signature = [seed_signature, vec![0; 1]].concat();
690
691        // Create a proof with a length that would cause overflow
692        let mut proof = Vec::new();
693        proof.put_u64(1); // view
694        proof.put_u64(0); // parent
695        proof.extend_from_slice(&payload); // payload
696        proof.extend_from_slice(&proposal_signature); // proposal signature
697        proof.extend_from_slice(&seed_signature); // oversized signature
698
699        // Verify correct proof
700        let result = prover.deserialize_threshold(proof.into(), &prover.notarize_namespace);
701        assert!(result.is_none());
702    }
703}