1use super::{x25519, AUTHENTICATION_TAG_LENGTH};
4use crate::Error;
5use bytes::{Buf, BufMut};
6use chacha20poly1305::{
7 aead::{AeadMutInPlace, Tag},
8 ChaCha20Poly1305, Nonce,
9};
10use commonware_codec::{
11 varint::UInt, Encode, EncodeSize, Error as CodecError, FixedSize, Read, ReadExt, Write,
12};
13use commonware_cryptography::{Hasher, PublicKey, Sha256, Signer};
14use commonware_runtime::Clock;
15use commonware_utils::SystemTimeExt;
16use std::time::Duration;
17
18pub struct Info<P: PublicKey> {
20 recipient: P,
22
23 ephemeral_public_key: x25519::PublicKey,
27
28 timestamp: u64,
30
31 continuation_tag: Option<<Sha256 as Hasher>::Digest>,
33}
34
35impl<P: PublicKey> Info<P> {
36 pub fn new(recipient: P, ephemeral_public_key: x25519::PublicKey, timestamp: u64) -> Self {
38 Self {
39 recipient,
40 ephemeral_public_key,
41 timestamp,
42 continuation_tag: None,
43 }
44 }
45
46 pub fn new_with_tag(
51 recipient: P,
52 ephemeral_public_key: x25519::PublicKey,
53 timestamp: u64,
54 data: &[u8],
55 ) -> Self {
56 Self {
57 recipient,
58 ephemeral_public_key,
59 timestamp,
60 continuation_tag: Some(Sha256::hash(data)),
61 }
62 }
63
64 pub fn check_tag(&self, data: &[u8]) -> Result<(), Error> {
65 if self
66 .continuation_tag
67 .is_some_and(|tag| tag == Sha256::hash(data))
68 {
69 Ok(())
70 } else {
71 Err(Error::InvalidInfoContinuationTag)
72 }
73 }
74}
75
76impl<P: PublicKey> Write for Info<P> {
77 fn write(&self, buf: &mut impl BufMut) {
78 self.recipient.write(buf);
79 self.ephemeral_public_key.write(buf);
80 UInt(self.timestamp).write(buf);
81 self.continuation_tag.write(buf);
82 }
83}
84
85impl<P: PublicKey> Read for Info<P> {
86 type Cfg = ();
87
88 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
89 let recipient = P::read(buf)?;
90 let ephemeral_public_key = x25519::PublicKey::read(buf)?;
91 let timestamp = UInt::read(buf)?.into();
92 let continuation_tag = Option::read(buf)?;
93 Ok(Self {
94 recipient,
95 ephemeral_public_key,
96 timestamp,
97 continuation_tag,
98 })
99 }
100}
101
102impl<P: PublicKey> EncodeSize for Info<P> {
103 fn encode_size(&self) -> usize {
104 self.recipient.encode_size()
105 + self.ephemeral_public_key.encode_size()
106 + UInt(self.timestamp).encode_size()
107 + self.continuation_tag.encode_size()
108 }
109}
110
111pub struct Hello<P: PublicKey> {
124 info: Info<P>,
126
127 signer: P,
129
130 signature: P::Signature,
132}
133
134impl<P: PublicKey> Hello<P> {
135 pub fn sign<Sk: Signer<PublicKey = P, Signature = P::Signature>>(
137 crypto: &mut Sk,
138 namespace: &[u8],
139 info: Info<P>,
140 ) -> Self {
141 let signature = crypto.sign(Some(namespace), &info.encode());
142 Self {
143 info,
144 signer: crypto.public_key(),
145 signature,
146 }
147 }
148
149 pub fn signer(&self) -> P {
151 self.signer.clone()
152 }
153
154 pub fn ephemeral(&self) -> x25519::PublicKey {
156 self.info.ephemeral_public_key
157 }
158
159 pub fn verify<E: Clock>(
161 &self,
162 context: &E,
163 crypto: &P,
164 namespace: &[u8],
165 synchrony_bound: Duration,
166 max_handshake_age: Duration,
167 tag_data: Option<&[u8]>,
168 ) -> Result<(), Error> {
169 if *crypto != self.info.recipient {
175 return Err(Error::HelloNotForUs);
176 }
177
178 if *crypto == self.signer {
184 return Err(Error::HelloUsesOurKey);
185 }
186
187 let current_timestamp = context.current().epoch();
195 let hello_timestamp = Duration::from_millis(self.info.timestamp);
196 if hello_timestamp + max_handshake_age < current_timestamp {
197 return Err(Error::InvalidTimestampOld(self.info.timestamp));
198 }
199 if hello_timestamp > current_timestamp + synchrony_bound {
200 return Err(Error::InvalidTimestampFuture(self.info.timestamp));
201 }
202
203 if !self
205 .signer
206 .verify(Some(namespace), &self.info.encode(), &self.signature)
207 {
208 return Err(Error::InvalidSignature);
209 }
210
211 if let Some(d) = tag_data {
213 self.info.check_tag(d)?;
214 }
215 Ok(())
216 }
217}
218
219impl<P: PublicKey> Write for Hello<P> {
220 fn write(&self, buf: &mut impl BufMut) {
221 self.info.write(buf);
222 self.signer.write(buf);
223 self.signature.write(buf);
224 }
225}
226
227impl<P: PublicKey> Read for Hello<P> {
228 type Cfg = ();
229
230 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
231 let info = Info::read(buf)?;
232 let signer = P::read(buf)?;
233 let signature = P::Signature::read(buf)?;
234 Ok(Self {
235 info,
236 signer,
237 signature,
238 })
239 }
240}
241
242impl<P: PublicKey> EncodeSize for Hello<P> {
243 fn encode_size(&self) -> usize {
244 self.info.encode_size() + self.signer.encode_size() + self.signature.encode_size()
245 }
246}
247
248pub struct Confirmation {
255 tag: Tag<ChaCha20Poly1305>,
257}
258
259impl Confirmation {
260 pub fn create(mut cipher: ChaCha20Poly1305, transcript: &[u8]) -> Result<Self, Error> {
271 let tag = cipher
273 .encrypt_in_place_detached(&Nonce::default(), transcript, &mut [])
274 .map_err(|_| Error::ConfirmationFailed)?;
275
276 Ok(Self { tag })
277 }
278
279 pub fn verify(&self, mut cipher: ChaCha20Poly1305, transcript: &[u8]) -> Result<(), Error> {
283 cipher
285 .decrypt_in_place_detached(&Nonce::default(), transcript, &mut [], &self.tag)
286 .map_err(|_| Error::InvalidConfirmation)?;
287 Ok(())
288 }
289}
290
291impl Write for Confirmation {
292 fn write(&self, buf: &mut impl BufMut) {
293 let tag_bytes: [u8; Self::SIZE] = self.tag.into();
294 tag_bytes.write(buf);
295 }
296}
297
298impl Read for Confirmation {
299 type Cfg = ();
300
301 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
302 let tag = <[u8; Self::SIZE]>::read_cfg(buf, &())?;
303 Ok(Self { tag: tag.into() })
304 }
305}
306
307impl FixedSize for Confirmation {
308 const SIZE: usize = AUTHENTICATION_TAG_LENGTH;
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::{
315 public_key::{Config, IncomingConnection},
316 utils::codec::send_frame,
317 };
318 use commonware_codec::DecodeExt;
319 use commonware_cryptography::{
320 ed25519::{PrivateKey, PublicKey as edPublicKey},
321 PrivateKeyExt as _, Verifier as _,
322 };
323 use commonware_runtime::{deterministic, mocks, Metrics, Runner, Spawner};
324 use x25519::PublicKey;
325
326 const TEST_NAMESPACE: &[u8] = b"test_namespace";
327 const ONE_MEGABYTE: usize = 1024 * 1024;
328
329 #[test]
330 fn test_hello_create_verify() {
331 let executor = deterministic::Runner::default();
333 executor.start(|context| async move {
334 let mut sender = PrivateKey::from_seed(0);
336 let recipient = PrivateKey::from_seed(1).public_key();
337 let ephemeral_public_key = PublicKey::from_bytes([3u8; 32]);
338
339 let timestamp = context.current().epoch_millis();
341 let hello = Hello::sign(
342 &mut sender,
343 TEST_NAMESPACE,
344 Info::new(recipient.clone(), ephemeral_public_key, timestamp),
345 );
346
347 let hello =
349 Hello::<edPublicKey>::decode(hello.encode()).expect("failed to decode hello");
350
351 let synchrony_bound = Duration::from_secs(5);
353 let max_handshake_age = Duration::from_secs(5);
354 let hello_timestamp = Duration::from_millis(hello.info.timestamp);
355 let current_timestamp = Duration::from_millis(timestamp);
356 assert!(hello_timestamp <= current_timestamp + synchrony_bound);
357 assert!(hello_timestamp + max_handshake_age >= current_timestamp);
358
359 assert_eq!(hello.info.recipient, recipient);
361 assert_eq!(hello.info.ephemeral_public_key, ephemeral_public_key,);
362
363 assert!(sender.public_key().verify(
365 Some(TEST_NAMESPACE),
366 &hello.info.encode(),
367 &hello.signature,
368 ));
369
370 hello
372 .verify(
373 &context,
374 &recipient,
375 TEST_NAMESPACE,
376 synchrony_bound,
377 max_handshake_age,
378 None,
379 )
380 .unwrap();
381 assert_eq!(hello.signer, sender.public_key());
382 assert_eq!(hello.info.ephemeral_public_key, ephemeral_public_key);
383 });
384 }
385
386 #[test]
387 fn test_hello() {
388 let executor = deterministic::Runner::default();
390 executor.start(|context| async move {
391 let mut sender = PrivateKey::from_seed(0);
393 let recipient = PrivateKey::from_seed(1);
394 let ephemeral_public_key = PublicKey::from_bytes([3u8; 32]);
395
396 let hello = Hello::sign(
398 &mut sender,
399 TEST_NAMESPACE,
400 Info::new(recipient.public_key(), ephemeral_public_key, 0),
401 );
402
403 let (sink, _) = mocks::Channel::init();
405 let (mut stream_sender, stream) = mocks::Channel::init();
406
407 context.with_label("stream_sender").spawn(|_| async move {
409 send_frame(&mut stream_sender, &hello.encode(), ONE_MEGABYTE)
410 .await
411 .unwrap();
412 });
413
414 let config = Config {
416 crypto: recipient.clone(),
417 namespace: TEST_NAMESPACE.to_vec(),
418 max_message_size: ONE_MEGABYTE,
419 synchrony_bound: Duration::from_secs(5),
420 max_handshake_age: Duration::from_secs(5),
421 handshake_timeout: Duration::from_secs(5),
422 };
423 let result = IncomingConnection::verify(&context, config, sink, stream)
424 .await
425 .unwrap();
426
427 assert_eq!(result.peer(), sender.public_key());
429 assert_eq!(result.ephemeral(), ephemeral_public_key);
430 });
431 }
432
433 #[test]
434 fn test_hello_not_for_us() {
435 let executor = deterministic::Runner::default();
437 executor.start(|context| async move {
438 let mut sender = PrivateKey::from_seed(0);
440 let ephemeral_public_key = PublicKey::from_bytes([3u8; 32]);
441
442 let hello = Hello::sign(
444 &mut sender,
445 TEST_NAMESPACE,
446 Info::new(
447 PrivateKey::from_seed(1).public_key(),
448 ephemeral_public_key,
449 0,
450 ),
451 );
452
453 let (sink, _) = mocks::Channel::init();
455 let (mut stream_sender, stream) = mocks::Channel::init();
456
457 context.with_label("stream_sender").spawn(|_| async move {
459 send_frame(&mut stream_sender, &hello.encode(), ONE_MEGABYTE)
460 .await
461 .unwrap();
462 });
463
464 let config = Config {
466 crypto: PrivateKey::from_seed(2),
467 namespace: TEST_NAMESPACE.to_vec(),
468 max_message_size: ONE_MEGABYTE,
469 synchrony_bound: Duration::from_secs(5),
470 max_handshake_age: Duration::from_secs(5),
471 handshake_timeout: Duration::from_secs(5),
472 };
473 let result = IncomingConnection::verify(&context, config, sink, stream).await;
474
475 assert!(matches!(result, Err(Error::HelloNotForUs)));
477 });
478 }
479
480 #[test]
481 fn test_incoming_hello_invalid_data() {
482 let executor = deterministic::Runner::default();
484 executor.start(|context| async move {
485 let (sink, _) = mocks::Channel::init();
487 let (mut stream_sender, stream) = mocks::Channel::init();
488
489 context.with_label("stream_sender").spawn(|_| async move {
491 send_frame(&mut stream_sender, b"mock data", ONE_MEGABYTE)
492 .await
493 .unwrap();
494 });
495
496 let config = Config {
498 crypto: PrivateKey::from_seed(0),
499 namespace: TEST_NAMESPACE.to_vec(),
500 max_message_size: ONE_MEGABYTE,
501 synchrony_bound: Duration::from_secs(1),
502 max_handshake_age: Duration::from_secs(1),
503 handshake_timeout: Duration::from_secs(1),
504 };
505 let result = IncomingConnection::verify(&context, config, sink, stream).await;
506
507 assert!(matches!(result, Err(Error::UnableToDecode(_))));
509 });
510 }
511
512 #[test]
513 fn test_incoming_hello_verify_timeout() {
514 let executor = deterministic::Runner::default();
515 executor.start(|context| async move {
516 let (sink, _stream) = mocks::Channel::init();
518 let (_sink, stream) = mocks::Channel::init();
519
520 let config = Config {
522 crypto: PrivateKey::from_seed(1),
523 namespace: TEST_NAMESPACE.to_vec(),
524 max_message_size: ONE_MEGABYTE,
525 synchrony_bound: Duration::from_secs(1),
526 max_handshake_age: Duration::from_secs(1),
527 handshake_timeout: Duration::from_secs(1),
528 };
529 let result = IncomingConnection::verify(&context, config, sink, stream).await;
530
531 assert!(matches!(result, Err(Error::HandshakeTimeout)));
533 });
534 }
535
536 #[test]
537 fn test_hello_verify_invalid_signature() {
538 let executor = deterministic::Runner::default();
539 executor.start(|context| async move {
540 let mut sender = PrivateKey::from_seed(0);
541 let recipient = PrivateKey::from_seed(1);
542 let ephemeral_public_key = x25519::PublicKey::from_bytes([0u8; 32]);
543
544 let hello = Hello::sign(
546 &mut sender,
547 TEST_NAMESPACE,
548 Info::new(recipient.public_key(), ephemeral_public_key, 0),
549 );
550
551 let mut hello =
553 Hello::<edPublicKey>::decode(hello.encode()).expect("failed to decode hello");
554 hello.info.timestamp += 1;
555
556 let result = hello.verify(
558 &context,
559 &recipient.public_key(),
560 TEST_NAMESPACE,
561 Duration::from_secs(5),
562 Duration::from_secs(5),
563 None,
564 );
565 assert!(matches!(result, Err(Error::InvalidSignature)));
566 });
567 }
568
569 #[test]
570 fn test_hello_verify_invalid_timestamp_old() {
571 let executor = deterministic::Runner::default();
572 executor.start(|context| async move {
573 let mut signer = PrivateKey::from_seed(0);
574 let recipient = PrivateKey::from_seed(1).public_key();
575 let ephemeral_public_key = x25519::PublicKey::from_bytes([0u8; 32]);
576
577 let timeout_duration = Duration::from_secs(5);
578 let synchrony_bound = Duration::from_secs(0);
579
580 let hello = Hello::sign(
582 &mut signer,
583 TEST_NAMESPACE,
584 Info::new(recipient.clone(), ephemeral_public_key, 0),
585 );
586
587 context.sleep(timeout_duration).await;
590
591 hello
593 .verify(
594 &context,
595 &recipient,
596 TEST_NAMESPACE,
597 synchrony_bound,
598 timeout_duration,
599 None,
600 )
601 .unwrap();
602
603 context.sleep(Duration::from_millis(1)).await;
605
606 let result = hello.verify(
608 &context,
609 &recipient,
610 TEST_NAMESPACE,
611 synchrony_bound,
612 timeout_duration,
613 None,
614 );
615 assert!(matches!(result, Err(Error::InvalidTimestampOld(t)) if t == 0));
616 });
617 }
618
619 #[test]
620 fn test_confirmation_create_and_verify() {
621 use chacha20poly1305::KeyInit;
622
623 let key = [1u8; 32];
624 let cipher = ChaCha20Poly1305::new(&key.into());
625 let transcript = b"test_transcript_data";
626
627 let confirmation = Confirmation::create(cipher, transcript).unwrap();
629
630 let cipher = ChaCha20Poly1305::new(&key.into());
632 confirmation.verify(cipher, transcript).unwrap();
633
634 let different_transcript = b"different_transcript_data";
636 let cipher = ChaCha20Poly1305::new(&key.into());
637 let result = confirmation.verify(cipher, different_transcript);
638 assert!(matches!(result, Err(Error::InvalidConfirmation)));
639
640 let different_key = [2u8; 32];
642 let different_cipher = ChaCha20Poly1305::new(&different_key.into());
643 let result = confirmation.verify(different_cipher, transcript);
644 assert!(matches!(result, Err(Error::InvalidConfirmation)));
645 }
646
647 #[test]
648 fn test_confirmation_encoding() {
649 use chacha20poly1305::KeyInit;
650 use commonware_codec::{DecodeExt, Encode};
651
652 let key = [1u8; 32];
653 let cipher = ChaCha20Poly1305::new(&key.into());
654 let transcript = b"test_transcript_for_encoding";
655
656 let original_confirmation = Confirmation::create(cipher, transcript).unwrap();
658 let encoded = original_confirmation.encode();
659
660 let decoded_confirmation = Confirmation::decode(encoded).unwrap();
662 let cipher = ChaCha20Poly1305::new(&key.into());
663 decoded_confirmation.verify(cipher, transcript).unwrap();
664 }
665
666 #[test]
667 fn test_hello_verify_invalid_timestamp_future() {
668 let executor = deterministic::Runner::default();
669 executor.start(|context| async move {
670 let mut signer = PrivateKey::from_seed(0);
671 let recipient = PrivateKey::from_seed(1).public_key();
672 let ephemeral_public_key = x25519::PublicKey::from_bytes([0u8; 32]);
673
674 let timeout_duration = Duration::from_secs(0);
675 const SYNCHRONY_BOUND_MILLIS: u64 = 5_000;
676 let synchrony_bound = Duration::from_millis(SYNCHRONY_BOUND_MILLIS);
677
678 let hello_ok = Hello::sign(
680 &mut signer,
681 TEST_NAMESPACE,
682 Info::new(recipient.clone(), ephemeral_public_key, SYNCHRONY_BOUND_MILLIS)
683 );
684
685 let hello_late = Hello::sign(
687 &mut signer,
688 TEST_NAMESPACE,
689 Info::new(recipient.clone(), ephemeral_public_key, SYNCHRONY_BOUND_MILLIS + 1)
690 );
691
692 hello_ok.verify(
694 &context,
695 &recipient,
696 TEST_NAMESPACE,
697 synchrony_bound,
698 timeout_duration,
699 None,
700 ).unwrap(); let result = hello_late.verify(
704 &context,
705 &recipient,
706 TEST_NAMESPACE,
707 synchrony_bound,
708 timeout_duration,
709 None
710 );
711 assert!(matches!(result, Err(Error::InvalidTimestampFuture(t)) if t == SYNCHRONY_BOUND_MILLIS + 1));
712 });
713 }
714
715 #[test]
716 fn test_info_tag_confirmation_happy_path() {
717 let info = Info::new_with_tag(
718 PrivateKey::from_seed(1).public_key(),
719 PublicKey::from_bytes([1u8; 32]),
720 0,
721 b"ABC",
722 );
723 assert!(info.check_tag(b"ABC").is_ok())
724 }
725
726 #[test]
727 fn test_info_no_tag_but_check_with_data_fails() {
728 let info = Info::new(
729 PrivateKey::from_seed(1).public_key(),
730 PublicKey::from_bytes([1u8; 32]),
731 0,
732 );
733 assert!(info.check_tag(b"ABC").is_err())
734 }
735
736 #[test]
737 fn test_info_tag_but_check_with_different_data_fails() {
738 let info = Info::new_with_tag(
739 PrivateKey::from_seed(1).public_key(),
740 PublicKey::from_bytes([1u8; 32]),
741 0,
742 b"ABC",
743 );
744 assert!(info.check_tag(b"not ABC").is_err())
745 }
746}