tcn/
report.rs

1use std::convert::TryFrom;
2
3pub use super::{Error, ReportAuthorizationKey, TemporaryContactKey, TemporaryContactNumber};
4
5/// Describes the intended type of the contents of a memo field.
6#[derive(Copy, Clone, Debug, Eq, PartialEq)]
7#[repr(u8)]
8pub enum MemoType {
9    /// The CoEpi symptom self-report format, version 1 (TBD)
10    CoEpiV1 = 0,
11    /// The CovidWatch test data format, version 1 (TBD)
12    CovidWatchV1 = 1,
13    /// Reserved for future use.
14    Reserved = 0xff,
15}
16
17/// A report of potential exposure.
18#[derive(Clone, Debug)]
19pub struct Report {
20    pub(crate) rvk: ed25519_zebra::PublicKeyBytes,
21    pub(crate) tck_bytes: [u8; 32],
22    // Invariant: j_1 > 0.
23    pub(crate) j_1: u16,
24    pub(crate) j_2: u16,
25    pub(crate) memo_type: MemoType,
26    pub(crate) memo_data: Vec<u8>,
27}
28
29impl Report {
30    /// Get the type of the memo field.
31    pub fn memo_type(&self) -> MemoType {
32        self.memo_type
33    }
34
35    /// Get the memo data.
36    pub fn memo_data(&self) -> &[u8] {
37        &self.memo_data
38    }
39
40    /// Return an iterator over all temporary contact numbers included in the report.
41    pub fn temporary_contact_numbers(&self) -> impl Iterator<Item = TemporaryContactNumber> {
42        let mut tck = TemporaryContactKey {
43            // Does not underflow as j_1 > 0.
44            index: self.j_1 - 1,
45            rvk: self.rvk,
46            tck_bytes: self.tck_bytes,
47        };
48        // Ratchet to obtain tck_{j_1}.
49        tck = tck.ratchet().expect("j_1 - 1 < j_1 <= u16::MAX");
50
51        (self.j_1..self.j_2).map(move |_| {
52            let tcn = tck.temporary_contact_number();
53            tck = tck
54                .ratchet()
55                .expect("we do not ratchet past j_2 <= u16::MAX");
56            tcn
57        })
58    }
59}
60
61impl ReportAuthorizationKey {
62    /// Create a report of potential exposure.
63    ///
64    /// # Inputs
65    ///
66    /// - `memo_type`, `memo_data`: the type and data for the report's memo field.
67    /// - `j_1 > 0`: the ratchet index of the first temporary contact number in the report.
68    /// - `j_2`: the ratchet index of the last temporary contact number other users should check.
69    ///
70    /// # Notes
71    ///
72    /// Creating a report reveals *all* temporary contact numbers subsequent to
73    /// `j_1`, not just up to `j_2`, which is included for convenience.
74    ///
75    /// The `memo_data` must be less than 256 bytes long.
76    ///
77    /// Reports are unlinkable from each other **only up to the memo field**. In
78    /// other words, adding the same high-entropy data to the memo fields of
79    /// multiple reports will cause them to be linkable.
80    pub fn create_report(
81        &self,
82        memo_type: MemoType,
83        memo_data: Vec<u8>,
84        j_1: u16,
85        j_2: u16,
86    ) -> Result<SignedReport, Error> {
87        // Ensure that j_1 is at least 1.
88        let j_1 = if j_1 == 0 { 1 } else { j_1 };
89
90        // Recompute tck_{j_1-1}. This requires recomputing j_1-1 hashes, but
91        // creating reports is done infrequently and it means we don't force the
92        // caller to have saved all intermediate hashes.
93        let mut tck = self.tck_0();
94        for _ in 0..(j_1 - 1) {
95            tck = tck.ratchet().expect("j_1 - 1 < u16::MAX");
96        }
97
98        let report = Report {
99            rvk: ed25519_zebra::PublicKeyBytes::from(&self.rak),
100            tck_bytes: tck.tck_bytes,
101            // Invariant: we have ensured j_1 > 0 above.
102            j_1,
103            j_2,
104            memo_type,
105            memo_data,
106        };
107
108        use std::io::Cursor;
109        let mut report_bytes = Vec::with_capacity(report.size_hint());
110        report.write(Cursor::new(&mut report_bytes))?;
111        let sig = self.rak.sign(&report_bytes);
112
113        Ok(SignedReport { report, sig })
114    }
115}
116
117/// A signed exposure report, whose source integrity can be verified to produce a `Report`.
118#[derive(Clone, Debug)]
119pub struct SignedReport {
120    pub(crate) report: Report,
121    pub(crate) sig: ed25519_zebra::Signature,
122}
123
124impl SignedReport {
125    /// Verify the source integrity of this report, producing `Ok(Report)` if successful.
126    pub fn verify(self) -> Result<Report, Error> {
127        use std::io::Cursor;
128        let mut report_bytes = Vec::with_capacity(self.report.size_hint());
129        self.report.write(Cursor::new(&mut report_bytes))?;
130
131        match ed25519_zebra::PublicKey::try_from(self.report.rvk)
132            .and_then(|pk| pk.verify(&self.sig, &report_bytes))
133        {
134            Ok(_) => Ok(self.report),
135            Err(_) => Err(Error::ReportVerificationFailed),
136        }
137    }
138}