Skip to main content

roughtime/
request.rs

1//! Building Roughtime request messages.
2
3use alloc::vec::Vec;
4
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7use crate::tags::{TAG_NONC, TAG_PAD};
8
9/// The minimum size (in bytes) of a Roughtime request, per the protocol spec.
10const MIN_REQUEST_LEN: usize = 1024;
11
12/// A client-generated 64-byte nonce.
13///
14/// Zeroized on drop: the nonce is the one value this client generates itself that carries any
15/// unpredictability, so it is scrubbed as defense-in-depth even though the protocol does not
16/// treat it as a secret in the classic sense (see the crate-level security notes).
17#[derive(Zeroize, ZeroizeOnDrop)]
18pub struct Nonce([u8; 64]);
19
20impl core::fmt::Debug for Nonce {
21    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22        f.debug_struct("Nonce").finish_non_exhaustive()
23    }
24}
25
26impl Nonce {
27    /// Wraps caller-supplied nonce bytes.
28    ///
29    /// This is the `no_std`-safe constructor: a bare-metal caller supplies its own entropy
30    /// (e.g. from a hardware RNG) rather than relying on an OS random source.
31    #[must_use]
32    pub const fn new(bytes: [u8; 64]) -> Self {
33        Self(bytes)
34    }
35
36    /// Returns the nonce's raw bytes.
37    #[must_use]
38    pub const fn as_bytes(&self) -> &[u8; 64] {
39        &self.0
40    }
41
42    /// Generates a nonce using the active crypto backend's random source.
43    ///
44    /// # Errors
45    ///
46    /// Returns [`crate::Error::RandomUnavailable`] if the OS random source is unavailable.
47    #[cfg(feature = "std")]
48    pub fn random() -> Result<Self, crate::Error> {
49        let mut bytes = [0u8; 64];
50        crate::crypto::fill_random(&mut bytes).map_err(|_err| crate::Error::RandomUnavailable)?;
51        Ok(Self(bytes))
52    }
53}
54
55/// Builds a Roughtime request message containing `nonce`, padded to the protocol minimum size.
56#[must_use]
57pub fn build_request(nonce: &Nonce) -> Vec<u8> {
58    let mut req = Vec::with_capacity(MIN_REQUEST_LEN);
59    req.extend_from_slice(&2u32.to_le_bytes()); // num_tags
60    req.extend_from_slice(&64u32.to_le_bytes()); // offset of tag 1 (PAD)
61    req.extend_from_slice(&TAG_NONC.to_le_bytes());
62    req.extend_from_slice(&TAG_PAD.to_le_bytes());
63    req.extend_from_slice(nonce.as_bytes());
64    req.resize(MIN_REQUEST_LEN, 0);
65    req
66}
67
68/// Generates a random nonce and builds a request containing it, returning both.
69///
70/// # Errors
71///
72/// Returns [`crate::Error::RandomUnavailable`] if the OS random source is unavailable.
73#[cfg(feature = "std")]
74pub fn build_request_random() -> Result<(Nonce, Vec<u8>), crate::Error> {
75    let nonce = Nonce::random()?;
76    let req = build_request(&nonce);
77    Ok((nonce, req))
78}
79
80#[cfg(test)]
81mod tests {
82    use super::{Nonce, build_request};
83
84    #[test]
85    fn builds_padded_request() {
86        let nonce = Nonce::new([0x42; 64]);
87        let req = build_request(&nonce);
88        assert_eq!(req.len(), 1024);
89        assert_eq!(&req[16..80], nonce.as_bytes());
90    }
91}