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::simd::Simd;
20use core::{fmt, mem};
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;
32
33#[derive(
35 Debug, Display, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, From, Into,
36)]
37#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
38#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
39#[repr(C)]
40pub struct SolutionDistance(u64);
41
42impl SolutionDistance {
43 pub const MAX: Self = Self(u64::MAX / 2);
45
46 #[inline(always)]
49 pub const fn from_u64(n: u64) -> Self {
50 Self(n)
51 }
52
53 pub fn calculate(
58 global_challenge: &Blake3Hash,
59 chunk: &[u8; 32],
60 sector_slot_challenge: &SectorSlotChallenge,
61 ) -> Self {
62 let audit_chunk = single_block_keyed_hash(sector_slot_challenge, chunk)
64 .expect("Less than a single block worth of bytes; qed");
65 let audit_chunk_as_solution_range = SolutionRange::from_bytes([
66 audit_chunk[0],
67 audit_chunk[1],
68 audit_chunk[2],
69 audit_chunk[3],
70 audit_chunk[4],
71 audit_chunk[5],
72 audit_chunk[6],
73 audit_chunk[7],
74 ]);
75 let global_challenge_as_solution_range =
76 SolutionRange::from_bytes(global_challenge.as_chunks().0[0]);
77
78 global_challenge_as_solution_range.bidirectional_distance(audit_chunk_as_solution_range)
79 }
80
81 pub const fn is_within(self, solution_range: SolutionRange) -> bool {
83 self.0 <= u64::from(solution_range) / 2
84 }
85}
86
87#[derive(
89 Debug,
90 Display,
91 Default,
92 Copy,
93 Clone,
94 Ord,
95 PartialOrd,
96 Eq,
97 PartialEq,
98 Hash,
99 Add,
100 AddAssign,
101 Sub,
102 SubAssign,
103 TrivialType,
104)]
105#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
106#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
107#[repr(C)]
108pub struct SolutionRange(u64);
109
110const impl From<u64> for SolutionRange {
111 #[inline(always)]
112 fn from(value: u64) -> Self {
113 Self(value)
114 }
115}
116
117const impl From<SolutionRange> for u64 {
118 #[inline(always)]
119 fn from(value: SolutionRange) -> Self {
120 value.0
121 }
122}
123
124impl SolutionRange {
125 pub const SIZE: usize = size_of::<u64>();
127 pub const MIN: Self = Self(u64::MIN);
129 pub const MAX: Self = Self(u64::MAX);
131
132 #[inline(always)]
134 pub fn to_bytes(self) -> [u8; 8] {
135 self.0.to_le_bytes()
136 }
137
138 #[inline(always)]
140 pub fn from_bytes(bytes: [u8; 8]) -> Self {
141 Self(u64::from_le_bytes(bytes))
142 }
143
144 #[inline]
149 pub const fn from_pieces(pieces: u64, slot_probability: (u64, u64)) -> Self {
150 let solution_range = u64::MAX
151 / slot_probability.1 * slot_probability.0
153 / Record::NUM_CHUNKS as u64
155 * Record::NUM_S_BUCKETS as u64;
156
157 Self(solution_range / pieces)
159 }
160
161 #[inline]
166 pub const fn to_pieces(self, slot_probability: (u64, u64)) -> u64 {
167 let pieces = u64::MAX
168 / slot_probability.1 * slot_probability.0
170 / Record::NUM_CHUNKS as u64
172 * Record::NUM_S_BUCKETS as u64;
173
174 pieces / self.0
176 }
177
178 #[inline]
183 pub const fn to_leaf_shard(self, num_shards: NumShards) -> Self {
184 Self(
185 self.0
186 .saturating_mul(u64::from(num_shards.leaf_shards().get())),
187 )
188 }
189
190 #[inline]
193 pub const fn to_intermediate_shard(self, num_shards: NumShards) -> Self {
194 Self(
195 self.0
196 .saturating_mul(u64::from(num_shards.intermediate_shards().get())),
197 )
198 }
199
200 #[inline]
202 pub const fn bidirectional_distance(self, other: Self) -> SolutionDistance {
203 let a = self.0;
204 let b = other.0;
205 let diff = a.wrapping_sub(b);
206 let diff2 = b.wrapping_sub(a);
207 SolutionDistance::from_u64(if diff < diff2 { diff } else { diff2 })
209 }
210
211 #[inline]
213 pub fn derive_next(
214 self,
215 slots_in_last_interval: SlotNumber,
216 slot_probability: (u64, u64),
217 retarget_interval: BlockNumber,
218 ) -> Self {
219 let current_solution_range = self.0;
235 let next_solution_range = u64::try_from(
236 u128::from(current_solution_range)
237 .saturating_mul(u128::from(slots_in_last_interval))
238 .saturating_mul(u128::from(slot_probability.0))
239 / u128::from(u64::from(retarget_interval))
240 / u128::from(slot_probability.1),
241 );
242
243 Self(next_solution_range.unwrap_or(u64::MAX).clamp(
244 current_solution_range / 4,
245 current_solution_range.saturating_mul(4),
246 ))
247 }
248}
249
250const {
252 assert!(SolutionRange::from_pieces(1, (1, 6)).to_pieces((1, 6)) == 1);
253 assert!(SolutionRange::from_pieces(3, (1, 6)).to_pieces((1, 6)) == 3);
254 assert!(SolutionRange::from_pieces(5, (1, 6)).to_pieces((1, 6)) == 5);
255}
256
257#[derive(Copy, Clone, Eq, PartialEq, Hash, Deref, DerefMut, From, Into, TrivialType)]
259#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
260#[repr(C)]
261pub struct ChunkProof([[u8; OUT_LEN]; const { ChunkProof::NUM_HASHES }]);
262
263impl fmt::Debug for ChunkProof {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 write!(f, "[")?;
266 for hash in self.0 {
267 for byte in hash {
268 write!(f, "{byte:02x}")?;
269 }
270 write!(f, ", ")?;
271 }
272 write!(f, "]")?;
273 Ok(())
274 }
275}
276
277#[cfg(feature = "serde")]
278#[derive(Serialize, Deserialize)]
279#[serde(transparent)]
280struct ChunkProofBinary(
281 #[serde(with = "BigArray")] [[u8; OUT_LEN]; const { ChunkProof::NUM_HASHES }],
282);
283
284#[cfg(feature = "serde")]
285#[derive(Serialize, Deserialize)]
286#[serde(transparent)]
287struct ChunkProofHexHash(#[serde(with = "hex")] [u8; OUT_LEN]);
288
289#[cfg(feature = "serde")]
290#[derive(Serialize, Deserialize)]
291#[serde(transparent)]
292struct ChunkProofHex([ChunkProofHexHash; const { 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(unsafe {
305 mem::transmute::<
306 [[u8; OUT_LEN]; const { Self::NUM_HASHES }],
307 [ChunkProofHexHash; const { Self::NUM_HASHES }],
308 >(self.0)
309 })
310 .serialize(serializer)
311 } else {
312 ChunkProofBinary(self.0).serialize(serializer)
313 }
314 }
315}
316
317#[cfg(feature = "serde")]
318impl<'de> Deserialize<'de> for ChunkProof {
319 #[inline]
320 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
321 where
322 D: Deserializer<'de>,
323 {
324 Ok(Self(if deserializer.is_human_readable() {
325 unsafe {
328 mem::transmute::<
329 [ChunkProofHexHash; const { Self::NUM_HASHES }],
330 [[u8; OUT_LEN]; const { Self::NUM_HASHES }],
331 >(ChunkProofHex::deserialize(deserializer)?.0)
332 }
333 } else {
334 ChunkProofBinary::deserialize(deserializer)?.0
335 }))
336 }
337}
338
339impl Default for ChunkProof {
340 #[inline]
341 fn default() -> Self {
342 Self([[0; OUT_LEN]; _])
343 }
344}
345
346impl AsRef<[u8]> for ChunkProof {
347 #[inline]
348 fn as_ref(&self) -> &[u8] {
349 self.0.as_flattened()
350 }
351}
352
353impl AsMut<[u8]> for ChunkProof {
354 #[inline]
355 fn as_mut(&mut self) -> &mut [u8] {
356 self.0.as_flattened_mut()
357 }
358}
359
360impl ChunkProof {
361 pub const SIZE: usize = OUT_LEN * Self::NUM_HASHES;
363 const NUM_HASHES: usize = Record::NUM_S_BUCKETS.ilog2() as usize;
364}
365
366#[derive(Debug, Eq, PartialEq, thiserror::Error)]
368pub enum SolutionVerifyError {
369 #[error("Piece verification failed")]
371 InvalidPieceOffset {
372 piece_offset: u16,
374 max_pieces_in_sector: u16,
376 },
377 #[error("History size {solution} is in the future, current is {current}")]
379 FutureHistorySize {
380 current: HistorySize,
382 solution: HistorySize,
384 },
385 #[error("Sector expired")]
387 SectorExpired {
388 expiration_history_size: HistorySize,
390 current_history_size: HistorySize,
392 },
393 #[error("Record does not belong to the segment")]
395 RecordNotInSegment,
396 #[error("Segment doesn't belong to the super segment")]
398 SegmentNotInSuperSegment,
399 #[error("Solution distance {solution_distance} is outside of solution range {solution_range}")]
401 OutsideSolutionRange {
402 solution_range: SolutionRange,
404 solution_distance: SolutionDistance,
406 },
407 #[error("Invalid proof of space")]
409 InvalidProofOfSpace,
410 #[error("Invalid shard commitment")]
412 InvalidShardCommitment,
413 #[error("Invalid input shard {shard_index} ({shard_kind:?})")]
415 InvalidInputShard {
416 shard_index: ShardIndex,
418 shard_kind: Option<ShardKind>,
420 },
421 #[error(
423 "Invalid solution shard {solution_shard_index} (parent {solution_parent_shard_index:?}), \
424 expected shard {expected_shard_index} ({expected_shard_kind:?})"
425 )]
426 InvalidSolutionShard {
427 solution_shard_index: ShardIndex,
429 solution_parent_shard_index: Option<ShardIndex>,
431 expected_shard_index: ShardIndex,
433 expected_shard_kind: RealShardKind,
435 },
436 #[error("Invalid chunk proof")]
438 InvalidChunkProof,
439 #[error("Invalid history size")]
441 InvalidHistorySize,
442}
443
444#[derive(Debug, Clone)]
449#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
450pub struct SolutionVerifyStatelessParams {
451 pub shard_index: ShardIndex,
453 pub proof_of_time: PotOutput,
455 pub solution_range: SolutionRange,
457 pub shard_membership_entropy: ShardMembershipEntropy,
459 pub num_shards: NumShards,
461}
462
463#[derive(Debug, Clone)]
465#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
466pub struct SolutionVerifyPieceParams {
467 pub max_pieces_in_sector: u16,
469 pub super_segment_root: SuperSegmentRoot,
471 pub num_segments: u32,
473 pub recent_segments: HistorySize,
475 pub recent_history_fraction: (HistorySize, HistorySize),
477 pub min_sector_lifetime: HistorySize,
479 pub current_history_size: HistorySize,
481 pub sector_expiration_check_super_segment_root: Option<SuperSegmentRoot>,
484}
485
486#[derive(Debug, Clone)]
488#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
489pub struct SolutionVerifyFullParams {
490 pub stateless: SolutionVerifyStatelessParams,
492 pub piece: SolutionVerifyPieceParams,
494}
495
496pub trait SolutionPotVerifier {
498 fn is_proof_valid(seed: &PosSeed, s_bucket: SBucket, proof: &PosProof) -> bool;
500}
501
502#[derive(
504 Default,
505 Copy,
506 Clone,
507 Eq,
508 PartialEq,
509 Ord,
510 PartialOrd,
511 Hash,
512 From,
513 Into,
514 AsRef,
515 AsMut,
516 Deref,
517 DerefMut,
518 TrivialType,
519)]
520#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
521#[repr(C)]
522pub struct ShardMembershipEntropy([u8; const { ShardMembershipEntropy::SIZE }]);
523
524impl fmt::Display for ShardMembershipEntropy {
525 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526 for byte in self.0 {
527 write!(f, "{byte:02x}")?;
528 }
529 Ok(())
530 }
531}
532
533#[cfg(feature = "serde")]
534#[derive(Serialize, Deserialize)]
535#[serde(transparent)]
536struct ShardMembershipEntropyBinary([u8; const { ShardMembershipEntropy::SIZE }]);
537
538#[cfg(feature = "serde")]
539#[derive(Serialize, Deserialize)]
540#[serde(transparent)]
541struct ShardMembershipEntropyHex(
542 #[serde(with = "hex")] [u8; const { ShardMembershipEntropy::SIZE }],
543);
544
545#[cfg(feature = "serde")]
546impl Serialize for ShardMembershipEntropy {
547 #[inline]
548 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
549 where
550 S: Serializer,
551 {
552 if serializer.is_human_readable() {
553 ShardMembershipEntropyHex(self.0).serialize(serializer)
554 } else {
555 ShardMembershipEntropyBinary(self.0).serialize(serializer)
556 }
557 }
558}
559
560#[cfg(feature = "serde")]
561impl<'de> Deserialize<'de> for ShardMembershipEntropy {
562 #[inline]
563 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
564 where
565 D: Deserializer<'de>,
566 {
567 Ok(Self(if deserializer.is_human_readable() {
568 ShardMembershipEntropyHex::deserialize(deserializer)?.0
569 } else {
570 ShardMembershipEntropyBinary::deserialize(deserializer)?.0
571 }))
572 }
573}
574
575impl fmt::Debug for ShardMembershipEntropy {
576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577 for byte in self.0 {
578 write!(f, "{byte:02x}")?;
579 }
580 Ok(())
581 }
582}
583
584impl AsRef<[u8]> for ShardMembershipEntropy {
585 #[inline(always)]
586 fn as_ref(&self) -> &[u8] {
587 &self.0
588 }
589}
590
591impl AsMut<[u8]> for ShardMembershipEntropy {
592 #[inline(always)]
593 fn as_mut(&mut self) -> &mut [u8] {
594 &mut self.0
595 }
596}
597
598impl ShardMembershipEntropy {
599 pub const SIZE: usize = PotOutput::SIZE;
601
602 #[inline(always)]
604 pub const fn new(bytes: [u8; const { Self::SIZE }]) -> Self {
605 Self(bytes)
606 }
607
608 #[inline(always)]
610 pub const fn as_bytes(&self) -> &[u8; const { Self::SIZE }] {
611 &self.0
612 }
613
614 #[inline(always)]
616 pub const fn slice_from_repr(value: &[[u8; const { Self::SIZE }]]) -> &[Self] {
617 unsafe { mem::transmute(value) }
620 }
621
622 #[inline(always)]
624 pub const fn repr_from_slice(value: &[Self]) -> &[[u8; const { Self::SIZE }]] {
625 unsafe { mem::transmute(value) }
628 }
629}
630
631#[derive(
633 Default,
634 Copy,
635 Clone,
636 Eq,
637 PartialEq,
638 Ord,
639 PartialOrd,
640 Hash,
641 From,
642 Into,
643 AsRef,
644 AsMut,
645 Deref,
646 DerefMut,
647 TrivialType,
648)]
649#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
650#[repr(C)]
651pub struct ShardCommitmentHash([u8; const { ShardCommitmentHash::SIZE }]);
652
653impl fmt::Display for ShardCommitmentHash {
654 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655 for byte in self.0 {
656 write!(f, "{byte:02x}")?;
657 }
658 Ok(())
659 }
660}
661
662#[cfg(feature = "serde")]
663#[derive(Serialize, Deserialize)]
664#[serde(transparent)]
665struct ShardCommitmentHashBinary([u8; const { ShardCommitmentHash::SIZE }]);
666
667#[cfg(feature = "serde")]
668#[derive(Serialize, Deserialize)]
669#[serde(transparent)]
670struct ShardCommitmentHashHex(#[serde(with = "hex")] [u8; const { ShardCommitmentHash::SIZE }]);
671
672#[cfg(feature = "serde")]
673impl Serialize for ShardCommitmentHash {
674 #[inline]
675 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
676 where
677 S: Serializer,
678 {
679 if serializer.is_human_readable() {
680 ShardCommitmentHashHex(self.0).serialize(serializer)
681 } else {
682 ShardCommitmentHashBinary(self.0).serialize(serializer)
683 }
684 }
685}
686
687#[cfg(feature = "serde")]
688impl<'de> Deserialize<'de> for ShardCommitmentHash {
689 #[inline]
690 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
691 where
692 D: Deserializer<'de>,
693 {
694 Ok(Self(if deserializer.is_human_readable() {
695 ShardCommitmentHashHex::deserialize(deserializer)?.0
696 } else {
697 ShardCommitmentHashBinary::deserialize(deserializer)?.0
698 }))
699 }
700}
701
702impl fmt::Debug for ShardCommitmentHash {
703 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704 for byte in self.0 {
705 write!(f, "{byte:02x}")?;
706 }
707 Ok(())
708 }
709}
710
711impl AsRef<[u8]> for ShardCommitmentHash {
712 #[inline(always)]
713 fn as_ref(&self) -> &[u8] {
714 &self.0
715 }
716}
717
718impl AsMut<[u8]> for ShardCommitmentHash {
719 #[inline(always)]
720 fn as_mut(&mut self) -> &mut [u8] {
721 &mut self.0
722 }
723}
724
725impl From<Hash> for ShardCommitmentHash {
726 #[inline(always)]
727 fn from(value: Hash) -> Self {
728 let bytes = value.as_bytes();
729 Self(*bytes)
730 }
734}
735
736impl ShardCommitmentHash {
737 pub const SIZE: usize = 32;
740
741 #[inline(always)]
743 pub const fn new(hash: [u8; const { Self::SIZE }]) -> Self {
744 Self(hash)
745 }
746
747 #[inline(always)]
749 pub const fn as_bytes(&self) -> &[u8; const { Self::SIZE }] {
750 &self.0
751 }
752
753 #[inline(always)]
755 pub const fn slice_from_repr(value: &[[u8; const { Self::SIZE }]]) -> &[Self] {
756 unsafe { mem::transmute(value) }
759 }
760
761 #[inline(always)]
763 pub const fn array_from_repr<const N: usize>(
764 value: [[u8; const { Self::SIZE }]; N],
765 ) -> [Self; N] {
766 unsafe { mem::transmute_copy(&value) }
770 }
771
772 #[inline(always)]
774 pub const fn repr_from_slice(value: &[Self]) -> &[[u8; const { Self::SIZE }]] {
775 unsafe { mem::transmute(value) }
778 }
779
780 #[inline(always)]
782 pub const fn repr_from_array<const N: usize>(
783 value: [Self; N],
784 ) -> [[u8; const { Self::SIZE }]; N] {
785 unsafe { mem::transmute_copy(&value) }
789 }
790}
791
792#[derive(Clone, Copy, Debug, Eq, PartialEq, TrivialType)]
794#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
795#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
796#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
797#[repr(C)]
798pub struct SolutionShardCommitment {
799 pub root: ShardCommitmentHash,
801 pub proof: [ShardCommitmentHash; SolutionShardCommitment::NUM_LEAVES.ilog2() as usize],
803 pub leaf: ShardCommitmentHash,
805}
806
807impl SolutionShardCommitment {
808 pub const NUM_LEAVES: usize = 2u32.pow(20) as usize;
810}
811
812#[derive(Clone, Copy, Debug, Eq, PartialEq, TrivialType)]
814#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
815#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
816#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
817#[repr(C)]
818pub struct Solution {
819 pub public_key_hash: Blake3Hash,
821 pub shard_commitment: SolutionShardCommitment,
823 pub piece_local_segment_index: LocalSegmentIndex,
825 pub piece_super_segment_index: SuperSegmentIndex,
827 pub segment_root: SegmentRoot,
829 pub segment_proof: SegmentProof,
831 pub record_root: RecordRoot,
833 pub record_proof: RecordProof,
835 pub chunk: RecordChunk,
837 pub chunk_proof: ChunkProof,
839 pub proof_of_space: PosProof,
841 pub history_size: HistorySize,
843 pub sector_index: SectorIndex,
845 pub piece_offset: PieceOffset,
847 pub segment_position: SegmentPosition,
849 pub piece_shard_index: ShardIndex,
851 pub padding: [u8; 4],
853}
854
855impl Solution {
856 pub fn genesis_solution() -> Self {
858 Self {
859 public_key_hash: Ed25519PublicKey::default().hash(),
860 shard_commitment: SolutionShardCommitment {
861 root: ShardCommitmentHash::default(),
862 proof: [ShardCommitmentHash::default(); _],
863 leaf: ShardCommitmentHash::default(),
864 },
865 piece_local_segment_index: LocalSegmentIndex::ZERO,
866 piece_super_segment_index: SuperSegmentIndex::ZERO,
867 segment_root: SegmentRoot::default(),
868 segment_proof: SegmentProof::default(),
869 record_root: RecordRoot::default(),
870 record_proof: RecordProof::default(),
871 chunk: RecordChunk::default(),
872 chunk_proof: ChunkProof::default(),
873 proof_of_space: PosProof::default(),
874 history_size: HistorySize::from(SegmentIndex::ZERO),
875 sector_index: SectorIndex::ZERO,
876 piece_offset: PieceOffset::default(),
877 segment_position: SegmentPosition::default(),
878 piece_shard_index: ShardIndex::BEACON_CHAIN,
879 padding: [0; _],
880 }
881 }
882
883 pub fn verify_full<PotVerifier>(
885 &self,
886 slot: SlotNumber,
887 params: &SolutionVerifyFullParams,
888 ) -> Result<(), SolutionVerifyError>
889 where
890 PotVerifier: SolutionPotVerifier,
891 {
892 let sector_id = SectorId::new(
893 &self.public_key_hash,
894 &self.shard_commitment.root,
895 self.sector_index,
896 self.history_size,
897 );
898
899 self.verify_stateless_inner::<PotVerifier>(§or_id, slot, ¶ms.stateless)?;
900
901 self.verify_piece_inner(§or_id, ¶ms.piece)
902 }
903
904 pub fn verify_stateless<PotVerifier>(
911 &self,
912 slot: SlotNumber,
913 params: &SolutionVerifyStatelessParams,
914 ) -> Result<(), SolutionVerifyError>
915 where
916 PotVerifier: SolutionPotVerifier,
917 {
918 let sector_id = SectorId::new(
919 &self.public_key_hash,
920 &self.shard_commitment.root,
921 self.sector_index,
922 self.history_size,
923 );
924
925 self.verify_stateless_inner::<PotVerifier>(§or_id, slot, params)
926 }
927
928 fn verify_stateless_inner<PotVerifier>(
929 &self,
930 sector_id: &SectorId,
931 slot: SlotNumber,
932 params: &SolutionVerifyStatelessParams,
933 ) -> Result<(), SolutionVerifyError>
934 where
935 PotVerifier: SolutionPotVerifier,
936 {
937 let SolutionVerifyStatelessParams {
938 shard_index,
939 proof_of_time,
940 solution_range,
941 shard_membership_entropy,
942 num_shards,
943 } = params;
944
945 let shard_kind = shard_index
946 .shard_kind()
947 .and_then(ShardKind::to_real)
948 .ok_or(SolutionVerifyError::InvalidInputShard {
949 shard_index: *shard_index,
950 shard_kind: shard_index.shard_kind(),
951 })?;
952
953 let (solution_shard_index, shard_commitment_index) = num_shards
954 .derive_shard_index_and_shard_commitment_index(
955 &self.public_key_hash,
956 &self.shard_commitment.root,
957 shard_membership_entropy,
958 self.history_size,
959 );
960
961 let solution_range = match shard_kind {
963 RealShardKind::BeaconChain => *solution_range,
964 RealShardKind::IntermediateShard => {
965 if solution_shard_index.parent_shard() != Some(*shard_index) {
966 return Err(SolutionVerifyError::InvalidSolutionShard {
967 solution_shard_index,
968 solution_parent_shard_index: solution_shard_index.parent_shard(),
969 expected_shard_index: *shard_index,
970 expected_shard_kind: RealShardKind::IntermediateShard,
971 });
972 }
973
974 solution_range.to_intermediate_shard(*num_shards)
975 }
976 RealShardKind::LeafShard => {
977 if solution_shard_index != *shard_index {
978 return Err(SolutionVerifyError::InvalidSolutionShard {
979 solution_shard_index,
980 solution_parent_shard_index: solution_shard_index.parent_shard(),
981 expected_shard_index: *shard_index,
982 expected_shard_kind: RealShardKind::LeafShard,
983 });
984 }
985
986 solution_range.to_leaf_shard(*num_shards)
987 }
988 };
989
990 if !BalancedMerkleTree::<const { SolutionShardCommitment::NUM_LEAVES }>::verify(
991 &self.shard_commitment.root,
992 &ShardCommitmentHash::repr_from_array(self.shard_commitment.proof),
993 shard_commitment_index as usize,
994 *self.shard_commitment.leaf,
995 ) {
996 return Err(SolutionVerifyError::InvalidShardCommitment);
997 }
998
999 let global_challenge = proof_of_time.derive_global_challenge(slot);
1000 let sector_slot_challenge = sector_id.derive_sector_slot_challenge(&global_challenge);
1001 let s_bucket_audit_index = sector_slot_challenge.s_bucket_audit_index();
1002
1003 if !PotVerifier::is_proof_valid(
1005 §or_id.derive_evaluation_seed(self.piece_offset),
1006 s_bucket_audit_index,
1007 &self.proof_of_space,
1008 ) {
1009 return Err(SolutionVerifyError::InvalidProofOfSpace);
1010 }
1011
1012 let masked_chunk =
1013 (Simd::from(*self.chunk) ^ Simd::from(*self.proof_of_space.hash())).to_array();
1014
1015 let solution_distance =
1016 SolutionDistance::calculate(&global_challenge, &masked_chunk, §or_slot_challenge);
1017
1018 if !solution_distance.is_within(solution_range) {
1019 return Err(SolutionVerifyError::OutsideSolutionRange {
1020 solution_range,
1021 solution_distance,
1022 });
1023 }
1024
1025 if !BalancedMerkleTree::<const { Record::NUM_S_BUCKETS }>::verify(
1027 &self.record_root,
1028 &self.chunk_proof,
1029 usize::from(s_bucket_audit_index),
1030 *self.chunk,
1031 ) {
1032 return Err(SolutionVerifyError::InvalidChunkProof);
1033 }
1034
1035 Ok(())
1036 }
1037
1038 pub fn verify_piece(
1040 &self,
1041 piece_check_params: &SolutionVerifyPieceParams,
1042 ) -> Result<(), SolutionVerifyError> {
1043 let sector_id = SectorId::new(
1044 &self.public_key_hash,
1045 &self.shard_commitment.root,
1046 self.sector_index,
1047 self.history_size,
1048 );
1049
1050 self.verify_piece_inner(§or_id, piece_check_params)
1051 }
1052
1053 fn verify_piece_inner(
1054 &self,
1055 sector_id: &SectorId,
1056 piece_check_params: &SolutionVerifyPieceParams,
1057 ) -> Result<(), SolutionVerifyError> {
1058 let SolutionVerifyPieceParams {
1059 max_pieces_in_sector,
1060 super_segment_root,
1061 num_segments,
1062 recent_segments,
1063 recent_history_fraction,
1064 min_sector_lifetime,
1065 current_history_size,
1066 sector_expiration_check_super_segment_root,
1067 } = piece_check_params;
1068
1069 if &self.history_size > current_history_size {
1070 return Err(SolutionVerifyError::FutureHistorySize {
1071 current: *current_history_size,
1072 solution: self.history_size,
1073 });
1074 }
1075
1076 if u16::from(self.piece_offset) >= *max_pieces_in_sector {
1077 return Err(SolutionVerifyError::InvalidPieceOffset {
1078 piece_offset: u16::from(self.piece_offset),
1079 max_pieces_in_sector: *max_pieces_in_sector,
1080 });
1081 }
1082
1083 if let Some(sector_expiration_check_super_segment_root) =
1084 sector_expiration_check_super_segment_root
1085 {
1086 let Some(expiration_history_size) = sector_id.derive_expiration_history_size(
1087 self.history_size,
1088 sector_expiration_check_super_segment_root,
1089 *min_sector_lifetime,
1090 ) else {
1091 return Err(SolutionVerifyError::InvalidHistorySize);
1092 };
1093
1094 if expiration_history_size <= *current_history_size {
1095 return Err(SolutionVerifyError::SectorExpired {
1096 expiration_history_size,
1097 current_history_size: *current_history_size,
1098 });
1099 }
1100 }
1101
1102 let position = sector_id
1103 .derive_piece_index(
1104 self.piece_offset,
1105 self.history_size,
1106 *max_pieces_in_sector,
1107 *recent_segments,
1108 *recent_history_fraction,
1109 )
1110 .position();
1111
1112 if !self
1114 .record_root
1115 .is_valid(&self.segment_root, &self.record_proof, position)
1116 {
1117 return Err(SolutionVerifyError::RecordNotInSegment);
1118 }
1119
1120 if !self.segment_root.is_valid(
1122 self.piece_shard_index,
1123 self.piece_local_segment_index,
1124 self.segment_position,
1125 &self.segment_proof,
1126 *num_segments,
1127 super_segment_root,
1128 ) {
1129 return Err(SolutionVerifyError::SegmentNotInSuperSegment);
1130 }
1131
1132 Ok(())
1133 }
1134}