1use crate::block::BlockNumber;
4use crate::ed25519::Ed25519PublicKey;
5use crate::hashes::Blake3Hash;
6use crate::pieces::{PieceOffset, Record, RecordChunk, RecordProof, RecordRoot, SegmentProof};
7use crate::pos::{PosProof, PosSeed};
8use crate::pot::{PotOutput, SlotNumber};
9use crate::sectors::{SBucket, SectorId, SectorIndex, SectorSlotChallenge};
10use crate::segments::{
11 HistorySize, LocalSegmentIndex, SegmentIndex, SegmentPosition, SegmentRoot, SuperSegmentIndex,
12 SuperSegmentRoot,
13};
14use crate::shard::{NumShards, RealShardKind, ShardIndex, ShardKind};
15use ab_blake3::single_block_keyed_hash;
16use ab_io_type::trivial_type::TrivialType;
17use ab_merkle_tree::balanced::BalancedMerkleTree;
18use blake3::{Hash, OUT_LEN};
19use core::fmt;
20use core::simd::Simd;
21use derive_more::{
22 Add, AddAssign, AsMut, AsRef, Deref, DerefMut, Display, From, Into, Sub, SubAssign,
23};
24#[cfg(feature = "scale-codec")]
25use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
26#[cfg(feature = "serde")]
27use serde::{Deserialize, Serialize};
28#[cfg(feature = "serde")]
29use serde::{Deserializer, Serializer};
30#[cfg(feature = "serde")]
31use serde_big_array::BigArray;
32use transparent_wrapper::TransparentWrapper;
33
34#[derive(
36 Debug, Display, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, From, Into,
37)]
38#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40#[repr(C)]
41pub struct SolutionDistance(u64);
42
43impl SolutionDistance {
44 pub const MAX: Self = Self(u64::MAX / 2);
46
47 #[inline(always)]
50 pub const fn from_u64(n: u64) -> Self {
51 Self(n)
52 }
53
54 pub fn calculate(
59 global_challenge: &Blake3Hash,
60 chunk: &[u8; 32],
61 sector_slot_challenge: &SectorSlotChallenge,
62 ) -> Self {
63 let audit_chunk = single_block_keyed_hash(sector_slot_challenge, chunk)
65 .expect("Less than a single block worth of bytes; qed");
66 let audit_chunk_as_solution_range = SolutionRange::from_bytes([
67 audit_chunk[0],
68 audit_chunk[1],
69 audit_chunk[2],
70 audit_chunk[3],
71 audit_chunk[4],
72 audit_chunk[5],
73 audit_chunk[6],
74 audit_chunk[7],
75 ]);
76 let global_challenge_as_solution_range =
77 SolutionRange::from_bytes(global_challenge.as_chunks().0[0]);
78
79 global_challenge_as_solution_range.bidirectional_distance(audit_chunk_as_solution_range)
80 }
81
82 pub const fn is_within(self, solution_range: SolutionRange) -> bool {
84 self.0 <= u64::from(solution_range) / 2
85 }
86}
87
88#[derive(
90 Debug,
91 Display,
92 Default,
93 Copy,
94 Clone,
95 Ord,
96 PartialOrd,
97 Eq,
98 PartialEq,
99 Hash,
100 Add,
101 AddAssign,
102 Sub,
103 SubAssign,
104 TrivialType,
105)]
106#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
107#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
108#[repr(C)]
109pub struct SolutionRange(u64);
110
111const impl From<u64> for SolutionRange {
112 #[inline(always)]
113 fn from(value: u64) -> Self {
114 Self(value)
115 }
116}
117
118const impl From<SolutionRange> for u64 {
119 #[inline(always)]
120 fn from(value: SolutionRange) -> Self {
121 value.0
122 }
123}
124
125impl SolutionRange {
126 pub const SIZE: usize = size_of::<u64>();
128 pub const MIN: Self = Self(u64::MIN);
130 pub const MAX: Self = Self(u64::MAX);
132
133 #[inline(always)]
135 pub fn to_bytes(self) -> [u8; 8] {
136 self.0.to_le_bytes()
137 }
138
139 #[inline(always)]
141 pub fn from_bytes(bytes: [u8; 8]) -> Self {
142 Self(u64::from_le_bytes(bytes))
143 }
144
145 #[inline]
150 pub const fn from_pieces(pieces: u64, slot_probability: (u64, u64)) -> Self {
151 let solution_range = u64::MAX
152 / slot_probability.1 * slot_probability.0
154 / Record::NUM_CHUNKS as u64
156 * Record::NUM_S_BUCKETS as u64;
157
158 Self(solution_range / pieces)
160 }
161
162 #[inline]
167 pub const fn to_pieces(self, slot_probability: (u64, u64)) -> u64 {
168 let pieces = u64::MAX
169 / slot_probability.1 * slot_probability.0
171 / Record::NUM_CHUNKS as u64
173 * Record::NUM_S_BUCKETS as u64;
174
175 pieces / self.0
177 }
178
179 #[inline]
184 pub const fn to_leaf_shard(self, num_shards: NumShards) -> Self {
185 Self(
186 self.0
187 .saturating_mul(u64::from(num_shards.leaf_shards().get())),
188 )
189 }
190
191 #[inline]
194 pub const fn to_intermediate_shard(self, num_shards: NumShards) -> Self {
195 Self(
196 self.0
197 .saturating_mul(u64::from(num_shards.intermediate_shards().get())),
198 )
199 }
200
201 #[inline]
203 pub const fn bidirectional_distance(self, other: Self) -> SolutionDistance {
204 let a = self.0;
205 let b = other.0;
206 let diff = a.wrapping_sub(b);
207 let diff2 = b.wrapping_sub(a);
208 SolutionDistance::from_u64(if diff < diff2 { diff } else { diff2 })
210 }
211
212 #[inline]
214 pub fn derive_next(
215 self,
216 slots_in_last_interval: SlotNumber,
217 slot_probability: (u64, u64),
218 retarget_interval: BlockNumber,
219 ) -> Self {
220 let current_solution_range = self.0;
236 let next_solution_range = u64::try_from(
237 u128::from(current_solution_range)
238 .saturating_mul(u128::from(slots_in_last_interval))
239 .saturating_mul(u128::from(slot_probability.0))
240 / u128::from(u64::from(retarget_interval))
241 / u128::from(slot_probability.1),
242 );
243
244 Self(next_solution_range.unwrap_or(u64::MAX).clamp(
245 current_solution_range / 4,
246 current_solution_range.saturating_mul(4),
247 ))
248 }
249}
250
251const {
253 assert!(SolutionRange::from_pieces(1, (1, 6)).to_pieces((1, 6)) == 1);
254 assert!(SolutionRange::from_pieces(3, (1, 6)).to_pieces((1, 6)) == 3);
255 assert!(SolutionRange::from_pieces(5, (1, 6)).to_pieces((1, 6)) == 5);
256}
257
258#[derive(Copy, Clone, Eq, PartialEq, Hash, Deref, DerefMut, From, Into, TrivialType)]
260#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
261#[repr(C)]
262pub struct ChunkProof([[u8; OUT_LEN]; ChunkProof::NUM_HASHES]);
263
264impl fmt::Debug for ChunkProof {
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 write!(f, "[")?;
267 for hash in self.0 {
268 for byte in hash {
269 write!(f, "{byte:02x}")?;
270 }
271 write!(f, ", ")?;
272 }
273 write!(f, "]")?;
274 Ok(())
275 }
276}
277
278#[cfg(feature = "serde")]
279#[derive(Serialize, Deserialize)]
280#[serde(transparent)]
281struct ChunkProofBinary(#[serde(with = "BigArray")] [[u8; OUT_LEN]; ChunkProof::NUM_HASHES]);
282
283#[cfg(feature = "serde")]
284#[derive(Serialize, Deserialize, TransparentWrapper)]
285#[serde(transparent)]
286#[repr(transparent)]
287struct ChunkProofHexHash(#[serde(with = "hex")] [u8; OUT_LEN]);
288
289#[cfg(feature = "serde")]
290#[derive(Serialize, Deserialize)]
291#[serde(transparent)]
292struct ChunkProofHex([ChunkProofHexHash; ChunkProof::NUM_HASHES]);
293
294#[cfg(feature = "serde")]
295impl Serialize for ChunkProof {
296 #[inline]
297 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
298 where
299 S: Serializer,
300 {
301 if serializer.is_human_readable() {
302 ChunkProofHex(<[ChunkProofHexHash; Self::NUM_HASHES]>::wrap(self.0))
303 .serialize(serializer)
304 } else {
305 ChunkProofBinary(self.0).serialize(serializer)
306 }
307 }
308}
309
310#[cfg(feature = "serde")]
311impl<'de> Deserialize<'de> for ChunkProof {
312 #[inline]
313 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
314 where
315 D: Deserializer<'de>,
316 {
317 Ok(Self(if deserializer.is_human_readable() {
318 ChunkProofHex::deserialize(deserializer)?.0.peel()
319 } else {
320 ChunkProofBinary::deserialize(deserializer)?.0
321 }))
322 }
323}
324
325impl Default for ChunkProof {
326 #[inline]
327 fn default() -> Self {
328 Self([[0; OUT_LEN]; _])
329 }
330}
331
332impl AsRef<[u8]> for ChunkProof {
333 #[inline]
334 fn as_ref(&self) -> &[u8] {
335 self.0.as_flattened()
336 }
337}
338
339impl AsMut<[u8]> for ChunkProof {
340 #[inline]
341 fn as_mut(&mut self) -> &mut [u8] {
342 self.0.as_flattened_mut()
343 }
344}
345
346impl ChunkProof {
347 pub const SIZE: usize = OUT_LEN * Self::NUM_HASHES;
349 const NUM_HASHES: usize = Record::NUM_S_BUCKETS.ilog2() as usize;
350}
351
352#[derive(Debug, Eq, PartialEq, thiserror::Error)]
354pub enum SolutionVerifyError {
355 #[error("Piece verification failed")]
357 InvalidPieceOffset {
358 piece_offset: u16,
360 max_pieces_in_sector: u16,
362 },
363 #[error("History size {solution} is in the future, current is {current}")]
365 FutureHistorySize {
366 current: HistorySize,
368 solution: HistorySize,
370 },
371 #[error("Sector expired")]
373 SectorExpired {
374 expiration_history_size: HistorySize,
376 current_history_size: HistorySize,
378 },
379 #[error("Record does not belong to the segment")]
381 RecordNotInSegment,
382 #[error("Segment doesn't belong to the super segment")]
384 SegmentNotInSuperSegment,
385 #[error("Solution distance {solution_distance} is outside of solution range {solution_range}")]
387 OutsideSolutionRange {
388 solution_range: SolutionRange,
390 solution_distance: SolutionDistance,
392 },
393 #[error("Invalid proof of space")]
395 InvalidProofOfSpace,
396 #[error("Invalid shard commitment")]
398 InvalidShardCommitment,
399 #[error("Invalid input shard {shard_index} ({shard_kind:?})")]
401 InvalidInputShard {
402 shard_index: ShardIndex,
404 shard_kind: Option<ShardKind>,
406 },
407 #[error(
409 "Invalid solution shard {solution_shard_index} (parent {solution_parent_shard_index:?}), \
410 expected shard {expected_shard_index} ({expected_shard_kind:?})"
411 )]
412 InvalidSolutionShard {
413 solution_shard_index: ShardIndex,
415 solution_parent_shard_index: Option<ShardIndex>,
417 expected_shard_index: ShardIndex,
419 expected_shard_kind: RealShardKind,
421 },
422 #[error("Invalid chunk proof")]
424 InvalidChunkProof,
425 #[error("Invalid history size")]
427 InvalidHistorySize,
428}
429
430#[derive(Debug, Clone)]
435#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
436pub struct SolutionVerifyStatelessParams {
437 pub shard_index: ShardIndex,
439 pub proof_of_time: PotOutput,
441 pub solution_range: SolutionRange,
443 pub shard_membership_entropy: ShardMembershipEntropy,
445 pub num_shards: NumShards,
447}
448
449#[derive(Debug, Clone)]
451#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
452pub struct SolutionVerifyPieceParams {
453 pub max_pieces_in_sector: u16,
455 pub super_segment_root: SuperSegmentRoot,
457 pub num_segments: u32,
459 pub recent_segments: HistorySize,
461 pub recent_history_fraction: (HistorySize, HistorySize),
463 pub min_sector_lifetime: HistorySize,
465 pub current_history_size: HistorySize,
467 pub sector_expiration_check_super_segment_root: Option<SuperSegmentRoot>,
470}
471
472#[derive(Debug, Clone)]
474#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
475pub struct SolutionVerifyFullParams {
476 pub stateless: SolutionVerifyStatelessParams,
478 pub piece: SolutionVerifyPieceParams,
480}
481
482pub trait SolutionPotVerifier {
484 fn is_proof_valid(seed: &PosSeed, s_bucket: SBucket, proof: &PosProof) -> bool;
486}
487
488#[derive(
490 Default,
491 Copy,
492 Clone,
493 Eq,
494 PartialEq,
495 Ord,
496 PartialOrd,
497 Hash,
498 From,
499 Into,
500 AsRef,
501 AsMut,
502 Deref,
503 DerefMut,
504 TrivialType,
505 TransparentWrapper,
506)]
507#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
508#[repr(C)]
509pub struct ShardMembershipEntropy([u8; ShardMembershipEntropy::SIZE]);
510
511impl fmt::Display for ShardMembershipEntropy {
512 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
513 for byte in self.0 {
514 write!(f, "{byte:02x}")?;
515 }
516 Ok(())
517 }
518}
519
520#[cfg(feature = "serde")]
521#[derive(Serialize, Deserialize)]
522#[serde(transparent)]
523struct ShardMembershipEntropyBinary([u8; ShardMembershipEntropy::SIZE]);
524
525#[cfg(feature = "serde")]
526#[derive(Serialize, Deserialize)]
527#[serde(transparent)]
528struct ShardMembershipEntropyHex(#[serde(with = "hex")] [u8; ShardMembershipEntropy::SIZE]);
529
530#[cfg(feature = "serde")]
531impl Serialize for ShardMembershipEntropy {
532 #[inline]
533 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
534 where
535 S: Serializer,
536 {
537 if serializer.is_human_readable() {
538 ShardMembershipEntropyHex(self.0).serialize(serializer)
539 } else {
540 ShardMembershipEntropyBinary(self.0).serialize(serializer)
541 }
542 }
543}
544
545#[cfg(feature = "serde")]
546impl<'de> Deserialize<'de> for ShardMembershipEntropy {
547 #[inline]
548 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
549 where
550 D: Deserializer<'de>,
551 {
552 Ok(Self(if deserializer.is_human_readable() {
553 ShardMembershipEntropyHex::deserialize(deserializer)?.0
554 } else {
555 ShardMembershipEntropyBinary::deserialize(deserializer)?.0
556 }))
557 }
558}
559
560impl fmt::Debug for ShardMembershipEntropy {
561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 for byte in self.0 {
563 write!(f, "{byte:02x}")?;
564 }
565 Ok(())
566 }
567}
568
569impl AsRef<[u8]> for ShardMembershipEntropy {
570 #[inline(always)]
571 fn as_ref(&self) -> &[u8] {
572 &self.0
573 }
574}
575
576impl AsMut<[u8]> for ShardMembershipEntropy {
577 #[inline(always)]
578 fn as_mut(&mut self) -> &mut [u8] {
579 &mut self.0
580 }
581}
582
583impl ShardMembershipEntropy {
584 pub const SIZE: usize = PotOutput::SIZE;
586
587 #[inline(always)]
589 pub const fn new(bytes: [u8; Self::SIZE]) -> Self {
590 Self(bytes)
591 }
592
593 #[inline(always)]
595 pub const fn as_bytes(&self) -> &[u8; Self::SIZE] {
596 &self.0
597 }
598
599 #[inline(always)]
601 pub const fn slice_from_repr(value: &[[u8; Self::SIZE]]) -> &[Self] {
602 Self::wrap_slice(value)
603 }
604
605 #[inline(always)]
607 pub const fn repr_from_slice(value: &[Self]) -> &[[u8; Self::SIZE]] {
608 Self::peel_slice(value)
609 }
610}
611
612#[derive(
614 Default,
615 Copy,
616 Clone,
617 Eq,
618 PartialEq,
619 Ord,
620 PartialOrd,
621 Hash,
622 From,
623 Into,
624 AsRef,
625 AsMut,
626 Deref,
627 DerefMut,
628 TrivialType,
629 TransparentWrapper,
630)]
631#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
632#[repr(C)]
633pub struct ShardCommitmentHash([u8; ShardCommitmentHash::SIZE]);
634
635impl fmt::Display for ShardCommitmentHash {
636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637 for byte in self.0 {
638 write!(f, "{byte:02x}")?;
639 }
640 Ok(())
641 }
642}
643
644#[cfg(feature = "serde")]
645#[derive(Serialize, Deserialize)]
646#[serde(transparent)]
647struct ShardCommitmentHashBinary([u8; ShardCommitmentHash::SIZE]);
648
649#[cfg(feature = "serde")]
650#[derive(Serialize, Deserialize)]
651#[serde(transparent)]
652struct ShardCommitmentHashHex(#[serde(with = "hex")] [u8; ShardCommitmentHash::SIZE]);
653
654#[cfg(feature = "serde")]
655impl Serialize for ShardCommitmentHash {
656 #[inline]
657 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
658 where
659 S: Serializer,
660 {
661 if serializer.is_human_readable() {
662 ShardCommitmentHashHex(self.0).serialize(serializer)
663 } else {
664 ShardCommitmentHashBinary(self.0).serialize(serializer)
665 }
666 }
667}
668
669#[cfg(feature = "serde")]
670impl<'de> Deserialize<'de> for ShardCommitmentHash {
671 #[inline]
672 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
673 where
674 D: Deserializer<'de>,
675 {
676 Ok(Self(if deserializer.is_human_readable() {
677 ShardCommitmentHashHex::deserialize(deserializer)?.0
678 } else {
679 ShardCommitmentHashBinary::deserialize(deserializer)?.0
680 }))
681 }
682}
683
684impl fmt::Debug for ShardCommitmentHash {
685 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
686 for byte in self.0 {
687 write!(f, "{byte:02x}")?;
688 }
689 Ok(())
690 }
691}
692
693impl AsRef<[u8]> for ShardCommitmentHash {
694 #[inline(always)]
695 fn as_ref(&self) -> &[u8] {
696 &self.0
697 }
698}
699
700impl AsMut<[u8]> for ShardCommitmentHash {
701 #[inline(always)]
702 fn as_mut(&mut self) -> &mut [u8] {
703 &mut self.0
704 }
705}
706
707impl From<Hash> for ShardCommitmentHash {
708 #[inline(always)]
709 fn from(value: Hash) -> Self {
710 let bytes = value.as_bytes();
711 Self(*bytes)
712 }
716}
717
718impl ShardCommitmentHash {
719 pub const SIZE: usize = 32;
722
723 #[inline(always)]
725 pub const fn new(hash: [u8; Self::SIZE]) -> Self {
726 Self(hash)
727 }
728
729 #[inline(always)]
731 pub const fn as_bytes(&self) -> &[u8; Self::SIZE] {
732 &self.0
733 }
734
735 #[inline(always)]
737 pub const fn slice_from_repr(value: &[[u8; Self::SIZE]]) -> &[Self] {
738 Self::wrap_slice(value)
739 }
740
741 #[inline(always)]
743 pub const fn array_from_repr<const N: usize>(value: [[u8; Self::SIZE]; N]) -> [Self; N] {
744 <[Self; N]>::wrap(value)
745 }
746
747 #[inline(always)]
749 pub const fn repr_from_slice(value: &[Self]) -> &[[u8; Self::SIZE]] {
750 Self::peel_slice(value)
751 }
752
753 #[inline(always)]
755 pub const fn repr_from_array<const N: usize>(value: [Self; N]) -> [[u8; Self::SIZE]; N] {
756 value.peel()
757 }
758}
759
760#[derive(Clone, Copy, Debug, Eq, PartialEq, TrivialType)]
762#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
763#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
764#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
765#[repr(C)]
766pub struct SolutionShardCommitment {
767 pub root: ShardCommitmentHash,
769 pub proof: [ShardCommitmentHash; SolutionShardCommitment::NUM_LEAVES.ilog2() as usize],
771 pub leaf: ShardCommitmentHash,
773}
774
775impl SolutionShardCommitment {
776 pub const NUM_LEAVES: usize = 2u32.pow(20) as usize;
778}
779
780#[derive(Clone, Copy, Debug, Eq, PartialEq, TrivialType)]
782#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
783#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
784#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
785#[repr(C)]
786pub struct Solution {
787 pub public_key_hash: Blake3Hash,
789 pub shard_commitment: SolutionShardCommitment,
791 pub piece_local_segment_index: LocalSegmentIndex,
793 pub piece_super_segment_index: SuperSegmentIndex,
795 pub segment_root: SegmentRoot,
797 pub segment_proof: SegmentProof,
799 pub record_root: RecordRoot,
801 pub record_proof: RecordProof,
803 pub chunk: RecordChunk,
805 pub chunk_proof: ChunkProof,
807 pub proof_of_space: PosProof,
809 pub history_size: HistorySize,
811 pub sector_index: SectorIndex,
813 pub piece_offset: PieceOffset,
815 pub segment_position: SegmentPosition,
817 pub piece_shard_index: ShardIndex,
819 pub padding: [u8; 4],
821}
822
823impl Solution {
824 pub fn genesis_solution() -> Self {
826 Self {
827 public_key_hash: Ed25519PublicKey::default().hash(),
828 shard_commitment: SolutionShardCommitment {
829 root: ShardCommitmentHash::default(),
830 proof: [ShardCommitmentHash::default(); _],
831 leaf: ShardCommitmentHash::default(),
832 },
833 piece_local_segment_index: LocalSegmentIndex::ZERO,
834 piece_super_segment_index: SuperSegmentIndex::ZERO,
835 segment_root: SegmentRoot::default(),
836 segment_proof: SegmentProof::default(),
837 record_root: RecordRoot::default(),
838 record_proof: RecordProof::default(),
839 chunk: RecordChunk::default(),
840 chunk_proof: ChunkProof::default(),
841 proof_of_space: PosProof::default(),
842 history_size: HistorySize::from(SegmentIndex::ZERO),
843 sector_index: SectorIndex::ZERO,
844 piece_offset: PieceOffset::default(),
845 segment_position: SegmentPosition::default(),
846 piece_shard_index: ShardIndex::BEACON_CHAIN,
847 padding: [0; _],
848 }
849 }
850
851 pub fn verify_full<PotVerifier>(
853 &self,
854 slot: SlotNumber,
855 params: &SolutionVerifyFullParams,
856 ) -> Result<(), SolutionVerifyError>
857 where
858 PotVerifier: SolutionPotVerifier,
859 {
860 let sector_id = SectorId::new(
861 &self.public_key_hash,
862 &self.shard_commitment.root,
863 self.sector_index,
864 self.history_size,
865 );
866
867 self.verify_stateless_inner::<PotVerifier>(§or_id, slot, ¶ms.stateless)?;
868
869 self.verify_piece_inner(§or_id, ¶ms.piece)
870 }
871
872 pub fn verify_stateless<PotVerifier>(
879 &self,
880 slot: SlotNumber,
881 params: &SolutionVerifyStatelessParams,
882 ) -> Result<(), SolutionVerifyError>
883 where
884 PotVerifier: SolutionPotVerifier,
885 {
886 let sector_id = SectorId::new(
887 &self.public_key_hash,
888 &self.shard_commitment.root,
889 self.sector_index,
890 self.history_size,
891 );
892
893 self.verify_stateless_inner::<PotVerifier>(§or_id, slot, params)
894 }
895
896 fn verify_stateless_inner<PotVerifier>(
897 &self,
898 sector_id: &SectorId,
899 slot: SlotNumber,
900 params: &SolutionVerifyStatelessParams,
901 ) -> Result<(), SolutionVerifyError>
902 where
903 PotVerifier: SolutionPotVerifier,
904 {
905 let SolutionVerifyStatelessParams {
906 shard_index,
907 proof_of_time,
908 solution_range,
909 shard_membership_entropy,
910 num_shards,
911 } = params;
912
913 let shard_kind = shard_index
914 .shard_kind()
915 .and_then(ShardKind::to_real)
916 .ok_or(SolutionVerifyError::InvalidInputShard {
917 shard_index: *shard_index,
918 shard_kind: shard_index.shard_kind(),
919 })?;
920
921 let (solution_shard_index, shard_commitment_index) = num_shards
922 .derive_shard_index_and_shard_commitment_index(
923 &self.public_key_hash,
924 &self.shard_commitment.root,
925 shard_membership_entropy,
926 self.history_size,
927 );
928
929 let solution_range = match shard_kind {
931 RealShardKind::BeaconChain => *solution_range,
932 RealShardKind::IntermediateShard => {
933 if solution_shard_index.parent_shard() != Some(*shard_index) {
934 return Err(SolutionVerifyError::InvalidSolutionShard {
935 solution_shard_index,
936 solution_parent_shard_index: solution_shard_index.parent_shard(),
937 expected_shard_index: *shard_index,
938 expected_shard_kind: RealShardKind::IntermediateShard,
939 });
940 }
941
942 solution_range.to_intermediate_shard(*num_shards)
943 }
944 RealShardKind::LeafShard => {
945 if solution_shard_index != *shard_index {
946 return Err(SolutionVerifyError::InvalidSolutionShard {
947 solution_shard_index,
948 solution_parent_shard_index: solution_shard_index.parent_shard(),
949 expected_shard_index: *shard_index,
950 expected_shard_kind: RealShardKind::LeafShard,
951 });
952 }
953
954 solution_range.to_leaf_shard(*num_shards)
955 }
956 };
957
958 if !BalancedMerkleTree::<{ SolutionShardCommitment::NUM_LEAVES }>::verify(
959 &self.shard_commitment.root,
960 &ShardCommitmentHash::repr_from_array(self.shard_commitment.proof),
961 shard_commitment_index as usize,
962 *self.shard_commitment.leaf,
963 ) {
964 return Err(SolutionVerifyError::InvalidShardCommitment);
965 }
966
967 let global_challenge = proof_of_time.derive_global_challenge(slot);
968 let sector_slot_challenge = sector_id.derive_sector_slot_challenge(&global_challenge);
969 let s_bucket_audit_index = sector_slot_challenge.s_bucket_audit_index();
970
971 if !PotVerifier::is_proof_valid(
973 §or_id.derive_evaluation_seed(self.piece_offset),
974 s_bucket_audit_index,
975 &self.proof_of_space,
976 ) {
977 return Err(SolutionVerifyError::InvalidProofOfSpace);
978 }
979
980 let masked_chunk =
981 (Simd::from(*self.chunk) ^ Simd::from(*self.proof_of_space.hash())).to_array();
982
983 let solution_distance =
984 SolutionDistance::calculate(&global_challenge, &masked_chunk, §or_slot_challenge);
985
986 if !solution_distance.is_within(solution_range) {
987 return Err(SolutionVerifyError::OutsideSolutionRange {
988 solution_range,
989 solution_distance,
990 });
991 }
992
993 if !BalancedMerkleTree::<{ Record::NUM_S_BUCKETS }>::verify(
995 &self.record_root,
996 &self.chunk_proof,
997 usize::from(s_bucket_audit_index),
998 *self.chunk,
999 ) {
1000 return Err(SolutionVerifyError::InvalidChunkProof);
1001 }
1002
1003 Ok(())
1004 }
1005
1006 pub fn verify_piece(
1008 &self,
1009 piece_check_params: &SolutionVerifyPieceParams,
1010 ) -> Result<(), SolutionVerifyError> {
1011 let sector_id = SectorId::new(
1012 &self.public_key_hash,
1013 &self.shard_commitment.root,
1014 self.sector_index,
1015 self.history_size,
1016 );
1017
1018 self.verify_piece_inner(§or_id, piece_check_params)
1019 }
1020
1021 fn verify_piece_inner(
1022 &self,
1023 sector_id: &SectorId,
1024 piece_check_params: &SolutionVerifyPieceParams,
1025 ) -> Result<(), SolutionVerifyError> {
1026 let SolutionVerifyPieceParams {
1027 max_pieces_in_sector,
1028 super_segment_root,
1029 num_segments,
1030 recent_segments,
1031 recent_history_fraction,
1032 min_sector_lifetime,
1033 current_history_size,
1034 sector_expiration_check_super_segment_root,
1035 } = piece_check_params;
1036
1037 if &self.history_size > current_history_size {
1038 return Err(SolutionVerifyError::FutureHistorySize {
1039 current: *current_history_size,
1040 solution: self.history_size,
1041 });
1042 }
1043
1044 if u16::from(self.piece_offset) >= *max_pieces_in_sector {
1045 return Err(SolutionVerifyError::InvalidPieceOffset {
1046 piece_offset: u16::from(self.piece_offset),
1047 max_pieces_in_sector: *max_pieces_in_sector,
1048 });
1049 }
1050
1051 if let Some(sector_expiration_check_super_segment_root) =
1052 sector_expiration_check_super_segment_root
1053 {
1054 let Some(expiration_history_size) = sector_id.derive_expiration_history_size(
1055 self.history_size,
1056 sector_expiration_check_super_segment_root,
1057 *min_sector_lifetime,
1058 ) else {
1059 return Err(SolutionVerifyError::InvalidHistorySize);
1060 };
1061
1062 if expiration_history_size <= *current_history_size {
1063 return Err(SolutionVerifyError::SectorExpired {
1064 expiration_history_size,
1065 current_history_size: *current_history_size,
1066 });
1067 }
1068 }
1069
1070 let position = sector_id
1071 .derive_piece_index(
1072 self.piece_offset,
1073 self.history_size,
1074 *max_pieces_in_sector,
1075 *recent_segments,
1076 *recent_history_fraction,
1077 )
1078 .position();
1079
1080 if !self
1082 .record_root
1083 .is_valid(&self.segment_root, &self.record_proof, position)
1084 {
1085 return Err(SolutionVerifyError::RecordNotInSegment);
1086 }
1087
1088 if !self.segment_root.is_valid(
1090 self.piece_shard_index,
1091 self.piece_local_segment_index,
1092 self.segment_position,
1093 &self.segment_proof,
1094 *num_segments,
1095 super_segment_root,
1096 ) {
1097 return Err(SolutionVerifyError::SegmentNotInSuperSegment);
1098 }
1099
1100 Ok(())
1101 }
1102}