1use anyhow::{Context, anyhow};
2use async_trait::async_trait;
3use slog::{Logger, debug, warn};
4
5use mithril_common::{
6 AggregateSignatureType, StdResult,
7 crypto_helper::{ProtocolAggregationError, ProtocolMultiSignature},
8 entities::{self},
9 logging::LoggerExtensions,
10 protocol::MultiSigner as ProtocolMultiSigner,
11};
12
13use crate::dependency_injection::EpochServiceWrapper;
14use crate::entities::OpenMessage;
15
16#[cfg_attr(test, mockall::automock)]
18#[async_trait]
19pub trait MultiSigner: Sync + Send {
20 async fn verify_single_signature(
22 &self,
23 message: &str,
24 signature: &entities::SingleSignature,
25 ) -> StdResult<()>;
26
27 async fn verify_single_signature_for_next_stake_distribution(
29 &self,
30 message: &str,
31 signature: &entities::SingleSignature,
32 ) -> StdResult<()>;
33
34 async fn create_multi_signature(
36 &self,
37 open_message: &OpenMessage,
38 ) -> StdResult<Option<ProtocolMultiSignature>>;
39}
40
41pub struct MultiSignerImpl {
43 aggregate_signature_type: AggregateSignatureType,
44 epoch_service: EpochServiceWrapper,
45 logger: Logger,
46}
47
48impl MultiSignerImpl {
49 pub fn new(
51 aggregate_signature_type: AggregateSignatureType,
52 epoch_service: EpochServiceWrapper,
53 logger: Logger,
54 ) -> Self {
55 let logger = logger.new_with_component_name::<Self>();
56 debug!(logger, "New MultiSignerImpl created");
57 Self {
58 aggregate_signature_type,
59 epoch_service,
60 logger,
61 }
62 }
63
64 fn run_verify_single_signature(
65 &self,
66 message: &str,
67 single_signature: &entities::SingleSignature,
68 protocol_multi_signer: &ProtocolMultiSigner,
69 ) -> StdResult<()> {
70 debug!(
71 self.logger,
72 "Verify single signature from {} at indexes {:?} for message {:?}",
73 single_signature.party_id,
74 single_signature.won_indexes,
75 message
76 );
77
78 protocol_multi_signer
79 .verify_single_signature(&message, single_signature)
80 .with_context(|| {
81 format!("Multi Signer can not verify single signature for message '{message:?}'")
82 })
83 }
84}
85
86#[async_trait]
87impl MultiSigner for MultiSignerImpl {
88 async fn verify_single_signature(
90 &self,
91 message: &str,
92 single_signature: &entities::SingleSignature,
93 ) -> StdResult<()> {
94 let epoch_service = self.epoch_service.read().await;
95 let protocol_multi_signer = epoch_service.protocol_multi_signer().with_context(
96 || "Multi Signer could not get protocol multi-signer from epoch service",
97 )?;
98
99 self.run_verify_single_signature(message, single_signature, protocol_multi_signer)
100 }
101
102 async fn verify_single_signature_for_next_stake_distribution(
103 &self,
104 message: &str,
105 single_signature: &entities::SingleSignature,
106 ) -> StdResult<()> {
107 let epoch_service = self.epoch_service.read().await;
108 let next_protocol_multi_signer = epoch_service.next_protocol_multi_signer().with_context(
109 || "Multi Signer could not get next protocol multi-signer from epoch service",
110 )?;
111
112 self.run_verify_single_signature(message, single_signature, next_protocol_multi_signer)
113 }
114
115 async fn create_multi_signature(
117 &self,
118 open_message: &OpenMessage,
119 ) -> StdResult<Option<ProtocolMultiSignature>> {
120 debug!(self.logger, ">> create_multi_signature"; "open_message" => ?open_message);
121
122 let epoch_service = self.epoch_service.read().await;
123 let protocol_multi_signer = epoch_service.protocol_multi_signer().with_context(
124 || "Multi Signer could not get protocol multi-signer from epoch service",
125 )?;
126
127 match protocol_multi_signer.aggregate_single_signatures(
128 &open_message.single_signatures,
129 &open_message.protocol_message,
130 self.aggregate_signature_type,
131 ) {
132 Ok(multi_signature) => Ok(Some(multi_signature)),
133 Err(ProtocolAggregationError::NotEnoughSignatures(actual, expected)) => {
134 warn!(
135 self.logger,
136 "Could not compute multi-signature: Not enough signatures. Got only {actual} out of {expected}."
137 );
138 Ok(None)
139 }
140 Err(err) => Err(anyhow!(err).context(format!(
141 "Multi Signer can not create multi-signature for entity type '{:?}'",
142 open_message.signed_entity_type
143 ))),
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use std::sync::Arc;
151 use tokio::sync::RwLock;
152
153 use mithril_common::entities::{CardanoDbBeacon, Epoch, SignedEntityType, SignerWithStake};
154 use mithril_common::protocol::ToMessage;
155 use mithril_common::test::{
156 builder::MithrilFixtureBuilder,
157 crypto_helper::setup_message,
158 double::{Dummy, fake_data},
159 };
160
161 use crate::entities::AggregatorEpochSettings;
162 use crate::services::{FakeEpochService, FakeEpochServiceBuilder};
163 use crate::test::TestLogger;
164
165 use super::*;
166
167 fn take_signatures_until_quorum_is_almost_reached(
168 signatures: &mut Vec<entities::SingleSignature>,
169 quorum: usize,
170 ) -> Vec<entities::SingleSignature> {
171 signatures.sort_by(|l, r| l.won_indexes.len().cmp(&r.won_indexes.len()));
172
173 let mut result = vec![];
174 let mut nb_won_indexes = 0;
175
176 while let Some(signature) = signatures.first() {
177 if signature.won_indexes.len() + nb_won_indexes >= quorum {
178 break;
179 }
180 nb_won_indexes += signature.won_indexes.len();
181 result.push(signatures.remove(0));
182 }
183
184 result
185 }
186
187 #[tokio::test]
188 async fn test_verify_single_signature() {
189 let epoch = Epoch(5);
190 let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
191 let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build();
192 let multi_signer = MultiSignerImpl::new(
193 AggregateSignatureType::default(),
194 Arc::new(RwLock::new(
195 FakeEpochServiceBuilder {
196 current_epoch_settings: AggregatorEpochSettings {
197 protocol_parameters: fixture.protocol_parameters(),
198 ..AggregatorEpochSettings::dummy()
199 },
200 next_epoch_settings: AggregatorEpochSettings {
201 protocol_parameters: next_fixture.protocol_parameters(),
202 ..AggregatorEpochSettings::dummy()
203 },
204 signer_registration_epoch_settings: AggregatorEpochSettings {
205 protocol_parameters: next_fixture.protocol_parameters(),
206 ..AggregatorEpochSettings::dummy()
207 },
208 current_signers_with_stake: fixture.signers_with_stake(),
209 next_signers_with_stake: next_fixture.signers_with_stake(),
210 ..FakeEpochServiceBuilder::dummy(epoch)
211 }
212 .build(),
213 )),
214 TestLogger::stdout(),
215 );
216
217 {
218 let message = setup_message();
219 let signature = fixture.signers_fixture()[0].sign(&message).unwrap();
220
221 multi_signer
222 .verify_single_signature(&message.to_message(), &signature)
223 .await
224 .unwrap();
225
226 multi_signer.verify_single_signature_for_next_stake_distribution(&message.to_message(), &signature).await.expect_err(
227 "single signature issued in the current epoch should not be valid for the next epoch",
228 );
229 }
230 {
231 let message = setup_message();
232 let next_epoch_signature = next_fixture.signers_fixture()[0].sign(&message).unwrap();
233
234 multi_signer
235 .verify_single_signature_for_next_stake_distribution(
236 &message.to_message(),
237 &next_epoch_signature,
238 )
239 .await
240 .unwrap();
241
242 multi_signer.verify_single_signature(&message.to_message(), &next_epoch_signature).await.expect_err(
243 "single signature issued in the next epoch should not be valid for the current epoch",
244 );
245 }
246 }
247
248 #[tokio::test]
249 async fn test_multi_signer_multi_signature_ok() {
250 let epoch = Epoch(5);
251 let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
252 let protocol_parameters = fixture.protocol_parameters();
253 let multi_signer = MultiSignerImpl::new(
254 AggregateSignatureType::default(),
255 Arc::new(RwLock::new(FakeEpochService::from_fixture(epoch, &fixture))),
256 TestLogger::stdout(),
257 );
258
259 let message = setup_message();
260
261 let mut signatures = Vec::new();
262
263 let mut expected_certificate_signers: Vec<SignerWithStake> = Vec::new();
264 for signer_fixture in fixture.signers_fixture() {
265 if let Some(signature) = signer_fixture.sign(&message) {
266 signatures.push(signature);
267 expected_certificate_signers.push(signer_fixture.signer_with_stake.to_owned())
268 }
269 }
270
271 for signature in &signatures {
272 multi_signer
273 .verify_single_signature(&message.to_message(), signature)
274 .await
275 .expect("single signature should be valid");
276 }
277
278 let signatures_to_almost_reach_quorum = take_signatures_until_quorum_is_almost_reached(
279 &mut signatures,
280 protocol_parameters.k as usize,
281 );
282 assert!(
283 !signatures_to_almost_reach_quorum.is_empty(),
284 "they should be at least one signature that can be registered without reaching the quorum"
285 );
286
287 let mut open_message = OpenMessage {
288 epoch,
289 signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon {
290 epoch,
291 ..fake_data::beacon()
292 }),
293 protocol_message: message.clone(),
294 is_certified: false,
295 single_signatures: Vec::new(),
296 ..OpenMessage::dummy()
297 };
298
299 assert!(
301 multi_signer
302 .create_multi_signature(&open_message)
303 .await
304 .expect("create multi signature should not fail")
305 .is_none()
306 );
307
308 open_message.single_signatures = signatures_to_almost_reach_quorum;
310
311 assert!(
312 multi_signer
313 .create_multi_signature(&open_message)
314 .await
315 .expect("create multi signature should not fail")
316 .is_none()
317 );
318
319 open_message.single_signatures.append(&mut signatures);
321
322 assert!(
323 multi_signer
324 .create_multi_signature(&open_message)
325 .await
326 .expect("create multi signature should not fail")
327 .is_some(),
328 "no multi-signature were computed"
329 );
330 }
331}