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