hopr_internal_types/
announcement.rs1use std::fmt::{Display, Formatter};
2
3use hopr_crypto_types::prelude::*;
4use hopr_primitive_types::prelude::*;
5use multiaddr::Multiaddr;
6use tracing::debug;
7
8#[derive(Clone, Debug, PartialEq)]
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)]
84pub struct AnnouncementData {
85 multiaddress: Multiaddr,
86 pub key_binding: Option<KeyBinding>,
87}
88
89impl AnnouncementData {
90 pub fn new(multiaddress: Multiaddr, key_binding: Option<KeyBinding>) -> Result<Self, GeneralError> {
95 if multiaddress.is_empty() {
96 debug!("Received empty multiaddr");
97 return Err(GeneralError::InvalidInput);
98 }
99
100 if let Some(binding) = &key_binding {
101 match multiaddress.with_p2p(binding.packet_key.into()) {
103 Ok(mut multiaddress) => {
104 multiaddress.pop();
107 Ok(Self {
108 multiaddress,
109 key_binding,
110 })
111 }
112 Err(multiaddress) => Err(GeneralError::NonSpecificError(format!(
113 "{multiaddress} does not match the keybinding {} peer id",
114 binding.packet_key.to_peerid_str()
115 ))),
116 }
117 } else {
118 Ok(Self {
119 multiaddress: multiaddress.to_owned(),
120 key_binding: None,
121 })
122 }
123 }
124
125 pub fn multiaddress(&self) -> &Multiaddr {
128 &self.multiaddress
129 }
130}
131
132impl Display for AnnouncementData {
133 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
134 if let Some(binding) = &self.key_binding {
135 write!(f, "announcement of {} with {binding}", self.multiaddress)
136 } else {
137 write!(f, "announcement of {}", self.multiaddress)
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use hex_literal::hex;
145 use hopr_crypto_types::keypairs::{Keypair, OffchainKeypair};
146 use hopr_primitive_types::primitives::Address;
147 use multiaddr::Multiaddr;
148
149 use crate::{
150 announcement::{AnnouncementData, KeyBinding},
151 prelude::decapsulate_multiaddress,
152 };
153
154 lazy_static::lazy_static! {
155 static ref KEY_PAIR: OffchainKeypair = OffchainKeypair::from_secret(&hex!("60741b83b99e36aa0c1331578156e16b8e21166d01834abb6c64b103f885734d")).expect("lazy static keypair should be constructible");
156 static ref CHAIN_ADDR: Address = Address::try_from(hex!("78392d47e3522219e2802e7d6c45ee84b5d5c185").as_ref()).expect("lazy static address should be constructible");
157 static ref SECOND_KEY_PAIR: OffchainKeypair = OffchainKeypair::from_secret(&hex!("c24bd833704dd2abdae3933fcc9962c2ac404f84132224c474147382d4db2299")).expect("lazy static keypair should be constructible");
158 }
159
160 #[test]
161 fn test_key_binding() -> anyhow::Result<()> {
162 let kb_1 = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
163 let kb_2 = KeyBinding::from_parts(kb_1.chain_key, kb_1.packet_key, kb_1.signature.clone())?;
164
165 assert_eq!(kb_1, kb_2, "must be equal");
166
167 Ok(())
168 }
169
170 #[test]
171 fn test_announcement() -> anyhow::Result<()> {
172 let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
173 let peer_id = KEY_PAIR.public().to_peerid_str();
174
175 for (ma_str, decapsulated_ma_str) in vec![
176 (
177 format!("/ip4/127.0.0.1/tcp/10000/p2p/{peer_id}"),
178 "/ip4/127.0.0.1/tcp/10000".to_string(),
179 ),
180 (
181 format!("/ip6/::1/tcp/10000/p2p/{peer_id}"),
182 "/ip6/::1/tcp/10000".to_string(),
183 ),
184 (
185 format!("/dns4/hoprnet.org/tcp/10000/p2p/{peer_id}"),
186 "/dns4/hoprnet.org/tcp/10000".to_string(),
187 ),
188 (
189 format!("/dns6/hoprnet.org/tcp/10000/p2p/{peer_id}"),
190 "/dns6/hoprnet.org/tcp/10000".to_string(),
191 ),
192 (
193 format!("/ip4/127.0.0.1/udp/10000/quic/p2p/{peer_id}"),
194 "/ip4/127.0.0.1/udp/10000/quic".to_string(),
195 ),
196 ] {
197 let maddr: Multiaddr = ma_str.parse()?;
198
199 let ad = AnnouncementData::new(maddr, Some(key_binding.clone()))?;
200 assert_eq!(decapsulated_ma_str, ad.multiaddress().to_string());
201 assert_eq!(Some(key_binding.clone()), ad.key_binding);
202 }
203
204 Ok(())
205 }
206
207 #[test]
208 fn test_announcement_no_keybinding() -> anyhow::Result<()> {
209 let maddr: Multiaddr = "/ip4/127.0.0.1/tcp/10000".to_string().parse()?;
210
211 let ad = AnnouncementData::new(maddr, None)?;
212
213 assert_eq!(None, ad.key_binding);
214
215 Ok(())
216 }
217
218 #[test]
219 fn test_announcement_decapsulated_ma() -> anyhow::Result<()> {
220 let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
221 let maddr: Multiaddr = "/ip4/127.0.0.1/tcp/10000".to_string().parse()?;
222
223 let ad = AnnouncementData::new(maddr, Some(key_binding.clone()))?;
224 assert_eq!("/ip4/127.0.0.1/tcp/10000", ad.multiaddress().to_string());
225 assert_eq!(Some(key_binding), ad.key_binding);
226
227 Ok(())
228 }
229
230 #[test]
231 fn test_announcement_wrong_peerid() -> anyhow::Result<()> {
232 let key_binding = KeyBinding::new(*CHAIN_ADDR, &KEY_PAIR);
233 let peer_id = SECOND_KEY_PAIR.public().to_peerid_str();
234 let maddr: Multiaddr = format!("/ip4/127.0.0.1/tcp/10000/p2p/{peer_id}").parse()?;
235
236 assert!(AnnouncementData::new(maddr, Some(key_binding.clone())).is_err());
237
238 Ok(())
239 }
240
241 #[test]
242 fn test_decapsulate_multiaddr() -> anyhow::Result<()> {
243 let maddr_1: Multiaddr = "/ip4/127.0.0.1/tcp/10000".parse()?;
244 let maddr_2 = maddr_1
245 .clone()
246 .with_p2p(OffchainKeypair::random().public().into())
247 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
248
249 assert_eq!(maddr_1, decapsulate_multiaddress(maddr_2), "multiaddresses must match");
250 assert_eq!(
251 maddr_1,
252 decapsulate_multiaddress(maddr_1.clone()),
253 "decapsulation must be idempotent"
254 );
255
256 Ok(())
257 }
258}