hashcom_rs/
lib.rs

1use bincode::Result;
2use serde::Serialize;
3use sha2::{Digest, Sha256};
4
5/// A high-level representation of a party in a Hash Commitment Scheme.
6///
7/// ### Commit Phase
8/// During the commit phase, the prover hides the secret denoted as s using a random number r.
9/// Each implementation of this trait is responsible for the usage of a dedicated hash function.
10/// Once the commitment has been built, the prover sends it to the verifier.
11///
12/// ### Open Phase
13/// During the open phase, the prover reveals his secret s and the random number r he choose to
14/// forge the commitment.
15///
16/// ### Verification Phase
17/// During this last phase, the verifier uses the prover's secret and random number
18/// to forge the expected commitment. If the prover's initial commitment differs from the
19/// expected one, the commitment has not been fulfilled by the prover.
20pub trait HashCommitmentScheme<T: Serialize> {
21    fn commit(&self) -> Result<Vec<u8>>;
22    fn verify(&self, com: &[u8], s: &T, r: &[u8]) -> Result<bool>;
23}
24
25/// An implementation of the Hash Commitment Scheme using the SHA256 hash function.
26///
27/// We store the party's secret and random number as references because we don't want to take
28/// ownership over those variables and avoid useless copies (we only perform read operations
29/// with them).
30///
31/// We use lifetime annotations as we need to store references to existing variables in our
32/// structure, so that an instance of SHA256Commitment can not outlive the references
33/// it holds.
34pub struct SHA256Commitment<'a, T: 'a + Serialize> {
35    s: &'a T,
36    r: &'a [u8],
37}
38
39impl<'a, T: 'a + Serialize> SHA256Commitment<'a, T> {
40    /// Creates a new party for the SHA256 Commitment Scheme using its secret and random
41    /// number.
42    pub fn new(s: &'a T, r: &'a [u8]) -> SHA256Commitment<'a, T> {
43        SHA256Commitment { s, r }
44    }
45
46    /// Forges a commitment given a secret s and a random number r.
47    ///
48    /// We encode the secret to a byte array (which is padded by default), and use it along with
49    /// the random number, given as a byte array, to forge the commitment using the SHA256 hash
50    /// function.
51    fn forge_commitment(&self, s: &T, r: &[u8]) -> Result<Vec<u8>> {
52        let binary_encoded_s = bincode::serialize(s)?;
53
54        let hash = Sha256::new()
55            .chain_update(binary_encoded_s.as_slice())
56            .chain_update(r)
57            .finalize();
58
59        Ok(hash.as_slice().to_vec())
60    }
61}
62
63impl<'a, T: 'a + Serialize> HashCommitmentScheme<T> for SHA256Commitment<'a, T> {
64    /// Creates the commitment used during the commit phase.
65    fn commit(&self) -> Result<Vec<u8>> {
66        self.forge_commitment(self.s, self.r)
67    }
68
69    /// Creates the expected commitment using the prover's secret and random number.
70    /// Then, compares the expected commitment with the prover's one to verify if the commitment
71    /// holds.
72    fn verify(&self, com: &[u8], s: &T, r: &[u8]) -> Result<bool> {
73        let expected_commitment = self.forge_commitment(s, r)?;
74
75        Ok(expected_commitment == com)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::{HashCommitmentScheme, SHA256Commitment};
82    use hex_literal::hex;
83
84    #[test]
85    fn it_commits_correctly() {
86        let s: [u8; 4] = [52, 50, 52, 50]; // 4242 in string format.
87        let r: [u8; 4] = [50, 52, 50, 52]; // 2424 in string format.
88
89        let party = SHA256Commitment::new(&s, &r);
90        let commit = party.commit();
91
92        assert_eq!(commit.is_ok(), true);
93        assert_eq!(
94            commit.unwrap().as_slice(),
95            hex!("f4417d2878a0e2da0393e604b24a98627fd22506089baa83c165f9ac7b336fe9")
96        )
97    }
98
99    /// Here, one party acts as both the prover and the verifier,
100    /// assuming that the verifier is not malicious.
101    #[test]
102    fn it_verifies_valid_commitment() {
103        let s: [u8; 4] = [52, 50, 52, 50]; // 4242 in string format.
104        let r: [u8; 4] = [50, 52, 50, 52]; // 2424 in string format.
105
106        // Commit phase.
107        let party = SHA256Commitment::new(&s, &r);
108        let commit = party.commit();
109
110        // Verification phase.
111        let verification = party.verify(&commit.unwrap(), &s, &r);
112
113        assert_eq!(verification.is_ok(), true);
114        assert_eq!(verification.unwrap(), true)
115    }
116
117    /// Here, during the verification phase, we assume that the prover has given an invalid r.
118    #[test]
119    fn it_fails_to_verify_due_to_invalid_random() {
120        let s: [u8; 4] = [52, 50, 52, 50]; // 4242 in string format.
121        let r: [u8; 4] = [50, 52, 50, 52]; // 2424 in string format.
122
123        // Commit phase.
124        let party = SHA256Commitment::new(&s, &r);
125        let commit = party.commit();
126
127        // Verification phase.
128        let fake_r: [u8; 4] = [66, 68, 66, 68];
129        let verification = party.verify(&commit.unwrap(), &s, &fake_r);
130
131        assert_eq!(verification.is_ok(), true);
132        assert_eq!(verification.unwrap(), false)
133    }
134
135    /// Here, during the verification phase, we assume that the prover has given an invalid secret.
136    /// This happens when the prover decides to break his initial commitment.
137    #[test]
138    fn it_fails_to_verify_due_to_invalid_secret() {
139        let s: [u8; 4] = [52, 50, 52, 50]; // 4242 in string format.
140        let r: [u8; 4] = [50, 52, 50, 52]; // 2424 in string format.
141
142        // Commit phase.
143        let party = SHA256Commitment::new(&s, &r);
144        let commit = party.commit();
145
146        // Verification phase.
147        let fake_s: [u8; 4] = [66, 68, 66, 68];
148        let verification = party.verify(&commit.unwrap(), &fake_s, &r);
149
150        assert_eq!(verification.is_ok(), true);
151        assert_eq!(verification.unwrap(), false)
152    }
153}