subspace_core_primitives/
solutions.rs

1//! Solutions-related data structures and functions.
2
3use crate::pieces::{PieceOffset, Record, RecordCommitment, RecordWitness};
4use crate::pos::PosProof;
5use crate::sectors::SectorIndex;
6use crate::segments::{HistorySize, SegmentIndex};
7use crate::{PublicKey, ScalarBytes};
8use core::array::TryFromSliceError;
9use core::fmt;
10use derive_more::{AsMut, AsRef, Deref, DerefMut, From, Into};
11use num_traits::WrappingSub;
12use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
13use scale_info::TypeInfo;
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16#[cfg(feature = "serde")]
17use serde::{Deserializer, Serializer};
18#[cfg(feature = "serde")]
19use serde_big_array::BigArray;
20use static_assertions::const_assert;
21
22// TODO: Add related methods to `SolutionRange`.
23/// Type of solution range.
24pub type SolutionRange = u64;
25
26/// Computes the following:
27/// ```text
28/// MAX * slot_probability / chunks * s_buckets / pieces
29/// ```
30pub const fn pieces_to_solution_range(pieces: u64, slot_probability: (u64, u64)) -> SolutionRange {
31    let solution_range = SolutionRange::MAX
32        // Account for slot probability
33        / slot_probability.1 * slot_probability.0
34        // Now take probability of hitting occupied s-bucket in a piece into account
35        / Record::NUM_CHUNKS as u64
36        * Record::NUM_S_BUCKETS as u64;
37
38    // Take number of pieces into account
39    solution_range / pieces
40}
41
42/// Computes the following:
43/// ```text
44/// MAX * slot_probability / chunks * s_buckets / solution_range
45/// ```
46pub const fn solution_range_to_pieces(
47    solution_range: SolutionRange,
48    slot_probability: (u64, u64),
49) -> u64 {
50    let pieces = SolutionRange::MAX
51        // Account for slot probability
52        / slot_probability.1 * slot_probability.0
53        // Now take probability of hitting occupied s-bucket in sector into account
54        / Record::NUM_CHUNKS as u64
55        * Record::NUM_S_BUCKETS as u64;
56
57    // Take solution range into account
58    pieces / solution_range
59}
60
61// Quick test to ensure functions above are the inverse of each other
62const_assert!(solution_range_to_pieces(pieces_to_solution_range(1, (1, 6)), (1, 6)) == 1);
63const_assert!(solution_range_to_pieces(pieces_to_solution_range(3, (1, 6)), (1, 6)) == 3);
64const_assert!(solution_range_to_pieces(pieces_to_solution_range(5, (1, 6)), (1, 6)) == 5);
65
66/// A Ristretto Schnorr signature as bytes produced by `schnorrkel` crate.
67#[derive(
68    Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Encode, Decode, TypeInfo, Deref, From, Into,
69)]
70pub struct RewardSignature([u8; RewardSignature::SIZE]);
71
72impl fmt::Debug for RewardSignature {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "{}", hex::encode(self.0))
75    }
76}
77
78#[cfg(feature = "serde")]
79#[derive(Serialize, Deserialize)]
80#[serde(transparent)]
81struct RewardSignatureBinary(#[serde(with = "BigArray")] [u8; RewardSignature::SIZE]);
82
83#[cfg(feature = "serde")]
84#[derive(Serialize, Deserialize)]
85#[serde(transparent)]
86struct RewardSignatureHex(#[serde(with = "hex")] [u8; RewardSignature::SIZE]);
87
88#[cfg(feature = "serde")]
89impl Serialize for RewardSignature {
90    #[inline]
91    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
92    where
93        S: Serializer,
94    {
95        if serializer.is_human_readable() {
96            RewardSignatureHex(self.0).serialize(serializer)
97        } else {
98            RewardSignatureBinary(self.0).serialize(serializer)
99        }
100    }
101}
102
103#[cfg(feature = "serde")]
104impl<'de> Deserialize<'de> for RewardSignature {
105    #[inline]
106    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107    where
108        D: Deserializer<'de>,
109    {
110        Ok(Self(if deserializer.is_human_readable() {
111            RewardSignatureHex::deserialize(deserializer)?.0
112        } else {
113            RewardSignatureBinary::deserialize(deserializer)?.0
114        }))
115    }
116}
117
118impl AsRef<[u8]> for RewardSignature {
119    #[inline]
120    fn as_ref(&self) -> &[u8] {
121        &self.0
122    }
123}
124
125impl RewardSignature {
126    /// Reward signature size in bytes
127    pub const SIZE: usize = 64;
128}
129
130/// Witness for chunk contained within a record.
131#[derive(
132    Copy,
133    Clone,
134    Eq,
135    PartialEq,
136    Hash,
137    Deref,
138    DerefMut,
139    From,
140    Into,
141    Encode,
142    Decode,
143    TypeInfo,
144    MaxEncodedLen,
145)]
146#[repr(transparent)]
147pub struct ChunkWitness([u8; ChunkWitness::SIZE]);
148
149impl fmt::Debug for ChunkWitness {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(f, "{}", hex::encode(self.0))
152    }
153}
154
155#[cfg(feature = "serde")]
156#[derive(Serialize, Deserialize)]
157#[serde(transparent)]
158struct ChunkWitnessBinary(#[serde(with = "BigArray")] [u8; ChunkWitness::SIZE]);
159
160#[cfg(feature = "serde")]
161#[derive(Serialize, Deserialize)]
162#[serde(transparent)]
163struct ChunkWitnessHex(#[serde(with = "hex")] [u8; ChunkWitness::SIZE]);
164
165#[cfg(feature = "serde")]
166impl Serialize for ChunkWitness {
167    #[inline]
168    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
169    where
170        S: Serializer,
171    {
172        if serializer.is_human_readable() {
173            ChunkWitnessHex(self.0).serialize(serializer)
174        } else {
175            ChunkWitnessBinary(self.0).serialize(serializer)
176        }
177    }
178}
179
180#[cfg(feature = "serde")]
181impl<'de> Deserialize<'de> for ChunkWitness {
182    #[inline]
183    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184    where
185        D: Deserializer<'de>,
186    {
187        Ok(Self(if deserializer.is_human_readable() {
188            ChunkWitnessHex::deserialize(deserializer)?.0
189        } else {
190            ChunkWitnessBinary::deserialize(deserializer)?.0
191        }))
192    }
193}
194
195impl Default for ChunkWitness {
196    #[inline]
197    fn default() -> Self {
198        Self([0; Self::SIZE])
199    }
200}
201
202impl TryFrom<&[u8]> for ChunkWitness {
203    type Error = TryFromSliceError;
204
205    #[inline]
206    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
207        <[u8; Self::SIZE]>::try_from(slice).map(Self)
208    }
209}
210
211impl AsRef<[u8]> for ChunkWitness {
212    #[inline]
213    fn as_ref(&self) -> &[u8] {
214        &self.0
215    }
216}
217
218impl AsMut<[u8]> for ChunkWitness {
219    #[inline]
220    fn as_mut(&mut self) -> &mut [u8] {
221        &mut self.0
222    }
223}
224
225impl ChunkWitness {
226    /// Size of chunk witness in bytes.
227    pub const SIZE: usize = 48;
228}
229
230/// Farmer solution for slot challenge.
231#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo)]
232#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
233#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
234pub struct Solution<RewardAddress> {
235    /// Public key of the farmer that created the solution
236    pub public_key: PublicKey,
237    /// Address for receiving block reward
238    pub reward_address: RewardAddress,
239    /// Index of the sector where solution was found
240    pub sector_index: SectorIndex,
241    /// Size of the blockchain history at time of sector creation
242    pub history_size: HistorySize,
243    /// Pieces offset within sector
244    pub piece_offset: PieceOffset,
245    /// Record commitment that can use used to verify that piece was included in blockchain history
246    pub record_commitment: RecordCommitment,
247    /// Witness for above record commitment
248    pub record_witness: RecordWitness,
249    /// Chunk at above offset
250    pub chunk: ScalarBytes,
251    /// Witness for above chunk
252    pub chunk_witness: ChunkWitness,
253    /// Proof of space for piece offset
254    pub proof_of_space: PosProof,
255}
256
257impl<RewardAddressA> Solution<RewardAddressA> {
258    /// Transform solution with one reward address type into solution with another compatible
259    /// reward address type.
260    pub fn into_reward_address_format<T, RewardAddressB>(self) -> Solution<RewardAddressB>
261    where
262        RewardAddressA: Into<T>,
263        T: Into<RewardAddressB>,
264    {
265        let Solution {
266            public_key,
267            reward_address,
268            sector_index,
269            history_size,
270            piece_offset,
271            record_commitment,
272            record_witness,
273            chunk,
274            chunk_witness,
275            proof_of_space,
276        } = self;
277        Solution {
278            public_key,
279            reward_address: Into::<T>::into(reward_address).into(),
280            sector_index,
281            history_size,
282            piece_offset,
283            record_commitment,
284            record_witness,
285            chunk,
286            chunk_witness,
287            proof_of_space,
288        }
289    }
290}
291
292impl<RewardAddress> Solution<RewardAddress> {
293    /// Dummy solution for the genesis block
294    pub fn genesis_solution(public_key: PublicKey, reward_address: RewardAddress) -> Self {
295        Self {
296            public_key,
297            reward_address,
298            sector_index: 0,
299            history_size: HistorySize::from(SegmentIndex::ZERO),
300            piece_offset: PieceOffset::default(),
301            record_commitment: RecordCommitment::default(),
302            record_witness: RecordWitness::default(),
303            chunk: ScalarBytes::default(),
304            chunk_witness: ChunkWitness::default(),
305            proof_of_space: PosProof::default(),
306        }
307    }
308}
309
310/// Bidirectional distance metric implemented on top of subtraction
311#[inline(always)]
312pub fn bidirectional_distance<T: WrappingSub + Ord>(a: &T, b: &T) -> T {
313    let diff = a.wrapping_sub(b);
314    let diff2 = b.wrapping_sub(a);
315    // Find smaller diff between 2 directions.
316    diff.min(diff2)
317}