Skip to main content

tendermint_machine/
message.rs

1use core::{iter::Flatten, fmt};
2
3use crate::{
4  BlockNumber, RoundNumber, Validator, ValidatorSet, Signature, AggregateSignature,
5  SignatureScheme, Signer, BlockHash, Block, Commit, Blockchain, Evidence, SlashReason,
6};
7
8/// A valid round.
9///
10/// This is not just the specification of the valid round but also the evidence needed to convince
11/// other validators this round was valid. This allows each validator to only store their view of
12/// the valid round, with a bounded amount of memory, yet to recognize the proposer's argument for
13/// the valid round without issue.
14///
15/// This does increase the amount communicated by the proposer, but maintains the same `O(n^2)`
16/// communication complexity for each round as here we have the single proposer sending a message
17/// of size `n` to `n` other participants, while the following rounds have `n` participants sending
18/// messages of size `1` to `n` other participants. This assumes the aggregate signature is of size
19/// linear to the individual signatures, such as by a naïve concatenation into a list, though with
20/// threshold signatures or similar, this could be of equal complexity to the traditional concept
21/// of a proposal message.
22///
23/// In order for this to be valid, the signature MUST be valid and aggregated from signatures by
24/// validators whose weight is sufficient for the threshold. Deserialization or instantiation alone
25/// DOES NOT signify validity.
26#[derive(Clone)]
27#[cfg_attr(feature = "alloc", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
28pub(crate) struct ValidRound<A: AggregateSignature> {
29  pub(crate) round_number: RoundNumber,
30  pub(crate) aggregate_signature: A,
31}
32
33impl<A: AggregateSignature> PartialEq for ValidRound<A> {
34  /// This equality is semantic and does not consider if any present signatures are equal. This
35  /// ensures even for signature schemes with malleable signatures, multiple signatures over the
36  /// same messages are not semantically treated as distinct signatures. However, this also means a
37  /// value with an invalid signature will be considered equal to a message with a valid signature.
38  fn eq(&self, other: &Self) -> bool {
39    let Self { round_number, aggregate_signature: _ } = self;
40    (*round_number) == other.round_number
41  }
42}
43impl<A: AggregateSignature> Eq for ValidRound<A> {}
44
45impl<A: AggregateSignature> fmt::Debug for ValidRound<A> {
46  fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47    let Self { round_number, aggregate_signature: _ } = self;
48    formatter.debug_struct("ValidRound").field("round_number", round_number).finish()
49  }
50}
51
52/// The data within a message.
53#[derive(Clone)]
54#[cfg_attr(feature = "alloc", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
55pub(crate) enum Data<S: Signature, A: AggregateSignature, B: Block> {
56  Proposal {
57    valid_round: Option<ValidRound<A>>,
58    proposal: B,
59  },
60  Prevote {
61    #[cfg_attr(
62      feature = "alloc",
63      borsh(bound(
64        serialize = "B::Hash: borsh::BorshSerialize",
65        deserialize = "B::Hash: borsh::BorshDeserialize"
66      ))
67    )]
68    block: Option<B::Hash>,
69  },
70  Precommit {
71    #[cfg_attr(
72      feature = "alloc",
73      borsh(bound(
74        serialize = "S: borsh::BorshSerialize, B::Hash: borsh::BorshSerialize",
75        deserialize = "S: borsh::BorshDeserialize, B::Hash: borsh::BorshDeserialize"
76      ))
77    )]
78    block_and_precommit_signature: Option<(B::Hash, S)>,
79  },
80}
81
82impl<S: Signature, A: AggregateSignature, B: Block> PartialEq for Data<S, A, B> {
83  /// This equality is semantic and does not consider if any present signatures are equal. This
84  /// ensures even for signature schemes with malleable signatures, multiple signatures over the
85  /// same messages are not semantically treated as distinct signatures. However, this also means a
86  /// value with an invalid signature will be considered equal to a message with a valid signature.
87  fn eq(&self, other: &Self) -> bool {
88    match (self, other) {
89      (
90        Data::Proposal { valid_round, proposal },
91        Data::Proposal { valid_round: other_valid_round, proposal: other_proposal },
92      ) => (valid_round == other_valid_round) && (proposal.hash() == other_proposal.hash()),
93      (Data::Prevote { block }, Data::Prevote { block: other_block }) => block == other_block,
94      (
95        Data::Precommit { block_and_precommit_signature: None },
96        Data::Precommit { block_and_precommit_signature: None },
97      ) => true,
98      (
99        Data::Precommit { block_and_precommit_signature: Some((block, _)) },
100        Data::Precommit { block_and_precommit_signature: Some((other_block, _)) },
101      ) => block == other_block,
102      (
103        Data::Precommit { block_and_precommit_signature: Some(_) },
104        Data::Precommit { block_and_precommit_signature: None },
105      ) |
106      (
107        Data::Precommit { block_and_precommit_signature: None },
108        Data::Precommit { block_and_precommit_signature: Some(_) },
109      ) |
110      (Data::Proposal { .. }, Data::Prevote { .. } | Data::Precommit { .. }) |
111      (Data::Prevote { .. }, Data::Proposal { .. } | Data::Precommit { .. }) |
112      (Data::Precommit { .. }, Data::Proposal { .. } | Data::Prevote { .. }) => false,
113    }
114  }
115}
116impl<S: Signature, A: AggregateSignature, B: Block> Eq for Data<S, A, B> {}
117
118#[doc(hidden)]
119pub(crate) enum MessageSegment<'genesis, S: Signature, A: AggregateSignature, B: Block> {
120  Dst([u8; 1]),
121  Genesis(&'genesis [u8]),
122  U64([u8; 8]),
123  AggregateSignature(A),
124  Block(B::Hash),
125  PrecommitSignature(S),
126}
127impl<S: Signature, A: AggregateSignature, B: Block> AsRef<[u8]> for MessageSegment<'_, S, A, B> {
128  fn as_ref(&self) -> &[u8] {
129    match self {
130      Self::Dst(dst) => dst.as_slice(),
131      Self::Genesis(genesis) => genesis,
132      Self::U64(round_number) => round_number.as_slice(),
133      Self::AggregateSignature(aggregate_signature) => aggregate_signature.as_ref(),
134      Self::Block(block) => block.as_ref(),
135      Self::PrecommitSignature(precommit_signature) => precommit_signature.as_ref(),
136    }
137  }
138}
139
140impl<S: Signature, A: AggregateSignature, B: Block> Data<S, A, B> {
141  /// The serialization used for signing.
142  ///
143  /// This exists primarily as we do not want to sign the block in a proposal but solely its hash.
144  /// This allows proving an equivocation occurred without rebroadcasting the blocks as a whole but
145  /// solely their hashes.
146  ///
147  /// The result can be mapped to an `Iterator<Item = &[u8]>` as needed to be chained with a larger
148  /// context of such schema. While this is rather annoying to do here, the borrow-checker somewhat
149  /// requires this pattern.
150  #[must_use]
151  fn signature_message(&self) -> [Option<MessageSegment<'static, S, A, B>>; 5] {
152    let (kind, round_number, aggregate_signature, block, precommit_signature) = match self {
153      Data::Proposal { valid_round, proposal } => {
154        let (round_number, aggregate_signature) = valid_round
155          .as_ref()
156          .map(|ValidRound { round_number, aggregate_signature }| {
157            (u64::from(*round_number).to_le_bytes(), aggregate_signature.clone())
158          })
159          .unzip();
160        (
161          0u8,
162          /*
163            `round_number` and `aggregate_signature` are both `Some` or both `None`, and
164            `round_number` is fixed-length, preventing collisions between the two of them.
165          */
166          round_number,
167          aggregate_signature,
168          /*
169            We only sign the hash to avoid having to serialize/store the entire block.
170
171            This is bound to be of fixed-length so it does not need length-prefixing, nor do the
172            prior two fields (which behave as a single field, the sole field of variable length in
173            this message).
174          */
175          Some(proposal.hash()),
176          None,
177        )
178      }
179      /*
180        This omits the encoding of the block hash to signify `None`, which is fine as this is
181        without collisions and this doesn't have to be able to be deserialized from.
182      */
183      Data::Prevote { block } => (1u8, None, None, *block, None),
184      Data::Precommit { block_and_precommit_signature } => {
185        let (block, precommit_signature) = block_and_precommit_signature.clone().unzip();
186        (
187          2u8,
188          None,
189          None,
190          /*
191            The block hash has a fixed length and these are both `Some` or both `None`, meaning
192            that regardless of the precommit signature's encoding, this is without conflict.
193          */
194          block,
195          precommit_signature,
196        )
197      }
198    };
199
200    [
201      Some(MessageSegment::<S, A, B>::Dst([kind])),
202      round_number.map(MessageSegment::U64),
203      aggregate_signature.map(MessageSegment::AggregateSignature),
204      block.map(MessageSegment::Block),
205      precommit_signature.map(MessageSegment::PrecommitSignature),
206    ]
207  }
208}
209
210/// A message from the Tendermint consensus process.
211///
212/// This encapsulates a [`Data`] with the validator, block and round numbers, and signature.
213#[derive(Clone)]
214#[cfg_attr(feature = "alloc", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
215pub struct Message<V: Validator, S: Signature, A: AggregateSignature, B: Block> {
216  pub(crate) validator: V,
217  pub(crate) block_number: BlockNumber,
218  pub(crate) round_number: RoundNumber,
219  #[cfg_attr(
220    feature = "alloc",
221    borsh(bound(
222      serialize = "
223        A: borsh::BorshSerialize,
224        B: borsh::BorshSerialize,
225        B::Hash: borsh::BorshSerialize
226      ",
227      deserialize = "
228        A: borsh::BorshDeserialize,
229        B: borsh::BorshDeserialize,
230        B::Hash: borsh::BorshDeserialize
231      "
232    ))
233  )]
234  pub(crate) data: Data<S, A, B>,
235  pub(crate) signature: S,
236}
237
238impl<V: Validator, S: Signature, A: AggregateSignature, B: Block> PartialEq
239  for Message<V, S, A, B>
240{
241  /// This equality is semantic and does not consider if any present signatures are equal. This
242  /// ensures even for signature schemes with malleable signatures, multiple signatures over the
243  /// same messages are not semantically treated as distinct signatures. However, this also means a
244  /// value with an invalid signature will be considered equal to a message with a valid signature.
245  fn eq(&self, other: &Self) -> bool {
246    let Self { validator, block_number, round_number, data, signature: _ } = self;
247    ((*validator) == other.validator) &&
248      ((*block_number) == other.block_number) &&
249      ((*round_number) == other.round_number) &&
250      (data == &other.data)
251  }
252}
253impl<V: Validator, S: Signature, A: AggregateSignature, B: Block> Eq for Message<V, S, A, B> {}
254
255/// The message type for a blockchain.
256pub type MessageFor<B> = Message<
257  <B as Blockchain>::Validator,
258  <<B as Blockchain>::SignatureScheme as SignatureScheme>::Signature,
259  <<B as Blockchain>::SignatureScheme as SignatureScheme>::AggregateSignature,
260  <B as Blockchain>::Block,
261>;
262
263#[cfg_attr(test, derive(Debug))]
264pub(crate) enum MessageError<S: Signature, A: AggregateSignature, H: BlockHash> {
265  /// The message was stale within the current context.
266  Stale,
267  /// The message was for a future context.
268  ///
269  /// This MAY suggest the local view is historic.
270  Future,
271  /// The message was not from a validator.
272  NotValidator,
273  /// The message had an invalid signature and could not be authenticated as from the validator.
274  InvalidOuterSignature,
275  /// The message has an invalid intenal structure.
276  Invalid(SlashReason<S, A, H>),
277  /// This message has already been handled.
278  AlreadyHandled,
279}
280
281type MessageSignatureMessage<'genesis, S, A, B> =
282  Flatten<<[Option<MessageSegment<'genesis, S, A, B>>; 10] as IntoIterator>::IntoIter>;
283impl<V: Validator, S: Signature, A: AggregateSignature, B: Block> Message<V, S, A, B> {
284  #[expect(clippy::many_single_char_names)]
285  pub(crate) fn signature_message<'genesis>(
286    genesis: &'genesis [u8],
287    block_number: BlockNumber,
288    round_number: RoundNumber,
289    data: &Data<S, A, B>,
290  ) -> MessageSignatureMessage<'genesis, S, A, B> {
291    let [a, b, c, d, e] = [
292      MessageSegment::Dst([1]),
293      MessageSegment::Dst([u8::try_from(genesis.len()).unwrap()]),
294      MessageSegment::Genesis(genesis),
295      MessageSegment::U64(u64::from(block_number).to_le_bytes()),
296      MessageSegment::U64(u64::from(round_number).to_le_bytes()),
297    ]
298    .map(Some);
299    let [f, g, h, i, j] = data.signature_message();
300
301    [a, b, c, d, e, f, g, h, i, j].into_iter().flatten()
302  }
303
304  #[must_use]
305  pub(crate) async fn sign(
306    signer: &(impl ?Sized + Signer<Validator = V, Signature = S>),
307    genesis: &[u8],
308    block_number: BlockNumber,
309    round_number: RoundNumber,
310    data: Data<S, A, B>,
311  ) -> Self {
312    let signature =
313      signer.sign(Self::signature_message(genesis, block_number, round_number, &data)).await;
314    Self { validator: signer.validator(), block_number, round_number, data, signature }
315  }
316
317  /// Perform static verification for this message.
318  ///
319  /// This only verifies the values within this message which can be verified against solely the
320  /// `blockchain` itself, without any state from the Tendermint consensus process. This includes:
321  ///
322  /// - The message is from a validator
323  /// - The message's signature
324  /// - The signatures inside the message ([`ValidRound`], the precommit signature)
325  /// - The proposer is actually the proposer for this round
326  /// - The valid round's round number is less than this message's round number
327  pub(crate) fn static_verificiation(
328    &self,
329    genesis: impl AsRef<[u8]>,
330    validator_set: &(impl ?Sized + ValidatorSet<Validator = V>),
331    signature_scheme: &(impl ?Sized
332        + SignatureScheme<Validator = V, Signature = S, AggregateSignature = A>),
333  ) -> Result<(), MessageError<S, A, B::Hash>> {
334    let genesis = genesis.as_ref();
335
336    if validator_set.weight(&self.validator).is_none() {
337      Err(MessageError::NotValidator)?;
338    }
339
340    if !signature_scheme.verify(
341      &self.validator,
342      Self::signature_message(genesis, self.block_number, self.round_number, &self.data),
343      &self.signature,
344    ) {
345      Err(MessageError::InvalidOuterSignature)?;
346    }
347
348    match &self.data {
349      Data::Proposal { valid_round, proposal } => {
350        if !(valid_round.as_ref().is_none_or(|ValidRound { round_number, aggregate_signature }| {
351          ((*round_number) < self.round_number) &&
352            signature_scheme
353              .verify_aggregate(
354                Self::signature_message(
355                  genesis,
356                  self.block_number,
357                  *round_number,
358                  &Data::Prevote { block: Some(proposal.hash()) },
359                ),
360                aggregate_signature,
361              )
362              .is_ok_and(|validators| {
363                crate::validators_satisfy_threshold(validators, validator_set)
364              })
365        }) && (self.validator == validator_set.proposer(self.block_number, self.round_number)))
366        {
367          Err(MessageError::Invalid(SlashReason {
368            block_number: self.block_number,
369            round_number: self.round_number,
370            evidence: Evidence::InvalidProposal {
371              valid_round: valid_round.clone(),
372              proposal: proposal.hash(),
373              signature: self.signature.clone(),
374            },
375          }))?;
376        }
377      }
378      Data::Precommit { block_and_precommit_signature: Some((block, precommit_signature)) } => {
379        if !Commit::verify_precommit(
380          signature_scheme,
381          &self.validator,
382          genesis,
383          self.block_number,
384          self.round_number,
385          block.as_ref(),
386          precommit_signature,
387        ) {
388          Err(MessageError::Invalid(SlashReason {
389            block_number: self.block_number,
390            round_number: self.round_number,
391            evidence: Evidence::InvalidPrecommit {
392              block: *block,
393              precommit_signature: precommit_signature.clone(),
394              signature: self.signature.clone(),
395            },
396          }))?;
397        }
398      }
399      Data::Prevote { .. } | Data::Precommit { block_and_precommit_signature: None } => {}
400    }
401
402    Ok(())
403  }
404}
405
406#[cfg(test)]
407pub(crate) fn random_valid_round(
408) -> Option<ValidRound<<crate::TestSignatureScheme as SignatureScheme>::AggregateSignature>> {
409  use core::num::NonZero;
410  use rand_core::{TryRngCore as _, OsRng};
411
412  ((OsRng.try_next_u64().unwrap() & 1) == 1).then(|| {
413    let round_number =
414      RoundNumber(NonZero::new(OsRng.try_next_u64().unwrap().saturating_add(1)).unwrap());
415    let mut aggregate_signature = [0; 8 + 32];
416    OsRng.try_fill_bytes(&mut aggregate_signature).unwrap();
417    ValidRound { round_number, aggregate_signature }
418  })
419}
420
421#[test]
422fn test_random_valid_round() {
423  let mut some = false;
424  let mut none = false;
425  for _ in 0 .. 128 {
426    some |= random_valid_round().is_some();
427    none |= random_valid_round().is_none();
428  }
429  assert!(some);
430  assert!(none);
431}
432
433#[cfg(feature = "alloc")]
434#[test]
435fn signature_message() {
436  use core::num::NonZero;
437  use alloc::{vec::Vec, vec};
438
439  use rand_core::{TryRngCore as _, OsRng};
440
441  use crate::{StubBlock, TestSignatureScheme};
442
443  for _ in 0 .. 128 {
444    #[expect(clippy::as_conversions, clippy::cast_possible_truncation)]
445    let genesis_len = OsRng.try_next_u64().unwrap() as u8;
446    let mut genesis = vec![0xff; usize::from(genesis_len)];
447    OsRng.try_fill_bytes(&mut genesis).unwrap();
448    let genesis = genesis.as_ref();
449
450    let block_number =
451      BlockNumber(NonZero::new(OsRng.try_next_u64().unwrap().saturating_add(1)).unwrap());
452    let round_number =
453      RoundNumber(NonZero::new(OsRng.try_next_u64().unwrap().saturating_add(1)).unwrap());
454
455    let mut block = [0xff; 32];
456    OsRng.try_fill_bytes(&mut block).unwrap();
457    let block = StubBlock::from(&block);
458
459    type TestData<'hash> = Data<
460      <TestSignatureScheme as SignatureScheme>::Signature,
461      <TestSignatureScheme as SignatureScheme>::AggregateSignature,
462      StubBlock<'hash>,
463    >;
464
465    let mut signature = [0; 1 + 8];
466    OsRng.try_fill_bytes(&mut signature).unwrap();
467
468    let valid_round = loop {
469      let valid_round = random_valid_round();
470      if let Some(valid_round) = valid_round {
471        break valid_round;
472      }
473    };
474
475    let prefix = [
476      [1].as_slice(),
477      [genesis_len].as_slice(),
478      genesis,
479      u64::from(block_number).to_le_bytes().as_slice(),
480      u64::from(round_number).to_le_bytes().as_slice(),
481    ]
482    .concat();
483
484    assert_eq!(
485      Message::<<TestSignatureScheme as SignatureScheme>::Validator, _, _, _>::signature_message(
486        genesis,
487        block_number,
488        round_number,
489        &TestData::Proposal { valid_round: Some(valid_round.clone()), proposal: block.clone() }
490      )
491      .fold(Vec::<u8>::new(), |mut accum, item| {
492        accum.extend(item.as_ref());
493        accum
494      }),
495      [
496        prefix.as_slice(),
497        [0].as_slice(),
498        u64::from(valid_round.round_number).to_le_bytes().as_slice(),
499        valid_round.aggregate_signature.as_ref(),
500        block.hash().as_ref(),
501      ]
502      .concat(),
503    );
504
505    assert_eq!(
506      Message::<<TestSignatureScheme as SignatureScheme>::Validator, _, _, _>::signature_message(
507        genesis,
508        block_number,
509        round_number,
510        &TestData::Proposal { valid_round: None, proposal: block.clone() }
511      )
512      .fold(Vec::<u8>::new(), |mut accum, item| {
513        accum.extend(item.as_ref());
514        accum
515      }),
516      [prefix.as_slice(), [0].as_slice(), block.hash().as_ref()].concat(),
517    );
518
519    assert_eq!(
520      Message::<<TestSignatureScheme as SignatureScheme>::Validator, _, _, _>::signature_message(
521        genesis,
522        block_number,
523        round_number,
524        &TestData::Prevote { block: Some(block.hash()) }
525      )
526      .fold(Vec::<u8>::new(), |mut accum, item| {
527        accum.extend(item.as_ref());
528        accum
529      }),
530      [prefix.as_slice(), [1].as_slice(), block.hash().as_ref()].concat(),
531    );
532
533    assert_eq!(
534      Message::<<TestSignatureScheme as SignatureScheme>::Validator, _, _, _>::signature_message(
535        genesis,
536        block_number,
537        round_number,
538        &TestData::Prevote { block: None }
539      )
540      .fold(Vec::<u8>::new(), |mut accum, item| {
541        accum.extend(item.as_ref());
542        accum
543      }),
544      [prefix.as_slice(), [1].as_slice()].concat(),
545    );
546
547    assert_eq!(
548      Message::<<TestSignatureScheme as SignatureScheme>::Validator, _, _, _>::signature_message(
549        genesis,
550        block_number,
551        round_number,
552        &TestData::Precommit { block_and_precommit_signature: Some((block.hash(), signature)) }
553      )
554      .fold(Vec::<u8>::new(), |mut accum, item| {
555        accum.extend(item.as_ref());
556        accum
557      }),
558      [prefix.as_slice(), [2].as_slice(), block.hash().as_ref(), signature.as_ref()].concat(),
559    );
560
561    assert_eq!(
562      Message::<<TestSignatureScheme as SignatureScheme>::Validator, _, _, _>::signature_message(
563        genesis,
564        block_number,
565        round_number,
566        &TestData::Precommit { block_and_precommit_signature: None }
567      )
568      .fold(Vec::<u8>::new(), |mut accum, item| {
569        accum.extend(item.as_ref());
570        accum
571      }),
572      [prefix.as_slice(), [2].as_slice()].concat(),
573    );
574  }
575}
576
577#[cfg(feature = "alloc")]
578#[test]
579fn sign() {
580  use core::num::NonZero;
581  use core::{
582    pin::pin,
583    task::{Poll, Waker, Context},
584    future::Future as _,
585  };
586
587  use rand_core::{TryRngCore as _, OsRng};
588
589  use crate::{StubBlock, TestSignatureScheme};
590
591  for _ in 0 .. 128 {
592    let mut block = [0; 32];
593    OsRng.try_fill_bytes(&mut block).unwrap();
594    let block = ((OsRng.try_next_u64().unwrap() & 1) == 1).then_some(block);
595
596    let data =
597      Data::Prevote { block: block.as_ref().map(|block| StubBlock::from(block.as_slice()).hash()) };
598
599    let mut genesis = [0; 32];
600    OsRng.try_fill_bytes(&mut genesis).unwrap();
601
602    let block_number =
603      BlockNumber(NonZero::new(OsRng.try_next_u64().unwrap().saturating_add(1)).unwrap());
604    let round_number =
605      RoundNumber(NonZero::new(OsRng.try_next_u64().unwrap().saturating_add(1)).unwrap());
606
607    let signature_scheme = TestSignatureScheme::new();
608    let signer = signature_scheme.signer(0);
609
610    let mut context = Context::from_waker(Waker::noop());
611    let Poll::Ready(message) =
612      pin!(Message::<
613        _,
614        _,
615        <TestSignatureScheme as SignatureScheme>::AggregateSignature,
616        StubBlock<'_>,
617      >::sign(&signer, &genesis, block_number, round_number, data))
618      .poll(&mut context)
619    else {
620      panic!("`TestSignatureScheme::sign` returned `Poll::Pending`")
621    };
622
623    let one_weight = NonZero::new(1).unwrap();
624    let validator_set = alloc::collections::BTreeMap::from([(0, one_weight), (1, one_weight)]);
625    message.static_verificiation(genesis, &validator_set, &signature_scheme).unwrap();
626
627    {
628      let mut message = message.clone();
629      message.signature[0] ^= 1;
630      assert!(matches!(
631        message.static_verificiation(genesis, &validator_set, &signature_scheme),
632        Err(MessageError::InvalidOuterSignature)
633      ));
634    }
635
636    {
637      let mut message = message.clone();
638      message.validator = 1;
639      assert!(matches!(
640        message.static_verificiation(genesis, &validator_set, &signature_scheme),
641        Err(MessageError::InvalidOuterSignature)
642      ));
643    }
644
645    {
646      let mut message = message.clone();
647      message.validator = 2;
648      assert!(matches!(
649        message.static_verificiation(genesis, &validator_set, &signature_scheme),
650        Err(MessageError::NotValidator)
651      ));
652    }
653  }
654}