1use std::{
2 future::Future,
3 io::{Error as IoError, ErrorKind as IoErrorKind},
4 pin::Pin,
5 sync::Arc,
6 task::Poll,
7};
8
9use anyhow::{Context, Result as AnyhowResult, ensure};
10use bimap::BiMap;
11use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, future::poll_fn};
12use hotshot_types::traits::signature_key::SignatureKey;
13use libp2p::{
14 Transport,
15 core::{
16 StreamMuxer,
17 muxing::StreamMuxerExt,
18 transport::{DialOpts, TransportEvent},
19 },
20 identity::PeerId,
21};
22use parking_lot::Mutex;
23use pin_project::pin_project;
24use serde::{Deserialize, Serialize};
25use tokio::time::timeout;
26use tracing::debug;
27
28use crate::network::log_summary::LogEvent;
29
30const MAX_AUTH_MESSAGE_SIZE: usize = 1024;
33
34const AUTH_HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
38
39#[pin_project]
42pub struct ConsensusKeyAuthentication<
43 T: Transport,
44 S: SignatureKey + 'static,
45 C: StreamMuxer + Unpin,
46> {
47 #[pin]
48 pub inner: T,
50
51 pub auth_message: Arc<Option<Vec<u8>>>,
53
54 pub consensus_key_to_pid_map: Arc<Mutex<BiMap<S, PeerId>>>,
56
57 pd: std::marker::PhantomData<(C, S)>,
59}
60
61type UpgradeFuture<T> =
63 Pin<Box<dyn Future<Output = Result<<T as Transport>::Output, <T as Transport>::Error>> + Send>>;
64
65impl<T: Transport, S: SignatureKey + 'static, C: StreamMuxer + Unpin>
66 ConsensusKeyAuthentication<T, S, C>
67{
68 pub fn new(
72 inner: T,
73 auth_message: Option<Vec<u8>>,
74 consensus_key_to_pid_map: Arc<Mutex<BiMap<S, PeerId>>>,
75 ) -> Self {
76 Self {
77 inner,
78 auth_message: Arc::from(auth_message),
79 consensus_key_to_pid_map,
80 pd: std::marker::PhantomData,
81 }
82 }
83
84 pub async fn authenticate_with_remote_peer<W: AsyncWrite + Unpin>(
90 stream: &mut W,
91 auth_message: &[u8],
92 ) -> AnyhowResult<()> {
93 write_length_delimited(stream, auth_message).await?;
95
96 Ok(())
97 }
98
99 pub async fn verify_peer_authentication<R: AsyncReadExt + Unpin>(
113 stream: &mut R,
114 required_peer_id: &PeerId,
115 consensus_key_to_pid_map: Arc<Mutex<BiMap<S, PeerId>>>,
116 ) -> AnyhowResult<()> {
117 let message = read_length_delimited(stream, MAX_AUTH_MESSAGE_SIZE).await?;
119
120 let auth_message: AuthMessage<S> =
122 bincode::deserialize(&message).with_context(|| "Failed to deserialize auth message")?;
123
124 let public_key = auth_message
126 .validate()
127 .with_context(|| "Failed to verify authentication message")?;
128
129 let peer_id = PeerId::from_bytes(&auth_message.peer_id_bytes)
131 .with_context(|| "Failed to deserialize peer ID")?;
132
133 if peer_id != *required_peer_id {
135 return Err(anyhow::anyhow!("Peer ID mismatch"));
136 }
137
138 consensus_key_to_pid_map.lock().insert(public_key, peer_id);
140
141 Ok(())
142 }
143
144 fn gen_handshake<F: Future<Output = Result<T::Output, T::Error>> + Send + 'static>(
149 original_future: F,
150 outgoing: bool,
151 auth_message: Arc<Option<Vec<u8>>>,
152 consensus_key_to_pid_map: Arc<Mutex<BiMap<S, PeerId>>>,
153 ) -> UpgradeFuture<T>
154 where
155 T::Error: From<<C as StreamMuxer>::Error> + From<IoError>,
156 T::Output: AsOutput<C> + Send,
157 C::Substream: Unpin + Send,
158 {
159 Box::pin(async move {
161 let mut stream = original_future.await?;
163
164 timeout(AUTH_HANDSHAKE_TIMEOUT, async {
166 let mut substream = if outgoing {
169 poll_fn(|cx| stream.as_connection().poll_outbound_unpin(cx)).await?
170 } else {
171 poll_fn(|cx| stream.as_connection().poll_inbound_unpin(cx)).await?
172 };
173
174 if let Some(auth_message) = auth_message.as_ref() {
176 if outgoing {
177 Self::authenticate_with_remote_peer(&mut substream, auth_message)
179 .await
180 .map_err(|e| {
181 LogEvent::AuthFailure.record();
182 debug!("Failed to authenticate with remote peer: {e:?}");
183 IoError::other(e)
184 })?;
185
186 Self::verify_peer_authentication(
188 &mut substream,
189 stream.as_peer_id(),
190 consensus_key_to_pid_map,
191 )
192 .await
193 .map_err(|e| {
194 LogEvent::VerifyFailure.record();
195 debug!("Failed to verify remote peer: {e:?}");
196 IoError::other(e)
197 })?;
198 } else {
199 Self::verify_peer_authentication(
201 &mut substream,
202 stream.as_peer_id(),
203 consensus_key_to_pid_map,
204 )
205 .await
206 .map_err(|e| {
207 LogEvent::VerifyFailure.record();
208 debug!("Failed to verify remote peer: {e:?}");
209 IoError::other(e)
210 })?;
211
212 Self::authenticate_with_remote_peer(&mut substream, auth_message)
214 .await
215 .map_err(|e| {
216 LogEvent::AuthFailure.record();
217 debug!("Failed to authenticate with remote peer: {e:?}");
218 IoError::other(e)
219 })?;
220 }
221 }
222
223 Ok(stream)
224 })
225 .await
226 .map_err(|e| {
227 LogEvent::AuthHandshakeTimeout.record();
228 debug!("Timed out performing authentication handshake: {e:?}");
229 IoError::new(IoErrorKind::TimedOut, e)
230 })?
231 })
232 }
233}
234
235#[derive(Clone, Serialize, Deserialize)]
237struct AuthMessage<S: SignatureKey> {
238 #[serde(with = "serde_bytes")]
241 public_key_bytes: Vec<u8>,
242
243 #[serde(with = "serde_bytes")]
246 peer_id_bytes: Vec<u8>,
247
248 signature: S::PureAssembledSignatureType,
250}
251
252impl<S: SignatureKey> AuthMessage<S> {
253 pub fn validate(&self) -> AnyhowResult<S> {
255 let public_key = S::from_bytes(&self.public_key_bytes)
257 .with_context(|| "Failed to deserialize public key")?;
258
259 let mut signed_message = public_key.to_bytes();
261 signed_message.extend(self.peer_id_bytes.clone());
262
263 if !public_key.validate(&self.signature, &signed_message) {
265 return Err(anyhow::anyhow!("Invalid signature"));
266 }
267
268 Ok(public_key)
269 }
270}
271
272pub fn construct_auth_message<S: SignatureKey + 'static>(
278 public_key: &S,
279 peer_id: &PeerId,
280 private_key: &S::PrivateKey,
281) -> AnyhowResult<Vec<u8>> {
282 let mut public_key_bytes = public_key.to_bytes();
284
285 let peer_id_bytes = peer_id.to_bytes();
287 public_key_bytes.extend_from_slice(&peer_id_bytes);
288
289 let signature =
291 S::sign(private_key, &public_key_bytes).with_context(|| "Failed to sign public key")?;
292
293 let auth_message = AuthMessage::<S> {
295 public_key_bytes,
296 peer_id_bytes,
297 signature,
298 };
299
300 bincode::serialize(&auth_message).with_context(|| "Failed to serialize auth message")
302}
303
304impl<T: Transport, S: SignatureKey + 'static, C: StreamMuxer + Unpin> Transport
305 for ConsensusKeyAuthentication<T, S, C>
306where
307 T::Dial: Future<Output = Result<T::Output, T::Error>> + Send + 'static,
308 T::ListenerUpgrade: Send + 'static,
309 T::Output: AsOutput<C> + Send,
310 T::Error: From<<C as StreamMuxer>::Error> + From<IoError>,
311 C::Substream: Unpin + Send,
312{
313 type Dial = Pin<Box<dyn Future<Output = Result<T::Output, T::Error>> + Send>>;
315 type ListenerUpgrade = Pin<Box<dyn Future<Output = Result<T::Output, T::Error>> + Send>>;
316
317 type Output = T::Output;
319 type Error = T::Error;
320
321 fn dial(
324 &mut self,
325 addr: libp2p::Multiaddr,
326 opts: DialOpts,
327 ) -> Result<Self::Dial, libp2p::TransportError<Self::Error>> {
328 let res = self.inner.dial(addr, opts);
330
331 let auth_message = Arc::clone(&self.auth_message);
333
334 match res {
336 Ok(dial) => Ok(Self::gen_handshake(
337 dial,
338 true,
339 auth_message,
340 Arc::clone(&self.consensus_key_to_pid_map),
341 )),
342 Err(err) => Err(err),
343 }
344 }
345
346 fn poll(
350 mut self: std::pin::Pin<&mut Self>,
351 cx: &mut std::task::Context<'_>,
352 ) -> std::task::Poll<libp2p::core::transport::TransportEvent<Self::ListenerUpgrade, Self::Error>>
353 {
354 match Transport::poll(self.as_mut().project().inner, cx) {
355 Poll::Ready(event) => Poll::Ready(match event {
356 TransportEvent::Incoming {
358 listener_id,
359 upgrade,
360 local_addr,
361 send_back_addr,
362 } => {
363 let auth_message = Arc::clone(&self.auth_message);
365
366 let auth_upgrade = Self::gen_handshake(
368 upgrade,
369 false,
370 auth_message,
371 Arc::clone(&self.consensus_key_to_pid_map),
372 );
373
374 TransportEvent::Incoming {
376 listener_id,
377 upgrade: auth_upgrade,
378 local_addr,
379 send_back_addr,
380 }
381 },
382
383 TransportEvent::AddressExpired {
385 listener_id,
386 listen_addr,
387 } => TransportEvent::AddressExpired {
388 listener_id,
389 listen_addr,
390 },
391 TransportEvent::ListenerClosed {
392 listener_id,
393 reason,
394 } => TransportEvent::ListenerClosed {
395 listener_id,
396 reason,
397 },
398 TransportEvent::ListenerError { listener_id, error } => {
399 TransportEvent::ListenerError { listener_id, error }
400 },
401 TransportEvent::NewAddress {
402 listener_id,
403 listen_addr,
404 } => TransportEvent::NewAddress {
405 listener_id,
406 listen_addr,
407 },
408 }),
409
410 Poll::Pending => Poll::Pending,
411 }
412 }
413
414 fn remove_listener(&mut self, id: libp2p::core::transport::ListenerId) -> bool {
417 self.inner.remove_listener(id)
418 }
419 fn listen_on(
420 &mut self,
421 id: libp2p::core::transport::ListenerId,
422 addr: libp2p::Multiaddr,
423 ) -> Result<(), libp2p::TransportError<Self::Error>> {
424 self.inner.listen_on(id, addr)
425 }
426}
427
428trait AsOutput<C: StreamMuxer + Unpin> {
431 fn as_connection(&mut self) -> &mut C;
433
434 fn as_peer_id(&mut self) -> &mut PeerId;
436}
437
438impl<C: StreamMuxer + Unpin> AsOutput<C> for (PeerId, C) {
441 fn as_connection(&mut self) -> &mut C {
443 &mut self.1
444 }
445
446 fn as_peer_id(&mut self) -> &mut PeerId {
448 &mut self.0
449 }
450}
451
452pub async fn read_length_delimited<S: AsyncRead + Unpin>(
459 stream: &mut S,
460 max_size: usize,
461) -> AnyhowResult<Vec<u8>> {
462 let mut len_bytes = [0u8; 4];
464 stream
465 .read_exact(&mut len_bytes)
466 .await
467 .with_context(|| "Failed to read message length")?;
468
469 let len = usize::try_from(u32::from_be_bytes(len_bytes))?;
471
472 ensure!(len <= max_size, "Message too large");
474
475 let mut message = vec![0u8; len];
477 stream
478 .read_exact(&mut message)
479 .await
480 .with_context(|| "Failed to read message")?;
481
482 Ok(message)
483}
484
485pub async fn write_length_delimited<S: AsyncWrite + Unpin>(
490 stream: &mut S,
491 message: &[u8],
492) -> AnyhowResult<()> {
493 stream
495 .write_all(&u32::try_from(message.len())?.to_be_bytes())
496 .await
497 .with_context(|| "Failed to write message length")?;
498
499 stream
501 .write_all(message)
502 .await
503 .with_context(|| "Failed to write message")?;
504
505 Ok(())
506}
507
508#[cfg(test)]
509mod test {
510 use hotshot_types::{signature_key::BLSPubKey, traits::signature_key::SignatureKey};
511 use libp2p::{core::transport::dummy::DummyTransport, quic::Connection};
512 use rand::Rng;
513
514 use super::*;
515
516 type MockStakeTableAuth = ConsensusKeyAuthentication<DummyTransport, BLSPubKey, Connection>;
518
519 macro_rules! new_identity {
521 () => {{
522 let seed = rand::rngs::OsRng.r#gen::<[u8; 32]>();
524
525 let keypair = BLSPubKey::generated_from_seed_indexed(seed, 1337);
527
528 let peer_id = libp2p::identity::Keypair::generate_ed25519()
530 .public()
531 .to_peer_id();
532
533 let auth_message =
535 super::construct_auth_message(&keypair.0, &peer_id, &keypair.1).unwrap();
536
537 (keypair, peer_id, auth_message)
538 }};
539 }
540
541 macro_rules! cursor_from {
543 ($auth_message:expr) => {{
544 let mut stream = futures::io::Cursor::new(vec![]);
545 write_length_delimited(&mut stream, &$auth_message)
546 .await
547 .expect("Failed to write message");
548 stream.set_position(0);
549 stream
550 }};
551 }
552
553 #[test]
555 fn signature_verify() {
556 let (_, _, auth_message) = new_identity!();
558
559 let public_key = super::AuthMessage::<BLSPubKey>::validate(
561 &bincode::deserialize(&auth_message).unwrap(),
562 );
563 assert!(public_key.is_ok());
564 }
565
566 #[test]
569 fn signature_verify_invalid_public_key() {
570 let (_, _, auth_message) = new_identity!();
572
573 let mut auth_message: super::AuthMessage<BLSPubKey> =
575 bincode::deserialize(&auth_message).unwrap();
576
577 auth_message.public_key_bytes[0] ^= 0x01;
579
580 let auth_message = bincode::serialize(&auth_message).unwrap();
582
583 let public_key = super::AuthMessage::<BLSPubKey>::validate(
585 &bincode::deserialize(&auth_message).unwrap(),
586 );
587 assert!(public_key.is_err());
588 }
589
590 #[test]
593 fn signature_verify_invalid_peer_id() {
594 let (_, _, auth_message) = new_identity!();
596
597 let mut auth_message: super::AuthMessage<BLSPubKey> =
599 bincode::deserialize(&auth_message).unwrap();
600
601 auth_message.peer_id_bytes[0] ^= 0x01;
603
604 let auth_message = bincode::serialize(&auth_message).unwrap();
606
607 let public_key = super::AuthMessage::<BLSPubKey>::validate(
609 &bincode::deserialize(&auth_message).unwrap(),
610 );
611 assert!(public_key.is_err());
612 }
613
614 #[tokio::test(flavor = "multi_thread")]
615 async fn valid_authentication() {
616 let (keypair, peer_id, auth_message) = new_identity!();
618
619 let mut stream = cursor_from!(auth_message);
621
622 let consensus_key_to_pid_map = Arc::new(parking_lot::Mutex::new(BiMap::new()));
624
625 let result = MockStakeTableAuth::verify_peer_authentication(
627 &mut stream,
628 &peer_id,
629 Arc::clone(&consensus_key_to_pid_map),
630 )
631 .await;
632
633 assert!(
635 consensus_key_to_pid_map
636 .lock()
637 .get_by_left(&keypair.0)
638 .unwrap()
639 == &peer_id,
640 "Map does not have the correct entry"
641 );
642
643 assert!(
644 result.is_ok(),
645 "Should have passed authentication but did not"
646 );
647 }
648
649 #[tokio::test(flavor = "multi_thread")]
650 async fn peer_id_mismatch() {
651 let (_, _, auth_message) = new_identity!();
653
654 let (_, malicious_peer_id, _) = new_identity!();
656
657 let mut stream = cursor_from!(auth_message);
659
660 let consensus_key_to_pid_map = Arc::new(parking_lot::Mutex::new(BiMap::new()));
662
663 let result = MockStakeTableAuth::verify_peer_authentication(
665 &mut stream,
666 &malicious_peer_id,
667 Arc::clone(&consensus_key_to_pid_map),
668 )
669 .await;
670
671 assert!(
673 result
674 .expect_err("Should have failed authentication but did not")
675 .to_string()
676 .contains("Peer ID mismatch"),
677 "Did not fail with the correct error"
678 );
679
680 assert!(
682 consensus_key_to_pid_map.lock().is_empty(),
683 "Malicious peer ID should not be in the map"
684 );
685 }
686
687 #[tokio::test(flavor = "multi_thread")]
688 async fn read_and_write_length_delimited() {
689 let message = b"Hello, world!";
691
692 let mut buffer = Vec::new();
694 write_length_delimited(&mut buffer, message).await.unwrap();
695
696 let read_message = read_length_delimited(&mut buffer.as_slice(), 1024)
698 .await
699 .unwrap();
700
701 assert_eq!(message, read_message.as_slice());
703 }
704}