use std::fmt::{Display, Formatter};
use crate::crypto::prelude::*;
use crate::primitive::prelude::*;
use multiaddr::Multiaddr;
use tracing::error;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
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::internal::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, Eq)]
pub struct AnnouncementData {
multiaddress: Option<Multiaddr>,
key_binding: KeyBinding,
}
impl AnnouncementData {
pub fn new(
key_binding: KeyBinding,
multiaddress: Option<Multiaddr>,
) -> Result<Self, GeneralError> {
if let Some(multiaddress) = multiaddress {
if multiaddress.is_empty() {
error!("Received empty multiaddr");
return Err(GeneralError::InvalidInput);
}
match multiaddress.with_p2p(key_binding.packet_key.into()) {
Ok(mut multiaddress) => {
multiaddress.pop();
Ok(Self {
multiaddress: Some(multiaddress),
key_binding,
})
}
Err(multiaddress) => Err(GeneralError::NonSpecificError(format!(
"{multiaddress} does not match the keybinding {} peer id",
key_binding.packet_key.to_peerid_str()
))),
}
} else {
Ok(Self {
multiaddress: None,
key_binding,
})
}
}
pub fn multiaddress(&self) -> Option<&Multiaddr> {
self.multiaddress.as_ref()
}
pub fn key_binding(&self) -> &KeyBinding {
&self.key_binding
}
}
impl Display for AnnouncementData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(multiaddr) = &self.multiaddress {
write!(f, "announcement of {multiaddr} with {}", self.key_binding)
} else {
write!(f, "announcement of key binding only {}", self.key_binding)
}
}
}
#[cfg(test)]
mod tests {
use crate::crypto::keypairs::{Keypair, OffchainKeypair};
use crate::primitive::primitives::Address;
use hex_literal::hex;
use multiaddr::Multiaddr;
use crate::internal::{
announcement::{AnnouncementData, KeyBinding},
prelude::decapsulate_multiaddress,
};
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)?;
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 [
(
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(key_binding, Some(maddr))?;
assert_eq!(
Some(decapsulated_ma_str),
ad.multiaddress().map(|m| m.to_string())
);
assert_eq!(&key_binding, ad.key_binding());
}
Ok(())
}
#[test]
fn test_announcement_no_keybinding() -> anyhow::Result<()> {
let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
let ad = AnnouncementData::new(key_binding, None)?;
assert_eq!(None, ad.multiaddress());
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(key_binding, Some(maddr))?;
assert_eq!(
Some("/ip4/127.0.0.1/tcp/10000".to_string()),
ad.multiaddress().map(|m| m.to_string())
);
assert_eq!(&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(key_binding, Some(maddr)).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(())
}
}