mithril_common/protocol/
multi_signer.rs

1use anyhow::{Context, anyhow};
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            .ok_or_else(|| {
67                anyhow!(format!(
68                    "Unregistered party: '{}'",
69                    single_signature.party_id
70                ))
71            })?;
72
73        protocol_signature
74            .verify(
75                &self.protocol_parameters,
76                &vk,
77                &stake,
78                &avk,
79                message.to_message().as_bytes(),
80            )
81            .with_context(|| {
82                format!(
83                    "Invalid signature for party: '{}'",
84                    single_signature.party_id
85                )
86            })?;
87
88        Ok(())
89    }
90}
91
92#[cfg(test)]
93mod test {
94    use mithril_stm::MultiSignatureError;
95
96    use crate::{
97        crypto_helper::ProtocolAggregationError,
98        entities::{ProtocolMessage, ProtocolMessagePartKey, ProtocolParameters},
99        protocol::SignerBuilder,
100        test::{
101            builder::{MithrilFixture, MithrilFixtureBuilder, StakeDistributionGenerationMethod},
102            double::fake_keys,
103        },
104    };
105
106    use super::*;
107
108    fn build_multi_signer(fixture: &MithrilFixture) -> MultiSigner {
109        SignerBuilder::new(
110            &fixture.signers_with_stake(),
111            &fixture.protocol_parameters(),
112        )
113        .unwrap()
114        .build_multi_signer()
115    }
116
117    #[test]
118    fn cant_aggregate_if_signatures_list_empty() {
119        let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
120        let multi_signer = build_multi_signer(&fixture);
121        let message = ProtocolMessage::default();
122
123        let error = multi_signer
124            .aggregate_single_signatures(&[], &message, AggregateSignatureType::default())
125            .expect_err(
126                "Multi-signature should not be created with an empty single signatures list",
127            );
128
129        assert!(
130            matches!(
131                error.downcast_ref::<ProtocolAggregationError>(),
132                Some(ProtocolAggregationError::NotEnoughSignatures(_, _))
133            ),
134            "Expected ProtocolAggregationError::NotEnoughSignatures, got: {error:?}"
135        )
136    }
137
138    #[test]
139    fn can_aggregate_if_valid_signatures_and_quorum_reached() {
140        let fixture = MithrilFixtureBuilder::default().with_signers(10).build();
141        let multi_signer = build_multi_signer(&fixture);
142        let message = ProtocolMessage::default();
143        let signatures: Vec<SingleSignature> = fixture
144            .signers_fixture()
145            .iter()
146            .map(|s| s.sign(&message).unwrap())
147            .collect();
148
149        multi_signer
150            .aggregate_single_signatures(&signatures, &message, AggregateSignatureType::default())
151            .expect("Multi-signature should be created");
152    }
153
154    #[test]
155    fn can_aggregate_even_with_one_invalid_signature_if_the_other_are_enough_for_the_quorum() {
156        let fixture = MithrilFixtureBuilder::default()
157            .with_signers(10)
158            .with_stake_distribution(StakeDistributionGenerationMethod::Uniform(20))
159            .with_protocol_parameters(ProtocolParameters::new(6, 200, 1.0))
160            .build();
161        let multi_signer = build_multi_signer(&fixture);
162        let message = ProtocolMessage::default();
163        let mut signatures: Vec<SingleSignature> = fixture
164            .signers_fixture()
165            .iter()
166            .map(|s| s.sign(&message).unwrap())
167            .collect();
168        signatures[4].signature = fake_keys::single_signature()[3].try_into().unwrap();
169
170        multi_signer
171            .aggregate_single_signatures(&signatures, &message, AggregateSignatureType::default())
172            .expect("Multi-signature should be created even with one invalid signature");
173    }
174
175    #[test]
176    fn verify_single_signature_fail_if_signature_signer_isnt_in_the_registered_parties() {
177        let multi_signer = build_multi_signer(
178            &MithrilFixtureBuilder::default()
179                .with_signers(1)
180                .with_stake_distribution(StakeDistributionGenerationMethod::RandomDistribution {
181                    seed: [3u8; 32],
182                    min_stake: 1,
183                })
184                .build(),
185        );
186        let fixture = MithrilFixtureBuilder::default().with_signers(1).build();
187        let message = ProtocolMessage::default();
188        let single_signature = fixture.signers_fixture().last().unwrap().sign(&message).unwrap();
189
190        // Will fail because the single signature was issued by a signer from a stake distribution
191        // that is not the one used by the multi-signer.
192        let error = multi_signer
193            .verify_single_signature(&message, &single_signature)
194            .expect_err(
195                "Verify single signature should fail if the signer isn't in the registered parties",
196            );
197
198        match error.downcast_ref::<MultiSignatureError>() {
199            Some(MultiSignatureError::SignatureInvalid(_)) => (),
200            _ => panic!("Expected an SignatureInvalid error, got: {error:?}"),
201        }
202    }
203
204    #[test]
205    fn verify_single_signature_fail_if_signature_signed_message_isnt_the_given_one() {
206        let fixture = MithrilFixtureBuilder::default().with_signers(1).build();
207        let multi_signer = build_multi_signer(&fixture);
208        let mut signed_message = ProtocolMessage::default();
209        signed_message.set_message_part(
210            ProtocolMessagePartKey::SnapshotDigest,
211            "a_digest".to_string(),
212        );
213        let single_signature = fixture
214            .signers_fixture()
215            .first()
216            .unwrap()
217            .sign(&signed_message)
218            .unwrap();
219
220        let error = multi_signer
221            .verify_single_signature(&ProtocolMessage::default(), &single_signature)
222            .expect_err("Verify single signature should fail");
223
224        match error.downcast_ref::<MultiSignatureError>() {
225            Some(MultiSignatureError::SignatureInvalid(_)) => (),
226            _ => panic!("Expected an SignatureInvalid error, got: {error:?}"),
227        }
228    }
229
230    #[test]
231    fn can_verify_valid_single_signature() {
232        let fixture = MithrilFixtureBuilder::default().with_signers(1).build();
233        let multi_signer = build_multi_signer(&fixture);
234        let message = ProtocolMessage::default();
235        let single_signature = fixture.signers_fixture().first().unwrap().sign(&message).unwrap();
236
237        multi_signer
238            .verify_single_signature(&message, &single_signature)
239            .expect("Verify single signature should succeed");
240    }
241}