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#[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 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#[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 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 #[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 round_number,
167 aggregate_signature,
168 Some(proposal.hash()),
176 None,
177 )
178 }
179 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 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#[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 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
255pub 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 Stale,
267 Future,
271 NotValidator,
273 InvalidOuterSignature,
275 Invalid(SlashReason<S, A, H>),
277 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 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}