spartan_codec/
lib.rs

1#![warn(rust_2018_idioms, missing_debug_implementations, missing_docs)]
2//! This is an adaptation of [SLOTH](https://eprint.iacr.org/2015/366) (slow-timed hash function) into a time-asymmetric permutation using a standard CBC block cipher. This code is largely based on the C implementation used in [PySloth](https://github.com/randomchain/pysloth/blob/master/sloth.c) which is the same as used in the paper.
3
4mod sloth;
5
6use crate::sloth::Sloth;
7
8/// Spartan struct used to encode and validate
9#[derive(Debug, Clone)]
10pub struct Spartan<const PRIME_SIZE_BYTES: usize, const PIECE_SIZE_BYTES: usize> {
11    genesis_piece: [u8; PIECE_SIZE_BYTES],
12    sloth: Sloth<PRIME_SIZE_BYTES, PIECE_SIZE_BYTES>,
13}
14
15/// Spartan configured for 64-bit prime and 4096-byte genesis piece size
16pub type Spartan64bit4096 = Spartan<8, 4096>;
17/// Spartan configured for 128-bit prime and 4096-byte genesis piece size
18pub type Spartan128bit4096 = Spartan<16, 4096>;
19/// Spartan configured for 256-bit prime and 4096-byte genesis piece size
20pub type Spartan256bit4096 = Spartan<32, 4096>;
21/// Spartan configured for 512-bit prime and 4096-byte genesis piece size
22pub type Spartan512bit4096 = Spartan<64, 4096>;
23/// Spartan configured for 1024-bit prime and 4096-byte genesis piece size
24pub type Spartan1024bit4096 = Spartan<128, 4096>;
25/// Spartan configured for 2048-bit prime and 4096-byte genesis piece size
26pub type Spartan2048bit4096 = Spartan<256, 4096>;
27/// Spartan configured for 4096-bit prime and 4096-byte genesis piece size
28pub type Spartan4096bit4096 = Spartan<512, 4096>;
29
30impl<const PRIME_SIZE_BYTES: usize, const PIECE_SIZE_BYTES: usize>
31    Spartan<PRIME_SIZE_BYTES, PIECE_SIZE_BYTES>
32{
33    fn new_internal(genesis_piece: [u8; PIECE_SIZE_BYTES]) -> Self {
34        let sloth = Sloth::new();
35        Self {
36            genesis_piece,
37            sloth,
38        }
39    }
40}
41
42impl Spartan<8, 4096> {
43    /// New instance with 64-bit prime and 4096-byte genesis piece size
44    pub fn new(genesis_piece: [u8; 4096]) -> Self {
45        Self::new_internal(genesis_piece)
46    }
47}
48
49impl Spartan<16, 4096> {
50    /// New instance with 128-bit prime and 4096-byte genesis piece size
51    pub fn new(genesis_piece: [u8; 4096]) -> Self {
52        Self::new_internal(genesis_piece)
53    }
54}
55
56impl Spartan<32, 4096> {
57    /// New instance with 256-bit prime and 4096-byte genesis piece size
58    pub fn new(genesis_piece: [u8; 4096]) -> Self {
59        Self::new_internal(genesis_piece)
60    }
61}
62
63impl Spartan<64, 4096> {
64    /// New instance with 512-bit prime and 4096-byte genesis piece size
65    pub fn new(genesis_piece: [u8; 4096]) -> Self {
66        Self::new_internal(genesis_piece)
67    }
68}
69
70impl Spartan<128, 4096> {
71    /// New instance with 1024-bit prime and 4096-byte genesis piece size
72    pub fn new(genesis_piece: [u8; 4096]) -> Self {
73        Self::new_internal(genesis_piece)
74    }
75}
76
77impl Spartan<256, 4096> {
78    /// New instance with 2048-bit prime and 4096-byte genesis piece size
79    pub fn new(genesis_piece: [u8; 4096]) -> Self {
80        Self::new_internal(genesis_piece)
81    }
82}
83
84impl Spartan<512, 4096> {
85    /// New instance with 4096-bit prime and 4096-byte genesis piece size
86    pub fn new(genesis_piece: [u8; 4096]) -> Self {
87        Self::new_internal(genesis_piece)
88    }
89}
90
91impl<const PRIME_SIZE_BYTES: usize, const PIECE_SIZE_BYTES: usize>
92    Spartan<PRIME_SIZE_BYTES, PIECE_SIZE_BYTES>
93{
94    /// Create an encoding based on genesis piece using provided encoding key hash, nonce and
95    /// desired number of rounds
96    pub fn encode(
97        &self,
98        encoding_key_hash: [u8; PRIME_SIZE_BYTES],
99        nonce: u64,
100        rounds: usize,
101    ) -> [u8; PIECE_SIZE_BYTES] {
102        let mut expanded_iv = encoding_key_hash;
103        for (i, &byte) in nonce.to_le_bytes().iter().rev().enumerate() {
104            expanded_iv[PRIME_SIZE_BYTES - i - 1] ^= byte;
105        }
106
107        let mut encoding = self.genesis_piece;
108        // TODO: Better error handling
109        self.sloth
110            .encode(&mut encoding, expanded_iv, rounds)
111            .unwrap();
112
113        encoding
114    }
115
116    /// Check if previously created encoding is valid
117    pub fn is_valid(
118        &self,
119        mut encoding: [u8; PIECE_SIZE_BYTES],
120        encoding_key_hash: [u8; PRIME_SIZE_BYTES],
121        nonce: u64,
122        rounds: usize,
123    ) -> bool {
124        let mut expanded_iv = encoding_key_hash;
125        for (i, &byte) in nonce.to_le_bytes().iter().rev().enumerate() {
126            expanded_iv[PRIME_SIZE_BYTES - i - 1] ^= byte;
127        }
128
129        self.sloth.decode(&mut encoding, expanded_iv, rounds);
130
131        encoding == self.genesis_piece
132    }
133
134    /// Check if previously created encoding is valid by leveraging parallelism
135    #[cfg(feature = "parallel")]
136    pub fn is_valid_parallel(
137        &self,
138        piece: [u8; PIECE_SIZE_BYTES],
139        encoding_key_hash: [u8; PRIME_SIZE_BYTES],
140        nonce: u64,
141        rounds: usize,
142    ) -> bool {
143        let mut piece = piece;
144        let mut expanded_iv = encoding_key_hash;
145        for (i, &byte) in nonce.to_le_bytes().iter().rev().enumerate() {
146            expanded_iv[PRIME_SIZE_BYTES - i - 1] ^= byte;
147        }
148
149        self.sloth.decode_parallel(&mut piece, expanded_iv, rounds);
150
151        piece == self.genesis_piece
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use rand::prelude::*;
159
160    fn random_bytes<const BYTES: usize>() -> [u8; BYTES] {
161        let mut bytes = [0u8; BYTES];
162        rand::thread_rng().fill(&mut bytes[..]);
163        bytes
164    }
165
166    #[test]
167    fn test_random_piece() {
168        let genesis_piece = random_bytes();
169        let encoding_key = random_bytes();
170        let nonce = rand::random();
171
172        let spartan = Spartan256bit4096::new(genesis_piece);
173        let encoding = spartan.encode(encoding_key, nonce, 1);
174
175        assert!(spartan.is_valid(encoding, encoding_key, nonce, 1));
176
177        assert!(spartan.is_valid_parallel(encoding, encoding_key, nonce, 1));
178    }
179}