Skip to main content

diem_types/
validator_verifier.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{account_address::AccountAddress, on_chain_config::ValidatorSet};
5use diem_crypto::{
6    ed25519::{Ed25519PublicKey, Ed25519Signature},
7    hash::CryptoHash,
8    Signature, VerifyingKey,
9};
10use serde::{Deserialize, Serialize};
11use std::{collections::BTreeMap, fmt};
12use thiserror::Error;
13
14#[cfg(any(test, feature = "fuzzing"))]
15use anyhow::{ensure, Result};
16#[cfg(any(test, feature = "fuzzing"))]
17use proptest_derive::Arbitrary;
18
19/// Errors possible during signature verification.
20#[derive(Debug, Error, PartialEq)]
21pub enum VerifyError {
22    #[error("Author is unknown")]
23    /// The author for this signature is unknown by this validator.
24    UnknownAuthor,
25    #[error(
26        "The voting power ({}) is less than quorum voting power ({})",
27        voting_power,
28        quorum_voting_power
29    )]
30    TooLittleVotingPower {
31        voting_power: u64,
32        quorum_voting_power: u64,
33    },
34    #[error(
35        "The number of signatures ({}) is greater than total number of authors ({})",
36        num_of_signatures,
37        num_of_authors
38    )]
39    TooManySignatures {
40        num_of_signatures: usize,
41        num_of_authors: usize,
42    },
43    #[error("Signature is invalid")]
44    /// The signature does not match the hash.
45    InvalidSignature,
46    #[error("Inconsistent Block Info")]
47    InconsistentBlockInfo,
48}
49
50/// Helper struct to manage validator information for validation
51#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
52#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
53pub struct ValidatorConsensusInfo {
54    public_key: Ed25519PublicKey,
55    voting_power: u64,
56}
57
58impl ValidatorConsensusInfo {
59    pub fn new(public_key: Ed25519PublicKey, voting_power: u64) -> Self {
60        ValidatorConsensusInfo {
61            public_key,
62            voting_power,
63        }
64    }
65}
66
67/// Supports validation of signatures for known authors with individual voting powers. This struct
68/// can be used for all signature verification operations including block and network signature
69/// verification, respectively.
70#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
71#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
72pub struct ValidatorVerifier {
73    /// An ordered map of each validator's on-chain account address to its pubkeys
74    /// and voting power.
75    address_to_validator_info: BTreeMap<AccountAddress, ValidatorConsensusInfo>,
76    /// The minimum voting power required to achieve a quorum
77    quorum_voting_power: u64,
78    /// Total voting power of all validators (cached from address_to_validator_info)
79    total_voting_power: u64,
80}
81
82impl ValidatorVerifier {
83    /// Initialize with a map of account address to validator info and set quorum size to
84    /// default (`2f + 1`) or zero if `address_to_validator_info` is empty.
85    pub fn new(
86        address_to_validator_info: BTreeMap<AccountAddress, ValidatorConsensusInfo>,
87    ) -> Self {
88        let total_voting_power = sum_voting_power(&address_to_validator_info);
89        let quorum_voting_power = if address_to_validator_info.is_empty() {
90            0
91        } else {
92            total_voting_power * 2 / 3 + 1
93        };
94        ValidatorVerifier {
95            address_to_validator_info,
96            quorum_voting_power,
97            total_voting_power,
98        }
99    }
100
101    /// Initializes a validator verifier with a specified quorum voting power.
102    #[cfg(any(test, feature = "fuzzing"))]
103    pub fn new_with_quorum_voting_power(
104        address_to_validator_info: BTreeMap<AccountAddress, ValidatorConsensusInfo>,
105        quorum_voting_power: u64,
106    ) -> Result<Self> {
107        let total_voting_power = sum_voting_power(&address_to_validator_info);
108        ensure!(
109            quorum_voting_power <= total_voting_power,
110            "Quorum voting power is greater than the sum of all voting power of authors: {}, \
111             quorum_size: {}.",
112            quorum_voting_power,
113            total_voting_power
114        );
115        Ok(ValidatorVerifier {
116            address_to_validator_info,
117            quorum_voting_power,
118            total_voting_power,
119        })
120    }
121
122    /// Initializes a validator verifier with a specified quorum voting power and total power.
123    #[cfg(any(test, feature = "fuzzing"))]
124    // This method should only used by tests and fuzzers to produce an arbitrary ValidatorVerifier.
125    pub fn new_for_testing(
126        address_to_validator_info: BTreeMap<AccountAddress, ValidatorConsensusInfo>,
127        quorum_voting_power: u64,
128        total_voting_power: u64,
129    ) -> Self {
130        ValidatorVerifier {
131            address_to_validator_info,
132            quorum_voting_power,
133            total_voting_power,
134        }
135    }
136
137    /// Helper method to initialize with a single author and public key with quorum voting power 1.
138    pub fn new_single(author: AccountAddress, public_key: Ed25519PublicKey) -> Self {
139        let mut author_to_validator_info = BTreeMap::new();
140        author_to_validator_info.insert(author, ValidatorConsensusInfo::new(public_key, 1));
141        Self::new(author_to_validator_info)
142    }
143
144    /// Verify the correctness of a signature of a message by a known author.
145    pub fn verify<T: Serialize + CryptoHash>(
146        &self,
147        author: AccountAddress,
148        message: &T,
149        signature: &Ed25519Signature,
150    ) -> std::result::Result<(), VerifyError> {
151        match self.get_public_key(&author) {
152            Some(public_key) => {
153                if public_key
154                    .verify_struct_signature(message, signature)
155                    .is_err()
156                {
157                    Err(VerifyError::InvalidSignature)
158                } else {
159                    Ok(())
160                }
161            }
162            None => Err(VerifyError::UnknownAuthor),
163        }
164    }
165
166    /// This function will successfully return when at least quorum_size signatures of known authors
167    /// are successfully verified. Also, an aggregated signature is considered invalid if any of the
168    /// attached signatures is invalid or it does not correspond to a known author. The latter is to
169    /// prevent malicious users from adding arbitrary content to the signature payload that would go
170    /// unnoticed.
171    pub fn verify_aggregated_struct_signature<T: CryptoHash + Serialize>(
172        &self,
173        message: &T,
174        aggregated_signature: &BTreeMap<AccountAddress, Ed25519Signature>,
175    ) -> std::result::Result<(), VerifyError> {
176        self.check_num_of_signatures(aggregated_signature)?;
177        self.check_voting_power(aggregated_signature.keys())?;
178        for (author, signature) in aggregated_signature {
179            self.verify(*author, message, &signature.clone())?;
180        }
181        Ok(())
182    }
183
184    /// This function will try batch signature verification and falls back to normal
185    /// iterated verification if batching fails.
186    pub fn batch_verify_aggregated_signatures<T: CryptoHash + Serialize>(
187        &self,
188        message: &T,
189        aggregated_signature: &BTreeMap<AccountAddress, Ed25519Signature>,
190    ) -> std::result::Result<(), VerifyError> {
191        self.check_num_of_signatures(aggregated_signature)?;
192        self.check_voting_power(aggregated_signature.keys())?;
193        let keys_and_signatures: Vec<(Ed25519PublicKey, Ed25519Signature)> = aggregated_signature
194            .iter()
195            .flat_map(|(address, signature)| {
196                let sig = signature.clone();
197                self.get_public_key(address).map(|pub_key| (pub_key, sig))
198            })
199            .collect();
200        // Fallback is required to identify the source of the problem if batching fails.
201        if Ed25519Signature::batch_verify(message, keys_and_signatures).is_err() {
202            self.verify_aggregated_struct_signature(message, aggregated_signature)?
203        }
204        Ok(())
205    }
206
207    /// Ensure there are not more than the maximum expected signatures (all possible signatures).
208    fn check_num_of_signatures(
209        &self,
210        aggregated_signature: &BTreeMap<AccountAddress, Ed25519Signature>,
211    ) -> std::result::Result<(), VerifyError> {
212        let num_of_signatures = aggregated_signature.len();
213        if num_of_signatures > self.len() {
214            return Err(VerifyError::TooManySignatures {
215                num_of_signatures,
216                num_of_authors: self.len(),
217            });
218        }
219        Ok(())
220    }
221    /// Ensure there is at least quorum_voting_power in the provided signatures and there
222    /// are only known authors. According to the threshold verification policy,
223    /// invalid public keys are not allowed.
224    pub fn check_voting_power<'a>(
225        &self,
226        authors: impl Iterator<Item = &'a AccountAddress>,
227    ) -> std::result::Result<(), VerifyError> {
228        // Add voting power for valid accounts, exiting early for unknown authors
229        let mut aggregated_voting_power = 0;
230        for account_address in authors {
231            match self.get_voting_power(account_address) {
232                Some(voting_power) => aggregated_voting_power += voting_power,
233                None => return Err(VerifyError::UnknownAuthor),
234            }
235        }
236
237        if aggregated_voting_power < self.quorum_voting_power {
238            return Err(VerifyError::TooLittleVotingPower {
239                voting_power: aggregated_voting_power,
240                quorum_voting_power: self.quorum_voting_power,
241            });
242        }
243        Ok(())
244    }
245
246    /// Returns the public key for this address.
247    pub fn get_public_key(&self, author: &AccountAddress) -> Option<Ed25519PublicKey> {
248        self.address_to_validator_info
249            .get(author)
250            .map(|validator_info| validator_info.public_key.clone())
251    }
252
253    /// Returns the voting power for this address.
254    pub fn get_voting_power(&self, author: &AccountAddress) -> Option<u64> {
255        self.address_to_validator_info
256            .get(author)
257            .map(|validator_info| validator_info.voting_power)
258    }
259
260    /// Returns an ordered list of account addresses as an `Iterator`.
261    pub fn get_ordered_account_addresses_iter(&self) -> impl Iterator<Item = AccountAddress> + '_ {
262        // Since `address_to_validator_info` is a `BTreeMap`, the `.keys()` iterator
263        // is guaranteed to be sorted.
264        self.address_to_validator_info.keys().copied()
265    }
266
267    /// Returns the number of authors to be validated.
268    pub fn len(&self) -> usize {
269        self.address_to_validator_info.len()
270    }
271
272    /// Is there at least one author?
273    pub fn is_empty(&self) -> bool {
274        self.len() == 0
275    }
276
277    /// Returns quorum voting power.
278    pub fn quorum_voting_power(&self) -> u64 {
279        self.quorum_voting_power
280    }
281}
282
283/// Returns sum of voting power from Map of validator account addresses, validator consensus info
284fn sum_voting_power(
285    address_to_validator_info: &BTreeMap<AccountAddress, ValidatorConsensusInfo>,
286) -> u64 {
287    address_to_validator_info.values().fold(0, |sum, x| {
288        sum.checked_add(x.voting_power)
289            .expect("sum of all voting power is greater than u64::max")
290    })
291}
292
293impl fmt::Display for ValidatorVerifier {
294    fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
295        write!(f, "ValidatorSet: [")?;
296        for (addr, info) in &self.address_to_validator_info {
297            write!(f, "{}: {}, ", addr.short_str_lossless(), info.voting_power)?;
298        }
299        write!(f, "]")
300    }
301}
302
303impl From<&ValidatorSet> for ValidatorVerifier {
304    fn from(validator_set: &ValidatorSet) -> Self {
305        ValidatorVerifier::new(validator_set.payload().iter().fold(
306            BTreeMap::new(),
307            |mut map, validator| {
308                map.insert(
309                    *validator.account_address(),
310                    ValidatorConsensusInfo::new(
311                        validator.consensus_public_key().clone(),
312                        validator.consensus_voting_power(),
313                    ),
314                );
315                map
316            },
317        ))
318    }
319}
320
321#[cfg(any(test, feature = "fuzzing"))]
322impl From<&ValidatorVerifier> for ValidatorSet {
323    fn from(verifier: &ValidatorVerifier) -> Self {
324        ValidatorSet::new(
325            verifier
326                .get_ordered_account_addresses_iter()
327                .map(|addr| {
328                    crate::validator_info::ValidatorInfo::new_with_test_network_keys(
329                        addr,
330                        verifier.get_public_key(&addr).unwrap(),
331                        verifier.get_voting_power(&addr).unwrap(),
332                    )
333                })
334                .collect(),
335        )
336    }
337}
338
339/// Helper function to get random validator signers and a corresponding validator verifier for
340/// testing.  If custom_voting_power_quorum is not None, set a custom voting power quorum amount.
341/// With pseudo_random_account_address enabled, logs show 0 -> [0000], 1 -> [1000]
342#[cfg(any(test, feature = "fuzzing"))]
343pub fn random_validator_verifier(
344    count: usize,
345    custom_voting_power_quorum: Option<u64>,
346    pseudo_random_account_address: bool,
347) -> (
348    Vec<crate::validator_signer::ValidatorSigner>,
349    ValidatorVerifier,
350) {
351    let mut signers = Vec::new();
352    let mut account_address_to_validator_info = BTreeMap::new();
353    for i in 0..count {
354        let random_signer = if pseudo_random_account_address {
355            crate::validator_signer::ValidatorSigner::from_int(i as u8)
356        } else {
357            crate::validator_signer::ValidatorSigner::random([i as u8; 32])
358        };
359        account_address_to_validator_info.insert(
360            random_signer.author(),
361            crate::validator_verifier::ValidatorConsensusInfo::new(random_signer.public_key(), 1),
362        );
363        signers.push(random_signer);
364    }
365    (
366        signers,
367        match custom_voting_power_quorum {
368            Some(custom_voting_power_quorum) => ValidatorVerifier::new_with_quorum_voting_power(
369                account_address_to_validator_info,
370                custom_voting_power_quorum,
371            )
372            .expect("Unable to create testing validator verifier"),
373            None => ValidatorVerifier::new(account_address_to_validator_info),
374        },
375    )
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381    use crate::validator_signer::ValidatorSigner;
382    use diem_crypto::test_utils::{TestDiemCrypto, TEST_SEED};
383    use std::collections::BTreeMap;
384
385    #[test]
386    fn test_check_voting_power() {
387        let (validator_signers, validator_verifier) = random_validator_verifier(2, None, false);
388        let mut author_to_signature_map = BTreeMap::new();
389
390        assert_eq!(
391            validator_verifier
392                .check_voting_power(author_to_signature_map.keys())
393                .unwrap_err(),
394            VerifyError::TooLittleVotingPower {
395                voting_power: 0,
396                quorum_voting_power: 2,
397            }
398        );
399
400        let dummy_struct = TestDiemCrypto("Hello, World".to_string());
401        for validator in validator_signers.iter() {
402            author_to_signature_map.insert(validator.author(), validator.sign(&dummy_struct));
403        }
404
405        assert_eq!(
406            validator_verifier.check_voting_power(author_to_signature_map.keys()),
407            Ok(())
408        );
409    }
410
411    #[test]
412    fn test_validator() {
413        let validator_signer = ValidatorSigner::random(TEST_SEED);
414        let dummy_struct = TestDiemCrypto("Hello, World".to_string());
415        let signature = validator_signer.sign(&dummy_struct);
416        let validator =
417            ValidatorVerifier::new_single(validator_signer.author(), validator_signer.public_key());
418        assert_eq!(
419            validator.verify(validator_signer.author(), &dummy_struct, &signature),
420            Ok(())
421        );
422        let unknown_validator_signer = ValidatorSigner::random([1; 32]);
423        let unknown_signature = unknown_validator_signer.sign(&dummy_struct);
424        assert_eq!(
425            validator.verify(
426                unknown_validator_signer.author(),
427                &dummy_struct,
428                &unknown_signature
429            ),
430            Err(VerifyError::UnknownAuthor)
431        );
432        assert_eq!(
433            validator.verify(validator_signer.author(), &dummy_struct, &unknown_signature),
434            Err(VerifyError::InvalidSignature)
435        );
436    }
437
438    #[test]
439    fn test_equal_vote_quorum_validators() {
440        const NUM_SIGNERS: u8 = 7;
441        // Generate NUM_SIGNERS random signers.
442        let validator_signers: Vec<ValidatorSigner> = (0..NUM_SIGNERS)
443            .map(|i| ValidatorSigner::random([i; 32]))
444            .collect();
445        let dummy_struct = TestDiemCrypto("Hello, World".to_string());
446
447        // Create a map from authors to public keys with equal voting power.
448        let mut author_to_public_key_map = BTreeMap::new();
449        for validator in validator_signers.iter() {
450            author_to_public_key_map.insert(
451                validator.author(),
452                ValidatorConsensusInfo::new(validator.public_key(), 1),
453            );
454        }
455
456        // Create a map from author to signatures.
457        let mut author_to_signature_map = BTreeMap::new();
458        for validator in validator_signers.iter() {
459            author_to_signature_map.insert(validator.author(), validator.sign(&dummy_struct));
460        }
461
462        // Let's assume our verifier needs to satisfy at least 5 signatures from the original
463        // NUM_SIGNERS.
464        let validator_verifier =
465            ValidatorVerifier::new_with_quorum_voting_power(author_to_public_key_map, 5)
466                .expect("Incorrect quorum size.");
467
468        // Check against signatures == N; this will pass.
469        assert_eq!(
470            validator_verifier
471                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
472            Ok(())
473        );
474
475        // Add an extra unknown signer, signatures > N; this will fail.
476        let unknown_validator_signer = ValidatorSigner::random([NUM_SIGNERS + 1; 32]);
477        let unknown_signature = unknown_validator_signer.sign(&dummy_struct);
478        author_to_signature_map
479            .insert(unknown_validator_signer.author(), unknown_signature.clone());
480        assert_eq!(
481            validator_verifier
482                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
483            Err(VerifyError::TooManySignatures {
484                num_of_signatures: 8,
485                num_of_authors: 7
486            })
487        );
488
489        // Add 5 valid signers only (quorum threshold is met); this will pass.
490        author_to_signature_map.clear();
491        for validator in validator_signers.iter().take(5) {
492            author_to_signature_map.insert(validator.author(), validator.sign(&dummy_struct));
493        }
494        assert_eq!(
495            validator_verifier
496                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
497            Ok(())
498        );
499
500        // Add an unknown signer, but quorum is satisfied and signatures <= N; this will fail as we
501        // don't tolerate invalid signatures.
502        author_to_signature_map
503            .insert(unknown_validator_signer.author(), unknown_signature.clone());
504        assert_eq!(
505            validator_verifier
506                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
507            Err(VerifyError::UnknownAuthor)
508        );
509
510        // Add 4 valid signers only (quorum threshold is NOT met); this will fail.
511        author_to_signature_map.clear();
512        for validator in validator_signers.iter().take(4) {
513            author_to_signature_map.insert(validator.author(), validator.sign(&dummy_struct));
514        }
515        assert_eq!(
516            validator_verifier
517                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
518            Err(VerifyError::TooLittleVotingPower {
519                voting_power: 4,
520                quorum_voting_power: 5
521            })
522        );
523
524        // Add an unknown signer, we have 5 signers, but one of them is invalid; this will fail.
525        author_to_signature_map.insert(unknown_validator_signer.author(), unknown_signature);
526        assert_eq!(
527            validator_verifier
528                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
529            Err(VerifyError::UnknownAuthor)
530        );
531    }
532
533    #[test]
534    #[should_panic]
535    fn test_very_unequal_vote_quorum_validators() {
536        const NUM_SIGNERS: u8 = 4;
537        // Generate NUM_SIGNERS random signers.
538        let validator_signers: Vec<ValidatorSigner> = (0..NUM_SIGNERS)
539            .map(|i| ValidatorSigner::random([i; 32]))
540            .collect();
541        let dummy_struct = TestDiemCrypto("Hello, World".to_string());
542
543        // Create a map from authors to public keys with increasing weights (0, 1, 2, 3) and
544        // a map of author to signature.
545        let mut author_to_public_key_map = BTreeMap::new();
546        let mut author_to_signature_map = BTreeMap::new();
547        for (i, validator_signer) in validator_signers.iter().enumerate() {
548            let mut voting_power: u64 = i as u64;
549            if i == 3 {
550                voting_power = u64::max_value()
551            }
552            author_to_public_key_map.insert(
553                validator_signer.author(),
554                ValidatorConsensusInfo::new(validator_signer.public_key(), voting_power),
555            );
556            author_to_signature_map.insert(
557                validator_signer.author(),
558                validator_signer.sign(&dummy_struct),
559            );
560        }
561
562        // expect this to panic
563        let _validator_verifier = ValidatorVerifier::new(author_to_public_key_map);
564    }
565
566    #[test]
567    fn test_unequal_vote_quorum_validators() {
568        const NUM_SIGNERS: u8 = 4;
569        // Generate NUM_SIGNERS random signers.
570        let validator_signers: Vec<ValidatorSigner> = (0..NUM_SIGNERS)
571            .map(|i| ValidatorSigner::random([i; 32]))
572            .collect();
573        let dummy_struct = TestDiemCrypto("Hello, World".to_string());
574
575        // Create a map from authors to public keys with increasing weights (0, 1, 2, 3) and
576        // a map of author to signature.
577        let mut author_to_public_key_map = BTreeMap::new();
578        let mut author_to_signature_map = BTreeMap::new();
579        for (i, validator_signer) in validator_signers.iter().enumerate() {
580            author_to_public_key_map.insert(
581                validator_signer.author(),
582                ValidatorConsensusInfo::new(validator_signer.public_key(), i as u64),
583            );
584            author_to_signature_map.insert(
585                validator_signer.author(),
586                validator_signer.sign(&dummy_struct),
587            );
588        }
589
590        // Let's assume our verifier needs to satisfy at least 5 quorum voting power
591        let validator_verifier =
592            ValidatorVerifier::new_with_quorum_voting_power(author_to_public_key_map, 5)
593                .expect("Incorrect quorum size.");
594
595        // Check against all signatures (6 voting power); this will pass.
596        assert_eq!(
597            validator_verifier
598                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
599            Ok(())
600        );
601
602        // Add an extra unknown signer, signatures > N; this will fail.
603        let unknown_validator_signer = ValidatorSigner::random([NUM_SIGNERS + 1; 32]);
604        let unknown_signature = unknown_validator_signer.sign(&dummy_struct);
605        author_to_signature_map
606            .insert(unknown_validator_signer.author(), unknown_signature.clone());
607        assert_eq!(
608            validator_verifier
609                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
610            Err(VerifyError::TooManySignatures {
611                num_of_signatures: 5,
612                num_of_authors: 4
613            })
614        );
615
616        // Add 5 voting power signers only (quorum threshold is met) with (2, 3) ; this will pass.
617        author_to_signature_map.clear();
618        for validator in validator_signers.iter().skip(2) {
619            author_to_signature_map.insert(validator.author(), validator.sign(&dummy_struct));
620        }
621        assert_eq!(
622            validator_verifier
623                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
624            Ok(())
625        );
626
627        // Add an unknown signer, but quorum is satisfied and signatures <= N; this will fail as we
628        // don't tolerate invalid signatures.
629        author_to_signature_map
630            .insert(unknown_validator_signer.author(), unknown_signature.clone());
631        assert_eq!(
632            validator_verifier
633                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
634            Err(VerifyError::UnknownAuthor)
635        );
636
637        // Add first 3 valid signers only (quorum threshold is NOT met); this will fail.
638        author_to_signature_map.clear();
639        for validator in validator_signers.iter().take(3) {
640            author_to_signature_map.insert(validator.author(), validator.sign(&dummy_struct));
641        }
642        assert_eq!(
643            validator_verifier
644                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
645            Err(VerifyError::TooLittleVotingPower {
646                voting_power: 3,
647                quorum_voting_power: 5
648            })
649        );
650
651        // Add an unknown signer, we have 5 signers, but one of them is invalid; this will fail.
652        author_to_signature_map.insert(unknown_validator_signer.author(), unknown_signature);
653        assert_eq!(
654            validator_verifier
655                .batch_verify_aggregated_signatures(&dummy_struct, &author_to_signature_map),
656            Err(VerifyError::UnknownAuthor)
657        );
658    }
659}