mithril_common/protocol/
multi_signer.rs

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