decaf377_fmd/
clue_key.rs

1use std::{cell::RefCell, convert::TryFrom};
2
3use bitvec::{array::BitArray, order};
4use decaf377::{Fq, Fr};
5use rand_core::{CryptoRng, RngCore};
6
7use crate::{hash, hkd, Clue, Error, Precision};
8
9/// Bytes representing a clue key corresponding to some
10/// [`DetectionKey`](crate::DetectionKey).
11///
12/// This type is a refinement type for plain bytes, and is suitable for use in
13/// situations where clue key might or might not actually be used.  This saves
14/// computation; at the point that a clue key will be used to create a [`Clue`],
15/// it can be expanded to an [`ExpandedClueKey`].
16#[derive(Copy, Clone, PartialEq, Eq, Debug)]
17pub struct ClueKey(pub [u8; 32]);
18
19/// An expanded and validated clue key that can be used to create [`Clue`]s
20/// intended for the corresponding [`DetectionKey`](crate::DetectionKey).
21pub struct ExpandedClueKey {
22    root_pub: decaf377::Element,
23    root_pub_enc: decaf377::Encoding,
24    subkeys: RefCell<Vec<decaf377::Element>>,
25}
26
27impl ClueKey {
28    /// Validate and expand this clue key encoding.
29    ///
30    /// # Errors
31    ///
32    /// Fails if the bytes don't encode a valid clue key.
33    pub fn expand(&self) -> Result<ExpandedClueKey, Error> {
34        ExpandedClueKey::new(self)
35    }
36
37    /// Expand this clue key encoding.
38    ///
39    /// This method always results in a valid clue key, though the clue key may not have
40    /// a known detection key.
41    pub fn expand_infallible(&self) -> ExpandedClueKey {
42        let mut counter = 0u32;
43        loop {
44            counter += 1;
45            let ck_fq_incremented = Fq::from_le_bytes_mod_order(&self.0) + Fq::from(counter);
46            let ck = ClueKey(ck_fq_incremented.to_bytes());
47            if let Ok(eck) = ck.expand() {
48                return eck;
49            }
50        }
51    }
52}
53
54impl ExpandedClueKey {
55    pub fn new(clue_key: &ClueKey) -> Result<Self, Error> {
56        let root_pub_enc = decaf377::Encoding(clue_key.0);
57        let root_pub = root_pub_enc
58            .vartime_decompress()
59            .map_err(|_| Error::InvalidAddress)?;
60
61        Ok(ExpandedClueKey {
62            root_pub,
63            root_pub_enc,
64            subkeys: RefCell::new(Vec::new()),
65        })
66    }
67
68    /// Checks that the expanded clue key has at least `precision` subkeys
69    fn ensure_at_least(&self, precision: usize) -> Result<(), Error> {
70        let current_precision = self.subkeys.borrow().len();
71
72        // The cached expansion is large enough to accommodate the specified precision.
73        if precision <= current_precision {
74            return Ok(());
75        }
76
77        let mut expanded_keys = (current_precision..precision)
78            .map(|i| hkd::derive_public(&self.root_pub, &self.root_pub_enc, i as u8))
79            .collect::<Vec<_>>();
80
81        self.subkeys.borrow_mut().append(&mut expanded_keys);
82
83        Ok(())
84    }
85
86    /// Create a [`Clue`] intended for the [`DetectionKey`](crate::DetectionKey)
87    /// corresponding to this clue key, deterministically, using the provided
88    /// random seed.
89    ///
90    /// The clue will be detected by the intended detection key with probability
91    /// 1, and by other detection keys with probability `2^{-precision_bits}`.
92    ///
93    /// # Errors
94    ///
95    /// `precision_bits` must be smaller than [`MAX_PRECISION`].
96    #[allow(non_snake_case)]
97    pub fn create_clue_deterministic(
98        &self,
99        precision: Precision,
100        rseed: [u8; 32],
101    ) -> Result<Clue, Error> {
102        let precision_bits = precision.bits() as usize;
103        // Ensure that at least `precision_bits` subkeys are available.
104        self.ensure_at_least(precision_bits)?;
105
106        let r = {
107            let hash = blake2b_simd::Params::default()
108                .personal(b"decaf377-fmd.rdv")
109                .to_state()
110                .update(&self.root_pub_enc.0)
111                .update(&rseed)
112                .finalize();
113
114            Fr::from_le_bytes_mod_order(hash.as_bytes())
115        };
116        let z = {
117            let hash = blake2b_simd::Params::default()
118                .personal(b"decaf377-fmd.zdv")
119                .to_state()
120                .update(&self.root_pub_enc.0)
121                .update(&rseed)
122                .finalize();
123
124            Fr::from_le_bytes_mod_order(hash.as_bytes())
125        };
126
127        let P = r * decaf377::Element::GENERATOR;
128        let P_encoding = P.vartime_compress();
129        let Q = z * decaf377::Element::GENERATOR;
130        let Q_encoding = Q.vartime_compress();
131
132        let mut ctxts = BitArray::<[u8; 3], order::Lsb0>::ZERO;
133        let Xs = self.subkeys.borrow();
134
135        for i in 0..precision_bits {
136            let rXi = (r * Xs[i]).vartime_compress();
137            let key_i = hash::to_bit(&P_encoding.0, &rXi.0, &Q_encoding.0);
138            let ctxt_i = key_i ^ 1u8;
139            ctxts.set(i, ctxt_i != 0);
140        }
141
142        let m = hash::to_scalar(&P_encoding.0, precision_bits as u8, ctxts.as_raw_slice());
143        let y = (z - m) * r.inverse().expect("random element is nonzero");
144
145        let mut buf = [0u8; 68];
146        buf[0..32].copy_from_slice(&P_encoding.0[..]);
147        buf[32..64].copy_from_slice(&y.to_bytes()[..]);
148        buf[64] = precision_bits as u8;
149        buf[65..68].copy_from_slice(ctxts.as_raw_slice());
150
151        Ok(Clue(buf))
152    }
153
154    /// Create a [`Clue`] intended for the [`DetectionKey`](crate::DetectionKey)
155    /// corresponding to this clue key.
156    ///
157    /// The clue will be detected by the intended detection key with probability
158    /// 1, and by other detection keys with probability `2^{-precision_bits}`.
159    ///
160    /// # Errors
161    ///
162    /// `precision_bits` must be smaller than [`MAX_PRECISION`].
163    #[allow(non_snake_case)]
164    pub fn create_clue<R: RngCore + CryptoRng>(
165        &self,
166        precision: Precision,
167        mut rng: R,
168    ) -> Result<Clue, Error> {
169        let mut rseed = [0u8; 32];
170        rng.fill_bytes(&mut rseed);
171        self.create_clue_deterministic(precision, rseed)
172    }
173}
174
175impl TryFrom<&[u8]> for ClueKey {
176    type Error = Error;
177
178    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
179        if bytes.len() == 32 {
180            let mut arr = [0u8; 32];
181            arr.copy_from_slice(&bytes[0..32]);
182            Ok(ClueKey(arr))
183        } else {
184            Err(Error::InvalidClueKey)
185        }
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_clue_key_infallible_expand() {
195        let valid_ck = ClueKey(decaf377::Element::GENERATOR.vartime_compress().0);
196        let ck_fq_invalid = Fq::from_le_bytes_mod_order(&valid_ck.0) + Fq::from(1u64);
197        let invalid_ck = ClueKey(ck_fq_invalid.to_bytes());
198        let _eck = invalid_ck.expand_infallible();
199    }
200}