Skip to main content

pow/
proof_of_work.rs

1use serde::{Deserialize, Serialize};
2use sha2::{digest::FixedOutput, Digest, Sha256};
3use std::marker::PhantomData;
4
5const SALT: &str = "35af8f4890981391c191e6df45b5f780812ddf0213f29299576ac1c98e18173e";
6
7/// Proof of work over concrete type T. T can be any type that implements serde::Serialize.
8#[derive(Serialize, Deserialize, PartialEq, Clone, Copy, Debug)]
9pub struct Pow<T> {
10    proof: u128,
11    #[serde(skip)]
12    _spook: PhantomData<T>,
13}
14
15// prove_work and score could theoretically be without allocations, by serializing to a Write
16// implementaion that performs sha256 lazily.
17// `impl io::Write for sha2::Sha256 { ... }`
18
19impl<T: Serialize> Pow<T> {
20    /// Prove work over T.
21    ///
22    /// Make sure difficulty is not too high. A 64 bit difficulty, for example, takes a long time
23    /// on a general purpose processor.
24    ///
25    /// Returns bincode::Error if serialization fails.
26    pub fn prove_work(t: &T, difficulty: u128) -> bincode::Result<Pow<T>> {
27        bincode_cfg()
28            .serialize(t)
29            .map(|v| Self::prove_work_serialized(&v, difficulty))
30    }
31
32    /// Prove work on an already serialized item of type T.
33    /// The input is assumed to be serialized using network byte order.
34    ///
35    /// Make sure difficulty is not too high. A 64 bit difficulty, for example, takes a long time
36    /// on a general purpose processor.
37    pub fn prove_work_serialized(prefix: &[u8], difficulty: u128) -> Pow<T> {
38        let prefix_sha = Sha256::new().chain(SALT).chain(prefix);
39        let mut n = 0;
40        while score(prefix_sha.clone(), n) < difficulty {
41            n += 1;
42        }
43        Pow {
44            proof: n,
45            _spook: PhantomData,
46        }
47    }
48
49    /// Calculate the pow score of t and self.
50    pub fn score(&self, t: &T) -> bincode::Result<u128> {
51        bincode_cfg()
52            .serialize(t)
53            .map(|v| self.score_serialized(&v))
54    }
55
56    /// Calculate the pow score of an already serialized T and self.
57    /// The input is assumed to be serialized using network byte order.
58    pub fn score_serialized(&self, target: &[u8]) -> u128 {
59        score(Sha256::new().chain(SALT).chain(target), self.proof)
60    }
61}
62
63fn score(prefix_sha: Sha256, proof: u128) -> u128 {
64    first_bytes_as_u128(
65        prefix_sha
66            .chain(&proof.to_be_bytes()) // to_be_bytes() converts to network endian
67            .fixed_result()
68            .as_slice(),
69    )
70}
71
72/// # Panics
73///
74/// panics if inp.len() < 16
75fn first_bytes_as_u128(inp: &[u8]) -> u128 {
76    bincode_cfg().deserialize(&inp).unwrap()
77}
78
79fn bincode_cfg() -> bincode::Config {
80    let mut cfg = bincode::config();
81    cfg.big_endian();
82    cfg
83}
84
85#[cfg(test)]
86mod test {
87    use super::*;
88
89    const DIFFICULTY: u128 = 0xff000000000000000000000000000000;
90
91    #[test]
92    fn base_functionality() {
93        // Let's prove we did work targeting a phrase.
94        let phrase = b"Corver bandar palladianism retroform.".to_vec();
95        let pw = Pow::prove_work(&phrase, DIFFICULTY).unwrap();
96        assert!(pw.score(&phrase).unwrap() >= DIFFICULTY);
97    }
98
99    #[test]
100    fn double_pow() {
101        let phrase = "Corver bandar palladianism retroform.".to_owned();
102        let pow = Pow::prove_work(&phrase, DIFFICULTY).unwrap();
103        let powpow: Pow<Pow<String>> = Pow::prove_work(&pow, DIFFICULTY).unwrap();
104        assert!(pow.score(&phrase).unwrap() >= DIFFICULTY);
105        assert!(powpow.score(&pow).unwrap() >= DIFFICULTY);
106    }
107
108    #[test]
109    fn ser_de() {
110        let target: u8 = 1;
111        let pw = Pow::prove_work(&target, DIFFICULTY).unwrap();
112        let message: (u8, Pow<u8>) = (target, pw);
113        let message_ser = bincode_cfg().serialize(&message).unwrap();
114        let recieved_message: (u8, Pow<u8>) = bincode_cfg().deserialize(&message_ser).unwrap();
115        assert_eq!(recieved_message, message);
116        assert!(message.1.score(&message.0).unwrap() >= DIFFICULTY);
117    }
118
119    #[test]
120    /// spook data field is ignored
121    fn ser_de_no_spook() {
122        let pw: Pow<u8> = Pow {
123            proof: 0,
124            _spook: PhantomData,
125        };
126        let ser = serde_json::to_string(&pw).unwrap();
127        assert_ne!(&ser, "{\"proof\":0,\"_spook\":null}");
128        assert_eq!(&ser, "{\"proof\":0}");
129    }
130}