Skip to main content

hypercore_protocol/crypto/
handshake.rs

1//! Handshake result and capability verification for hypercore replication.
2//!
3//! This module handles capability verification using the handshake hash from
4//! the underlying encrypted connection (e.g., from hyperswarm/hyperdht).
5
6use blake2::{
7    Blake2bMac,
8    digest::{FixedOutput, Update, typenum::U32},
9};
10use std::io::{Error, ErrorKind, Result};
11
12// These the output of, see `hash_namespace` test below for how they are produced
13// https://github.com/hypercore-protocol/hypercore/blob/70b271643c4e4b1e5ecae5bb579966dfe6361ff3/lib/caps.js#L9
14const REPLICATE_INITIATOR: [u8; 32] = [
15    0x51, 0x81, 0x2A, 0x2A, 0x35, 0x9B, 0x50, 0x36, 0x95, 0x36, 0x77, 0x5D, 0xF8, 0x9E, 0x18, 0xE4,
16    0x77, 0x40, 0xF3, 0xDB, 0x72, 0xAC, 0xA, 0xE7, 0xB, 0x29, 0x59, 0x4C, 0x19, 0x4D, 0xC3, 0x16,
17];
18const REPLICATE_RESPONDER: [u8; 32] = [
19    0x4, 0x38, 0x49, 0x2D, 0x2, 0x97, 0xC, 0xC1, 0x35, 0x28, 0xAC, 0x2, 0x62, 0xBC, 0xA0, 0x7,
20    0x4E, 0x9, 0x26, 0x26, 0x2, 0x56, 0x86, 0x5A, 0xCC, 0xC0, 0xBF, 0x15, 0xBD, 0x79, 0x12, 0x7D,
21];
22
23/// Result of a Noise handshake, used for capability verification.
24///
25/// When using hypercore-protocol with hyperswarm, the Noise handshake is performed
26/// at the transport layer (hyperdht). This struct holds the information needed
27/// for capability verification when opening channels.
28#[derive(Debug, Clone, Default)]
29pub struct HandshakeResult {
30    pub(crate) is_initiator: bool,
31    /// Local public key (32 bytes)
32    pub local_pubkey: Vec<u8>,
33    /// Remote public key (32 bytes)
34    pub remote_pubkey: Vec<u8>,
35    pub(crate) handshake_hash: Vec<u8>,
36}
37
38impl HandshakeResult {
39    /// Create a HandshakeResult for a pre-encrypted connection.
40    ///
41    /// This is used when the Noise handshake was performed at a lower layer
42    /// (e.g., hyperswarm/hyperdht) and we're reusing the encrypted channel.
43    /// The handshake_hash is used for capability verification.
44    ///
45    /// # Arguments
46    /// * `is_initiator` - Whether this peer initiated the connection
47    /// * `local_pubkey` - This peer's Noise public key
48    /// * `remote_pubkey` - The remote peer's Noise public key
49    /// * `handshake_hash` - The 64-byte handshake hash from the Noise handshake
50    pub fn from_pre_encrypted(
51        is_initiator: bool,
52        local_pubkey: [u8; 32],
53        remote_pubkey: [u8; 32],
54        handshake_hash: Vec<u8>,
55    ) -> Self {
56        Self {
57            is_initiator,
58            local_pubkey: local_pubkey.to_vec(),
59            remote_pubkey: remote_pubkey.to_vec(),
60            handshake_hash,
61        }
62    }
63
64    /// Compute capability for opening a channel with the given key.
65    pub(crate) fn capability(&self, key: &[u8]) -> Option<Vec<u8>> {
66        Some(replicate_capability(
67            self.is_initiator,
68            key,
69            &self.handshake_hash,
70        ))
71    }
72
73    /// Compute expected remote capability for the given key.
74    pub(crate) fn remote_capability(&self, key: &[u8]) -> Option<Vec<u8>> {
75        Some(replicate_capability(
76            !self.is_initiator,
77            key,
78            &self.handshake_hash,
79        ))
80    }
81
82    /// Verify a remote peer's capability for opening a channel.
83    pub(crate) fn verify_remote_capability(
84        &self,
85        capability: Option<Vec<u8>>,
86        key: &[u8],
87    ) -> Result<()> {
88        let expected_capability = self.remote_capability(key);
89        match (capability, expected_capability) {
90            (Some(c1), Some(c2)) if c1 == c2 => Ok(()),
91            (None, None) => Err(Error::new(
92                ErrorKind::PermissionDenied,
93                "Missing capabilities for verification",
94            )),
95            _ => Err(Error::new(
96                ErrorKind::PermissionDenied,
97                "Invalid remote channel capability",
98            )),
99        }
100    }
101}
102
103/// Create a hash used to indicate replication capability.
104/// See JavaScript [here](https://github.com/hypercore-protocol/hypercore/blob/70b271643c4e4b1e5ecae5bb579966dfe6361ff3/lib/caps.js#L11).
105fn replicate_capability(is_initiator: bool, key: &[u8], handshake_hash: &[u8]) -> Vec<u8> {
106    let seed = if is_initiator {
107        REPLICATE_INITIATOR
108    } else {
109        REPLICATE_RESPONDER
110    };
111
112    let mut hasher =
113        Blake2bMac::<U32>::new_with_salt_and_personal(handshake_hash, &[], &[]).unwrap();
114    hasher.update(&seed);
115    hasher.update(key);
116    let hash = hasher.finalize_fixed();
117
118    hash.as_slice().to_vec()
119}