1use core::fmt;
7
8use subtle::ConstantTimeEq;
9use zeroize::{Zeroize, ZeroizeOnDrop};
10
11use crate::constants::{
12 CRYPTO_BOX_BEFORENMBYTES, CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES,
13};
14use crate::error::Error;
15use crate::types::{ByteArray, Bytes, MutByteArray, MutBytes, StackByteArray};
16
17type InnerKey = StackByteArray<CRYPTO_BOX_BEFORENMBYTES>;
18
19#[derive(Zeroize, ZeroizeOnDrop, Clone)]
29pub struct PrecalcSecretKey<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize>(InnerKey);
30
31impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> fmt::Debug
32 for PrecalcSecretKey<InnerKey>
33{
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.debug_tuple("PrecalcSecretKey")
36 .field(&"[REDACTED]")
37 .finish()
38 }
39}
40
41impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> PartialEq
42 for PrecalcSecretKey<InnerKey>
43{
44 fn eq(&self, other: &Self) -> bool {
45 self.0.as_slice().ct_eq(other.0.as_slice()).into()
46 }
47}
48
49impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> Eq for PrecalcSecretKey<InnerKey> {}
50
51impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Bytes + Zeroize> Bytes
52 for PrecalcSecretKey<InnerKey>
53{
54 #[inline]
55 fn as_slice(&self) -> &[u8] {
56 self.0.as_slice()
57 }
58
59 #[inline]
60 fn is_empty(&self) -> bool {
61 self.0.is_empty()
62 }
63
64 #[inline]
65 fn len(&self) -> usize {
66 self.0.len()
67 }
68}
69
70impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> ByteArray<CRYPTO_BOX_BEFORENMBYTES>
71 for PrecalcSecretKey<InnerKey>
72{
73 #[inline]
74 fn as_array(&self) -> &[u8; CRYPTO_BOX_BEFORENMBYTES] {
75 self.0.as_array()
76 }
77}
78
79impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize + MutBytes> MutBytes
80 for PrecalcSecretKey<InnerKey>
81{
82 #[inline]
83 fn as_mut_slice(&mut self) -> &mut [u8] {
84 self.0.as_mut_slice()
85 }
86
87 #[inline]
88 fn copy_from_slice(&mut self, other: &[u8]) {
89 self.0.copy_from_slice(other);
90 }
91}
92
93impl<InnerKey: MutByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize>
94 MutByteArray<CRYPTO_BOX_BEFORENMBYTES> for PrecalcSecretKey<InnerKey>
95{
96 #[inline]
97 fn as_mut_array(&mut self) -> &mut [u8; CRYPTO_BOX_BEFORENMBYTES] {
98 self.0.as_mut_array()
99 }
100}
101
102impl PrecalcSecretKey<InnerKey> {
103 #[inline]
113 pub fn precalculate<
114 ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
115 SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
116 >(
117 third_party_public_key: &ThirdPartyPublicKey,
118 secret_key: &SecretKey,
119 ) -> Result<Self, Error> {
120 use crate::classic::crypto_box::crypto_box_beforenm;
121
122 Ok(Self(
123 crypto_box_beforenm(third_party_public_key.as_array(), secret_key.as_array())?.into(),
124 ))
125 }
126}
127
128#[cfg(any(
129 all(feature = "protected", any(unix, windows)),
130 all(doc, not(doctest), feature = "std")
131))]
132#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
133mod protected {
134 use super::*;
136 use crate::protected::*;
137
138 type InnerKey = HeapByteArray<CRYPTO_BOX_PUBLICKEYBYTES>;
139
140 fn beforenm_into_locked<
142 ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
143 SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
144 >(
145 third_party_public_key: &ThirdPartyPublicKey,
146 secret_key: &SecretKey,
147 ) -> Result<Locked<HeapByteArray<CRYPTO_BOX_BEFORENMBYTES>>, Error> {
148 use crate::classic::crypto_box::crypto_box_beforenm;
149
150 let mut precalc = HeapByteArray::<CRYPTO_BOX_BEFORENMBYTES>::new_locked()?;
151 let mut key =
152 crypto_box_beforenm(third_party_public_key.as_array(), secret_key.as_array())?;
153
154 precalc.copy_from_slice(&key);
155 key.zeroize();
156
157 Ok(precalc)
158 }
159
160 impl PrecalcSecretKey<Locked<InnerKey>> {
161 pub fn precalculate_locked<
176 ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
177 SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
178 >(
179 third_party_public_key: &ThirdPartyPublicKey,
180 secret_key: &SecretKey,
181 ) -> Result<Self, Error> {
182 Ok(PrecalcSecretKey(beforenm_into_locked(
183 third_party_public_key,
184 secret_key,
185 )?))
186 }
187 }
188
189 impl PrecalcSecretKey<LockedRO<InnerKey>> {
190 pub fn precalculate_readonly_locked<
207 ThirdPartyPublicKey: ByteArray<CRYPTO_BOX_PUBLICKEYBYTES>,
208 SecretKey: ByteArray<CRYPTO_BOX_SECRETKEYBYTES>,
209 >(
210 third_party_public_key: &ThirdPartyPublicKey,
211 secret_key: &SecretKey,
212 ) -> Result<Self, Error> {
213 Ok(PrecalcSecretKey(
214 beforenm_into_locked(third_party_public_key, secret_key)?.mprotect_readonly()?,
215 ))
216 }
217 }
218}
219
220impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> core::ops::Deref
221 for PrecalcSecretKey<InnerKey>
222{
223 type Target = InnerKey;
224
225 fn deref(&self) -> &Self::Target {
226 &self.0
227 }
228}
229
230impl<InnerKey: ByteArray<CRYPTO_BOX_BEFORENMBYTES> + Zeroize> core::ops::DerefMut
231 for PrecalcSecretKey<InnerKey>
232{
233 fn deref_mut(&mut self) -> &mut Self::Target {
234 &mut self.0
235 }
236}
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn precalculated_key_debug_redacts_contents_and_equality_is_value_based() {
243 let key = PrecalcSecretKey(StackByteArray::from([0xabu8; CRYPTO_BOX_BEFORENMBYTES]));
244 let same = key.clone();
245 let different = PrecalcSecretKey(StackByteArray::from([0xcdu8; CRYPTO_BOX_BEFORENMBYTES]));
246
247 assert_eq!(format!("{key:?}"), "PrecalcSecretKey(\"[REDACTED]\")");
248 assert_eq!(key, same);
249 assert_ne!(key, different);
250 }
251 use crate::constants::{CRYPTO_BOX_PUBLICKEYBYTES, CRYPTO_BOX_SECRETKEYBYTES};
252
253 const ALICE_SK: &str = "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a";
256 const ALICE_PK: &str = "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a";
257 const BOB_SK: &str = "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb";
258 const BOB_PK: &str = "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f";
259 const SHARED_KEY: &str = "1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389";
260
261 fn array<const N: usize>(hex: &str) -> StackByteArray<N> {
262 StackByteArray::try_from(hex::decode(hex).expect("hex").as_slice()).expect("length")
263 }
264
265 fn low_order_public_keys() -> [StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES>; 2] {
266 let mut identity = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
267 identity[0] = 1;
268 [StackByteArray::default(), identity]
269 }
270
271 #[test]
272 fn precalculate_matches_nacl_shared_key_from_both_sides() {
273 let alice_sk: StackByteArray<CRYPTO_BOX_SECRETKEYBYTES> = array(ALICE_SK);
274 let alice_pk: StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES> = array(ALICE_PK);
275 let bob_sk: StackByteArray<CRYPTO_BOX_SECRETKEYBYTES> = array(BOB_SK);
276 let bob_pk: StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES> = array(BOB_PK);
277 let expected: StackByteArray<CRYPTO_BOX_BEFORENMBYTES> = array(SHARED_KEY);
278
279 let alice_side = PrecalcSecretKey::precalculate(&bob_pk, &alice_sk).expect("precalc");
280 let bob_side = PrecalcSecretKey::precalculate(&alice_pk, &bob_sk).expect("precalc");
281 assert_eq!(alice_side.as_array(), expected.as_array());
282 assert_eq!(alice_side.as_slice(), expected.as_slice());
283 assert_eq!(alice_side, bob_side);
284 assert_eq!(alice_side.len(), CRYPTO_BOX_BEFORENMBYTES);
285
286 let stranger = PrecalcSecretKey::precalculate(&bob_pk, &bob_sk).expect("precalc");
288 assert_ne!(stranger, alice_side);
289
290 for low_order in low_order_public_keys() {
291 assert!(PrecalcSecretKey::precalculate(&low_order, &alice_sk).is_err());
292 }
293 }
294
295 #[cfg(all(feature = "protected", any(unix, windows)))]
296 #[test]
297 fn locked_precalculation_matches_stack_precalculation() {
298 let alice_sk: StackByteArray<CRYPTO_BOX_SECRETKEYBYTES> = array(ALICE_SK);
299 let bob_pk: StackByteArray<CRYPTO_BOX_PUBLICKEYBYTES> = array(BOB_PK);
300 let expected = hex::decode(SHARED_KEY).expect("hex");
301
302 let mut locked =
303 PrecalcSecretKey::precalculate_locked(&bob_pk, &alice_sk).expect("precalc locked");
304 assert_eq!(locked.as_slice(), expected.as_slice());
305 assert_eq!(locked.as_array(), &expected[..]);
306
307 let readonly = PrecalcSecretKey::precalculate_readonly_locked(&bob_pk, &alice_sk)
308 .expect("precalc readonly locked");
309 assert_eq!(readonly.as_slice(), expected.as_slice());
310
311 locked.as_mut_slice()[0] ^= 0xff;
313 locked.as_mut_array()[1] ^= 0xff;
314 assert_eq!(locked.as_slice()[0], expected[0] ^ 0xff);
315 assert_eq!(locked.as_slice()[1], expected[1] ^ 0xff);
316 assert_eq!(&locked.as_slice()[2..], &expected[2..]);
317
318 for low_order in low_order_public_keys() {
319 assert!(PrecalcSecretKey::precalculate_locked(&low_order, &alice_sk).is_err());
320 assert!(PrecalcSecretKey::precalculate_readonly_locked(&low_order, &alice_sk).is_err());
321 }
322 }
323
324 #[cfg(dryoc_native_tests)]
325 #[test]
326 fn precalculate_matches_libsodium_beforenm() {
327 use crate::utils::test_util::XorShift64;
328
329 let mut rng = XorShift64::new(0x7072_6563_616c_6321);
330 for _ in 0..16 {
331 let secret_key = StackByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::from(rng.next_bytes32());
332 let other_secret_key =
333 StackByteArray::<CRYPTO_BOX_SECRETKEYBYTES>::from(rng.next_bytes32());
334 let mut public_key = StackByteArray::<CRYPTO_BOX_PUBLICKEYBYTES>::default();
335 crate::classic::crypto_core::crypto_scalarmult_base(
336 public_key.as_mut_array(),
337 other_secret_key.as_array(),
338 );
339
340 let precalc =
341 PrecalcSecretKey::precalculate(&public_key, &secret_key).expect("precalc");
342
343 let sodium_key = crate::native_test_util::box_beforenm(&public_key, &secret_key)
344 .expect("libsodium beforenm");
345 assert_eq!(precalc.as_array(), &sodium_key);
346 }
347 }
348}