hotshot_libp2p_networking/network/behaviours/dht/
record.rs1use anyhow::{bail, Context, Result};
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(Vec<u8>, K::PureAssembledSignatureType),
13
14 Unsigned(Vec<u8>),
16}
17
18#[repr(u8)]
21#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq)]
22pub enum Namespace {
23 Lookup = 0,
25
26 #[cfg(test)]
28 Testing = 254,
29
30 #[cfg(test)]
32 TestingUnauthenticated = 255,
33}
34
35fn requires_authentication(namespace: Namespace) -> bool {
37 match namespace {
38 Namespace::Lookup => true,
39 #[cfg(test)]
40 Namespace::Testing => true,
41 #[cfg(test)]
42 Namespace::TestingUnauthenticated => false,
43 }
44}
45
46impl TryFrom<u8> for Namespace {
48 type Error = anyhow::Error;
49
50 fn try_from(value: u8) -> Result<Self> {
51 match value {
52 0 => Ok(Self::Lookup),
53 #[cfg(test)]
54 254 => Ok(Self::Testing),
55 #[cfg(test)]
56 255 => Ok(Self::TestingUnauthenticated),
57 _ => bail!("Unknown namespace"),
58 }
59 }
60}
61
62#[derive(Clone)]
64pub struct RecordKey {
65 pub namespace: Namespace,
67
68 pub key: Vec<u8>,
70}
71
72impl RecordKey {
73 #[must_use]
74 pub fn new(namespace: Namespace, key: Vec<u8>) -> Self {
76 Self { namespace, key }
77 }
78
79 #[must_use]
81 pub fn to_bytes(&self) -> Vec<u8> {
82 let mut bytes = vec![self.namespace as u8];
84 bytes.extend_from_slice(&self.key);
85 bytes
86 }
87
88 pub fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
93 if bytes.is_empty() {
95 bail!("Empty record key bytes")
96 }
97
98 let namespace = Namespace::try_from(bytes[0])?;
100
101 Ok(Self {
103 namespace,
104 key: bytes[1..].to_vec(),
105 })
106 }
107}
108
109impl<K: SignatureKey + 'static> RecordValue<K> {
110 pub fn new_signed(
117 record_key: &RecordKey,
118 value: Vec<u8>,
119 private_key: &K::PrivateKey,
120 ) -> Result<Self> {
121 let mut value_to_sign = record_key.to_bytes();
123 value_to_sign.extend_from_slice(&value);
124
125 let signature =
126 K::sign(private_key, &value_to_sign).with_context(|| "Failed to sign record")?;
127
128 Ok(Self::Signed(value, signature))
130 }
131
132 #[must_use]
134 pub fn new(value: Vec<u8>) -> Self {
135 Self::Unsigned(value)
136 }
137
138 pub fn validate(&self, record_key: &RecordKey) -> bool {
141 if !requires_authentication(record_key.namespace) {
143 return true;
144 }
145
146 let Self::Signed(value, signature) = self else {
148 warn!("Record should be signed but is not");
149 return false;
150 };
151
152 let Ok(public_key) = K::from_bytes(record_key.key.as_slice()) else {
154 warn!("Failed to deserialize signer's public key");
155 return false;
156 };
157
158 let mut signed_value = record_key.to_bytes();
160 signed_value.extend_from_slice(value);
161
162 public_key.validate(signature, &signed_value)
164 }
165
166 pub fn value(&self) -> &[u8] {
168 match self {
169 Self::Unsigned(value) | Self::Signed(value, _) => value,
170 }
171 }
172}
173
174impl<K: SignatureKey + 'static> TryFrom<Record> for RecordValue<K> {
175 type Error = anyhow::Error;
176
177 fn try_from(record: Record) -> Result<Self> {
178 let record: RecordValue<K> = bincode::deserialize(&record.value)
180 .with_context(|| "Failed to deserialize record value")?;
181
182 Ok(record)
184 }
185}
186
187#[cfg(test)]
188mod test {
189 use hotshot_types::signature_key::BLSPubKey;
190
191 use super::*;
192
193 #[test]
195 fn test_namespace_serialization_parity() {
196 let namespace = Namespace::Lookup;
198 let bytes = namespace as u8;
199
200 let namespace = Namespace::try_from(bytes).expect("Failed to deserialize namespace");
202 assert!(namespace == Namespace::Lookup, "Wrong namespace");
203 }
204
205 #[test]
207 fn test_record_key_serialization_parity() {
208 let namespace = Namespace::Lookup;
210 let key = vec![1, 2, 3, 4];
211 let record_key = RecordKey::new(namespace, key.clone());
212
213 let bytes = record_key.to_bytes();
215
216 let record_key =
218 RecordKey::try_from_bytes(&bytes).expect("Failed to deserialize record key");
219
220 assert!(record_key.namespace == namespace, "Namespace mismatch");
222 assert!(record_key.key == key, "Key mismatch");
223 }
224
225 #[test]
227 fn test_valid_signature() {
228 let (public_key, private_key) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
230
231 let value = vec![5, 6, 7, 8];
233
234 let record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
236
237 let record_value: RecordValue<BLSPubKey> =
239 RecordValue::new_signed(&record_key, value.clone(), &private_key).unwrap();
240
241 assert!(
243 record_value.validate(&record_key),
244 "Failed to validate signed record"
245 );
246 }
247
248 #[test]
250 fn test_invalid_namespace() {
251 let (public_key, private_key) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
253
254 let value = vec![5, 6, 7, 8];
256
257 let mut record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
259
260 let record_value: RecordValue<BLSPubKey> =
262 RecordValue::new_signed(&record_key, value.clone(), &private_key).unwrap();
263
264 record_key.namespace = Namespace::Testing;
266
267 assert!(
269 !record_value.validate(&record_key),
270 "Failed to detect invalid namespace"
271 );
272 }
273
274 #[test]
276 fn test_invalid_key() {
277 let (public_key, private_key) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
279
280 let value = vec![5, 6, 7, 8];
282
283 let mut record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
285
286 let record_value: RecordValue<BLSPubKey> =
288 RecordValue::new_signed(&record_key, value.clone(), &private_key).unwrap();
289
290 record_key.key = BLSPubKey::generated_from_seed_indexed([1; 32], 1338)
292 .0
293 .to_bytes();
294
295 assert!(
297 !record_value.validate(&record_key),
298 "Failed to detect invalid record key"
299 );
300 }
301
302 #[test]
304 fn test_unsigned_record_is_valid() {
305 let value = vec![5, 6, 7, 8];
307
308 let record_key = RecordKey::new(Namespace::TestingUnauthenticated, vec![1, 2, 3, 4]);
310
311 let record_value: RecordValue<BLSPubKey> = RecordValue::new(value.clone());
313
314 assert!(
316 record_value.validate(&record_key),
317 "Failed to validate unsigned record"
318 );
319 }
320
321 #[test]
323 fn test_unauthenticated_namespace() {
324 let (public_key, _) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
326
327 let record_key = RecordKey::new(Namespace::TestingUnauthenticated, public_key.to_bytes());
329
330 let record_value: RecordValue<BLSPubKey> = RecordValue::new(vec![5, 6, 7, 8]);
332
333 assert!(
335 record_value.validate(&record_key),
336 "Failed to validate unsigned record in unauthenticated namespace"
337 );
338 }
339
340 #[test]
342 fn test_authenticated_namespace() {
343 let (public_key, _) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
345
346 let record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
348
349 let record_value: RecordValue<BLSPubKey> = RecordValue::new(vec![5, 6, 7, 8]);
351
352 assert!(
354 !record_value.validate(&record_key),
355 "Failed to detect invalid unsigned record"
356 );
357 }
358}