hotshot_libp2p_networking/network/behaviours/dht/
record.rs1use anyhow::{Context, Result, bail};
2use hotshot_types::traits::signature_key::SignatureKey;
3use libp2p::kad::Record;
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
10pub enum RecordValue<K: SignatureKey + 'static> {
11 Signed(
13 #[serde(with = "serde_bytes")] Vec<u8>,
14 K::PureAssembledSignatureType,
15 ),
16
17 Unsigned(#[serde(with = "serde_bytes")] Vec<u8>),
19}
20
21#[repr(u8)]
24#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq)]
25pub enum Namespace {
26 Lookup = 0,
28
29 #[cfg(test)]
31 Testing = 254,
32
33 #[cfg(test)]
35 TestingUnauthenticated = 255,
36}
37
38fn requires_authentication(namespace: Namespace) -> bool {
40 match namespace {
41 Namespace::Lookup => true,
42 #[cfg(test)]
43 Namespace::Testing => true,
44 #[cfg(test)]
45 Namespace::TestingUnauthenticated => false,
46 }
47}
48
49impl TryFrom<u8> for Namespace {
51 type Error = anyhow::Error;
52
53 fn try_from(value: u8) -> Result<Self> {
54 match value {
55 0 => Ok(Self::Lookup),
56 #[cfg(test)]
57 254 => Ok(Self::Testing),
58 #[cfg(test)]
59 255 => Ok(Self::TestingUnauthenticated),
60 _ => bail!("Unknown namespace"),
61 }
62 }
63}
64
65#[derive(Clone)]
67pub struct RecordKey {
68 pub namespace: Namespace,
70
71 pub key: Vec<u8>,
73}
74
75impl RecordKey {
76 #[must_use]
77 pub fn new(namespace: Namespace, key: Vec<u8>) -> Self {
79 Self { namespace, key }
80 }
81
82 #[must_use]
84 pub fn to_bytes(&self) -> Vec<u8> {
85 let mut bytes = vec![self.namespace as u8];
87 bytes.extend_from_slice(&self.key);
88 bytes
89 }
90
91 pub fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
96 if bytes.is_empty() {
98 bail!("Empty record key bytes")
99 }
100
101 let namespace = Namespace::try_from(bytes[0])?;
103
104 Ok(Self {
106 namespace,
107 key: bytes[1..].to_vec(),
108 })
109 }
110}
111
112impl<K: SignatureKey + 'static> RecordValue<K> {
113 pub fn new_signed(
120 record_key: &RecordKey,
121 value: Vec<u8>,
122 private_key: &K::PrivateKey,
123 ) -> Result<Self> {
124 let mut value_to_sign = record_key.to_bytes();
126 value_to_sign.extend_from_slice(&value);
127
128 let signature =
129 K::sign(private_key, &value_to_sign).with_context(|| "Failed to sign record")?;
130
131 Ok(Self::Signed(value, signature))
133 }
134
135 #[must_use]
137 pub fn new(value: Vec<u8>) -> Self {
138 Self::Unsigned(value)
139 }
140
141 pub fn validate(&self, record_key: &RecordKey) -> bool {
144 if !requires_authentication(record_key.namespace) {
146 return true;
147 }
148
149 let Self::Signed(value, signature) = self else {
151 warn!("Record should be signed but is not");
152 return false;
153 };
154
155 let Ok(public_key) = K::from_bytes(record_key.key.as_slice()) else {
157 warn!("Failed to deserialize signer's public key");
158 return false;
159 };
160
161 let mut signed_value = record_key.to_bytes();
163 signed_value.extend_from_slice(value);
164
165 public_key.validate(signature, &signed_value)
167 }
168
169 pub fn value(&self) -> &[u8] {
171 match self {
172 Self::Unsigned(value) | Self::Signed(value, _) => value,
173 }
174 }
175}
176
177impl<K: SignatureKey + 'static> TryFrom<Record> for RecordValue<K> {
178 type Error = anyhow::Error;
179
180 fn try_from(record: Record) -> Result<Self> {
181 let record: RecordValue<K> = bincode::deserialize(&record.value)
183 .with_context(|| "Failed to deserialize record value")?;
184
185 Ok(record)
187 }
188}
189
190#[cfg(test)]
191mod test {
192 use hotshot_types::signature_key::BLSPubKey;
193
194 use super::*;
195
196 #[test]
198 fn test_namespace_serialization_parity() {
199 let namespace = Namespace::Lookup;
201 let bytes = namespace as u8;
202
203 let namespace = Namespace::try_from(bytes).expect("Failed to deserialize namespace");
205 assert!(namespace == Namespace::Lookup, "Wrong namespace");
206 }
207
208 #[test]
210 fn test_record_key_serialization_parity() {
211 let namespace = Namespace::Lookup;
213 let key = vec![1, 2, 3, 4];
214 let record_key = RecordKey::new(namespace, key.clone());
215
216 let bytes = record_key.to_bytes();
218
219 let record_key =
221 RecordKey::try_from_bytes(&bytes).expect("Failed to deserialize record key");
222
223 assert!(record_key.namespace == namespace, "Namespace mismatch");
225 assert!(record_key.key == key, "Key mismatch");
226 }
227
228 #[test]
230 fn test_valid_signature() {
231 let (public_key, private_key) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
233
234 let value = vec![5, 6, 7, 8];
236
237 let record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
239
240 let record_value: RecordValue<BLSPubKey> =
242 RecordValue::new_signed(&record_key, value.clone(), &private_key).unwrap();
243
244 assert!(
246 record_value.validate(&record_key),
247 "Failed to validate signed record"
248 );
249 }
250
251 #[test]
253 fn test_invalid_namespace() {
254 let (public_key, private_key) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
256
257 let value = vec![5, 6, 7, 8];
259
260 let mut record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
262
263 let record_value: RecordValue<BLSPubKey> =
265 RecordValue::new_signed(&record_key, value.clone(), &private_key).unwrap();
266
267 record_key.namespace = Namespace::Testing;
269
270 assert!(
272 !record_value.validate(&record_key),
273 "Failed to detect invalid namespace"
274 );
275 }
276
277 #[test]
279 fn test_invalid_key() {
280 let (public_key, private_key) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
282
283 let value = vec![5, 6, 7, 8];
285
286 let mut record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
288
289 let record_value: RecordValue<BLSPubKey> =
291 RecordValue::new_signed(&record_key, value.clone(), &private_key).unwrap();
292
293 record_key.key = BLSPubKey::generated_from_seed_indexed([1; 32], 1338)
295 .0
296 .to_bytes();
297
298 assert!(
300 !record_value.validate(&record_key),
301 "Failed to detect invalid record key"
302 );
303 }
304
305 #[test]
307 fn test_unsigned_record_is_valid() {
308 let value = vec![5, 6, 7, 8];
310
311 let record_key = RecordKey::new(Namespace::TestingUnauthenticated, vec![1, 2, 3, 4]);
313
314 let record_value: RecordValue<BLSPubKey> = RecordValue::new(value.clone());
316
317 assert!(
319 record_value.validate(&record_key),
320 "Failed to validate unsigned record"
321 );
322 }
323
324 #[test]
326 fn test_unauthenticated_namespace() {
327 let (public_key, _) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
329
330 let record_key = RecordKey::new(Namespace::TestingUnauthenticated, public_key.to_bytes());
332
333 let record_value: RecordValue<BLSPubKey> = RecordValue::new(vec![5, 6, 7, 8]);
335
336 assert!(
338 record_value.validate(&record_key),
339 "Failed to validate unsigned record in unauthenticated namespace"
340 );
341 }
342
343 #[test]
345 fn test_authenticated_namespace() {
346 let (public_key, _) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
348
349 let record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
351
352 let record_value: RecordValue<BLSPubKey> = RecordValue::new(vec![5, 6, 7, 8]);
354
355 assert!(
357 !record_value.validate(&record_key),
358 "Failed to detect invalid unsigned record"
359 );
360 }
361}