Skip to main content

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()
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_concatenation_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                #[cfg(feature = "future_snark")]
76                None,
77            )
78            .with_context(|| {
79                format!(
80                    "Invalid signature for party: '{}'",
81                    single_signature.party_id
82                )
83            })?;
84
85        Ok(())
86    }
87}
88
89#[cfg(test)]
90mod test {
91    use mithril_stm::BlsSignatureError;
92
93    use crate::{
94        crypto_helper::ProtocolAggregationError,
95        entities::{ProtocolMessage, ProtocolMessagePartKey, ProtocolParameters},
96        protocol::SignerBuilder,
97        test::{
98            builder::{MithrilFixture, MithrilFixtureBuilder, StakeDistributionGenerationMethod},
99            double::fake_keys,
100        },
101    };
102
103    use super::*;
104
105    fn build_multi_signer(fixture: &MithrilFixture) -> MultiSigner {
106        SignerBuilder::new(
107            &fixture.signers_with_stake(),
108            &fixture.protocol_parameters(),
109        )
110        .unwrap()
111        .build_multi_signer()
112    }
113
114    #[test]
115    fn cant_aggregate_if_signatures_list_empty() {
116        let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
117        let multi_signer = build_multi_signer(&fixture);
118        let message = ProtocolMessage::default();
119
120        let error = multi_signer
121            .aggregate_single_signatures(&[], &message, AggregateSignatureType::default())
122            .expect_err(
123                "Multi-signature should not be created with an empty single signatures list",
124            );
125
126        assert!(
127            matches!(
128                error.downcast_ref::<ProtocolAggregationError>(),
129                Some(ProtocolAggregationError::NotEnoughSignatures(_, _))
130            ),
131            "Expected ProtocolAggregationError::NotEnoughSignatures, got: {error:?}"
132        )
133    }
134
135    #[test]
136    fn can_aggregate_if_valid_signatures_and_quorum_reached() {
137        let fixture = MithrilFixtureBuilder::default().with_signers(10).build();
138        let multi_signer = build_multi_signer(&fixture);
139        let message = ProtocolMessage::default();
140        let signatures: Vec<SingleSignature> = fixture
141            .signers_fixture()
142            .iter()
143            .map(|s| s.sign(&message).unwrap())
144            .collect();
145
146        multi_signer
147            .aggregate_single_signatures(&signatures, &message, AggregateSignatureType::default())
148            .expect("Multi-signature should be created");
149    }
150
151    #[test]
152    fn can_aggregate_even_with_one_invalid_signature_if_the_other_are_enough_for_the_quorum() {
153        let fixture = MithrilFixtureBuilder::default()
154            .with_signers(10)
155            .with_stake_distribution(StakeDistributionGenerationMethod::Uniform(20))
156            .with_protocol_parameters(ProtocolParameters::new(6, 200, 1.0))
157            .build();
158        let multi_signer = build_multi_signer(&fixture);
159        let message = ProtocolMessage::default();
160        let mut signatures: Vec<SingleSignature> = fixture
161            .signers_fixture()
162            .iter()
163            .map(|s| s.sign(&message).unwrap())
164            .collect();
165        signatures[4].signature = fake_keys::single_signature()[3].try_into().unwrap();
166
167        multi_signer
168            .aggregate_single_signatures(&signatures, &message, AggregateSignatureType::default())
169            .expect("Multi-signature should be created even with one invalid signature");
170    }
171
172    #[test]
173    fn verify_single_signature_fail_if_signature_signer_isnt_in_the_registered_parties() {
174        let multi_signer = build_multi_signer(
175            &MithrilFixtureBuilder::default()
176                .with_signers(1)
177                .with_stake_distribution(StakeDistributionGenerationMethod::RandomDistribution {
178                    seed: [3u8; 32],
179                    min_stake: 1,
180                })
181                .build(),
182        );
183        let fixture = MithrilFixtureBuilder::default().with_signers(1).build();
184        let message = ProtocolMessage::default();
185        let single_signature = fixture.signers_fixture().last().unwrap().sign(&message).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::<BlsSignatureError>() {
196            Some(BlsSignatureError::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::<BlsSignatureError>() {
222            Some(BlsSignatureError::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.signers_fixture().first().unwrap().sign(&message).unwrap();
233
234        multi_signer
235            .verify_single_signature(&message, &single_signature)
236            .expect("Verify single signature should succeed");
237    }
238}