1use super::{
2 Error,
3 exchange::{EphemeralPublicKey, SecretKey},
4};
5use crate::{
6 PublicKey, Signature, Signer, Verifier,
7 transcript::{self, Summary, Transcript},
8};
9use commonware_codec::{Buf, Encode, FixedSize, Mode, Read, ReadExt, Write, mode, modes};
10use core::ops::Range;
11use rand_core::CryptoRng;
12
13const LABEL_CONFIRMATION_L2D: &[u8] = b"confirmation_l2d";
14const LABEL_CONFIRMATION_D2L: &[u8] = b"confirmation_d2l";
15
16#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
20#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
21pub enum Version {
22 V0,
27 V1,
29}
30
31impl From<Version> for Mode {
32 fn from(version: Version) -> Self {
33 match version {
34 Version::V0 => mode!(0),
35 Version::V1 => mode!(1),
36 }
37 }
38}
39
40impl Version {
41 const fn namespace(self) -> &'static [u8] {
46 match self {
47 Self::V0 => b"_COMMONWARE_CRYPTOGRAPHY_HANDSHAKE",
48 Self::V1 => b"_COMMONWARE_CRYPTOGRAPHY_SAKE",
49 }
50 }
51
52 const fn transcript(self) -> transcript::Version {
58 match self {
59 Self::V0 => transcript::Version::V0,
60 Self::V1 => transcript::Version::V1,
61 }
62 }
63
64 const fn binds_identity_before_syn(self) -> bool {
66 match self {
67 Self::V0 => false,
68 Self::V1 => true,
69 }
70 }
71}
72
73#[cfg_attr(test, derive(Debug, PartialEq))]
76pub struct Syn<S: Signature> {
77 time_ms: u64,
78 epk: EphemeralPublicKey,
79 sig: S,
80}
81
82impl<S: Signature> FixedSize for Syn<S> {
83 const SIZE: usize = u64::SIZE + EphemeralPublicKey::SIZE + S::SIZE;
84}
85
86impl<S: Signature> Write for Syn<S> {
87 fn write(&self, buf: &mut impl bytes::BufMut) {
88 self.time_ms.write(buf);
89 self.epk.write(buf);
90 self.sig.write(buf);
91 }
92}
93
94impl<S: Signature> Read for Syn<S> {
95 type Cfg = S::Cfg;
96
97 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
98 Ok(Self {
99 time_ms: ReadExt::read(buf)?,
100 epk: ReadExt::read(buf)?,
101 sig: Read::read_cfg(buf, cfg)?,
102 })
103 }
104}
105
106#[cfg(feature = "arbitrary")]
107impl<S: Signature> arbitrary::Arbitrary<'_> for Syn<S>
108where
109 S: for<'a> arbitrary::Arbitrary<'a>,
110{
111 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
112 Ok(Self {
113 time_ms: u.arbitrary()?,
114 epk: u.arbitrary()?,
115 sig: u.arbitrary()?,
116 })
117 }
118}
119
120#[cfg_attr(test, derive(Debug, PartialEq))]
123pub struct SynAck<S: Signature> {
124 time_ms: u64,
125 epk: EphemeralPublicKey,
126 sig: S,
127 confirmation: Summary,
128}
129
130impl<S: Signature> FixedSize for SynAck<S> {
131 const SIZE: usize = u64::SIZE + EphemeralPublicKey::SIZE + S::SIZE + Summary::SIZE;
132}
133
134impl<S: Signature> Write for SynAck<S> {
135 fn write(&self, buf: &mut impl bytes::BufMut) {
136 self.time_ms.write(buf);
137 self.epk.write(buf);
138 self.sig.write(buf);
139 self.confirmation.write(buf);
140 }
141}
142
143impl<S: Signature> Read for SynAck<S> {
144 type Cfg = S::Cfg;
145
146 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> 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(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
193 Ok(Self {
194 confirmation: ReadExt::read(buf)?,
195 })
196 }
197}
198
199pub struct DialState<P> {
202 esk: SecretKey,
203 peer_identity: P,
204 transcript: Transcript,
205 ok_timestamps: Range<u64>,
206}
207
208pub struct ListenState {
211 transcript: Transcript,
212}
213
214pub struct Context<S, P> {
217 version: Version,
218 transcript: Transcript,
219 current_time: u64,
220 ok_timestamps: Range<u64>,
221 my_identity: S,
222 peer_identity: P,
223}
224
225impl<S, P> Context<S, P> {
226 pub fn new(
230 namespace: &[u8],
231 current_time_ms: u64,
232 ok_timestamps: Range<u64>,
233 my_identity: S,
234 peer_identity: P,
235 version: Version,
236 ) -> Self {
237 let mut transcript =
238 Transcript::new(namespace, version.transcript()).fork(version.namespace());
239 if let Some(modes) = modes![version] {
240 transcript.commit(modes.encode());
241 }
242 Self {
243 version,
244 transcript,
245 current_time: current_time_ms,
246 ok_timestamps,
247 my_identity,
248 peer_identity,
249 }
250 }
251}
252
253pub fn dial_start<S: Signer, P: PublicKey>(
256 rng: impl CryptoRng,
257 ctx: Context<S, P>,
258) -> (DialState<P>, Syn<<S as Signer>::Signature>) {
259 let Context {
260 version,
261 current_time,
262 ok_timestamps,
263 my_identity,
264 peer_identity,
265 mut transcript,
266 } = ctx;
267
268 let esk = SecretKey::new(rng);
270 let epk = esk.public();
271 let dialer_identity = my_identity.public_key().encode();
272 transcript
273 .commit(current_time.encode())
274 .commit(peer_identity.encode());
275
276 if version.binds_identity_before_syn() {
279 transcript.commit(&dialer_identity[..]);
280 }
281 let sig = transcript.commit(epk.encode()).sign(&my_identity);
282 if !version.binds_identity_before_syn() {
283 transcript.commit(dialer_identity);
284 }
285 (
286 DialState {
287 esk,
288 peer_identity,
289 transcript,
290 ok_timestamps,
291 },
292 Syn {
293 time_ms: current_time,
294 epk,
295 sig,
296 },
297 )
298}
299
300pub fn dial_end<P: PublicKey>(
304 state: DialState<P>,
305 msg: SynAck<<P as Verifier>::Signature>,
306) -> Result<(Ack, Transcript), Error> {
307 let DialState {
308 esk,
309 peer_identity,
310 mut transcript,
311 ok_timestamps,
312 } = state;
313
314 if !ok_timestamps.contains(&msg.time_ms) {
316 return Err(Error::InvalidTimestamp(msg.time_ms, ok_timestamps));
317 }
318 if !transcript
319 .commit(msg.time_ms.encode())
320 .commit(msg.epk.encode())
321 .verify(&peer_identity, &msg.sig)
322 {
323 return Err(Error::InvalidSignature);
324 }
325
326 let Some(shared) = esk.exchange(&msg.epk) else {
328 return Err(Error::InvalidEphemeralKey);
329 };
330 shared
331 .secret
332 .expose(|secret| transcript.commit(secret.as_ref()));
333 let confirmation_l2d = transcript.fork(LABEL_CONFIRMATION_L2D).summarize();
334 let confirmation_d2l = transcript.fork(LABEL_CONFIRMATION_D2L).summarize();
335
336 if msg.confirmation != confirmation_l2d {
338 return Err(Error::InvalidConfirmation);
339 }
340
341 Ok((
342 Ack {
343 confirmation: confirmation_d2l,
344 },
345 transcript,
346 ))
347}
348
349pub fn listen_start<S: Signer, P: PublicKey>(
352 rng: impl CryptoRng,
353 ctx: Context<S, P>,
354 msg: Syn<<P as Verifier>::Signature>,
355) -> Result<(ListenState, SynAck<<S as Signer>::Signature>), Error> {
356 let Context {
357 version,
358 current_time,
359 my_identity,
360 peer_identity,
361 ok_timestamps,
362 mut transcript,
363 } = ctx;
364
365 if !ok_timestamps.contains(&msg.time_ms) {
367 return Err(Error::InvalidTimestamp(msg.time_ms, ok_timestamps));
368 }
369 let dialer_identity = peer_identity.encode();
370 transcript
371 .commit(msg.time_ms.encode())
372 .commit(my_identity.public_key().encode());
373
374 if version.binds_identity_before_syn() {
377 transcript.commit(&dialer_identity[..]);
378 }
379 if !transcript
380 .commit(msg.epk.encode())
381 .verify(&peer_identity, &msg.sig)
382 {
383 return Err(Error::InvalidSignature);
384 }
385 if !version.binds_identity_before_syn() {
386 transcript.commit(dialer_identity);
387 }
388
389 let esk = SecretKey::new(rng);
391 let epk = esk.public();
392 let sig = transcript
393 .commit(current_time.encode())
394 .commit(epk.encode())
395 .sign(&my_identity);
396
397 let Some(shared) = esk.exchange(&msg.epk) else {
399 return Err(Error::InvalidEphemeralKey);
400 };
401 shared
402 .secret
403 .expose(|secret| transcript.commit(secret.as_ref()));
404 let confirmation_l2d = transcript.fork(LABEL_CONFIRMATION_L2D).summarize();
405
406 Ok((
407 ListenState { transcript },
408 SynAck {
409 time_ms: current_time,
410 epk,
411 sig,
412 confirmation: confirmation_l2d,
413 },
414 ))
415}
416
417pub fn listen_end(state: ListenState, msg: Ack) -> Result<Transcript, Error> {
420 let confirmation_d2l = state.transcript.fork(LABEL_CONFIRMATION_D2L).summarize();
421 if msg.confirmation != confirmation_d2l {
422 return Err(Error::InvalidConfirmation);
423 }
424
425 Ok(state.transcript)
426}
427
428#[cfg(test)]
429mod test {
430 use super::*;
431 use crate::{Signer, ed25519::PrivateKey, secp256r1::standard};
432 use commonware_codec::{Codec, Copying, DecodeExt};
433 use commonware_math::algebra::Random;
434 use commonware_utils::{test_rng, union_unique};
435 use p256::{
436 AffinePoint, FieldBytes, ProjectivePoint, Scalar,
437 elliptic_curve::{ops::Reduce, sec1::ToSec1Point as _},
438 };
439 use sha2::{Digest, Sha256};
440
441 const VERSIONS: [Version; 2] = [Version::V0, Version::V1];
442
443 fn test_encode_roundtrip<T: Codec<Cfg = ()> + PartialEq>(value: &T) {
444 assert!(value == &<T as DecodeExt<_>>::decode(value.encode()).unwrap());
445 }
446
447 #[test]
450 fn test_can_setup() -> Result<(), Error> {
451 for version in VERSIONS {
452 let mut rng = test_rng();
453 let dialer_crypto = PrivateKey::random(&mut rng);
454 let listener_crypto = PrivateKey::random(&mut rng);
455
456 let (d_state, msg1) = dial_start(
458 &mut rng,
459 Context::new(
460 b"test_namespace",
461 0,
462 0..1,
463 dialer_crypto.clone(),
464 listener_crypto.public_key(),
465 version,
466 ),
467 );
468 test_encode_roundtrip(&msg1);
469 let (l_state, msg2) = listen_start(
470 &mut rng,
471 Context::new(
472 b"test_namespace",
473 0,
474 0..1,
475 listener_crypto,
476 dialer_crypto.public_key(),
477 version,
478 ),
479 msg1,
480 )?;
481 test_encode_roundtrip(&msg2);
482 let (msg3, dialer) = dial_end(d_state, msg2)?;
483 test_encode_roundtrip(&msg3);
484 let listener = listen_end(l_state, msg3)?;
485 assert_eq!(dialer.summarize(), listener.summarize());
486 }
487
488 Ok(())
489 }
490
491 #[test]
493 fn test_mismatched_confirmation_fails() {
494 for version in VERSIONS {
495 let mut rng = test_rng();
496 let dialer_crypto = PrivateKey::random(&mut rng);
497 let listener_crypto = PrivateKey::random(&mut rng);
498 let wrong = Transcript::new(b"wrong", version.transcript()).summarize();
499 let start = |rng: &mut _| {
500 let (d_state, msg1) = dial_start(
501 &mut *rng,
502 Context::new(
503 b"test_namespace",
504 0,
505 0..1,
506 dialer_crypto.clone(),
507 listener_crypto.public_key(),
508 version,
509 ),
510 );
511 let (l_state, msg2) = listen_start(
512 &mut *rng,
513 Context::new(
514 b"test_namespace",
515 0,
516 0..1,
517 listener_crypto.clone(),
518 dialer_crypto.public_key(),
519 version,
520 ),
521 msg1,
522 )
523 .unwrap();
524 (d_state, l_state, msg2)
525 };
526
527 let (d_state, _, mut msg2) = start(&mut rng);
529 msg2.confirmation = wrong;
530 assert!(matches!(
531 dial_end(d_state, msg2),
532 Err(Error::InvalidConfirmation)
533 ));
534
535 let (d_state, l_state, msg2) = start(&mut rng);
537 let (mut msg3, _) = dial_end(d_state, msg2).unwrap();
538 msg3.confirmation = wrong;
539 assert!(matches!(
540 listen_end(l_state, msg3),
541 Err(Error::InvalidConfirmation)
542 ));
543 }
544 }
545
546 #[test]
548 fn test_mismatched_namespace_fails() {
549 for version in VERSIONS {
550 let mut rng = test_rng();
551 let dialer_crypto = PrivateKey::random(&mut rng);
552 let listener_crypto = PrivateKey::random(&mut rng);
553
554 let (_, msg1) = dial_start(
555 &mut rng,
556 Context::new(
557 b"namespace_a",
558 0,
559 0..1,
560 dialer_crypto.clone(),
561 listener_crypto.public_key(),
562 version,
563 ),
564 );
565
566 let result = listen_start(
567 &mut rng,
568 Context::new(
569 b"namespace_b",
570 0,
571 0..1,
572 listener_crypto,
573 dialer_crypto.public_key(),
574 version,
575 ),
576 msg1,
577 );
578
579 assert!(matches!(result, Err(Error::InvalidSignature)));
580 }
581 }
582
583 #[test]
585 fn test_mismatched_version_fails() {
586 for (dialer_version, listener_version) in
587 [(Version::V0, Version::V1), (Version::V1, Version::V0)]
588 {
589 let mut rng = test_rng();
590 let dialer_crypto = PrivateKey::random(&mut rng);
591 let listener_crypto = PrivateKey::random(&mut rng);
592
593 let (_, msg1) = dial_start(
594 &mut rng,
595 Context::new(
596 b"test_namespace",
597 0,
598 0..1,
599 dialer_crypto.clone(),
600 listener_crypto.public_key(),
601 dialer_version,
602 ),
603 );
604
605 let result = listen_start(
606 &mut rng,
607 Context::new(
608 b"test_namespace",
609 0,
610 0..1,
611 listener_crypto,
612 dialer_crypto.public_key(),
613 listener_version,
614 ),
615 msg1,
616 );
617
618 assert!(matches!(result, Err(Error::InvalidSignature)));
619 }
620 }
621
622 #[test]
624 fn test_mismatched_dialer_identity_fails() {
625 for version in VERSIONS {
626 let mut rng = test_rng();
627 let dialer_crypto = PrivateKey::random(&mut rng);
628 let listener_crypto = PrivateKey::random(&mut rng);
629 let impostor_crypto = PrivateKey::random(&mut rng);
630
631 let (_, msg1) = dial_start(
632 &mut rng,
633 Context::new(
634 b"test_namespace",
635 0,
636 0..1,
637 dialer_crypto,
638 listener_crypto.public_key(),
639 version,
640 ),
641 );
642
643 let result = listen_start(
644 &mut rng,
645 Context::new(
646 b"test_namespace",
647 0,
648 0..1,
649 listener_crypto,
650 impostor_crypto.public_key(),
651 version,
652 ),
653 msg1,
654 );
655
656 assert!(matches!(result, Err(Error::InvalidSignature)));
657 }
658 }
659
660 fn syn_transcript<P: PublicKey>(
663 version: Version,
664 syn: &Syn<P::Signature>,
665 listener: &P,
666 dialer: Option<&P>,
667 ) -> Transcript {
668 let mut transcript =
669 Transcript::new(b"test_namespace", version.transcript()).fork(version.namespace());
670 if version == Version::V1 {
671 transcript.commit(&[1][..]);
672 }
673 transcript
674 .commit(syn.time_ms.encode())
675 .commit(listener.encode());
676 if let Some(dialer) = dialer {
677 transcript.commit(dialer.encode());
678 }
679 transcript.commit(syn.epk.encode());
680 transcript
681 }
682
683 #[test]
686 fn test_syn_signature_dialer_identity() {
687 for (version, covers) in [(Version::V0, false), (Version::V1, true)] {
688 let mut rng = test_rng();
689 let dialer_crypto = PrivateKey::random(&mut rng);
690 let listener_crypto = PrivateKey::random(&mut rng);
691 let dialer = dialer_crypto.public_key();
692 let listener = listener_crypto.public_key();
693
694 let (_, syn) = dial_start(
695 &mut rng,
696 Context::new(
697 b"test_namespace",
698 0,
699 0..1,
700 dialer_crypto,
701 listener.clone(),
702 version,
703 ),
704 );
705
706 let with = syn_transcript(version, &syn, &listener, Some(&dialer));
708 let without = syn_transcript(version, &syn, &listener, None);
709 assert_eq!(with.verify(&dialer, &syn.sig), covers, "{version:?}");
710 assert_eq!(without.verify(&dialer, &syn.sig), !covers, "{version:?}");
711 }
712 }
713
714 fn substitute(
718 key: &standard::PublicKey,
719 summary: &Summary,
720 sig: &standard::Signature,
721 ) -> standard::PublicKey {
722 let payload = union_unique(b"", summary.as_ref());
725 let hash: [u8; 32] = Sha256::digest(&payload).into();
726 let e = <Scalar as Reduce<FieldBytes>>::reduce(&FieldBytes::from(hash));
727 let sig = p256::ecdsa::Signature::from_slice(&sig.encode()).unwrap();
728 let r: Scalar = *sig.r();
729 let r_inv = r.invert().unwrap();
730 let q = p256::PublicKey::from_sec1_bytes(&key.encode())
731 .unwrap()
732 .to_projective();
733 let derived: AffinePoint = (-q - ProjectivePoint::GENERATOR * (e * r_inv).double()).into();
734 standard::PublicKey::decode(Copying(derived.to_sec1_point(true).as_bytes())).unwrap()
735 }
736
737 #[test]
744 fn test_v1_rejects_derived_identity() {
745 for version in VERSIONS {
746 let mut rng = test_rng();
747 let dialer = standard::PrivateKey::random(&mut rng);
748 let listener = standard::PrivateKey::random(&mut rng);
749
750 let (state, syn) = dial_start(
752 &mut rng,
753 Context::new(
754 b"test_namespace",
755 0,
756 0..1,
757 dialer.clone(),
758 listener.public_key(),
759 version,
760 ),
761 );
762
763 let (dialer_key, listener_key) = (dialer.public_key(), listener.public_key());
765 let signed = syn_transcript(
766 version,
767 &syn,
768 &listener_key,
769 version.binds_identity_before_syn().then_some(&dialer_key),
770 )
771 .summarize();
772 let derived = substitute(&dialer_key, &signed, &syn.sig);
773 assert_ne!(derived, dialer_key);
774 assert!(signed.verify(&derived, &syn.sig));
775
776 let mut claimed = syn_transcript(version, &syn, &listener_key, None);
778 claimed.commit(derived.encode());
779 let result = listen_start(
780 &mut rng,
781 Context::new(b"test_namespace", 0, 0..1, listener, derived, version),
782 syn,
783 );
784
785 if version == Version::V1 {
788 assert!(matches!(result, Err(Error::InvalidSignature)));
789 continue;
790 }
791
792 let (listen_state, syn_ack) = result.unwrap();
794 let state = DialState {
795 transcript: claimed,
796 ..state
797 };
798 let (ack, dialer) = dial_end(state, syn_ack).unwrap();
799 let listener = listen_end(listen_state, ack).unwrap();
800 assert_eq!(dialer.summarize(), listener.summarize());
801 }
802 }
803}