mithril_common/protocol/
multi_signer.rs

1use anyhow::Context;
2use mithril_stm::{AggregateSignatureType, Parameters};
3
4use crate::{
5    StdResult,
6    crypto_helper::{ProtocolAggregateVerificationKey, ProtocolClerk, ProtocolMultiSignature},
7    entities::SingleSignature,
8    protocol::ToMessage,
9};
10
11/// MultiSigner is the cryptographic engine in charge of producing multi-signatures from individual signatures
12pub struct MultiSigner {
13    protocol_clerk: ProtocolClerk,
14    protocol_parameters: Parameters,
15}
16
17impl MultiSigner {
18    pub(super) fn new(protocol_clerk: ProtocolClerk, protocol_parameters: Parameters) -> Self {
19        Self {
20            protocol_clerk,
21            protocol_parameters,
22        }
23    }
24
25    /// Aggregate the given single signatures into a multi-signature
26    pub fn aggregate_single_signatures<T: ToMessage>(
27        &self,
28        single_signatures: &[SingleSignature],
29        message: &T,
30        aggregate_signature_type: AggregateSignatureType,
31    ) -> StdResult<ProtocolMultiSignature> {
32        let protocol_signatures: Vec<_> = single_signatures
33            .iter()
34            .map(|single_signature| single_signature.to_protocol_signature())
35            .collect();
36
37        self.protocol_clerk
38            .aggregate_signatures_with_type(
39                &protocol_signatures,
40                message.to_message().as_bytes(),
41                aggregate_signature_type,
42            )
43            .map(|multi_sig| multi_sig.into())
44    }
45
46    /// Compute aggregate verification key from stake distribution
47    pub fn compute_aggregate_verification_key(&self) -> ProtocolAggregateVerificationKey {
48        self.protocol_clerk.compute_aggregate_verification_key().into()
49    }
50
51    /// Verify a single signature
52    pub fn verify_single_signature<T: ToMessage>(
53        &self,
54        message: &T,
55        single_signature: &SingleSignature,
56    ) -> StdResult<()> {
57        let protocol_signature = single_signature.to_protocol_signature();
58
59        let avk = self.compute_aggregate_verification_key();
60
61        // If there is no reg_party, then we simply received a signature from a non-registered
62        // party, and we can ignore the request.
63        let (vk, stake) = self
64            .protocol_clerk
65            .get_registered_party_for_index(&protocol_signature.signer_index)
66            .with_context(|| format!("Unregistered party: '{}'", single_signature.party_id))?;
67
68        protocol_signature
69            .verify(
70                &self.protocol_parameters,
71                &vk,
72                &stake,
73                &avk,
74                message.to_message().as_bytes(),
75            )
76            .with_context(|| {
77                format!(
78                    "Invalid signature for party: '{}'",
79                    single_signature.party_id
80                )
81            })?;
82
83        Ok(())
84    }
85}
86
87#[cfg(test)]
88mod test {
89    use mithril_stm::BlsSignatureError;
90
91    use crate::{
92        crypto_helper::ProtocolAggregationError,
93        entities::{ProtocolMessage, ProtocolMessagePartKey, ProtocolParameters},
94        protocol::SignerBuilder,
95        test::{
96            builder::{MithrilFixture, MithrilFixtureBuilder, StakeDistributionGenerationMethod},
97            double::fake_keys,
98        },
99    };
100
101    use super::*;
102
103    fn build_multi_signer(fixture: &MithrilFixture) -> MultiSigner {
104        SignerBuilder::new(
105            &fixture.signers_with_stake(),
106            &fixture.protocol_parameters(),
107        )
108        .unwrap()
109        .build_multi_signer()
110    }
111
112    #[test]
113    fn cant_aggregate_if_signatures_list_empty() {
114        let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
115        let multi_signer = build_multi_signer(&fixture);
116        let message = ProtocolMessage::default();
117
118        let error = multi_signer
119            .aggregate_single_signatures(&[], &message, AggregateSignatureType::default())
120            .expect_err(
121                "Multi-signature should not be created with an empty single signatures list",
122            );
123
124        assert!(
125            matches!(
126                error.downcast_ref::<ProtocolAggregationError>(),
127                Some(ProtocolAggregationError::NotEnoughSignatures(_, _))
128            ),
129            "Expected ProtocolAggregationError::NotEnoughSignatures, got: {error:?}"
130        )
131    }
132
133    #[test]
134    fn can_aggregate_if_valid_signatures_and_quorum_reached() {
135        let fixture = MithrilFixtureBuilder::default().with_signers(10).build();
136        let multi_signer = build_multi_signer(&fixture);
137        let message = ProtocolMessage::default();
138        let signatures: Vec<SingleSignature> = fixture
139            .signers_fixture()
140            .iter()
141            .map(|s| s.sign(&message).unwrap())
142            .collect();
143
144        multi_signer
145            .aggregate_single_signatures(&signatures, &message, AggregateSignatureType::default())
146            .expect("Multi-signature should be created");
147    }
148
149    #[test]
150    fn can_aggregate_even_with_one_invalid_signature_if_the_other_are_enough_for_the_quorum() {
151        let fixture = MithrilFixtureBuilder::default()
152            .with_signers(10)
153            .with_stake_distribution(StakeDistributionGenerationMethod::Uniform(20))
154            .with_protocol_parameters(ProtocolParameters::new(6, 200, 1.0))
155            .build();
156        let multi_signer = build_multi_signer(&fixture);
157        let message = ProtocolMessage::default();
158        let mut signatures: Vec<SingleSignature> = fixture
159            .signers_fixture()
160            .iter()
161            .map(|s| s.sign(&message).unwrap())
162            .collect();
163        signatures[4].signature = fake_keys::single_signature()[3].try_into().unwrap();
164
165        multi_signer
166            .aggregate_single_signatures(&signatures, &message, AggregateSignatureType::default())
167            .expect("Multi-signature should be created even with one invalid signature");
168    }
169
170    #[test]
171    fn verify_single_signature_fail_if_signature_signer_isnt_in_the_registered_parties() {
172        let multi_signer = build_multi_signer(
173            &MithrilFixtureBuilder::default()
174                .with_signers(1)
175                .with_stake_distribution(StakeDistributionGenerationMethod::RandomDistribution {
176                    seed: [3u8; 32],
177                    min_stake: 1,
178                })
179                .build(),
180        );
181        let fixture = MithrilFixtureBuilder::default().with_signers(1).build();
182        let message = ProtocolMessage::default();
183        let single_signature = fixture.signers_fixture().last().unwrap().sign(&message).unwrap();
184
185        // Will fail because the single signature was issued by a signer from a stake distribution
186        // that is not the one used by the multi-signer.
187        let error = multi_signer
188            .verify_single_signature(&message, &single_signature)
189            .expect_err(
190                "Verify single signature should fail if the signer isn't in the registered parties",
191            );
192
193        match error.downcast_ref::<BlsSignatureError>() {
194            Some(BlsSignatureError::SignatureInvalid(_)) => (),
195            _ => panic!("Expected an SignatureInvalid error, got: {error:?}"),
196        }
197    }
198
199    #[test]
200    fn verify_single_signature_fail_if_signature_signed_message_isnt_the_given_one() {
201        let fixture = MithrilFixtureBuilder::default().with_signers(1).build();
202        let multi_signer = build_multi_signer(&fixture);
203        let mut signed_message = ProtocolMessage::default();
204        signed_message.set_message_part(
205            ProtocolMessagePartKey::SnapshotDigest,
206            "a_digest".to_string(),
207        );
208        let single_signature = fixture
209            .signers_fixture()
210            .first()
211            .unwrap()
212            .sign(&signed_message)
213            .unwrap();
214
215        let error = multi_signer
216            .verify_single_signature(&ProtocolMessage::default(), &single_signature)
217            .expect_err("Verify single signature should fail");
218
219        match error.downcast_ref::<BlsSignatureError>() {
220            Some(BlsSignatureError::SignatureInvalid(_)) => (),
221            _ => panic!("Expected an SignatureInvalid error, got: {error:?}"),
222        }
223    }
224
225    #[test]
226    fn can_verify_valid_single_signature() {
227        let fixture = MithrilFixtureBuilder::default().with_signers(1).build();
228        let multi_signer = build_multi_signer(&fixture);
229        let message = ProtocolMessage::default();
230        let single_signature = fixture.signers_fixture().first().unwrap().sign(&message).unwrap();
231
232        multi_signer
233            .verify_single_signature(&message, &single_signature)
234            .expect("Verify single signature should succeed");
235    }
236}