1use std::fmt::{Display, Formatter};
2
3use hopr_crypto_types::prelude::*;
4use hopr_primitive_types::prelude::*;
5use multiaddr::Multiaddr;
6use tracing::error;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct KeyBinding {
16 pub chain_key: Address,
17 pub packet_key: OffchainPublicKey,
18 pub signature: OffchainSignature,
19}
20
21impl KeyBinding {
22 const SIGNING_SIZE: usize = 16 + Address::SIZE + OffchainPublicKey::SIZE;
23
24 fn prepare_for_signing(chain_key: &Address, packet_key: &OffchainPublicKey) -> [u8; Self::SIGNING_SIZE] {
25 let mut to_sign = [0u8; Self::SIGNING_SIZE];
26 to_sign[0..16].copy_from_slice(b"HOPR_KEY_BINDING");
27 to_sign[16..36].copy_from_slice(chain_key.as_ref());
28 to_sign[36..].copy_from_slice(packet_key.as_ref());
29 to_sign
30 }
31
32 pub fn new(chain_key: Address, packet_key: &OffchainKeypair) -> Self {
34 let to_sign = Self::prepare_for_signing(&chain_key, packet_key.public());
35 Self {
36 chain_key,
37 packet_key: *packet_key.public(),
38 signature: OffchainSignature::sign_message(&to_sign, packet_key),
39 }
40 }
41}
42
43impl KeyBinding {
44 pub fn from_parts(
47 chain_key: Address,
48 packet_key: OffchainPublicKey,
49 signature: OffchainSignature,
50 ) -> crate::errors::Result<Self> {
51 let to_verify = Self::prepare_for_signing(&chain_key, &packet_key);
52 signature
53 .verify_message(&to_verify, &packet_key)
54 .then_some(Self {
55 chain_key,
56 packet_key,
57 signature,
58 })
59 .ok_or(CryptoError::SignatureVerification.into())
60 }
61}
62
63impl Display for KeyBinding {
64 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
65 write!(f, "keybinding {} <-> {}", self.chain_key, self.packet_key)
66 }
67}
68
69pub fn decapsulate_multiaddress(multiaddr: Multiaddr) -> Multiaddr {
72 multiaddr
73 .into_iter()
74 .take_while(|p| !matches!(p, multiaddr::Protocol::P2p(_)))
75 .collect()
76}
77
78#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct AnnouncementData {
85 multiaddress: Option<Multiaddr>,
86 key_binding: KeyBinding,
87}
88
89impl AnnouncementData {
90 pub fn new(key_binding: KeyBinding, multiaddress: Option<Multiaddr>) -> Result<Self, GeneralError> {
96 if let Some(multiaddress) = multiaddress {
97 if multiaddress.is_empty() {
98 error!("Received empty multiaddr");
99 return Err(GeneralError::InvalidInput);
100 }
101 match multiaddress.with_p2p(key_binding.packet_key.into()) {
103 Ok(mut multiaddress) => {
104 multiaddress.pop();
107 Ok(Self {
108 multiaddress: Some(multiaddress),
109 key_binding,
110 })
111 }
112 Err(multiaddress) => Err(GeneralError::NonSpecificError(format!(
113 "{multiaddress} does not match the keybinding {} peer id",
114 key_binding.packet_key.to_peerid_str()
115 ))),
116 }
117 } else {
118 Ok(Self {
119 multiaddress: None,
120 key_binding,
121 })
122 }
123 }
124
125 pub fn multiaddress(&self) -> Option<&Multiaddr> {
129 self.multiaddress.as_ref()
130 }
131
132 pub fn key_binding(&self) -> &KeyBinding {
134 &self.key_binding
135 }
136}
137
138impl Display for AnnouncementData {
139 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
140 if let Some(multiaddr) = &self.multiaddress {
141 write!(f, "announcement of {multiaddr} with {}", self.key_binding)
142 } else {
143 write!(f, "announcement of key binding only {}", self.key_binding)
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use hex_literal::hex;
151 use hopr_crypto_types::keypairs::{Keypair, OffchainKeypair};
152 use hopr_primitive_types::primitives::Address;
153 use multiaddr::Multiaddr;
154
155 use crate::{
156 announcement::{AnnouncementData, KeyBinding},
157 prelude::decapsulate_multiaddress,
158 };
159
160 lazy_static::lazy_static! {
161 static ref KEY_PAIR: OffchainKeypair = OffchainKeypair::from_secret(&hex!("60741b83b99e36aa0c1331578156e16b8e21166d01834abb6c64b103f885734d")).expect("lazy static keypair should be constructible");
162 static ref CHAIN_ADDR: Address = Address::try_from(hex!("78392d47e3522219e2802e7d6c45ee84b5d5c185").as_ref()).expect("lazy static address should be constructible");
163 static ref SECOND_KEY_PAIR: OffchainKeypair = OffchainKeypair::from_secret(&hex!("c24bd833704dd2abdae3933fcc9962c2ac404f84132224c474147382d4db2299")).expect("lazy static keypair should be constructible");
164 }
165
166 #[test]
167 fn test_key_binding() -> anyhow::Result<()> {
168 let kb_1 = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
169 let kb_2 = KeyBinding::from_parts(kb_1.chain_key, kb_1.packet_key, kb_1.signature.clone())?;
170
171 assert_eq!(kb_1, kb_2, "must be equal");
172
173 Ok(())
174 }
175
176 #[test]
177 fn test_announcement() -> anyhow::Result<()> {
178 let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
179 let peer_id = KEY_PAIR.public().to_peerid_str();
180
181 for (ma_str, decapsulated_ma_str) in vec![
182 (
183 format!("/ip4/127.0.0.1/tcp/10000/p2p/{peer_id}"),
184 "/ip4/127.0.0.1/tcp/10000".to_string(),
185 ),
186 (
187 format!("/ip6/::1/tcp/10000/p2p/{peer_id}"),
188 "/ip6/::1/tcp/10000".to_string(),
189 ),
190 (
191 format!("/dns4/hoprnet.org/tcp/10000/p2p/{peer_id}"),
192 "/dns4/hoprnet.org/tcp/10000".to_string(),
193 ),
194 (
195 format!("/dns6/hoprnet.org/tcp/10000/p2p/{peer_id}"),
196 "/dns6/hoprnet.org/tcp/10000".to_string(),
197 ),
198 (
199 format!("/ip4/127.0.0.1/udp/10000/quic/p2p/{peer_id}"),
200 "/ip4/127.0.0.1/udp/10000/quic".to_string(),
201 ),
202 ] {
203 let maddr: Multiaddr = ma_str.parse()?;
204
205 let ad = AnnouncementData::new(key_binding.clone(), Some(maddr))?;
206 assert_eq!(Some(decapsulated_ma_str), ad.multiaddress().map(|m| m.to_string()));
207 assert_eq!(&key_binding, ad.key_binding());
208 }
209
210 Ok(())
211 }
212
213 #[test]
214 fn test_announcement_no_keybinding() -> anyhow::Result<()> {
215 let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
216 let ad = AnnouncementData::new(key_binding, None)?;
217
218 assert_eq!(None, ad.multiaddress());
219
220 Ok(())
221 }
222
223 #[test]
224 fn test_announcement_decapsulated_ma() -> anyhow::Result<()> {
225 let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
226 let maddr: Multiaddr = "/ip4/127.0.0.1/tcp/10000".to_string().parse()?;
227
228 let ad = AnnouncementData::new(key_binding.clone(), Some(maddr))?;
229 assert_eq!(
230 Some("/ip4/127.0.0.1/tcp/10000".to_string()),
231 ad.multiaddress().map(|m| m.to_string())
232 );
233 assert_eq!(&key_binding, ad.key_binding());
234
235 Ok(())
236 }
237
238 #[test]
239 fn test_announcement_wrong_peerid() -> anyhow::Result<()> {
240 let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
241 let peer_id = SECOND_KEY_PAIR.public().to_peerid_str();
242 let maddr: Multiaddr = format!("/ip4/127.0.0.1/tcp/10000/p2p/{peer_id}").parse()?;
243
244 assert!(AnnouncementData::new(key_binding, Some(maddr)).is_err());
245
246 Ok(())
247 }
248
249 #[test]
250 fn test_decapsulate_multiaddr() -> anyhow::Result<()> {
251 let maddr_1: Multiaddr = "/ip4/127.0.0.1/tcp/10000".parse()?;
252 let maddr_2 = maddr_1
253 .clone()
254 .with_p2p(OffchainKeypair::random().public().into())
255 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
256
257 assert_eq!(maddr_1, decapsulate_multiaddress(maddr_2), "multiaddresses must match");
258 assert_eq!(
259 maddr_1,
260 decapsulate_multiaddress(maddr_1.clone()),
261 "decapsulation must be idempotent"
262 );
263
264 Ok(())
265 }
266}