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}