Skip to main content

ab_core_primitives/
solutions.rs

1//! Solutions-related data structures and functions.
2
3use 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/// Solution distance
34#[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    /// Maximum value
44    pub const MAX: Self = Self(u64::MAX / 2);
45
46    // TODO: Remove once `From` is stable
47    /// Create a new instance
48    #[inline(always)]
49    pub const fn from_u64(n: u64) -> Self {
50        Self(n)
51    }
52
53    /// Calculate solution distance for given parameters.
54    ///
55    /// Typically used as a primitive to check whether solution distance is within solution range
56    /// (see [`Self::is_within()`]).
57    pub fn calculate(
58        global_challenge: &Blake3Hash,
59        chunk: &[u8; 32],
60        sector_slot_challenge: &SectorSlotChallenge,
61    ) -> Self {
62        // TODO: Is keyed hash really needed here?
63        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    /// Check if solution distance is within the provided solution range
82    pub const fn is_within(self, solution_range: SolutionRange) -> bool {
83        self.0 <= u64::from(solution_range) / 2
84    }
85}
86
87/// Solution range
88#[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    /// Size in bytes
126    pub const SIZE: usize = size_of::<u64>();
127    /// Minimum value
128    pub const MIN: Self = Self(u64::MIN);
129    /// Maximum value
130    pub const MAX: Self = Self(u64::MAX);
131
132    /// Create a new instance from bytes
133    #[inline(always)]
134    pub fn to_bytes(self) -> [u8; 8] {
135        self.0.to_le_bytes()
136    }
137
138    /// Create a new instance from bytes
139    #[inline(always)]
140    pub fn from_bytes(bytes: [u8; 8]) -> Self {
141        Self(u64::from_le_bytes(bytes))
142    }
143
144    /// Computes the following:
145    /// ```text
146    /// MAX * slot_probability / chunks * s_buckets / pieces
147    /// ```
148    #[inline]
149    pub const fn from_pieces(pieces: u64, slot_probability: (u64, u64)) -> Self {
150        let solution_range = u64::MAX
151            // Account for slot probability
152            / slot_probability.1 * slot_probability.0
153            // Now take the probability of hitting occupied s-bucket in a piece into account
154            / Record::NUM_CHUNKS as u64
155            * Record::NUM_S_BUCKETS as u64;
156
157        // Take the number of pieces into account
158        Self(solution_range / pieces)
159    }
160
161    /// Computes the following:
162    /// ```text
163    /// MAX * slot_probability / chunks * s_buckets / solution_range
164    /// ```
165    #[inline]
166    pub const fn to_pieces(self, slot_probability: (u64, u64)) -> u64 {
167        let pieces = u64::MAX
168            // Account for slot probability
169            / slot_probability.1 * slot_probability.0
170            // Now take the probability of hitting occupied s-bucket in sector into account
171            / Record::NUM_CHUNKS as u64
172            * Record::NUM_S_BUCKETS as u64;
173
174        // Take solution range into account
175        pieces / self.0
176    }
177
178    /// Expands the global solution range to a solution range that corresponds to a leaf shard.
179    ///
180    /// Global solution range is updated based on the beacon chain information, while a farmer also
181    /// creates intermediate shard and leaf shard solutions with a wider solution range.
182    #[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    /// Expands the global solution range to a solution range that corresponds to an intermediate
191    /// shard
192    #[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    /// Bidirectional distance between two solution ranges
201    #[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        // Find smaller diff between 2 directions
208        SolutionDistance::from_u64(if diff < diff2 { diff } else { diff2 })
209    }
210
211    /// Derives next solution range
212    #[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        // The idea here is to keep block production at the same pace while space pledged on the
220        // network changes. For this, we adjust the previous solution range according to actual and
221        // expected number of blocks per retarget interval.
222        //
223        // Below is code analogous to the following, but without using floats:
224        // ```rust
225        // let actual_slots_per_block = slots_in_last_interval as f64 / retarget_interval as f64;
226        // let expected_slots_per_block =
227        //     slot_probability.1 as f64 / slot_probability.0 as f64;
228        // let adjustment_factor =
229        //     (actual_slots_per_block / expected_slots_per_block).clamp(0.25, 4.0);
230        //
231        // next_solution_range =
232        //     (solution_ranges.current as f64 * adjustment_factor).round() as u64;
233        // ```
234        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
250// Quick test to ensure the functions above are the inverse of each other
251const {
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/// Proof for chunk contained within a record.
258#[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            // SAFETY: `ChunkProofHexHash` is `#[repr(C)]` and guaranteed to have the
303            // same memory layout
304            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            // SAFETY: `ChunkProofHexHash` is `#[repr(C)]` and guaranteed to have the
326            // same memory layout
327            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    /// Size of chunk proof in bytes.
362    pub const SIZE: usize = OUT_LEN * Self::NUM_HASHES;
363    const NUM_HASHES: usize = Record::NUM_S_BUCKETS.ilog2() as usize;
364}
365
366/// Solution verification errors
367#[derive(Debug, Eq, PartialEq, thiserror::Error)]
368pub enum SolutionVerifyError {
369    /// Invalid piece offset
370    #[error("Piece verification failed")]
371    InvalidPieceOffset {
372        /// Index of the piece that failed verification
373        piece_offset: u16,
374        /// How many pieces one sector is supposed to contain (max)
375        max_pieces_in_sector: u16,
376    },
377    /// History size is in the future
378    #[error("History size {solution} is in the future, current is {current}")]
379    FutureHistorySize {
380        /// Current history size
381        current: HistorySize,
382        /// History size solution was created for
383        solution: HistorySize,
384    },
385    /// Sector expired
386    #[error("Sector expired")]
387    SectorExpired {
388        /// Expiration history size
389        expiration_history_size: HistorySize,
390        /// Current history size
391        current_history_size: HistorySize,
392    },
393    /// Record does not belong to the segment
394    #[error("Record does not belong to the segment")]
395    RecordNotInSegment,
396    /// Segment doesn't belong to the super segment
397    #[error("Segment doesn't belong to the super segment")]
398    SegmentNotInSuperSegment,
399    /// Solution is outside the solution range
400    #[error("Solution distance {solution_distance} is outside of solution range {solution_range}")]
401    OutsideSolutionRange {
402        /// Solution range
403        solution_range: SolutionRange,
404        /// Solution distance
405        solution_distance: SolutionDistance,
406    },
407    /// Invalid proof of space
408    #[error("Invalid proof of space")]
409    InvalidProofOfSpace,
410    /// Invalid shard commitment
411    #[error("Invalid shard commitment")]
412    InvalidShardCommitment,
413    /// Invalid input shard
414    #[error("Invalid input shard {shard_index} ({shard_kind:?})")]
415    InvalidInputShard {
416        /// Input shard index
417        shard_index: ShardIndex,
418        /// Input shard kind
419        shard_kind: Option<ShardKind>,
420    },
421    /// Invalid solution shard
422    #[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
428        solution_shard_index: ShardIndex,
429        /// Solution shard index
430        solution_parent_shard_index: Option<ShardIndex>,
431        /// Expected shard index
432        expected_shard_index: ShardIndex,
433        /// Expected shard kind
434        expected_shard_kind: RealShardKind,
435    },
436    /// Invalid chunk proof
437    #[error("Invalid chunk proof")]
438    InvalidChunkProof,
439    /// Invalid history size
440    #[error("Invalid history size")]
441    InvalidHistorySize,
442}
443
444/// Parameters for stateless solution verification.
445///
446/// These only include the information already contained in the block itself, meaning verification
447/// of different blocks can be done concurrently.
448#[derive(Debug, Clone)]
449#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
450pub struct SolutionVerifyStatelessParams {
451    /// Shard for which the solution is built
452    pub shard_index: ShardIndex,
453    /// Proof of time for which solution is built
454    pub proof_of_time: PotOutput,
455    /// Solution range
456    pub solution_range: SolutionRange,
457    /// Shard membership entropy
458    pub shard_membership_entropy: ShardMembershipEntropy,
459    /// The number of shards in the network
460    pub num_shards: NumShards,
461}
462
463/// Parameters for checking piece validity used in a solution
464#[derive(Debug, Clone)]
465#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
466pub struct SolutionVerifyPieceParams {
467    /// How many pieces one sector is supposed to contain (max)
468    pub max_pieces_in_sector: u16,
469    /// Super segment root of the segment to which piece belongs
470    pub super_segment_root: SuperSegmentRoot,
471    /// Number of segments in the super segment
472    pub num_segments: u32,
473    /// Number of latest archived segments that are considered "recent history"
474    pub recent_segments: HistorySize,
475    /// Fraction of pieces from the "recent history" (`recent_segments`) in each sector
476    pub recent_history_fraction: (HistorySize, HistorySize),
477    /// Minimum lifetime of a plotted sector, measured in archived segments
478    pub min_sector_lifetime: HistorySize,
479    /// Current size of the history
480    pub current_history_size: HistorySize,
481    /// Super segment root that contains a segment at `min_sector_lifetime` from sector creation
482    /// (if exists)
483    pub sector_expiration_check_super_segment_root: Option<SuperSegmentRoot>,
484}
485
486/// Parameters for full solution verification
487#[derive(Debug, Clone)]
488#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
489pub struct SolutionVerifyFullParams {
490    /// Parameters for stateless solution verification
491    pub stateless: SolutionVerifyStatelessParams,
492    /// Parameters for checking piece validity used in a solution
493    pub piece: SolutionVerifyPieceParams,
494}
495
496/// Proof-of-time verifier to be used in [`Solution::verify_full()`]
497pub trait SolutionPotVerifier {
498    /// Check whether proof created earlier is valid
499    fn is_proof_valid(seed: &PosSeed, s_bucket: SBucket, proof: &PosProof) -> bool;
500}
501
502/// Entropy used for shard membership assignment
503#[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    /// Size in bytes
600    pub const SIZE: usize = PotOutput::SIZE;
601
602    /// Create a new instance
603    #[inline(always)]
604    pub const fn new(bytes: [u8; const { Self::SIZE }]) -> Self {
605        Self(bytes)
606    }
607
608    /// Get internal representation
609    #[inline(always)]
610    pub const fn as_bytes(&self) -> &[u8; const { Self::SIZE }] {
611        &self.0
612    }
613
614    /// Convenient conversion from slice of underlying representation for efficiency purposes
615    #[inline(always)]
616    pub const fn slice_from_repr(value: &[[u8; const { Self::SIZE }]]) -> &[Self] {
617        // SAFETY: `ShardMembershipEntropy` is `#[repr(C)]` and guaranteed to have the same memory
618        // layout
619        unsafe { mem::transmute(value) }
620    }
621
622    /// Convenient conversion to slice of underlying representation for efficiency purposes
623    #[inline(always)]
624    pub const fn repr_from_slice(value: &[Self]) -> &[[u8; const { Self::SIZE }]] {
625        // SAFETY: `ShardMembershipEntropy` is `#[repr(C)]` and guaranteed to have the same memory
626        // layout
627        unsafe { mem::transmute(value) }
628    }
629}
630
631/// Reduced hash used for shard assignment
632#[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        // Self([
731        //     bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
732        // ])
733    }
734}
735
736impl ShardCommitmentHash {
737    // TODO: Reduce to 8 bytes once Merkle Tree implementation exists that produces such hashes
738    /// Size in bytes
739    pub const SIZE: usize = 32;
740
741    /// Create a new instance
742    #[inline(always)]
743    pub const fn new(hash: [u8; const { Self::SIZE }]) -> Self {
744        Self(hash)
745    }
746
747    /// Get internal representation
748    #[inline(always)]
749    pub const fn as_bytes(&self) -> &[u8; const { Self::SIZE }] {
750        &self.0
751    }
752
753    /// Convenient conversion from slice of underlying representation for efficiency purposes
754    #[inline(always)]
755    pub const fn slice_from_repr(value: &[[u8; const { Self::SIZE }]]) -> &[Self] {
756        // SAFETY: `ShardCommitmentHash` is `#[repr(C)]` and guaranteed to have the same memory
757        // layout
758        unsafe { mem::transmute(value) }
759    }
760
761    /// Convenient conversion from array of underlying representation for efficiency purposes
762    #[inline(always)]
763    pub const fn array_from_repr<const N: usize>(
764        value: [[u8; const { Self::SIZE }]; N],
765    ) -> [Self; N] {
766        // TODO: Should have been transmute, but https://github.com/rust-lang/rust/issues/152507
767        // SAFETY: `ShardCommitmentHash` is `#[repr(C)]` and guaranteed to have the same memory
768        // layout
769        unsafe { mem::transmute_copy(&value) }
770    }
771
772    /// Convenient conversion to a slice of underlying representation for efficiency purposes
773    #[inline(always)]
774    pub const fn repr_from_slice(value: &[Self]) -> &[[u8; const { Self::SIZE }]] {
775        // SAFETY: `ShardCommitmentHash` is `#[repr(C)]` and guaranteed to have the same memory
776        // layout
777        unsafe { mem::transmute(value) }
778    }
779
780    /// Convenient conversion to an array of underlying representation for efficiency purposes
781    #[inline(always)]
782    pub const fn repr_from_array<const N: usize>(
783        value: [Self; N],
784    ) -> [[u8; const { Self::SIZE }]; N] {
785        // TODO: Should have been transmute, but https://github.com/rust-lang/rust/issues/152507
786        // SAFETY: `ShardCommitmentHash` is `#[repr(C)]` and guaranteed to have the same memory
787        // layout
788        unsafe { mem::transmute_copy(&value) }
789    }
790}
791
792/// Information about shard commitments in the solution
793#[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    /// Root of the Merkle Tree of shard commitments
800    pub root: ShardCommitmentHash,
801    /// Proof for the shard commitment used the solution
802    pub proof: [ShardCommitmentHash; SolutionShardCommitment::NUM_LEAVES.ilog2() as usize],
803    /// Shard commitment leaf used for the solution
804    pub leaf: ShardCommitmentHash,
805}
806
807impl SolutionShardCommitment {
808    /// Number of leaves in a Merkle Tree of shard commitments
809    pub const NUM_LEAVES: usize = 2u32.pow(20) as usize;
810}
811
812/// Farmer solution for slot challenge.
813#[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    /// Public key of the farmer that created the solution
820    pub public_key_hash: Blake3Hash,
821    /// Farmer's shard commitment
822    pub shard_commitment: SolutionShardCommitment,
823    /// Local segment index of the piece
824    pub piece_local_segment_index: LocalSegmentIndex,
825    /// Super segment index
826    pub piece_super_segment_index: SuperSegmentIndex,
827    /// Segment root
828    pub segment_root: SegmentRoot,
829    /// Segment proof
830    pub segment_proof: SegmentProof,
831    /// Record root that can use used to verify that the piece was included in blockchain history
832    pub record_root: RecordRoot,
833    /// Proof that the record (root) belongs to a segment
834    pub record_proof: RecordProof,
835    /// Chunk at the below piece offset
836    pub chunk: RecordChunk,
837    /// Proof for the above chunk
838    pub chunk_proof: ChunkProof,
839    /// Proof of space for piece offset
840    pub proof_of_space: PosProof,
841    /// Size of the blockchain history at the time of sector creation
842    pub history_size: HistorySize,
843    /// Index of the sector where the solution was found
844    pub sector_index: SectorIndex,
845    /// Pieces offset within sector
846    pub piece_offset: PieceOffset,
847    /// Position of the segment in the super segment
848    pub segment_position: SegmentPosition,
849    /// Shard index on which the piece was archived
850    pub piece_shard_index: ShardIndex,
851    /// Padding for data structure alignment
852    pub padding: [u8; 4],
853}
854
855impl Solution {
856    /// Fake solution for the genesis block
857    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    /// Check solution validity
884    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>(&sector_id, slot, &params.stateless)?;
900
901        self.verify_piece_inner(&sector_id, &params.piece)
902    }
903
904    /// Stateless solution verification.
905    ///
906    /// Checks most things, except checking that the piece belongs to the global history.
907    ///
908    /// For piece verification use [`Self::verify_piece()`] or call [`Self::verify_full()`] for more
909    /// efficient verification of both at once.
910    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>(&sector_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        // Adjust solution range according to shard kind
962        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        // Check that proof of space is valid
1004        if !PotVerifier::is_proof_valid(
1005            &sector_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, &sector_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        // Check that chunk belongs to the record
1026        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    /// Verify the piece details of the solution
1039    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(&sector_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        // Check that record belongs to the segment
1113        if !self
1114            .record_root
1115            .is_valid(&self.segment_root, &self.record_proof, position)
1116        {
1117            return Err(SolutionVerifyError::RecordNotInSegment);
1118        }
1119
1120        // Check that segment belongs to the super segment (global history)
1121        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}