1use crate::{
38 PublicKey, Signature, Signer, Verifier,
39 transcript::{Summary, Transcript, Version},
40};
41use commonware_codec::{Encode, FixedSize, Read, ReadExt, Write};
42use core::ops::Range;
43use rand_core::CryptoRng;
44
45mod error;
46pub use error::Error;
47
48mod key_exchange;
49use key_exchange::{EphemeralPublicKey, SecretKey};
50
51mod cipher;
52pub use cipher::{RecvCipher, SendCipher, TAG_SIZE};
53
54#[cfg(all(test, feature = "arbitrary"))]
55mod conformance;
56
57const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_HANDSHAKE";
58const LABEL_CIPHER_L2D: &[u8] = b"cipher_l2d";
59const LABEL_CIPHER_D2L: &[u8] = b"cipher_d2l";
60const LABEL_CONFIRMATION_L2D: &[u8] = b"confirmation_l2d";
61const LABEL_CONFIRMATION_D2L: &[u8] = b"confirmation_d2l";
62
63const TRANSCRIPT_VERSION: Version = Version::V0;
66
67#[cfg_attr(test, derive(Debug, PartialEq))]
70pub struct Syn<S: Signature> {
71 time_ms: u64,
72 epk: EphemeralPublicKey,
73 sig: S,
74}
75
76impl<S: Signature> FixedSize for Syn<S> {
77 const SIZE: usize = u64::SIZE + EphemeralPublicKey::SIZE + S::SIZE;
78}
79
80impl<S: Signature + Write> Write for Syn<S> {
81 fn write(&self, buf: &mut impl bytes::BufMut) {
82 self.time_ms.write(buf);
83 self.epk.write(buf);
84 self.sig.write(buf);
85 }
86}
87
88impl<S: Signature + Read> Read for Syn<S> {
89 type Cfg = S::Cfg;
90
91 fn read_cfg(
92 buf: &mut impl bytes::Buf,
93 cfg: &Self::Cfg,
94 ) -> Result<Self, commonware_codec::Error> {
95 Ok(Self {
96 time_ms: ReadExt::read(buf)?,
97 epk: ReadExt::read(buf)?,
98 sig: Read::read_cfg(buf, cfg)?,
99 })
100 }
101}
102
103#[cfg(feature = "arbitrary")]
104impl<S: Signature> arbitrary::Arbitrary<'_> for Syn<S>
105where
106 S: for<'a> arbitrary::Arbitrary<'a>,
107{
108 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
109 Ok(Self {
110 time_ms: u.arbitrary()?,
111 epk: u.arbitrary()?,
112 sig: u.arbitrary()?,
113 })
114 }
115}
116
117#[cfg_attr(test, derive(Debug, PartialEq))]
120pub struct SynAck<S: Signature> {
121 time_ms: u64,
122 epk: EphemeralPublicKey,
123 sig: S,
124 confirmation: Summary,
125}
126
127impl<S: Signature> FixedSize for SynAck<S> {
128 const SIZE: usize = u64::SIZE + EphemeralPublicKey::SIZE + S::SIZE + Summary::SIZE;
129}
130
131impl<S: Signature + Write> Write for SynAck<S> {
132 fn write(&self, buf: &mut impl bytes::BufMut) {
133 self.time_ms.write(buf);
134 self.epk.write(buf);
135 self.sig.write(buf);
136 self.confirmation.write(buf);
137 }
138}
139
140impl<S: Signature + Read> Read for SynAck<S> {
141 type Cfg = S::Cfg;
142
143 fn read_cfg(
144 buf: &mut impl bytes::Buf,
145 cfg: &Self::Cfg,
146 ) -> Result<Self, commonware_codec::Error> {
147 Ok(Self {
148 time_ms: ReadExt::read(buf)?,
149 epk: ReadExt::read(buf)?,
150 sig: Read::read_cfg(buf, cfg)?,
151 confirmation: ReadExt::read(buf)?,
152 })
153 }
154}
155
156#[cfg(feature = "arbitrary")]
157impl<S: Signature> arbitrary::Arbitrary<'_> for SynAck<S>
158where
159 S: for<'a> arbitrary::Arbitrary<'a>,
160{
161 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
162 Ok(Self {
163 time_ms: u.arbitrary()?,
164 epk: u.arbitrary()?,
165 sig: u.arbitrary()?,
166 confirmation: u.arbitrary()?,
167 })
168 }
169}
170
171#[cfg_attr(test, derive(PartialEq))]
174#[cfg_attr(feature = "arbitrary", derive(Debug, arbitrary::Arbitrary))]
175pub struct Ack {
176 confirmation: Summary,
177}
178
179impl FixedSize for Ack {
180 const SIZE: usize = Summary::SIZE;
181}
182
183impl Write for Ack {
184 fn write(&self, buf: &mut impl bytes::BufMut) {
185 self.confirmation.write(buf);
186 }
187}
188
189impl Read for Ack {
190 type Cfg = ();
191
192 fn read_cfg(
193 buf: &mut impl bytes::Buf,
194 _cfg: &Self::Cfg,
195 ) -> Result<Self, commonware_codec::Error> {
196 Ok(Self {
197 confirmation: ReadExt::read(buf)?,
198 })
199 }
200}
201
202pub struct DialState<P> {
205 esk: SecretKey,
206 peer_identity: P,
207 transcript: Transcript,
208 ok_timestamps: Range<u64>,
209}
210
211pub struct ListenState {
214 confirmation: Summary,
215 send: SendCipher,
216 recv: RecvCipher,
217}
218
219pub struct Context<S, P> {
222 transcript: Transcript,
223 current_time: u64,
224 ok_timestamps: Range<u64>,
225 my_identity: S,
226 peer_identity: P,
227}
228
229impl<S, P> Context<S, P> {
230 pub fn new(
232 namespace: &[u8],
233 current_time_ms: u64,
234 ok_timestamps: Range<u64>,
235 my_identity: S,
236 peer_identity: P,
237 ) -> Self {
238 let transcript = Transcript::new(namespace, TRANSCRIPT_VERSION).fork(NAMESPACE);
239 Self {
240 transcript,
241 current_time: current_time_ms,
242 ok_timestamps,
243 my_identity,
244 peer_identity,
245 }
246 }
247}
248
249pub fn dial_start<S: Signer, P: PublicKey>(
252 rng: impl CryptoRng,
253 ctx: Context<S, P>,
254) -> (DialState<P>, Syn<<S as Signer>::Signature>) {
255 let Context {
256 current_time,
257 ok_timestamps,
258 my_identity,
259 peer_identity,
260 mut transcript,
261 } = ctx;
262 let esk = SecretKey::new(rng);
263 let epk = esk.public();
264 let sig = transcript
265 .commit(current_time.encode())
266 .commit(peer_identity.encode())
267 .commit(epk.encode())
268 .sign(&my_identity);
269 transcript.commit(my_identity.public_key().encode());
270 (
271 DialState {
272 esk,
273 peer_identity,
274 transcript,
275 ok_timestamps,
276 },
277 Syn {
278 time_ms: current_time,
279 epk,
280 sig,
281 },
282 )
283}
284
285pub fn dial_end<P: PublicKey>(
288 state: DialState<P>,
289 msg: SynAck<<P as Verifier>::Signature>,
290) -> Result<(Ack, SendCipher, RecvCipher), Error> {
291 let DialState {
292 esk,
293 peer_identity,
294 mut transcript,
295 ok_timestamps,
296 } = state;
297 if !ok_timestamps.contains(&msg.time_ms) {
298 return Err(Error::InvalidTimestamp(msg.time_ms, ok_timestamps));
299 }
300 if !transcript
301 .commit(msg.time_ms.encode())
302 .commit(msg.epk.encode())
303 .verify(&peer_identity, &msg.sig)
304 {
305 return Err(Error::HandshakeFailed);
306 }
307 let Some(shared) = esk.exchange(&msg.epk) else {
308 return Err(Error::HandshakeFailed);
309 };
310 shared
311 .secret
312 .expose(|secret| transcript.commit(secret.as_ref()));
313 let recv = RecvCipher::new(transcript.noise(LABEL_CIPHER_L2D));
314 let send = SendCipher::new(transcript.noise(LABEL_CIPHER_D2L));
315 let confirmation_l2d = transcript.fork(LABEL_CONFIRMATION_L2D).summarize();
316 let confirmation_d2l = transcript.fork(LABEL_CONFIRMATION_D2L).summarize();
317 if msg.confirmation != confirmation_l2d {
318 return Err(Error::HandshakeFailed);
319 }
320
321 Ok((
322 Ack {
323 confirmation: confirmation_d2l,
324 },
325 send,
326 recv,
327 ))
328}
329
330pub fn listen_start<S: Signer, P: PublicKey>(
333 rng: impl CryptoRng,
334 ctx: Context<S, P>,
335 msg: Syn<<P as Verifier>::Signature>,
336) -> Result<(ListenState, SynAck<<S as Signer>::Signature>), Error> {
337 let Context {
338 current_time,
339 my_identity,
340 peer_identity,
341 ok_timestamps,
342 mut transcript,
343 } = ctx;
344 if !ok_timestamps.contains(&msg.time_ms) {
345 return Err(Error::InvalidTimestamp(msg.time_ms, ok_timestamps));
346 }
347 if !transcript
348 .commit(msg.time_ms.encode())
349 .commit(my_identity.public_key().encode())
350 .commit(msg.epk.encode())
351 .verify(&peer_identity, &msg.sig)
352 {
353 return Err(Error::HandshakeFailed);
354 }
355 let esk = SecretKey::new(rng);
356 let epk = esk.public();
357 let sig = transcript
358 .commit(peer_identity.encode())
359 .commit(current_time.encode())
360 .commit(epk.encode())
361 .sign(&my_identity);
362 let Some(shared) = esk.exchange(&msg.epk) else {
363 return Err(Error::HandshakeFailed);
364 };
365 shared
366 .secret
367 .expose(|secret| transcript.commit(secret.as_ref()));
368 let send = SendCipher::new(transcript.noise(LABEL_CIPHER_L2D));
369 let recv = RecvCipher::new(transcript.noise(LABEL_CIPHER_D2L));
370 let confirmation_l2d = transcript.fork(LABEL_CONFIRMATION_L2D).summarize();
371 let confirmation_d2l = transcript.fork(LABEL_CONFIRMATION_D2L).summarize();
372
373 Ok((
374 ListenState {
375 confirmation: confirmation_d2l,
376 send,
377 recv,
378 },
379 SynAck {
380 time_ms: current_time,
381 epk,
382 sig,
383 confirmation: confirmation_l2d,
384 },
385 ))
386}
387
388pub fn listen_end(state: ListenState, msg: Ack) -> Result<(SendCipher, RecvCipher), Error> {
391 if msg.confirmation != state.confirmation {
392 return Err(Error::HandshakeFailed);
393 }
394 Ok((state.send, state.recv))
395}
396
397#[cfg(test)]
398mod test {
399 use super::*;
400 use crate::{Signer, ed25519::PrivateKey};
401 use commonware_codec::{Codec, DecodeExt};
402 use commonware_math::algebra::Random;
403 use commonware_utils::test_rng;
404
405 fn test_encode_roundtrip<T: Codec<Cfg = ()> + PartialEq>(value: &T) {
406 assert!(value == &<T as DecodeExt<_>>::decode(value.encode()).unwrap());
407 }
408
409 #[test]
410 fn test_can_setup_and_send_messages() -> Result<(), Error> {
411 let mut rng = test_rng();
412 let dialer_crypto = PrivateKey::random(&mut rng);
413 let listener_crypto = PrivateKey::random(&mut rng);
414
415 let (d_state, msg1) = dial_start(
416 &mut rng,
417 Context::new(
418 b"test_namespace",
419 0,
420 0..1,
421 dialer_crypto.clone(),
422 listener_crypto.public_key(),
423 ),
424 );
425 test_encode_roundtrip(&msg1);
426 let (l_state, msg2) = listen_start(
427 &mut rng,
428 Context::new(
429 b"test_namespace",
430 0,
431 0..1,
432 listener_crypto,
433 dialer_crypto.public_key(),
434 ),
435 msg1,
436 )?;
437 test_encode_roundtrip(&msg2);
438 let (msg3, mut d_send, mut d_recv) = dial_end(d_state, msg2)?;
439 test_encode_roundtrip(&msg3);
440 let (mut l_send, mut l_recv) = listen_end(l_state, msg3)?;
441
442 let m1: &'static [u8] = b"message 1";
443
444 let c1 = d_send.send(m1)?;
445 let m1_prime = l_recv.recv(&c1)?;
446 assert_eq!(m1, &m1_prime);
447
448 let m2: &'static [u8] = b"message 2";
449 let c2 = l_send.send(m2)?;
450 let m2_prime = d_recv.recv(&c2)?;
451 assert_eq!(m2, &m2_prime);
452
453 Ok(())
454 }
455
456 #[test]
457 fn test_mismatched_namespace_fails() {
458 let mut rng = test_rng();
459 let dialer_crypto = PrivateKey::random(&mut rng);
460 let listener_crypto = PrivateKey::random(&mut rng);
461
462 let (_, msg1) = dial_start(
463 &mut rng,
464 Context::new(
465 b"namespace_a",
466 0,
467 0..1,
468 dialer_crypto.clone(),
469 listener_crypto.public_key(),
470 ),
471 );
472
473 let result = listen_start(
474 &mut rng,
475 Context::new(
476 b"namespace_b",
477 0,
478 0..1,
479 listener_crypto,
480 dialer_crypto.public_key(),
481 ),
482 msg1,
483 );
484
485 assert!(matches!(result, Err(Error::HandshakeFailed)));
486 }
487
488 #[cfg(feature = "arbitrary")]
489 mod conformance {
490 use super::*;
491 use commonware_codec::conformance::CodecConformance;
492
493 commonware_conformance::conformance_tests! {
494 CodecConformance<Syn<crate::ed25519::Signature>>,
495 CodecConformance<SynAck<crate::ed25519::Signature>>,
496 CodecConformance<Ack>,
497 }
498 }
499}