use hopr_crypto_types::prelude::*;
use hopr_primitive_types::prelude::*;
use multiaddr::Multiaddr;
use std::fmt::{Display, Formatter};
use tracing::debug;
#[derive(Clone, Debug, PartialEq)]
pub struct KeyBinding {
pub chain_key: Address,
pub packet_key: OffchainPublicKey,
pub signature: OffchainSignature,
}
impl KeyBinding {
const SIGNING_SIZE: usize = 16 + Address::SIZE + OffchainPublicKey::SIZE;
fn prepare_for_signing(chain_key: &Address, packet_key: &OffchainPublicKey) -> [u8; Self::SIGNING_SIZE] {
let mut to_sign = [0u8; Self::SIGNING_SIZE];
to_sign[0..16].copy_from_slice(b"HOPR_KEY_BINDING");
to_sign[16..36].copy_from_slice(chain_key.as_ref());
to_sign[36..].copy_from_slice(packet_key.as_ref());
to_sign
}
pub fn new(chain_key: Address, packet_key: &OffchainKeypair) -> Self {
let to_sign = Self::prepare_for_signing(&chain_key, packet_key.public());
Self {
chain_key,
packet_key: *packet_key.public(),
signature: OffchainSignature::sign_message(&to_sign, packet_key),
}
}
}
impl KeyBinding {
pub fn from_parts(
chain_key: Address,
packet_key: OffchainPublicKey,
signature: OffchainSignature,
) -> crate::errors::Result<Self> {
let to_verify = Self::prepare_for_signing(&chain_key, &packet_key);
signature
.verify_message(&to_verify, &packet_key)
.then_some(Self {
chain_key,
packet_key,
signature,
})
.ok_or(CryptoError::SignatureVerification.into())
}
}
impl Display for KeyBinding {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "keybinding {} <-> {}", self.chain_key, self.packet_key)
}
}
pub fn decapsulate_multiaddress(multiaddr: Multiaddr) -> Multiaddr {
multiaddr
.into_iter()
.take_while(|p| !matches!(p, multiaddr::Protocol::P2p(_)))
.collect()
}
#[derive(Clone, Debug, PartialEq)]
pub struct AnnouncementData {
multiaddress: Multiaddr,
pub key_binding: Option<KeyBinding>,
}
impl AnnouncementData {
pub fn new(multiaddress: Multiaddr, key_binding: Option<KeyBinding>) -> Result<Self, GeneralError> {
if multiaddress.is_empty() {
debug!("Received empty multiaddr");
return Err(GeneralError::InvalidInput);
}
if let Some(binding) = &key_binding {
match multiaddress.with_p2p(binding.packet_key.into()) {
Ok(mut multiaddress) => {
multiaddress.pop();
Ok(Self {
multiaddress,
key_binding,
})
}
Err(multiaddress) => Err(GeneralError::NonSpecificError(format!(
"{multiaddress} does not match the keybinding {} peer id",
binding.packet_key.to_peerid_str()
))),
}
} else {
Ok(Self {
multiaddress: multiaddress.to_owned(),
key_binding: None,
})
}
}
pub fn multiaddress(&self) -> &Multiaddr {
&self.multiaddress
}
}
impl Display for AnnouncementData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(binding) = &self.key_binding {
write!(f, "announcement of {} with {binding}", self.multiaddress)
} else {
write!(f, "announcement of {}", self.multiaddress)
}
}
}
#[cfg(test)]
mod tests {
use crate::announcement::{AnnouncementData, KeyBinding};
use crate::prelude::decapsulate_multiaddress;
use hex_literal::hex;
use hopr_crypto_types::keypairs::{Keypair, OffchainKeypair};
use hopr_primitive_types::primitives::Address;
use multiaddr::Multiaddr;
lazy_static::lazy_static! {
static ref KEY_PAIR: OffchainKeypair = OffchainKeypair::from_secret(&hex!("60741b83b99e36aa0c1331578156e16b8e21166d01834abb6c64b103f885734d")).expect("lazy static keypair should be constructible");
static ref CHAIN_ADDR: Address = Address::try_from(hex!("78392d47e3522219e2802e7d6c45ee84b5d5c185").as_ref()).expect("lazy static address should be constructible");
static ref SECOND_KEY_PAIR: OffchainKeypair = OffchainKeypair::from_secret(&hex!("c24bd833704dd2abdae3933fcc9962c2ac404f84132224c474147382d4db2299")).expect("lazy static keypair should be constructible");
}
#[test]
fn test_key_binding() -> anyhow::Result<()> {
let kb_1 = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
let kb_2 = KeyBinding::from_parts(kb_1.chain_key, kb_1.packet_key, kb_1.signature.clone())?;
assert_eq!(kb_1, kb_2, "must be equal");
Ok(())
}
#[test]
fn test_announcement() -> anyhow::Result<()> {
let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
let peer_id = KEY_PAIR.public().to_peerid_str();
for (ma_str, decapsulated_ma_str) in vec![
(
format!("/ip4/127.0.0.1/tcp/10000/p2p/{peer_id}"),
"/ip4/127.0.0.1/tcp/10000".to_string(),
),
(
format!("/ip6/::1/tcp/10000/p2p/{peer_id}"),
"/ip6/::1/tcp/10000".to_string(),
),
(
format!("/dns4/hoprnet.org/tcp/10000/p2p/{peer_id}"),
"/dns4/hoprnet.org/tcp/10000".to_string(),
),
(
format!("/dns6/hoprnet.org/tcp/10000/p2p/{peer_id}"),
"/dns6/hoprnet.org/tcp/10000".to_string(),
),
(
format!("/ip4/127.0.0.1/udp/10000/quic/p2p/{peer_id}"),
"/ip4/127.0.0.1/udp/10000/quic".to_string(),
),
] {
let maddr: Multiaddr = ma_str.parse()?;
let ad = AnnouncementData::new(maddr, Some(key_binding.clone()))?;
assert_eq!(decapsulated_ma_str, ad.multiaddress().to_string());
assert_eq!(Some(key_binding.clone()), ad.key_binding);
}
Ok(())
}
#[test]
fn test_announcement_no_keybinding() -> anyhow::Result<()> {
let maddr: Multiaddr = "/ip4/127.0.0.1/tcp/10000".to_string().parse()?;
let ad = AnnouncementData::new(maddr, None)?;
assert_eq!(None, ad.key_binding);
Ok(())
}
#[test]
fn test_announcement_decapsulated_ma() -> anyhow::Result<()> {
let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
let maddr: Multiaddr = "/ip4/127.0.0.1/tcp/10000".to_string().parse()?;
let ad = AnnouncementData::new(maddr, Some(key_binding.clone()))?;
assert_eq!("/ip4/127.0.0.1/tcp/10000", ad.multiaddress().to_string());
assert_eq!(Some(key_binding), ad.key_binding);
Ok(())
}
#[test]
fn test_announcement_wrong_peerid() -> anyhow::Result<()> {
let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
let peer_id = SECOND_KEY_PAIR.public().to_peerid_str();
let maddr: Multiaddr = format!("/ip4/127.0.0.1/tcp/10000/p2p/{peer_id}").parse()?;
assert!(AnnouncementData::new(maddr, Some(key_binding.clone())).is_err());
Ok(())
}
#[test]
fn test_decapsulate_multiaddr() -> anyhow::Result<()> {
let maddr_1: Multiaddr = "/ip4/127.0.0.1/tcp/10000".parse()?;
let maddr_2 = maddr_1
.clone()
.with_p2p(OffchainKeypair::random().public().into())
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
assert_eq!(maddr_1, decapsulate_multiaddress(maddr_2), "multiaddresses must match");
assert_eq!(
maddr_1,
decapsulate_multiaddress(maddr_1.clone()),
"decapsulation must be idempotent"
);
Ok(())
}
}