commonware_broadcast/linked/
prover.rs

1//! Generate and verify proofs of broadcast.
2//!
3//! The proofs contain threshold signatures of signers that have seen and validated a chunk.
4
5use super::{namespace, parsed, serializer, Context, Epoch};
6use crate::Proof;
7use bytes::{Buf, BufMut};
8use commonware_cryptography::{
9    bls12381::primitives::{
10        group::{self, Element},
11        ops,
12    },
13    Array, Scheme,
14};
15use commonware_utils::SizedSerialize;
16use std::marker::PhantomData;
17
18/// Encode and decode proofs of broadcast.
19///
20/// We don't use protobuf for proof encoding because we expect external parties
21/// to decode proofs in constrained environments where protobuf may not be implemented.
22#[derive(Clone)]
23pub struct Prover<C: Scheme, D: Array> {
24    _crypto: PhantomData<C>,
25    _digest: PhantomData<D>,
26    namespace: Vec<u8>,
27    public: group::Public,
28}
29
30impl<C: Scheme, D: Array> Prover<C, D> {
31    /// The length of a serialized proof.
32    const PROOF_LEN: usize = C::PublicKey::SERIALIZED_LEN
33        + u64::SERIALIZED_LEN
34        + D::SERIALIZED_LEN
35        + u64::SERIALIZED_LEN
36        + group::SIGNATURE_LENGTH;
37
38    /// Create a new prover with the given signing `namespace`.
39    pub fn new(namespace: &[u8], public: group::Public) -> Self {
40        Self {
41            _crypto: PhantomData,
42            _digest: PhantomData,
43            namespace: namespace::ack(namespace),
44            public,
45        }
46    }
47
48    /// Generate a proof for the given `context`, `payload`, `epoch`, and `threshold`.
49    pub fn serialize_threshold(
50        context: &Context<C::PublicKey>,
51        payload: &D,
52        epoch: Epoch,
53        threshold: &group::Signature,
54    ) -> Proof {
55        let mut proof = Vec::with_capacity(Self::PROOF_LEN);
56
57        // Encode proof
58        proof.extend_from_slice(&context.sequencer);
59        proof.put_u64(context.height);
60        proof.extend_from_slice(payload);
61        proof.put_u64(epoch);
62        proof.extend_from_slice(&threshold.serialize());
63        let result: Proof = proof.into();
64
65        // Ensure proof is the right size
66        assert!(result.len() == Self::PROOF_LEN);
67        result
68    }
69
70    /// Deserialize a proof into a `context`, `payload`, `epoch`, and `threshold`.
71    /// Returns `None` if the proof is invalid.
72    pub fn deserialize_threshold(
73        &self,
74        mut proof: Proof,
75    ) -> Option<(Context<C::PublicKey>, D, Epoch, group::Signature)> {
76        // Ensure proof is the right size
77        if proof.len() != Self::PROOF_LEN {
78            return None;
79        }
80
81        // Decode proof
82        let sequencer = C::PublicKey::read_from(&mut proof).ok()?;
83        let height = proof.get_u64();
84        let Ok(payload) = D::read_from(&mut proof) else {
85            return None;
86        };
87        let epoch = proof.get_u64();
88        let threshold = proof.copy_to_bytes(group::SIGNATURE_LENGTH);
89        let threshold = group::Signature::deserialize(&threshold)?;
90
91        // Verify signature
92        let chunk = parsed::Chunk {
93            sequencer: sequencer.clone(),
94            height,
95            payload: payload.clone(),
96        };
97        let msg = serializer::ack(&chunk, epoch);
98        if ops::verify_message(&self.public, Some(&self.namespace), &msg, &threshold).is_err() {
99            return None;
100        }
101
102        Some((Context { sequencer, height }, payload, epoch, threshold))
103    }
104}