mithril_common/protocol/
multi_signer.rs

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