1use crate::block::BlockNumber;
4use crate::ed25519::Ed25519PublicKey;
5use crate::hashes::Blake3Hash;
6use crate::pieces::{PieceOffset, Record, RecordChunk, RecordProof, RecordRoot};
7use crate::pos::{PosProof, PosSeed};
8use crate::pot::{PotOutput, SlotNumber};
9use crate::sectors::{SBucket, SectorId, SectorIndex, SectorSlotChallenge};
10use crate::segments::{HistorySize, SegmentIndex, SegmentRoot};
11use crate::shard::{NumShards, RealShardKind, ShardIndex, ShardKind};
12use ab_blake3::single_block_keyed_hash;
13use ab_io_type::trivial_type::TrivialType;
14use ab_merkle_tree::balanced::BalancedMerkleTree;
15use blake3::{Hash, OUT_LEN};
16use core::simd::Simd;
17use core::{fmt, mem};
18use derive_more::{
19 Add, AddAssign, AsMut, AsRef, Deref, DerefMut, Display, From, Into, Sub, SubAssign,
20};
21#[cfg(feature = "scale-codec")]
22use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
23#[cfg(feature = "serde")]
24use serde::{Deserialize, Serialize};
25#[cfg(feature = "serde")]
26use serde::{Deserializer, Serializer};
27#[cfg(feature = "serde")]
28use serde_big_array::BigArray;
29
30#[derive(
32 Debug, Display, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, From, Into,
33)]
34#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[repr(C)]
37pub struct SolutionDistance(u64);
38
39impl SolutionDistance {
40 pub const MAX: Self = Self(u64::MAX / 2);
42
43 #[inline(always)]
46 pub const fn from_u64(n: u64) -> Self {
47 Self(n)
48 }
49
50 pub fn calculate(
55 global_challenge: &Blake3Hash,
56 chunk: &[u8; 32],
57 sector_slot_challenge: &SectorSlotChallenge,
58 ) -> Self {
59 let audit_chunk = single_block_keyed_hash(sector_slot_challenge, chunk)
61 .expect("Less than a single block worth of bytes; qed");
62 let audit_chunk_as_solution_range: SolutionRange = SolutionRange::from_bytes([
63 audit_chunk[0],
64 audit_chunk[1],
65 audit_chunk[2],
66 audit_chunk[3],
67 audit_chunk[4],
68 audit_chunk[5],
69 audit_chunk[6],
70 audit_chunk[7],
71 ]);
72 let global_challenge_as_solution_range: SolutionRange =
73 SolutionRange::from_bytes(global_challenge.as_chunks().0[0]);
74
75 global_challenge_as_solution_range.bidirectional_distance(audit_chunk_as_solution_range)
76 }
77
78 pub const fn is_within(self, solution_range: SolutionRange) -> bool {
80 self.0 <= u64::from(solution_range) / 2
81 }
82}
83
84#[derive(
86 Debug,
87 Display,
88 Default,
89 Copy,
90 Clone,
91 Ord,
92 PartialOrd,
93 Eq,
94 PartialEq,
95 Hash,
96 Add,
97 AddAssign,
98 Sub,
99 SubAssign,
100 TrivialType,
101)]
102#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
103#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
104#[repr(C)]
105pub struct SolutionRange(u64);
106
107impl const From<u64> for SolutionRange {
108 #[inline(always)]
109 fn from(value: u64) -> Self {
110 Self(value)
111 }
112}
113
114impl const From<SolutionRange> for u64 {
115 #[inline(always)]
116 fn from(value: SolutionRange) -> Self {
117 value.0
118 }
119}
120
121impl SolutionRange {
122 pub const SIZE: usize = size_of::<u64>();
124 pub const MIN: Self = Self(u64::MIN);
126 pub const MAX: Self = Self(u64::MAX);
128
129 #[inline(always)]
131 pub fn to_bytes(self) -> [u8; 8] {
132 self.0.to_le_bytes()
133 }
134
135 #[inline(always)]
137 pub fn from_bytes(bytes: [u8; 8]) -> Self {
138 Self(u64::from_le_bytes(bytes))
139 }
140
141 #[inline]
146 pub const fn from_pieces(pieces: u64, slot_probability: (u64, u64)) -> Self {
147 let solution_range = u64::MAX
148 / slot_probability.1 * slot_probability.0
150 / Record::NUM_CHUNKS as u64
152 * Record::NUM_S_BUCKETS as u64;
153
154 Self(solution_range / pieces)
156 }
157
158 #[inline]
163 pub const fn to_pieces(self, slot_probability: (u64, u64)) -> u64 {
164 let pieces = u64::MAX
165 / slot_probability.1 * slot_probability.0
167 / Record::NUM_CHUNKS as u64
169 * Record::NUM_S_BUCKETS as u64;
170
171 pieces / self.0
173 }
174
175 #[inline]
180 pub const fn to_leaf_shard(self, num_shards: NumShards) -> Self {
181 Self(
182 self.0
183 .saturating_mul(u64::from(num_shards.leaf_shards().get())),
184 )
185 }
186
187 #[inline]
190 pub const fn to_intermediate_shard(self, num_shards: NumShards) -> Self {
191 Self(
192 self.0
193 .saturating_mul(u64::from(num_shards.intermediate_shards().get())),
194 )
195 }
196
197 #[inline]
199 pub const fn bidirectional_distance(self, other: Self) -> SolutionDistance {
200 let a = self.0;
201 let b = other.0;
202 let diff = a.wrapping_sub(b);
203 let diff2 = b.wrapping_sub(a);
204 SolutionDistance::from_u64(if diff < diff2 { diff } else { diff2 })
206 }
207
208 #[inline]
210 pub fn derive_next(
211 self,
212 slots_in_last_interval: SlotNumber,
213 slot_probability: (u64, u64),
214 retarget_interval: BlockNumber,
215 ) -> Self {
216 let current_solution_range = self.0;
232 let next_solution_range = u64::try_from(
233 u128::from(current_solution_range)
234 .saturating_mul(u128::from(slots_in_last_interval))
235 .saturating_mul(u128::from(slot_probability.0))
236 / u128::from(u64::from(retarget_interval))
237 / u128::from(slot_probability.1),
238 );
239
240 Self(next_solution_range.unwrap_or(u64::MAX).clamp(
241 current_solution_range / 4,
242 current_solution_range.saturating_mul(4),
243 ))
244 }
245}
246
247const {
249 assert!(SolutionRange::from_pieces(1, (1, 6)).to_pieces((1, 6)) == 1);
250 assert!(SolutionRange::from_pieces(3, (1, 6)).to_pieces((1, 6)) == 3);
251 assert!(SolutionRange::from_pieces(5, (1, 6)).to_pieces((1, 6)) == 5);
252}
253
254#[derive(Copy, Clone, Eq, PartialEq, Hash, Deref, DerefMut, From, Into, TrivialType)]
256#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
257#[repr(C)]
258pub struct ChunkProof([[u8; OUT_LEN]; ChunkProof::NUM_HASHES]);
259
260impl fmt::Debug for ChunkProof {
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 write!(f, "[")?;
263 for hash in self.0 {
264 for byte in hash {
265 write!(f, "{byte:02x}")?;
266 }
267 write!(f, ", ")?;
268 }
269 write!(f, "]")?;
270 Ok(())
271 }
272}
273
274#[cfg(feature = "serde")]
275#[derive(Serialize, Deserialize)]
276#[serde(transparent)]
277struct ChunkProofBinary(#[serde(with = "BigArray")] [[u8; OUT_LEN]; ChunkProof::NUM_HASHES]);
278
279#[cfg(feature = "serde")]
280#[derive(Serialize, Deserialize)]
281#[serde(transparent)]
282struct ChunkProofHexHash(#[serde(with = "hex")] [u8; OUT_LEN]);
283
284#[cfg(feature = "serde")]
285#[derive(Serialize, Deserialize)]
286#[serde(transparent)]
287struct ChunkProofHex([ChunkProofHexHash; ChunkProof::NUM_HASHES]);
288
289#[cfg(feature = "serde")]
290impl Serialize for ChunkProof {
291 #[inline]
292 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
293 where
294 S: Serializer,
295 {
296 if serializer.is_human_readable() {
297 ChunkProofHex(unsafe {
300 mem::transmute::<
301 [[u8; OUT_LEN]; ChunkProof::NUM_HASHES],
302 [ChunkProofHexHash; ChunkProof::NUM_HASHES],
303 >(self.0)
304 })
305 .serialize(serializer)
306 } else {
307 ChunkProofBinary(self.0).serialize(serializer)
308 }
309 }
310}
311
312#[cfg(feature = "serde")]
313impl<'de> Deserialize<'de> for ChunkProof {
314 #[inline]
315 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
316 where
317 D: Deserializer<'de>,
318 {
319 Ok(Self(if deserializer.is_human_readable() {
320 unsafe {
323 mem::transmute::<
324 [ChunkProofHexHash; ChunkProof::NUM_HASHES],
325 [[u8; OUT_LEN]; ChunkProof::NUM_HASHES],
326 >(ChunkProofHex::deserialize(deserializer)?.0)
327 }
328 } else {
329 ChunkProofBinary::deserialize(deserializer)?.0
330 }))
331 }
332}
333
334impl Default for ChunkProof {
335 #[inline]
336 fn default() -> Self {
337 Self([[0; OUT_LEN]; ChunkProof::NUM_HASHES])
338 }
339}
340
341impl AsRef<[u8]> for ChunkProof {
342 #[inline]
343 fn as_ref(&self) -> &[u8] {
344 self.0.as_flattened()
345 }
346}
347
348impl AsMut<[u8]> for ChunkProof {
349 #[inline]
350 fn as_mut(&mut self) -> &mut [u8] {
351 self.0.as_flattened_mut()
352 }
353}
354
355impl ChunkProof {
356 pub const SIZE: usize = OUT_LEN * Self::NUM_HASHES;
358 const NUM_HASHES: usize = Record::NUM_S_BUCKETS.ilog2() as usize;
359}
360
361#[derive(Debug, Eq, PartialEq, thiserror::Error)]
363pub enum SolutionVerifyError {
364 #[error("Piece verification failed")]
366 InvalidPieceOffset {
367 piece_offset: u16,
369 max_pieces_in_sector: u16,
371 },
372 #[error("History size {solution} is in the future, current is {current}")]
374 FutureHistorySize {
375 current: HistorySize,
377 solution: HistorySize,
379 },
380 #[error("Sector expired")]
382 SectorExpired {
383 expiration_history_size: HistorySize,
385 current_history_size: HistorySize,
387 },
388 #[error("Piece verification failed")]
390 InvalidPiece,
391 #[error("Solution distance {solution_distance} is outside of solution range {solution_range}")]
393 OutsideSolutionRange {
394 solution_range: SolutionRange,
396 solution_distance: SolutionDistance,
398 },
399 #[error("Invalid proof of space")]
401 InvalidProofOfSpace,
402 #[error("Invalid shard commitment")]
404 InvalidShardCommitment,
405 #[error("Invalid input shard {shard_index} ({shard_kind:?})")]
407 InvalidInputShard {
408 shard_index: ShardIndex,
410 shard_kind: Option<ShardKind>,
412 },
413 #[error(
415 "Invalid solution shard {solution_shard_index} (parent {solution_parent_shard_index:?}), \
416 expected shard {expected_shard_index} ({expected_shard_kind:?})"
417 )]
418 InvalidSolutionShard {
419 solution_shard_index: ShardIndex,
421 solution_parent_shard_index: Option<ShardIndex>,
423 expected_shard_index: ShardIndex,
425 expected_shard_kind: RealShardKind,
427 },
428 #[error("Invalid chunk proof")]
430 InvalidChunkProof,
431 #[error("Invalid history size")]
433 InvalidHistorySize,
434}
435
436#[derive(Debug, Clone)]
438#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
439pub struct SolutionVerifyPieceCheckParams {
440 pub max_pieces_in_sector: u16,
442 pub segment_root: SegmentRoot,
444 pub recent_segments: HistorySize,
446 pub recent_history_fraction: (HistorySize, HistorySize),
448 pub min_sector_lifetime: HistorySize,
450 pub current_history_size: HistorySize,
452 pub sector_expiration_check_segment_root: Option<SegmentRoot>,
454}
455
456#[derive(Debug, Clone)]
458#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
459pub struct SolutionVerifyParams {
460 pub shard_index: ShardIndex,
462 pub proof_of_time: PotOutput,
464 pub solution_range: SolutionRange,
466 pub shard_membership_entropy: ShardMembershipEntropy,
468 pub num_shards: NumShards,
470 pub piece_check_params: Option<SolutionVerifyPieceCheckParams>,
474}
475
476pub trait SolutionPotVerifier {
478 fn is_proof_valid(seed: &PosSeed, s_bucket: SBucket, proof: &PosProof) -> bool;
480}
481
482#[derive(
484 Default,
485 Copy,
486 Clone,
487 Eq,
488 PartialEq,
489 Ord,
490 PartialOrd,
491 Hash,
492 From,
493 Into,
494 AsRef,
495 AsMut,
496 Deref,
497 DerefMut,
498 TrivialType,
499)]
500#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
501#[repr(C)]
502pub struct ShardMembershipEntropy([u8; ShardMembershipEntropy::SIZE]);
503
504impl fmt::Display for ShardMembershipEntropy {
505 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506 for byte in self.0 {
507 write!(f, "{byte:02x}")?;
508 }
509 Ok(())
510 }
511}
512
513#[cfg(feature = "serde")]
514#[derive(Serialize, Deserialize)]
515#[serde(transparent)]
516struct ShardMembershipEntropyBinary([u8; ShardMembershipEntropy::SIZE]);
517
518#[cfg(feature = "serde")]
519#[derive(Serialize, Deserialize)]
520#[serde(transparent)]
521struct ShardMembershipEntropyHex(#[serde(with = "hex")] [u8; ShardMembershipEntropy::SIZE]);
522
523#[cfg(feature = "serde")]
524impl Serialize for ShardMembershipEntropy {
525 #[inline]
526 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
527 where
528 S: Serializer,
529 {
530 if serializer.is_human_readable() {
531 ShardMembershipEntropyHex(self.0).serialize(serializer)
532 } else {
533 ShardMembershipEntropyBinary(self.0).serialize(serializer)
534 }
535 }
536}
537
538#[cfg(feature = "serde")]
539impl<'de> Deserialize<'de> for ShardMembershipEntropy {
540 #[inline]
541 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
542 where
543 D: Deserializer<'de>,
544 {
545 Ok(Self(if deserializer.is_human_readable() {
546 ShardMembershipEntropyHex::deserialize(deserializer)?.0
547 } else {
548 ShardMembershipEntropyBinary::deserialize(deserializer)?.0
549 }))
550 }
551}
552
553impl fmt::Debug for ShardMembershipEntropy {
554 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555 for byte in self.0 {
556 write!(f, "{byte:02x}")?;
557 }
558 Ok(())
559 }
560}
561
562impl AsRef<[u8]> for ShardMembershipEntropy {
563 #[inline(always)]
564 fn as_ref(&self) -> &[u8] {
565 &self.0
566 }
567}
568
569impl AsMut<[u8]> for ShardMembershipEntropy {
570 #[inline(always)]
571 fn as_mut(&mut self) -> &mut [u8] {
572 &mut self.0
573 }
574}
575
576impl ShardMembershipEntropy {
577 pub const SIZE: usize = PotOutput::SIZE;
579
580 #[inline(always)]
582 pub const fn new(bytes: [u8; Self::SIZE]) -> Self {
583 Self(bytes)
584 }
585
586 #[inline(always)]
588 pub const fn as_bytes(&self) -> &[u8; Self::SIZE] {
589 &self.0
590 }
591
592 #[inline(always)]
594 pub const fn slice_from_repr(value: &[[u8; Self::SIZE]]) -> &[Self] {
595 unsafe { mem::transmute(value) }
598 }
599
600 #[inline(always)]
602 pub const fn repr_from_slice(value: &[Self]) -> &[[u8; Self::SIZE]] {
603 unsafe { mem::transmute(value) }
606 }
607}
608
609#[derive(
611 Default,
612 Copy,
613 Clone,
614 Eq,
615 PartialEq,
616 Ord,
617 PartialOrd,
618 Hash,
619 From,
620 Into,
621 AsRef,
622 AsMut,
623 Deref,
624 DerefMut,
625 TrivialType,
626)]
627#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
628#[repr(C)]
629pub struct ShardCommitmentHash([u8; ShardCommitmentHash::SIZE]);
630
631impl fmt::Display for ShardCommitmentHash {
632 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
633 for byte in self.0 {
634 write!(f, "{byte:02x}")?;
635 }
636 Ok(())
637 }
638}
639
640#[cfg(feature = "serde")]
641#[derive(Serialize, Deserialize)]
642#[serde(transparent)]
643struct ShardCommitmentHashBinary([u8; ShardCommitmentHash::SIZE]);
644
645#[cfg(feature = "serde")]
646#[derive(Serialize, Deserialize)]
647#[serde(transparent)]
648struct ShardCommitmentHashHex(#[serde(with = "hex")] [u8; ShardCommitmentHash::SIZE]);
649
650#[cfg(feature = "serde")]
651impl Serialize for ShardCommitmentHash {
652 #[inline]
653 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
654 where
655 S: Serializer,
656 {
657 if serializer.is_human_readable() {
658 ShardCommitmentHashHex(self.0).serialize(serializer)
659 } else {
660 ShardCommitmentHashBinary(self.0).serialize(serializer)
661 }
662 }
663}
664
665#[cfg(feature = "serde")]
666impl<'de> Deserialize<'de> for ShardCommitmentHash {
667 #[inline]
668 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
669 where
670 D: Deserializer<'de>,
671 {
672 Ok(Self(if deserializer.is_human_readable() {
673 ShardCommitmentHashHex::deserialize(deserializer)?.0
674 } else {
675 ShardCommitmentHashBinary::deserialize(deserializer)?.0
676 }))
677 }
678}
679
680impl fmt::Debug for ShardCommitmentHash {
681 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
682 for byte in self.0 {
683 write!(f, "{byte:02x}")?;
684 }
685 Ok(())
686 }
687}
688
689impl AsRef<[u8]> for ShardCommitmentHash {
690 #[inline(always)]
691 fn as_ref(&self) -> &[u8] {
692 &self.0
693 }
694}
695
696impl AsMut<[u8]> for ShardCommitmentHash {
697 #[inline(always)]
698 fn as_mut(&mut self) -> &mut [u8] {
699 &mut self.0
700 }
701}
702
703impl From<Hash> for ShardCommitmentHash {
704 #[inline(always)]
705 fn from(value: Hash) -> Self {
706 let bytes = value.as_bytes();
707 Self(*bytes)
708 }
712}
713
714impl ShardCommitmentHash {
715 pub const SIZE: usize = 32;
718
719 #[inline(always)]
721 pub const fn new(hash: [u8; Self::SIZE]) -> Self {
722 Self(hash)
723 }
724
725 #[inline(always)]
727 pub const fn as_bytes(&self) -> &[u8; Self::SIZE] {
728 &self.0
729 }
730
731 #[inline(always)]
733 pub const fn slice_from_repr(value: &[[u8; Self::SIZE]]) -> &[Self] {
734 unsafe { mem::transmute(value) }
737 }
738
739 #[inline(always)]
741 pub const fn array_from_repr<const N: usize>(value: [[u8; Self::SIZE]; N]) -> [Self; N] {
742 unsafe { mem::transmute_copy(&value) }
746 }
747
748 #[inline(always)]
750 pub const fn repr_from_slice(value: &[Self]) -> &[[u8; Self::SIZE]] {
751 unsafe { mem::transmute(value) }
754 }
755
756 #[inline(always)]
758 pub const fn repr_from_array<const N: usize>(value: [Self; N]) -> [[u8; Self::SIZE]; N] {
759 unsafe { mem::transmute_copy(&value) }
763 }
764}
765
766#[derive(Clone, Copy, Debug, Eq, PartialEq, TrivialType)]
768#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
769#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
770#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
771#[repr(C)]
772pub struct SolutionShardCommitment {
773 pub root: ShardCommitmentHash,
775 pub proof: [ShardCommitmentHash; SolutionShardCommitment::NUM_LEAVES.ilog2() as usize],
777 pub leaf: ShardCommitmentHash,
779}
780
781impl SolutionShardCommitment {
782 pub const NUM_LEAVES: usize = 2_u32.pow(20) as usize;
784}
785
786#[derive(Clone, Copy, Debug, Eq, PartialEq, TrivialType)]
788#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
789#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
790#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
791#[repr(C)]
792pub struct Solution {
793 pub public_key_hash: Blake3Hash,
795 pub shard_commitment: SolutionShardCommitment,
797 pub record_root: RecordRoot,
799 pub record_proof: RecordProof,
801 pub chunk: RecordChunk,
803 pub chunk_proof: ChunkProof,
805 pub proof_of_space: PosProof,
807 pub history_size: HistorySize,
809 pub sector_index: SectorIndex,
811 pub piece_offset: PieceOffset,
813 pub padding: [u8; 4],
815}
816
817impl Solution {
818 pub fn genesis_solution() -> Self {
820 Self {
821 public_key_hash: Ed25519PublicKey::default().hash(),
822 shard_commitment: SolutionShardCommitment {
823 root: Default::default(),
824 proof: [Default::default(); _],
825 leaf: Default::default(),
826 },
827 record_root: RecordRoot::default(),
828 record_proof: RecordProof::default(),
829 chunk: RecordChunk::default(),
830 chunk_proof: ChunkProof::default(),
831 proof_of_space: PosProof::default(),
832 history_size: HistorySize::from(SegmentIndex::ZERO),
833 sector_index: SectorIndex::ZERO,
834 piece_offset: PieceOffset::default(),
835 padding: [0; _],
836 }
837 }
838
839 pub fn verify<PotVerifier>(
841 &self,
842 slot: SlotNumber,
843 params: &SolutionVerifyParams,
844 ) -> Result<(), SolutionVerifyError>
845 where
846 PotVerifier: SolutionPotVerifier,
847 {
848 let SolutionVerifyParams {
849 shard_index,
850 proof_of_time,
851 solution_range,
852 shard_membership_entropy,
853 num_shards,
854 piece_check_params,
855 } = params;
856
857 let shard_kind = shard_index
858 .shard_kind()
859 .and_then(|shard_kind| shard_kind.to_real())
860 .ok_or(SolutionVerifyError::InvalidInputShard {
861 shard_index: *shard_index,
862 shard_kind: shard_index.shard_kind(),
863 })?;
864
865 let (solution_shard_index, shard_commitment_index) = num_shards
866 .derive_shard_index_and_shard_commitment_index(
867 &self.public_key_hash,
868 &self.shard_commitment.root,
869 shard_membership_entropy,
870 self.history_size,
871 );
872
873 let solution_range = match shard_kind {
875 RealShardKind::BeaconChain => *solution_range,
876 RealShardKind::IntermediateShard => {
877 if solution_shard_index.parent_shard() != Some(*shard_index) {
878 return Err(SolutionVerifyError::InvalidSolutionShard {
879 solution_shard_index,
880 solution_parent_shard_index: solution_shard_index.parent_shard(),
881 expected_shard_index: *shard_index,
882 expected_shard_kind: RealShardKind::IntermediateShard,
883 });
884 }
885
886 solution_range.to_intermediate_shard(*num_shards)
887 }
888 RealShardKind::LeafShard => {
889 if solution_shard_index != *shard_index {
890 return Err(SolutionVerifyError::InvalidSolutionShard {
891 solution_shard_index,
892 solution_parent_shard_index: solution_shard_index.parent_shard(),
893 expected_shard_index: *shard_index,
894 expected_shard_kind: RealShardKind::LeafShard,
895 });
896 }
897
898 solution_range.to_leaf_shard(*num_shards)
899 }
900 };
901
902 const {
906 assert!(SolutionShardCommitment::NUM_LEAVES == 1048576);
907 }
908 if !BalancedMerkleTree::<1048576>::verify(
909 &self.shard_commitment.root,
910 &ShardCommitmentHash::repr_from_array(self.shard_commitment.proof),
911 shard_commitment_index as usize,
912 *self.shard_commitment.leaf,
913 ) {
914 return Err(SolutionVerifyError::InvalidShardCommitment);
915 }
916
917 let sector_id = SectorId::new(
918 &self.public_key_hash,
919 &self.shard_commitment.root,
920 self.sector_index,
921 self.history_size,
922 );
923
924 let global_challenge = proof_of_time.derive_global_challenge(slot);
925 let sector_slot_challenge = sector_id.derive_sector_slot_challenge(&global_challenge);
926 let s_bucket_audit_index = sector_slot_challenge.s_bucket_audit_index();
927
928 if !PotVerifier::is_proof_valid(
930 §or_id.derive_evaluation_seed(self.piece_offset),
931 s_bucket_audit_index,
932 &self.proof_of_space,
933 ) {
934 return Err(SolutionVerifyError::InvalidProofOfSpace);
935 };
936
937 let masked_chunk =
938 (Simd::from(*self.chunk) ^ Simd::from(*self.proof_of_space.hash())).to_array();
939
940 let solution_distance =
941 SolutionDistance::calculate(&global_challenge, &masked_chunk, §or_slot_challenge);
942
943 if !solution_distance.is_within(solution_range) {
944 return Err(SolutionVerifyError::OutsideSolutionRange {
945 solution_range,
946 solution_distance,
947 });
948 }
949
950 const {
954 assert!(Record::NUM_S_BUCKETS == 65536);
955 }
956 if !BalancedMerkleTree::<65536>::verify(
958 &self.record_root,
959 &self.chunk_proof,
960 usize::from(s_bucket_audit_index),
961 *self.chunk,
962 ) {
963 return Err(SolutionVerifyError::InvalidChunkProof);
964 }
965
966 if let Some(SolutionVerifyPieceCheckParams {
967 max_pieces_in_sector,
968 segment_root,
969 recent_segments,
970 recent_history_fraction,
971 min_sector_lifetime,
972 current_history_size,
973 sector_expiration_check_segment_root,
974 }) = piece_check_params
975 {
976 if &self.history_size > current_history_size {
977 return Err(SolutionVerifyError::FutureHistorySize {
978 current: *current_history_size,
979 solution: self.history_size,
980 });
981 }
982
983 if u16::from(self.piece_offset) >= *max_pieces_in_sector {
984 return Err(SolutionVerifyError::InvalidPieceOffset {
985 piece_offset: u16::from(self.piece_offset),
986 max_pieces_in_sector: *max_pieces_in_sector,
987 });
988 }
989
990 if let Some(sector_expiration_check_segment_root) = sector_expiration_check_segment_root
991 {
992 let expiration_history_size = match sector_id.derive_expiration_history_size(
993 self.history_size,
994 sector_expiration_check_segment_root,
995 *min_sector_lifetime,
996 ) {
997 Some(expiration_history_size) => expiration_history_size,
998 None => {
999 return Err(SolutionVerifyError::InvalidHistorySize);
1000 }
1001 };
1002
1003 if expiration_history_size <= *current_history_size {
1004 return Err(SolutionVerifyError::SectorExpired {
1005 expiration_history_size,
1006 current_history_size: *current_history_size,
1007 });
1008 }
1009 }
1010
1011 let position = sector_id
1012 .derive_piece_index(
1013 self.piece_offset,
1014 self.history_size,
1015 *max_pieces_in_sector,
1016 *recent_segments,
1017 *recent_history_fraction,
1018 )
1019 .position();
1020
1021 if !self
1023 .record_root
1024 .is_valid(segment_root, &self.record_proof, position)
1025 {
1026 return Err(SolutionVerifyError::InvalidPiece);
1027 }
1028 }
1029
1030 Ok(())
1031 }
1032}