Skip to main content

atecc608b/command/
nonce.rs

1// Copyright (c) 2026 Tuloup Simon
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! `Nonce` command.
17//!
18//! Loads bytes into the chip's `TempKey` or `MsgDigBuf` register. The ATECC608B
19//! supports several modes, of which two are useful from the host side:
20//!
21//! - **Random** (mode 0): the host sends 20 bytes of input (`NumIn`), the
22//!   chip mixes them with its TRNG output, hashes the result, and stores
23//!   the hash in `TempKey`. The chip returns the 32-byte TRNG value
24//!   (`NumOut`) that was mixed in, so the host can reconstruct `TempKey`
25//!   if needed (this is what `CheckMac` and `GenDig` depend on).
26//!
27//! - **Passthrough** (mode 3): the host sends 32 bytes that are stored
28//!   verbatim in `TempKey` or in `MsgDigBuf`. No mixing, no RNG. The chip
29//!   responds with a single status byte. This is the mode used to load a
30//!   message digest into `TempKey` before calling `Sign`.
31//!
32//! Mode 1 ("no RNG re-generation") and the ATECC608-specific Mode 2 variants
33//! are intentionally omitted: nothing in the project's workflow needs them.
34//! They can be added later as additional methods rather than expanded as
35//! parameters on the existing ones, to keep each entry point unambiguous.
36//!
37//! Reference: `CryptoAuthLib` `lib/calib/calib_nonce.c`, constants
38//! `NONCE_MODE_SEED_UPDATE` (0x00), `NONCE_MODE_PASSTHROUGH` (0x03),
39//! `NONCE_MODE_TARGET_TEMPKEY` (0x00), `NONCE_MODE_TARGET_MSGDIGBUF` (0x40),
40//! `NONCE_NUMIN_SIZE` (20), `NONCE_NUMIN_SIZE_PASSTHROUGH` (32).
41
42use crate::driver::AteccChannel;
43use crate::error::AteccError;
44use crate::hal::AteccHal;
45use crate::opcodes::{EXEC_TIME_NONCE_MS, OP_NONCE};
46
47/// Size of the `NumIn` block in a random Nonce command.
48pub const NONCE_NUMIN_SIZE: usize = 20;
49
50/// Size of the `NumOut` block returned by a random Nonce command.
51pub const NONCE_NUMOUT_SIZE: usize = 32;
52
53/// Size of the data field in a passthrough Nonce command.
54pub const NONCE_PASSTHROUGH_SIZE: usize = 32;
55
56/// `param1` low bits for mode 0 (random nonce, seed update).
57const NONCE_MODE_RANDOM: u8 = 0x00;
58
59/// `param1` low bits for mode 3 (passthrough).
60const NONCE_MODE_PASSTHROUGH: u8 = 0x03;
61
62/// `param1` bit 6 cleared selects `TempKey` as the passthrough target.
63const NONCE_TARGET_TEMPKEY: u8 = 0x00;
64
65/// `param1` bit 6 set selects `MsgDigBuf` as the passthrough target.
66const NONCE_TARGET_MSGDIGBUF: u8 = 0x40;
67
68/// Destination register for a passthrough Nonce.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70#[cfg_attr(feature = "defmt", derive(defmt::Format))]
71pub enum NonceTarget
72{
73    /// 32-byte `TempKey` register. Used to load a digest before `Sign`.
74    TempKey,
75    /// 32-byte `MsgDigBuf` register.
76    MsgDigBuf,
77}
78
79impl NonceTarget
80{
81    /// Encode the target as the relevant bit in `param1`.
82    const fn as_param1_bits(self) -> u8
83    {
84        match self
85        {
86            NonceTarget::TempKey   => NONCE_TARGET_TEMPKEY,
87            NonceTarget::MsgDigBuf => NONCE_TARGET_MSGDIGBUF,
88        }
89    }
90}
91
92impl<H> AteccChannel<'_, H>
93where
94    H: AteccHal,
95{
96    /// Issue a random Nonce (mode 0).
97    ///
98    /// `num_in` is 20 bytes of host-provided entropy that the chip mixes
99    /// with its TRNG before hashing the combined block into `TempKey`.
100    ///
101    /// On success returns the 32-byte `NumOut` (the TRNG portion mixed in).
102    /// The host can compute the resulting `TempKey` value as
103    /// `SHA256(NumOut || NumIn || OpCode || Mode || LSB || 0..0)` per the
104    /// `CryptoAuthLib` reference, but the driver does not do that derivation:
105    /// it is a service-layer concern.
106    ///
107    /// # Errors
108    /// See [`AteccChannel::execute_command`].
109    pub async fn nonce_random
110    (
111        &mut self,
112        num_in: &[u8; NONCE_NUMIN_SIZE],
113    ) -> Result<[u8; NONCE_NUMOUT_SIZE], AteccError<H::Error>>
114    {
115        // Response: count(1) + 32 NumOut + crc(2) = 35 bytes.
116        let mut response_buf = [0u8; 1 + NONCE_NUMOUT_SIZE + 2];
117        let payload = self
118            .execute_command
119            (
120                OP_NONCE,
121                NONCE_MODE_RANDOM,
122                0x0000,
123                num_in,
124                EXEC_TIME_NONCE_MS,
125                &mut response_buf,
126            )
127            .await?;
128
129        let bytes: &[u8; NONCE_NUMOUT_SIZE] = payload
130            .try_into()
131            .map_err(|_| AteccError::MalformedResponse)?;
132        Ok(*bytes)
133    }
134
135    /// Issue a passthrough Nonce (mode 3).
136    ///
137    /// `value` is stored verbatim in the target register (no hashing, no RNG
138    /// mixing). Mainly used to load a message digest into `TempKey` ahead
139    /// of a `Sign` call.
140    ///
141    /// # Errors
142    /// See [`AteccChannel::execute_command_status`].
143    pub async fn nonce_passthrough
144    (
145        &mut self,
146        target: NonceTarget,
147        value: &[u8; NONCE_PASSTHROUGH_SIZE],
148    ) -> Result<(), AteccError<H::Error>>
149    {
150        let param1 = NONCE_MODE_PASSTHROUGH | target.as_param1_bits();
151        self.execute_command_status
152        (
153            OP_NONCE,
154            param1,
155            0x0000,
156            value,
157            EXEC_TIME_NONCE_MS,
158        )
159        .await
160    }
161}
162
163#[cfg(test)]
164mod tests
165{
166    use super::*;
167
168    #[test]
169    fn nonce_target_tempkey_clears_bit_6()
170    {
171        assert_eq!(NonceTarget::TempKey.as_param1_bits(), 0x00);
172    }
173
174    #[test]
175    fn nonce_target_msgdigbuf_sets_bit_6()
176    {
177        assert_eq!(NonceTarget::MsgDigBuf.as_param1_bits(), 0x40);
178    }
179
180    #[test]
181    fn passthrough_mode_bits_combine_with_target_bits()
182    {
183        let p1_tempkey = NONCE_MODE_PASSTHROUGH | NonceTarget::TempKey.as_param1_bits();
184        let p1_msgdig  = NONCE_MODE_PASSTHROUGH | NonceTarget::MsgDigBuf.as_param1_bits();
185        assert_eq!(p1_tempkey, 0x03);
186        assert_eq!(p1_msgdig,  0x43);
187    }
188
189    #[test]
190    fn sizes_match_protocol()
191    {
192        assert_eq!(NONCE_NUMIN_SIZE, 20);
193        assert_eq!(NONCE_NUMOUT_SIZE, 32);
194        assert_eq!(NONCE_PASSTHROUGH_SIZE, 32);
195    }
196}