decaf377_fmd/detection.rs
1use crate::{hash, hkd, Clue, ClueKey, Error, MAX_PRECISION};
2use bitvec::{order, slice::BitSlice};
3use decaf377::Fr;
4use rand_core::{CryptoRng, RngCore};
5use std::convert::{TryFrom, TryInto};
6
7/// Used to examine [`Clue`]s and determine whether they were possibly sent to
8/// the detection key's [`ClueKey`].
9pub struct DetectionKey {
10 /// The detection key.
11 dtk: Fr,
12 /// Cached copies of the child detection keys; these can be fully derived from `dtk`.
13 xs: [Fr; MAX_PRECISION as usize],
14}
15
16impl DetectionKey {
17 /// Create a random detection key using the supplied `rng`.
18 pub fn new<R: RngCore + CryptoRng>(mut rng: R) -> Self {
19 Self::from_field(decaf377::Fr::rand(&mut rng))
20 }
21
22 /// Create a detection key using the supplied field element directly.
23 ///
24 /// # Warning
25 ///
26 /// This function exists to support custom key derivation mechanisms; it's
27 /// the caller's responsibility to construct the detection key `dtk`
28 /// correctly.
29 pub fn from_field(dtk: Fr) -> Self {
30 let root_pub = dtk * decaf377::Element::GENERATOR;
31 let root_pub_enc = root_pub.vartime_compress();
32
33 let xs: [_; MAX_PRECISION as usize] = (0..MAX_PRECISION as usize)
34 .map(|i| {
35 hkd::derive_private(
36 &dtk,
37 &root_pub_enc,
38 u8::try_from(i).expect("i < MAX_PRECISION < 256"),
39 )
40 })
41 .collect::<Vec<_>>()
42 // this conversion into an array will always succeed because we started with an iterator
43 // of length `MAX_PRECISION` and we're converting to an array of the same length
44 .try_into()
45 .expect("iterator of length `MAX_PRECISION`");
46
47 Self { dtk, xs }
48 }
49
50 /// Serialize this detection key to bytes.
51 pub fn to_bytes(&self) -> [u8; 32] {
52 self.dtk.to_bytes()
53 }
54
55 /// Deserialize a detection key from bytes.
56 pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, Error> {
57 let dtk = Fr::from_bytes_checked(&bytes).map_err(|_| Error::InvalidDetectionKey)?;
58 Ok(Self::from_field(dtk))
59 }
60
61 /// Obtain the clue key corresponding to this detection key.
62 pub fn clue_key(&self) -> ClueKey {
63 ClueKey(
64 (self.dtk * decaf377::Element::GENERATOR)
65 .vartime_compress()
66 .0,
67 )
68 }
69
70 /// Use this detection key to examine the given `clue`, returning `true` if the
71 /// clue was possibly sent to this detection key's clue key.
72 ///
73 /// This test has false positives, but no false negatives.
74 ///
75 /// This function executes in constant time with respect to the detection
76 /// key material, but short-circuits to return early on a false detection.
77 #[allow(non_snake_case)]
78 pub fn examine(&self, clue: &Clue) -> bool {
79 let P_encoding = decaf377::Encoding::try_from(&clue.0[0..32]).expect("slice is right len");
80
81 let P = if let Ok(P) = P_encoding.vartime_decompress() {
82 P
83 } else {
84 // Invalid P encoding => not a match
85 return false;
86 };
87
88 let y = if let Ok(y) =
89 Fr::from_bytes_checked(&clue.0[32..64].try_into().expect("expected 32 bytes"))
90 {
91 y
92 } else {
93 // Invalid y encoding => not a match
94 return false;
95 };
96
97 // Reject P = 0 or y = 0, as these never occur in well-formed clues; as
98 // noted in the OpenPrivacy implementation, these could allow clues to
99 // match any detection key.
100 // https://docs.rs/fuzzytags/0.6.0/src/fuzzytags/lib.rs.html#348-351
101 if P.is_identity() || y == Fr::ZERO {
102 return false;
103 }
104
105 let precision_bits = match clue.precision() {
106 Err(_) => return false,
107 Ok(x) => x.bits() as u8,
108 };
109 let ciphertexts = BitSlice::<u8, order::Lsb0>::from_slice(&clue.0[65..68]);
110
111 let m = hash::to_scalar(&P_encoding.0, precision_bits, &clue.0[65..68]);
112 let Q_bytes = ((y * P) + (m * decaf377::Element::GENERATOR)).vartime_compress();
113
114 for i in 0..(precision_bits as usize) {
115 let Px_i = (P * self.xs[i]).vartime_compress();
116 let key_i = hash::to_bit(&P_encoding.0, &Px_i.0, &Q_bytes.0);
117 let msg_i = (ciphertexts[i] as u8) ^ key_i;
118 // Short-circuit if we get a zero; this branch is dependent on the
119 // ephemeral key bit `key_i`, not the long-term key `xs[i]`, so we
120 // don't risk leaking any long-term secrets through timing channels.
121 //
122 // On the other hand, this gives a massive speedup, since we have a
123 // 1/2 chance of rejecting after 1 iteration, 1/4 chance of
124 // rejecting after 2 iterations, ..., so (in expectation) we do <= 2
125 // iterations instead of n iterations.
126 if msg_i == 0 {
127 return false;
128 }
129 }
130
131 // Otherwise, all message bits were 1 and we return true.
132 true
133 }
134}