Skip to main content

ab_core_primitives/
pot.rs

1//! Proof of time-related data structures.
2
3use crate::block::BlockRoot;
4use crate::hashes::Blake3Hash;
5use crate::pieces::RecordChunk;
6use crate::solutions::ShardMembershipEntropy;
7use ab_blake3::single_block_hash;
8use ab_io_type::trivial_type::TrivialType;
9use core::iter::Step;
10use core::num::{NonZeroU8, NonZeroU32};
11use core::str::FromStr;
12use core::time::Duration;
13use core::{fmt, mem};
14use derive_more::{
15    Add, AddAssign, AsMut, AsRef, Deref, DerefMut, Display, Div, DivAssign, From, Into, Mul,
16    MulAssign, Sub, SubAssign,
17};
18#[cfg(feature = "scale-codec")]
19use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22#[cfg(feature = "serde")]
23use serde::{Deserializer, Serializer};
24
25/// Slot duration
26#[derive(
27    Debug, Display, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, From, Into,
28)]
29#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[repr(C)]
32pub struct SlotDuration(u16);
33
34impl SlotDuration {
35    /// Size in bytes
36    pub const SIZE: usize = size_of::<u16>();
37
38    /// Create a new instance
39    #[inline(always)]
40    pub const fn from_millis(ms: u16) -> Self {
41        Self(ms)
42    }
43
44    /// Get internal representation
45    #[inline(always)]
46    pub const fn as_millis(self) -> u16 {
47        self.0
48    }
49
50    /// Get the value as [`Duration`] instance
51    #[inline(always)]
52    pub const fn as_duration(self) -> Duration {
53        Duration::from_millis(self.as_millis() as u64)
54    }
55}
56
57/// Slot number
58#[derive(
59    Debug,
60    Display,
61    Default,
62    Copy,
63    Clone,
64    Ord,
65    PartialOrd,
66    Eq,
67    PartialEq,
68    Hash,
69    Add,
70    AddAssign,
71    Sub,
72    SubAssign,
73    Mul,
74    MulAssign,
75    Div,
76    DivAssign,
77    TrivialType,
78)]
79#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
80#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
81#[repr(C)]
82pub struct SlotNumber(u64);
83
84impl Step for SlotNumber {
85    #[inline(always)]
86    fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
87        u64::steps_between(&start.0, &end.0)
88    }
89
90    #[inline(always)]
91    fn forward_checked(start: Self, count: usize) -> Option<Self> {
92        u64::forward_checked(start.0, count).map(Self)
93    }
94
95    #[inline(always)]
96    fn forward_overflowing(start: Self, count: usize) -> (Self, bool) {
97        let (n, overflowing) = u64::forward_overflowing(start.0, count);
98        (Self(n), overflowing)
99    }
100
101    #[inline(always)]
102    fn backward_checked(start: Self, count: usize) -> Option<Self> {
103        u64::backward_checked(start.0, count).map(Self)
104    }
105
106    #[inline(always)]
107    fn backward_overflowing(start: Self, count: usize) -> (Self, bool) {
108        let (n, overflowing) = u64::backward_overflowing(start.0, count);
109        (Self(n), overflowing)
110    }
111}
112
113const impl From<u64> for SlotNumber {
114    #[inline(always)]
115    fn from(value: u64) -> Self {
116        Self(value)
117    }
118}
119
120const impl From<SlotNumber> for u64 {
121    #[inline(always)]
122    fn from(value: SlotNumber) -> Self {
123        value.0
124    }
125}
126
127impl From<SlotNumber> for u128 {
128    #[inline(always)]
129    fn from(original: SlotNumber) -> Self {
130        u128::from(original.0)
131    }
132}
133
134impl SlotNumber {
135    /// Size in bytes
136    pub const SIZE: usize = size_of::<u64>();
137    /// Slot 0
138    pub const ZERO: Self = Self(0);
139    /// Slot 1
140    pub const ONE: Self = Self(1);
141    /// Max slot
142    pub const MAX: Self = Self(u64::MAX);
143
144    /// Create slot number from bytes
145    #[inline(always)]
146    pub const fn from_bytes(bytes: [u8; const { Self::SIZE }]) -> Self {
147        Self(u64::from_le_bytes(bytes))
148    }
149
150    /// Convert slot number to bytes
151    #[inline(always)]
152    pub const fn to_bytes(self) -> [u8; const { Self::SIZE }] {
153        self.0.to_le_bytes()
154    }
155
156    /// Checked addition, returns `None` on overflow
157    #[inline(always)]
158    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
159        if let Some(n) = self.0.checked_add(rhs.0) {
160            Some(Self(n))
161        } else {
162            None
163        }
164    }
165
166    /// Saturating addition
167    #[inline(always)]
168    pub const fn saturating_add(self, rhs: Self) -> Self {
169        Self(self.0.saturating_add(rhs.0))
170    }
171
172    /// Checked subtraction, returns `None` on underflow
173    #[inline(always)]
174    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
175        if let Some(n) = self.0.checked_sub(rhs.0) {
176            Some(Self(n))
177        } else {
178            None
179        }
180    }
181
182    /// Saturating subtraction
183    #[inline(always)]
184    pub const fn saturating_sub(self, rhs: Self) -> Self {
185        Self(self.0.saturating_sub(rhs.0))
186    }
187}
188
189/// Proof of time key(input to the encryption).
190#[derive(Default, Copy, Clone, Eq, PartialEq, From, Into, AsRef, AsMut, Deref, DerefMut)]
191#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
192pub struct PotKey([u8; const { PotKey::SIZE }]);
193
194impl fmt::Debug for PotKey {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        for byte in self.0 {
197            write!(f, "{byte:02x}")?;
198        }
199        Ok(())
200    }
201}
202
203#[cfg(feature = "serde")]
204#[derive(Serialize, Deserialize)]
205#[serde(transparent)]
206struct PotKeyBinary([u8; const { PotKey::SIZE }]);
207
208#[cfg(feature = "serde")]
209#[derive(Serialize, Deserialize)]
210#[serde(transparent)]
211struct PotKeyHex(#[serde(with = "hex")] [u8; const { PotKey::SIZE }]);
212
213#[cfg(feature = "serde")]
214impl Serialize for PotKey {
215    #[inline]
216    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
217    where
218        S: Serializer,
219    {
220        if serializer.is_human_readable() {
221            PotKeyHex(self.0).serialize(serializer)
222        } else {
223            PotKeyBinary(self.0).serialize(serializer)
224        }
225    }
226}
227
228#[cfg(feature = "serde")]
229impl<'de> Deserialize<'de> for PotKey {
230    #[inline]
231    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
232    where
233        D: Deserializer<'de>,
234    {
235        Ok(Self(if deserializer.is_human_readable() {
236            PotKeyHex::deserialize(deserializer)?.0
237        } else {
238            PotKeyBinary::deserialize(deserializer)?.0
239        }))
240    }
241}
242
243impl fmt::Display for PotKey {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        for byte in self.0 {
246            write!(f, "{byte:02x}")?;
247        }
248        Ok(())
249    }
250}
251
252impl FromStr for PotKey {
253    type Err = hex::FromHexError;
254
255    #[inline]
256    fn from_str(s: &str) -> Result<Self, Self::Err> {
257        let mut key = Self::default();
258        hex::decode_to_slice(s, key.as_mut())?;
259
260        Ok(key)
261    }
262}
263
264impl PotKey {
265    /// Size in bytes
266    pub const SIZE: usize = 16;
267}
268
269/// Proof of time seed
270#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, From, Into, AsRef, AsMut, Deref, DerefMut)]
271#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
272pub struct PotSeed([u8; const { PotSeed::SIZE }]);
273
274impl fmt::Debug for PotSeed {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        for byte in self.0 {
277            write!(f, "{byte:02x}")?;
278        }
279        Ok(())
280    }
281}
282
283#[cfg(feature = "serde")]
284#[derive(Serialize, Deserialize)]
285#[serde(transparent)]
286struct PotSeedBinary([u8; const { PotSeed::SIZE }]);
287
288#[cfg(feature = "serde")]
289#[derive(Serialize, Deserialize)]
290#[serde(transparent)]
291struct PotSeedHex(#[serde(with = "hex")] [u8; const { PotSeed::SIZE }]);
292
293#[cfg(feature = "serde")]
294impl Serialize for PotSeed {
295    #[inline]
296    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
297    where
298        S: Serializer,
299    {
300        if serializer.is_human_readable() {
301            PotSeedHex(self.0).serialize(serializer)
302        } else {
303            PotSeedBinary(self.0).serialize(serializer)
304        }
305    }
306}
307
308#[cfg(feature = "serde")]
309impl<'de> Deserialize<'de> for PotSeed {
310    #[inline]
311    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
312    where
313        D: Deserializer<'de>,
314    {
315        Ok(Self(if deserializer.is_human_readable() {
316            PotSeedHex::deserialize(deserializer)?.0
317        } else {
318            PotSeedBinary::deserialize(deserializer)?.0
319        }))
320    }
321}
322
323impl fmt::Display for PotSeed {
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        for byte in self.0 {
326            write!(f, "{byte:02x}")?;
327        }
328        Ok(())
329    }
330}
331
332impl PotSeed {
333    /// Size in bytes
334    pub const SIZE: usize = 16;
335
336    /// Derive initial PoT seed from genesis block root
337    #[inline]
338    pub fn from_genesis(genesis_block_root: &BlockRoot, external_entropy: &[u8]) -> Self {
339        let mut hasher = blake3::Hasher::new();
340        hasher.update(genesis_block_root.as_ref());
341        hasher.update(external_entropy);
342        let hash = hasher.finalize();
343        let mut seed = Self::default();
344        seed.copy_from_slice(&hash.as_bytes()[..Self::SIZE]);
345        seed
346    }
347
348    /// Derive key from proof of time seed
349    #[inline]
350    pub fn key(&self) -> PotKey {
351        let mut key = PotKey::default();
352        key.copy_from_slice(
353            &single_block_hash(&self.0).expect("Less than a single block worth of bytes; qed")
354                [..Self::SIZE],
355        );
356        key
357    }
358}
359
360/// Proof of time output, can be intermediate checkpoint or final slot output
361#[derive(
362    Default,
363    Copy,
364    Clone,
365    Eq,
366    PartialEq,
367    Hash,
368    From,
369    Into,
370    AsRef,
371    AsMut,
372    Deref,
373    DerefMut,
374    TrivialType,
375)]
376#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
377#[repr(C)]
378pub struct PotOutput([u8; const { PotOutput::SIZE }]);
379
380impl fmt::Debug for PotOutput {
381    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382        for byte in self.0 {
383            write!(f, "{byte:02x}")?;
384        }
385        Ok(())
386    }
387}
388
389#[cfg(feature = "serde")]
390#[derive(Serialize, Deserialize)]
391#[serde(transparent)]
392struct PotOutputBinary([u8; const { PotOutput::SIZE }]);
393
394#[cfg(feature = "serde")]
395#[derive(Serialize, Deserialize)]
396#[serde(transparent)]
397struct PotOutputHex(#[serde(with = "hex")] [u8; const { PotOutput::SIZE }]);
398
399#[cfg(feature = "serde")]
400impl Serialize for PotOutput {
401    #[inline]
402    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
403    where
404        S: Serializer,
405    {
406        if serializer.is_human_readable() {
407            PotOutputHex(self.0).serialize(serializer)
408        } else {
409            PotOutputBinary(self.0).serialize(serializer)
410        }
411    }
412}
413
414#[cfg(feature = "serde")]
415impl<'de> Deserialize<'de> for PotOutput {
416    #[inline]
417    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
418    where
419        D: Deserializer<'de>,
420    {
421        Ok(Self(if deserializer.is_human_readable() {
422            PotOutputHex::deserialize(deserializer)?.0
423        } else {
424            PotOutputBinary::deserialize(deserializer)?.0
425        }))
426    }
427}
428
429impl fmt::Display for PotOutput {
430    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431        for byte in self.0 {
432            write!(f, "{byte:02x}")?;
433        }
434        Ok(())
435    }
436}
437
438impl PotOutput {
439    /// Size in bytes
440    pub const SIZE: usize = 16;
441
442    /// Derives the global challenge from the output and slot
443    #[inline]
444    pub fn derive_global_challenge(&self, slot: SlotNumber) -> Blake3Hash {
445        let mut bytes_to_hash = [0; const { Self::SIZE + SlotNumber::SIZE }];
446        bytes_to_hash[..Self::SIZE].copy_from_slice(&self.0);
447        bytes_to_hash[Self::SIZE..].copy_from_slice(&slot.to_bytes());
448        Blake3Hash::new(
449            single_block_hash(&bytes_to_hash)
450                .expect("Less than a single block worth of bytes; qed"),
451        )
452    }
453
454    /// Derive seed from proof of time in case entropy injection is not needed
455    #[inline]
456    pub fn seed(&self) -> PotSeed {
457        PotSeed(self.0)
458    }
459
460    /// Derive seed from proof of time with entropy injection
461    #[inline]
462    pub fn seed_with_entropy(&self, entropy: &Blake3Hash) -> PotSeed {
463        let mut bytes_to_hash = [0; const { Blake3Hash::SIZE + Self::SIZE }];
464        bytes_to_hash[..Blake3Hash::SIZE].copy_from_slice(entropy.as_ref());
465        bytes_to_hash[Blake3Hash::SIZE..].copy_from_slice(&self.0);
466        let hash = single_block_hash(&bytes_to_hash)
467            .expect("Less than a single block worth of bytes; qed");
468        let mut seed = PotSeed::default();
469        seed.copy_from_slice(&hash[..Self::SIZE]);
470        seed
471    }
472
473    /// Derive proof of time entropy from chunk and proof of time for injection purposes
474    #[inline]
475    pub fn derive_pot_entropy(&self, solution_chunk: &RecordChunk) -> Blake3Hash {
476        let mut bytes_to_hash = [0; const { RecordChunk::SIZE + Self::SIZE }];
477        bytes_to_hash[..RecordChunk::SIZE].copy_from_slice(solution_chunk.as_ref());
478        bytes_to_hash[RecordChunk::SIZE..].copy_from_slice(&self.0);
479        Blake3Hash::new(
480            single_block_hash(&bytes_to_hash)
481                .expect("Less than a single block worth of bytes; qed"),
482        )
483    }
484
485    /// Derive shard membership entropy
486    #[inline]
487    pub fn shard_membership_entropy(&self) -> ShardMembershipEntropy {
488        ShardMembershipEntropy::new(self.0)
489    }
490
491    /// Convenient conversion from slice of underlying representation for efficiency purposes
492    #[inline(always)]
493    pub const fn slice_from_repr(value: &[[u8; const { Self::SIZE }]]) -> &[Self] {
494        // SAFETY: `PotOutput` is `#[repr(C)]` and guaranteed to have the same memory layout
495        unsafe { mem::transmute(value) }
496    }
497
498    /// Convenient conversion to slice of underlying representation for efficiency purposes
499    #[inline(always)]
500    pub const fn repr_from_slice(value: &[Self]) -> &[[u8; const { Self::SIZE }]] {
501        // SAFETY: `PotOutput` is `#[repr(C)]` and guaranteed to have the same memory layout
502        unsafe { mem::transmute(value) }
503    }
504}
505
506/// Proof of time checkpoints, result of proving
507#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deref, DerefMut, TrivialType)]
508#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
509#[repr(C)]
510pub struct PotCheckpoints([PotOutput; PotCheckpoints::NUM_CHECKPOINTS.get() as usize]);
511
512impl PotCheckpoints {
513    /// Size in bytes
514    pub const SIZE: usize = PotOutput::SIZE * Self::NUM_CHECKPOINTS.get() as usize;
515    /// Number of PoT checkpoints produced (used to optimize verification)
516    pub const NUM_CHECKPOINTS: NonZeroU8 = NonZeroU8::new(8).expect("Not zero; qed");
517
518    /// Get proof of time output out of checkpoints (last checkpoint)
519    #[inline]
520    pub fn output(&self) -> PotOutput {
521        self.0[Self::NUM_CHECKPOINTS.get() as usize - 1]
522    }
523
524    /// Convenient conversion from slice of underlying representation for efficiency purposes
525    #[inline(always)]
526    pub const fn slice_from_bytes(value: &[[u8; const { Self::SIZE }]]) -> &[Self] {
527        // SAFETY: `PotOutput` and `PotCheckpoints` are `#[repr(C)]` and guaranteed to have the same
528        // memory layout
529        unsafe { mem::transmute(value) }
530    }
531
532    /// Convenient conversion to slice of underlying representation for efficiency purposes
533    #[inline(always)]
534    pub const fn bytes_from_slice(value: &[Self]) -> &[[u8; const { Self::SIZE }]] {
535        // SAFETY: `PotOutput` and `PotCheckpoints` are `#[repr(C)]` and guaranteed to have the same
536        // memory layout
537        unsafe { mem::transmute(value) }
538    }
539}
540
541/// Change of parameters to apply to the proof of time chain.
542///
543/// Corresponds to scheduled PoT parameters change, which is applied after the slot of this block.
544/// It is carried into the next blocks until it is applied on or before slot of the current block.
545#[derive(Debug, Copy, Clone, PartialEq, Eq)]
546#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
547#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
548#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
549pub struct PotParametersChange {
550    // TODO: Reduce this to `u16` or even `u8` since it is always an offset relatively to current
551    //  block's slot number
552    /// At which slot change of parameters takes effect
553    pub slot: SlotNumber,
554    /// New number of slot iterations
555    pub slot_iterations: NonZeroU32,
556    /// Entropy that should be injected at this time
557    pub entropy: Blake3Hash,
558}