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