mpc_engine/primitives/
commitment.rs

1//! This module implements a commitment scheme in the random oracle model.
2//!
3//! Assume `H` is a hash function modeled as a random oracle. To commit to value
4//! `v`, sample a random string `r` from `{0,1}^\rho` and compute the
5//! commitment as `c <- H(v || r)`, the opening being `r`. To verify the
6//! commitment given `(v', c, r')` compute `c' <- H(v' || r')` and check that `c
7//! == c'`.
8
9use hacspec_lib::Randomness;
10use hmac::hkdf_extract;
11
12use crate::{Error, STATISTICAL_SECURITY};
13
14/// The length of a commitment value, derived from the output of a HKDF
15/// extraction using SHA-256.
16pub const COMMITMENT_LENGTH: usize = 32;
17
18/// A Commitment to some value.
19#[derive(Debug, Clone)]
20pub struct Commitment {
21    commitment: [u8; COMMITMENT_LENGTH],
22    domain_separator: Vec<u8>,
23}
24
25/// The opening information for a commitment.
26#[derive(Debug, Clone)]
27pub struct Opening {
28    value: Vec<u8>,
29    opening: [u8; STATISTICAL_SECURITY],
30}
31
32impl Opening {
33    /// Serialize an opening to a byte vector.
34    ///
35    /// The serialization format is
36    /// `opening || value`
37    pub fn as_bytes(&self) -> Vec<u8> {
38        let mut result = Vec::new();
39        result.extend_from_slice(&self.opening);
40        result.extend_from_slice(&self.value);
41
42        result
43    }
44
45    /// Deserialize an opening from a serialization created using [Opening::as_bytes].
46    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
47        if bytes.len() < STATISTICAL_SECURITY + 1 {
48            return Err(Error::InvalidSerialization);
49        }
50        let opening: [u8; STATISTICAL_SECURITY] = Vec::from(&bytes[0..STATISTICAL_SECURITY])
51            .try_into()
52            .map_err(|_| Error::InvalidSerialization)?;
53        let value = Vec::from(&bytes[STATISTICAL_SECURITY..]);
54        Ok(Self { value, opening })
55    }
56}
57
58impl Commitment {
59    /// Commit to a value.
60    ///
61    /// Given input value `value`, samples a random bitstring `r` of length
62    /// `STATISTICAL_SECURITY` and returns a domain separated commitment
63    /// `H(value||r)` as well as the corresponding opening.
64    pub fn new(value: &[u8], domain_separator: &[u8], entropy: &mut Randomness) -> (Self, Opening) {
65        let mut opening = [0u8; STATISTICAL_SECURITY];
66        opening.copy_from_slice(
67            entropy
68                .bytes(STATISTICAL_SECURITY)
69                .expect("sufficient randomness should have been provided externally"),
70        );
71
72        let mut ikm = Vec::from(value);
73        ikm.extend_from_slice(&opening);
74
75        let commitment = hkdf_extract(domain_separator, &ikm)
76            .try_into()
77            .expect("should use HKDF with SHA-256 for correct output length");
78        (
79            Commitment {
80                commitment,
81                domain_separator: domain_separator.to_vec(),
82            },
83            Opening {
84                value: value.to_vec(),
85                opening,
86            },
87        )
88    }
89
90    /// Open the commitment, returning the committed value, if successful.
91    pub fn open(&self, opening: &Opening) -> Result<Vec<u8>, Error> {
92        let mut ikm = vec![0u8; opening.value.len()];
93        ikm.copy_from_slice(&opening.value);
94        ikm.extend_from_slice(&opening.opening);
95        let reconstructed_commitment: [u8; COMMITMENT_LENGTH] =
96            hkdf_extract(&self.domain_separator, &ikm)
97                .try_into()
98                .expect("should use HKDF with SHA-256 for correct output length");
99        if self.commitment != reconstructed_commitment {
100            return Err(Error::BadCommitment(
101                self.commitment,
102                reconstructed_commitment,
103            ));
104        }
105        Ok(opening.value.to_vec())
106    }
107
108    /// Serialize a commitment to a byte vector.
109    ///
110    /// The serialization format is `commitment ||
111    /// len(dst) || dst_bytes`, where the length is represented as big-endian
112    /// byte arrays.
113    pub fn as_bytes(&self) -> Vec<u8> {
114        let dst_len = self.domain_separator.len();
115        let mut result = Vec::new();
116        result.extend_from_slice(&self.commitment);
117        result.extend_from_slice(&dst_len.to_be_bytes());
118        result.extend_from_slice(&self.domain_separator);
119        result
120    }
121
122    /// Deserialize a commitment from a serialization created using [Commitment::as_bytes].
123    pub fn from_bytes(bytes: &[u8]) -> Result<(Self, Vec<u8>), Error> {
124        if bytes.len() < COMMITMENT_LENGTH + std::mem::size_of::<usize>() {
125            return Err(Error::InvalidSerialization);
126        }
127        let (commitment_bytes, rest) = bytes.split_at(COMMITMENT_LENGTH);
128        let commitment = commitment_bytes.try_into().unwrap();
129
130        let (dst_len_bytes, rest) = rest.split_at(std::mem::size_of::<usize>());
131        let dst_len = usize::from_be_bytes(dst_len_bytes.try_into().unwrap());
132        let (dst_bytes, rest) = rest.split_at(dst_len);
133        let domain_separator = Vec::from(dst_bytes);
134
135        Ok((
136            Self {
137                commitment,
138                domain_separator,
139            },
140            rest.to_vec(),
141        ))
142    }
143}
144
145#[test]
146fn simple() {
147    use rand::{thread_rng, RngCore};
148
149    let mut rng = thread_rng();
150    let mut entropy = [0u8; 32];
151    rng.fill_bytes(&mut entropy);
152    let mut entropy = Randomness::new(entropy.to_vec());
153    let value = b"Hello";
154    let another_value = b"Heya";
155    let dst = b"Test";
156    let (commitment, opening) = Commitment::new(value, dst, &mut entropy);
157    let (_another_commitment, another_opening) = Commitment::new(another_value, dst, &mut entropy);
158    debug_assert!(commitment.open(&opening).is_ok());
159    debug_assert!(commitment.open(&another_opening).is_err());
160}