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::{PublicKey, 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
32impl<P: PublicKey> Info<P> {
33 pub fn new(recipient: P, ephemeral_public_key: x25519::PublicKey, timestamp: u64) -> Self {
35 Self {
36 recipient,
37 ephemeral_public_key,
38 timestamp,
39 }
40 }
41}
42
43impl<P: PublicKey> Write for Info<P> {
44 fn write(&self, buf: &mut impl BufMut) {
45 self.recipient.write(buf);
46 self.ephemeral_public_key.write(buf);
47 UInt(self.timestamp).write(buf);
48 }
49}
50
51impl<P: PublicKey> Read for Info<P> {
52 type Cfg = ();
53
54 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
55 let recipient = P::read(buf)?;
56 let ephemeral_public_key = x25519::PublicKey::read(buf)?;
57 let timestamp = UInt::read(buf)?.into();
58 Ok(Info {
59 recipient,
60 ephemeral_public_key,
61 timestamp,
62 })
63 }
64}
65
66impl<P: PublicKey> EncodeSize for Info<P> {
67 fn encode_size(&self) -> usize {
68 self.recipient.encode_size()
69 + self.ephemeral_public_key.encode_size()
70 + UInt(self.timestamp).encode_size()
71 }
72}
73
74pub struct Hello<P: PublicKey> {
87 info: Info<P>,
89
90 signer: P,
92
93 signature: P::Signature,
95}
96
97impl<P: PublicKey> Hello<P> {
98 pub fn sign<Sk: Signer<PublicKey = P, Signature = P::Signature>>(
100 crypto: &mut Sk,
101 namespace: &[u8],
102 info: Info<P>,
103 ) -> Self {
104 let signature = crypto.sign(Some(namespace), &info.encode());
105 Self {
106 info,
107 signer: crypto.public_key(),
108 signature,
109 }
110 }
111
112 pub fn signer(&self) -> P {
114 self.signer.clone()
115 }
116
117 pub fn ephemeral(&self) -> x25519::PublicKey {
119 self.info.ephemeral_public_key
120 }
121
122 pub fn verify<E: Clock>(
124 &self,
125 context: &E,
126 crypto: &P,
127 namespace: &[u8],
128 synchrony_bound: Duration,
129 max_handshake_age: Duration,
130 ) -> Result<(), Error> {
131 if *crypto != self.info.recipient {
137 return Err(Error::HelloNotForUs);
138 }
139
140 if *crypto == self.signer {
146 return Err(Error::HelloUsesOurKey);
147 }
148
149 let current_timestamp = context.current().epoch();
157 let hello_timestamp = Duration::from_millis(self.info.timestamp);
158 if hello_timestamp + max_handshake_age < current_timestamp {
159 return Err(Error::InvalidTimestampOld(self.info.timestamp));
160 }
161 if hello_timestamp > current_timestamp + synchrony_bound {
162 return Err(Error::InvalidTimestampFuture(self.info.timestamp));
163 }
164
165 if !self
167 .signer
168 .verify(Some(namespace), &self.info.encode(), &self.signature)
169 {
170 return Err(Error::InvalidSignature);
171 }
172 Ok(())
173 }
174}
175
176impl<P: PublicKey> Write for Hello<P> {
177 fn write(&self, buf: &mut impl BufMut) {
178 self.info.write(buf);
179 self.signer.write(buf);
180 self.signature.write(buf);
181 }
182}
183
184impl<P: PublicKey> Read for Hello<P> {
185 type Cfg = ();
186
187 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
188 let info = Info::read(buf)?;
189 let signer = P::read(buf)?;
190 let signature = P::Signature::read(buf)?;
191 Ok(Self {
192 info,
193 signer,
194 signature,
195 })
196 }
197}
198
199impl<P: PublicKey> EncodeSize for Hello<P> {
200 fn encode_size(&self) -> usize {
201 self.info.encode_size() + self.signer.encode_size() + self.signature.encode_size()
202 }
203}
204
205pub struct Confirmation {
212 tag: Tag<ChaCha20Poly1305>,
214}
215
216impl Confirmation {
217 pub fn create(mut cipher: ChaCha20Poly1305, transcript: &[u8]) -> Result<Self, Error> {
228 let tag = cipher
230 .encrypt_in_place_detached(&Nonce::default(), transcript, &mut [])
231 .map_err(|_| Error::ConfirmationFailed)?;
232
233 Ok(Self { tag })
234 }
235
236 pub fn verify(&self, mut cipher: ChaCha20Poly1305, transcript: &[u8]) -> Result<(), Error> {
240 cipher
242 .decrypt_in_place_detached(&Nonce::default(), transcript, &mut [], &self.tag)
243 .map_err(|_| Error::InvalidConfirmation)?;
244 Ok(())
245 }
246}
247
248impl Write for Confirmation {
249 fn write(&self, buf: &mut impl BufMut) {
250 let tag_bytes: [u8; Self::SIZE] = self.tag.into();
251 tag_bytes.write(buf);
252 }
253}
254
255impl Read for Confirmation {
256 type Cfg = ();
257
258 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
259 let tag = <[u8; Self::SIZE]>::read_cfg(buf, &())?;
260 Ok(Self { tag: tag.into() })
261 }
262}
263
264impl FixedSize for Confirmation {
265 const SIZE: usize = AUTHENTICATION_TAG_LENGTH;
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use crate::{
272 public_key::{Config, IncomingConnection},
273 utils::codec::send_frame,
274 };
275 use commonware_codec::DecodeExt;
276 use commonware_cryptography::{
277 ed25519::{PrivateKey, PublicKey as edPublicKey},
278 PrivateKeyExt as _, Verifier as _,
279 };
280 use commonware_runtime::{deterministic, mocks, Metrics, Runner, Spawner};
281 use x25519::PublicKey;
282
283 const TEST_NAMESPACE: &[u8] = b"test_namespace";
284 const ONE_MEGABYTE: usize = 1024 * 1024;
285
286 #[test]
287 fn test_hello_create_verify() {
288 let executor = deterministic::Runner::default();
290 executor.start(|context| async move {
291 let mut sender = PrivateKey::from_seed(0);
293 let recipient = PrivateKey::from_seed(1).public_key();
294 let ephemeral_public_key = PublicKey::from_bytes([3u8; 32]);
295
296 let timestamp = context.current().epoch_millis();
298 let hello = Hello::sign(
299 &mut sender,
300 TEST_NAMESPACE,
301 Info {
302 timestamp,
303 recipient: recipient.clone(),
304 ephemeral_public_key,
305 },
306 );
307
308 let hello =
310 Hello::<edPublicKey>::decode(hello.encode()).expect("failed to decode hello");
311
312 let synchrony_bound = Duration::from_secs(5);
314 let max_handshake_age = Duration::from_secs(5);
315 let hello_timestamp = Duration::from_millis(hello.info.timestamp);
316 let current_timestamp = Duration::from_millis(timestamp);
317 assert!(hello_timestamp <= current_timestamp + synchrony_bound);
318 assert!(hello_timestamp + max_handshake_age >= current_timestamp);
319
320 assert_eq!(hello.info.recipient, recipient);
322 assert_eq!(hello.info.ephemeral_public_key, ephemeral_public_key,);
323
324 assert!(sender.public_key().verify(
326 Some(TEST_NAMESPACE),
327 &hello.info.encode(),
328 &hello.signature,
329 ));
330
331 hello
333 .verify(
334 &context,
335 &recipient,
336 TEST_NAMESPACE,
337 synchrony_bound,
338 max_handshake_age,
339 )
340 .unwrap();
341 assert_eq!(hello.signer, sender.public_key());
342 assert_eq!(hello.info.ephemeral_public_key, ephemeral_public_key);
343 });
344 }
345
346 #[test]
347 fn test_hello() {
348 let executor = deterministic::Runner::default();
350 executor.start(|context| async move {
351 let mut sender = PrivateKey::from_seed(0);
353 let recipient = PrivateKey::from_seed(1);
354 let ephemeral_public_key = PublicKey::from_bytes([3u8; 32]);
355
356 let hello = Hello::sign(
358 &mut sender,
359 TEST_NAMESPACE,
360 Info {
361 timestamp: 0,
362 recipient: recipient.public_key(),
363 ephemeral_public_key,
364 },
365 );
366
367 let (sink, _) = mocks::Channel::init();
369 let (mut stream_sender, stream) = mocks::Channel::init();
370
371 context.with_label("stream_sender").spawn(|_| async move {
373 send_frame(&mut stream_sender, &hello.encode(), ONE_MEGABYTE)
374 .await
375 .unwrap();
376 });
377
378 let config = Config {
380 crypto: recipient.clone(),
381 namespace: TEST_NAMESPACE.to_vec(),
382 max_message_size: ONE_MEGABYTE,
383 synchrony_bound: Duration::from_secs(5),
384 max_handshake_age: Duration::from_secs(5),
385 handshake_timeout: Duration::from_secs(5),
386 };
387 let result = IncomingConnection::verify(&context, config, sink, stream)
388 .await
389 .unwrap();
390
391 assert_eq!(result.peer(), sender.public_key());
393 assert_eq!(result.ephemeral(), ephemeral_public_key);
394 });
395 }
396
397 #[test]
398 fn test_hello_not_for_us() {
399 let executor = deterministic::Runner::default();
401 executor.start(|context| async move {
402 let mut sender = PrivateKey::from_seed(0);
404 let ephemeral_public_key = PublicKey::from_bytes([3u8; 32]);
405
406 let hello = Hello::sign(
408 &mut sender,
409 TEST_NAMESPACE,
410 Info {
411 timestamp: 0,
412 recipient: PrivateKey::from_seed(1).public_key(),
413 ephemeral_public_key,
414 },
415 );
416
417 let (sink, _) = mocks::Channel::init();
419 let (mut stream_sender, stream) = mocks::Channel::init();
420
421 context.with_label("stream_sender").spawn(|_| async move {
423 send_frame(&mut stream_sender, &hello.encode(), ONE_MEGABYTE)
424 .await
425 .unwrap();
426 });
427
428 let config = Config {
430 crypto: PrivateKey::from_seed(2),
431 namespace: TEST_NAMESPACE.to_vec(),
432 max_message_size: ONE_MEGABYTE,
433 synchrony_bound: Duration::from_secs(5),
434 max_handshake_age: Duration::from_secs(5),
435 handshake_timeout: Duration::from_secs(5),
436 };
437 let result = IncomingConnection::verify(&context, config, sink, stream).await;
438
439 assert!(matches!(result, Err(Error::HelloNotForUs)));
441 });
442 }
443
444 #[test]
445 fn test_incoming_hello_invalid_data() {
446 let executor = deterministic::Runner::default();
448 executor.start(|context| async move {
449 let (sink, _) = mocks::Channel::init();
451 let (mut stream_sender, stream) = mocks::Channel::init();
452
453 context.with_label("stream_sender").spawn(|_| async move {
455 send_frame(&mut stream_sender, b"mock data", ONE_MEGABYTE)
456 .await
457 .unwrap();
458 });
459
460 let config = Config {
462 crypto: PrivateKey::from_seed(0),
463 namespace: TEST_NAMESPACE.to_vec(),
464 max_message_size: ONE_MEGABYTE,
465 synchrony_bound: Duration::from_secs(1),
466 max_handshake_age: Duration::from_secs(1),
467 handshake_timeout: Duration::from_secs(1),
468 };
469 let result = IncomingConnection::verify(&context, config, sink, stream).await;
470
471 assert!(matches!(result, Err(Error::UnableToDecode(_))));
473 });
474 }
475
476 #[test]
477 fn test_incoming_hello_verify_timeout() {
478 let executor = deterministic::Runner::default();
479 executor.start(|context| async move {
480 let (sink, _stream) = mocks::Channel::init();
482 let (_sink, stream) = mocks::Channel::init();
483
484 let config = Config {
486 crypto: PrivateKey::from_seed(1),
487 namespace: TEST_NAMESPACE.to_vec(),
488 max_message_size: ONE_MEGABYTE,
489 synchrony_bound: Duration::from_secs(1),
490 max_handshake_age: Duration::from_secs(1),
491 handshake_timeout: Duration::from_secs(1),
492 };
493 let result = IncomingConnection::verify(&context, config, sink, stream).await;
494
495 assert!(matches!(result, Err(Error::HandshakeTimeout)));
497 });
498 }
499
500 #[test]
501 fn test_hello_verify_invalid_signature() {
502 let executor = deterministic::Runner::default();
503 executor.start(|context| async move {
504 let mut sender = PrivateKey::from_seed(0);
505 let recipient = PrivateKey::from_seed(1);
506 let ephemeral_public_key = x25519::PublicKey::from_bytes([0u8; 32]);
507
508 let hello = Hello::sign(
510 &mut sender,
511 TEST_NAMESPACE,
512 Info {
513 timestamp: 0,
514 recipient: recipient.public_key(),
515 ephemeral_public_key,
516 },
517 );
518
519 let mut hello =
521 Hello::<edPublicKey>::decode(hello.encode()).expect("failed to decode hello");
522 hello.info.timestamp += 1;
523
524 let result = hello.verify(
526 &context,
527 &recipient.public_key(),
528 TEST_NAMESPACE,
529 Duration::from_secs(5),
530 Duration::from_secs(5),
531 );
532 assert!(matches!(result, Err(Error::InvalidSignature)));
533 });
534 }
535
536 #[test]
537 fn test_hello_verify_invalid_timestamp_old() {
538 let executor = deterministic::Runner::default();
539 executor.start(|context| async move {
540 let mut signer = PrivateKey::from_seed(0);
541 let recipient = PrivateKey::from_seed(1).public_key();
542 let ephemeral_public_key = x25519::PublicKey::from_bytes([0u8; 32]);
543
544 let timeout_duration = Duration::from_secs(5);
545 let synchrony_bound = Duration::from_secs(0);
546
547 let hello = Hello::sign(
549 &mut signer,
550 TEST_NAMESPACE,
551 Info {
552 timestamp: 0,
553 recipient: recipient.clone(),
554 ephemeral_public_key,
555 },
556 );
557
558 context.sleep(timeout_duration).await;
561
562 hello
564 .verify(
565 &context,
566 &recipient,
567 TEST_NAMESPACE,
568 synchrony_bound,
569 timeout_duration,
570 )
571 .unwrap();
572
573 context.sleep(Duration::from_millis(1)).await;
575
576 let result = hello.verify(
578 &context,
579 &recipient,
580 TEST_NAMESPACE,
581 synchrony_bound,
582 timeout_duration,
583 );
584 assert!(matches!(result, Err(Error::InvalidTimestampOld(t)) if t == 0));
585 });
586 }
587
588 #[test]
589 fn test_confirmation_create_and_verify() {
590 use chacha20poly1305::KeyInit;
591
592 let key = [1u8; 32];
593 let cipher = ChaCha20Poly1305::new(&key.into());
594 let transcript = b"test_transcript_data";
595
596 let confirmation = Confirmation::create(cipher, transcript).unwrap();
598
599 let cipher = ChaCha20Poly1305::new(&key.into());
601 confirmation.verify(cipher, transcript).unwrap();
602
603 let different_transcript = b"different_transcript_data";
605 let cipher = ChaCha20Poly1305::new(&key.into());
606 let result = confirmation.verify(cipher, different_transcript);
607 assert!(matches!(result, Err(Error::InvalidConfirmation)));
608
609 let different_key = [2u8; 32];
611 let different_cipher = ChaCha20Poly1305::new(&different_key.into());
612 let result = confirmation.verify(different_cipher, transcript);
613 assert!(matches!(result, Err(Error::InvalidConfirmation)));
614 }
615
616 #[test]
617 fn test_confirmation_encoding() {
618 use chacha20poly1305::KeyInit;
619 use commonware_codec::{DecodeExt, Encode};
620
621 let key = [1u8; 32];
622 let cipher = ChaCha20Poly1305::new(&key.into());
623 let transcript = b"test_transcript_for_encoding";
624
625 let original_confirmation = Confirmation::create(cipher, transcript).unwrap();
627 let encoded = original_confirmation.encode();
628
629 let decoded_confirmation = Confirmation::decode(encoded).unwrap();
631 let cipher = ChaCha20Poly1305::new(&key.into());
632 decoded_confirmation.verify(cipher, transcript).unwrap();
633 }
634
635 #[test]
636 fn test_hello_verify_invalid_timestamp_future() {
637 let executor = deterministic::Runner::default();
638 executor.start(|context| async move {
639 let mut signer = PrivateKey::from_seed(0);
640 let recipient = PrivateKey::from_seed(1).public_key();
641 let ephemeral_public_key = x25519::PublicKey::from_bytes([0u8; 32]);
642
643 let timeout_duration = Duration::from_secs(0);
644 const SYNCHRONY_BOUND_MILLIS: u64 = 5_000;
645 let synchrony_bound = Duration::from_millis(SYNCHRONY_BOUND_MILLIS);
646
647 let hello_ok = Hello::sign(
649 &mut signer,
650 TEST_NAMESPACE,
651 Info{
652 timestamp: SYNCHRONY_BOUND_MILLIS,
653 recipient: recipient.clone(),
654 ephemeral_public_key,
655 },
656 );
657
658 let hello_late = Hello::sign(
660 &mut signer,
661 TEST_NAMESPACE,
662 Info{
663 timestamp:SYNCHRONY_BOUND_MILLIS + 1,
664 recipient: recipient.clone(),
665 ephemeral_public_key,
666 },
667 );
668
669 hello_ok.verify(
671 &context,
672 &recipient,
673 TEST_NAMESPACE,
674 synchrony_bound,
675 timeout_duration,
676 ).unwrap(); let result = hello_late.verify(
680 &context,
681 &recipient,
682 TEST_NAMESPACE,
683 synchrony_bound,
684 timeout_duration,
685 );
686 assert!(matches!(result, Err(Error::InvalidTimestampFuture(t)) if t == SYNCHRONY_BOUND_MILLIS + 1));
687 });
688 }
689}