resource_proof/
lib.rs

1// Copyright 2018 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT
4// http://opensource.org/licenses/MIT> or the Modified BSD license <LICENSE-BSD
5// https://opensource.org/licenses/BSD-3-Clause>, at your option. This file may not be copied,
6// modified, or distributed except according to those terms. Please review the Licences for the
7// specific language governing permissions and limitations relating to use of the SAFE Network
8// Software.
9
10//! # Resource proof
11//!
12//! A mechanism to test resource availability (CPU and bandwidth) of a machine prior to it joining
13//! a network. This crate provides the creation and validation algorithms.
14//!
15//! Validation has some CPU and memory requirements but far less than proof creation. Bandwidth
16//! tests (data transfer) affect the machine being proved and the machine doing validation equally;
17//! it is suggested that multiple machines test any new machine to apply an asymmetric load.
18//!
19//! [GitHub repository](https://github.com/maidsafe/resource_proof)
20
21#![doc(
22    html_logo_url = "https://raw.githubusercontent.com/maidsafe/QA/master/Images/maidsafe_logo.png",
23    html_favicon_url = "https://maidsafe.net/img/favicon.ico",
24    test(attr(forbid(warnings)))
25)]
26// For explanation of lint checks, run `rustc -W help` or see
27// https://github.com/maidsafe/QA/blob/master/Documentation/Rust%20Lint%20Checks.md
28#![forbid(
29    bad_style,
30    arithmetic_overflow,
31    mutable_transmutes,
32    no_mangle_const_items,
33    unknown_crate_types
34)]
35#![deny(
36    deprecated,
37    improper_ctypes,
38    missing_docs,
39    non_shorthand_field_patterns,
40    overflowing_literals,
41    stable_features,
42    unconditional_recursion,
43    unknown_lints,
44    unsafe_code,
45    unused,
46    unused_allocation,
47    unused_attributes,
48    unused_comparisons,
49    unused_features,
50    unused_parens,
51    while_true,
52    warnings
53)]
54#![warn(
55    trivial_casts,
56    trivial_numeric_casts,
57    unused_extern_crates,
58    unused_import_braces,
59    unused_qualifications,
60    unused_results
61)]
62#![allow(
63    box_pointers,
64    missing_copy_implementations,
65    missing_debug_implementations,
66    variant_size_differences
67)]
68
69use std::collections::VecDeque;
70use tiny_keccak::{Hasher, Sha3};
71
72/// Holds the prover requirements
73pub struct ResourceProof {
74    min_size: usize,
75    /// minimum size of proof in bytes
76    difficulty: u8,
77}
78
79impl ResourceProof {
80    /// Configure a new prover.
81    ///
82    /// `min_size` is target data size in bytes. It may be small or large to test bandwidth
83    /// (although it may be compressible).
84    ///
85    /// `difficulty` is the number of leading binary zeros required in the hash. Each extra zero
86    /// doubles the difficulty.
87    pub fn new(min_size: usize, difficulty: u8) -> ResourceProof {
88        ResourceProof {
89            min_size,
90            difficulty,
91        }
92    }
93
94    /// Create the proof data with a given nonce.
95    pub fn create_proof_data(&self, nonce: &[u8]) -> VecDeque<u8> {
96        nonce.iter().cloned().cycle().take(self.min_size).collect()
97    }
98
99    /// Create a prover object. Requires a copy of the data (from `create_proof_data`) to be
100    /// passed in.
101    pub fn create_prover(&self, data: VecDeque<u8>) -> ResourceProver {
102        ResourceProver {
103            difficulty: self.difficulty,
104            count: 0,
105            data,
106        }
107    }
108
109    /// Validate the proof data and key (this is the number of zeros to be pushed onto the data).
110    pub fn validate_all(&self, nonce: &[u8], received_data: &VecDeque<u8>, key: u64) -> bool {
111        let mut data = self.create_proof_data(nonce);
112        if data != *received_data {
113            return false;
114        }
115        for _ in 0..key {
116            data.push_front(0u8);
117        }
118        self.check_hash(&data) >= self.difficulty
119    }
120
121    /// Validate the data for the given `nonce` and size data.
122    pub fn validate_data(&self, nonce: &[u8], data: &VecDeque<u8>) -> bool {
123        self.create_proof_data(nonce) == *data
124    }
125
126    /// Validate the proof key (this must recreate the data, hence `validate_all` is faster when
127    /// both must be checked).
128    pub fn validate_proof(&self, nonce: &[u8], key: u64) -> bool {
129        let mut data = self.create_proof_data(nonce);
130        for _ in 0..key {
131            data.push_front(0u8);
132        }
133        self.check_hash(&data) >= self.difficulty
134    }
135
136    fn check_hash(&self, data: &VecDeque<u8>) -> u8 {
137        ResourceProof::leading_zeros(&hash(&data.as_slices()))
138    }
139
140    fn leading_zeros(data: &[u8]) -> u8 {
141        let mut zeros = 0u8;
142        for (count, i) in data.iter().enumerate() {
143            zeros = i.leading_zeros() as u8 + (count as u8 * 8);
144            if i.leading_zeros() < 8 {
145                break;
146            }
147        }
148        zeros
149    }
150}
151
152/// Object used to compute a result
153pub struct ResourceProver {
154    difficulty: u8,
155    count: u64,
156    data: VecDeque<u8>,
157}
158
159impl ResourceProver {
160    /// The expected number of steps is `pow(2, difficulty)`.
161    /// The process is probabilistic, so the actual number of steps required may be more or less.
162    ///
163    /// The length of each step depends on data size. Total expected time is proportional to
164    /// `length * pow(2, difficulty)`.
165    pub fn expected_steps(&self) -> u64 {
166        2u64.pow(u32::from(self.difficulty))
167    }
168
169    /// Try one step; if successful return the proof result.
170    ///
171    /// (This does not invalidate the prover. Continuing might find another valid solution.)
172    pub fn try_step(&mut self) -> Option<u64> {
173        if self.check_hash() >= self.difficulty {
174            return Some(self.count);
175        }
176
177        self.data.push_front(0u8);
178        self.count += 1;
179        None
180    }
181
182    /// Keep stepping until a solution is found. Expected time can be calculated roughly (see
183    /// `expected_steps`) but there is no upper bound (besides `u64::MAX`).
184    pub fn solve(&mut self) -> u64 {
185        loop {
186            if let Some(solution) = self.try_step() {
187                return solution;
188            }
189        }
190    }
191
192    fn check_hash(&self) -> u8 {
193        ResourceProof::leading_zeros(&hash(&self.data.as_slices()))
194    }
195}
196
197/// Hashes given seed into a nonce for use in proofs
198pub fn nonce_from_seed(seed: &[u8]) -> [u8; 32] {
199    let mut hasher = Sha3::v256();
200    hasher.update(seed);
201
202    let mut nonce = [0u8; 32];
203    hasher.finalize(&mut nonce);
204    nonce
205}
206
207/// Simple wrapper around tiny-keccak for use with deques
208fn hash(data: &(&[u8], &[u8])) -> [u8; 32] {
209    let mut hasher = Sha3::v256();
210    let mut res = [0u8; 32];
211    hasher.update(data.0);
212    hasher.update(data.1);
213    hasher.finalize(&mut res);
214    res
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn valid_proof() {
223        for i in 0..20 {
224            let nonce = nonce_from_seed(&[i]);
225            let rp = ResourceProof::new(1024, 3);
226            let data = rp.create_proof_data(&nonce);
227            let proof = rp.create_prover(data.clone()).solve();
228            assert!(rp.validate_proof(&nonce, proof));
229            assert!(rp.validate_data(&nonce, &data));
230            assert!(rp.validate_all(&nonce, &data, proof));
231        }
232    }
233}