hotshot_libp2p_networking/network/behaviours/dht/
record.rs

1use anyhow::{bail, Context, Result};
2use hotshot_types::traits::signature_key::SignatureKey;
3use libp2p::kad::Record;
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7/// A (signed or unsigned) record value to be stored (serialized) in the DHT.
8/// This is a wrapper around a value that includes a possible signature.
9#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
10pub enum RecordValue<K: SignatureKey + 'static> {
11    /// A signed record value
12    Signed(Vec<u8>, K::PureAssembledSignatureType),
13
14    /// An unsigned record value
15    Unsigned(Vec<u8>),
16}
17
18/// The namespace of a record. This is included with the key
19/// and allows for multiple types of records to be stored in the DHT.
20#[repr(u8)]
21#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq)]
22pub enum Namespace {
23    /// A namespace for looking up P2P identities
24    Lookup = 0,
25
26    /// An authenticated namespace useful for testing
27    #[cfg(test)]
28    Testing = 254,
29
30    /// An unauthenticated namespace useful for testing
31    #[cfg(test)]
32    TestingUnauthenticated = 255,
33}
34
35/// Require certain namespaces to be authenticated
36fn 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
46/// Allow fallible conversion from a byte to a namespace
47impl 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/// A record's key. This is a concatenation of the namespace and the key.
63#[derive(Clone)]
64pub struct RecordKey {
65    /// The namespace of the record key
66    pub namespace: Namespace,
67
68    /// The actual key
69    pub key: Vec<u8>,
70}
71
72impl RecordKey {
73    #[must_use]
74    /// Create and return a new record key in the given namespace
75    pub fn new(namespace: Namespace, key: Vec<u8>) -> Self {
76        Self { namespace, key }
77    }
78
79    /// Convert the record key to a byte vector
80    #[must_use]
81    pub fn to_bytes(&self) -> Vec<u8> {
82        // Concatenate the namespace and the key
83        let mut bytes = vec![self.namespace as u8];
84        bytes.extend_from_slice(&self.key);
85        bytes
86    }
87
88    /// Try to convert a byte vector to a record key
89    ///
90    /// # Errors
91    /// If the provided array is empty
92    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
93        // Check if the bytes are empty
94        if bytes.is_empty() {
95            bail!("Empty record key bytes")
96        }
97
98        // The first byte is the namespace
99        let namespace = Namespace::try_from(bytes[0])?;
100
101        // Return the record key
102        Ok(Self {
103            namespace,
104            key: bytes[1..].to_vec(),
105        })
106    }
107}
108
109impl<K: SignatureKey + 'static> RecordValue<K> {
110    /// Creates and returns a new signed record by signing the key and value
111    /// with the private key
112    ///
113    /// # Errors
114    /// - If we fail to sign the value
115    /// - If we fail to serialize the signature
116    pub fn new_signed(
117        record_key: &RecordKey,
118        value: Vec<u8>,
119        private_key: &K::PrivateKey,
120    ) -> Result<Self> {
121        // The value to sign should be the record key concatenated with the value
122        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        // Return the signed record
129        Ok(Self::Signed(value, signature))
130    }
131
132    /// Creates and returns a new unsigned record
133    #[must_use]
134    pub fn new(value: Vec<u8>) -> Self {
135        Self::Unsigned(value)
136    }
137
138    /// If the message requires authentication, validate the record by verifying the signature with the
139    /// given key
140    pub fn validate(&self, record_key: &RecordKey) -> bool {
141        // If the record requires authentication, validate the signature
142        if !requires_authentication(record_key.namespace) {
143            return true;
144        }
145
146        // The record must be signed
147        let Self::Signed(value, signature) = self else {
148            warn!("Record should be signed but is not");
149            return false;
150        };
151
152        // If the request is "signed", the public key is the record's key
153        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        // The value to sign should be the record key concatenated with the value
159        let mut signed_value = record_key.to_bytes();
160        signed_value.extend_from_slice(value);
161
162        // Validate the signature
163        public_key.validate(signature, &signed_value)
164    }
165
166    /// Get the underlying value of the record
167    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        // Deserialize the record value
179        let record: RecordValue<K> = bincode::deserialize(&record.value)
180            .with_context(|| "Failed to deserialize record value")?;
181
182        // Return the record
183        Ok(record)
184    }
185}
186
187#[cfg(test)]
188mod test {
189    use hotshot_types::signature_key::BLSPubKey;
190
191    use super::*;
192
193    /// Test that namespace serialization and deserialization is consistent
194    #[test]
195    fn test_namespace_serialization_parity() {
196        // Serialize the namespace
197        let namespace = Namespace::Lookup;
198        let bytes = namespace as u8;
199
200        // Deserialize the namespace
201        let namespace = Namespace::try_from(bytes).expect("Failed to deserialize namespace");
202        assert!(namespace == Namespace::Lookup, "Wrong namespace");
203    }
204
205    /// Test that record key serialization and deserialization is consistent
206    #[test]
207    fn test_record_key_serialization_parity() {
208        // Create a new record key
209        let namespace = Namespace::Lookup;
210        let key = vec![1, 2, 3, 4];
211        let record_key = RecordKey::new(namespace, key.clone());
212
213        // Serialize it
214        let bytes = record_key.to_bytes();
215
216        // Deserialize it
217        let record_key =
218            RecordKey::try_from_bytes(&bytes).expect("Failed to deserialize record key");
219
220        // Make sure the deserialized record key is the same as the original
221        assert!(record_key.namespace == namespace, "Namespace mismatch");
222        assert!(record_key.key == key, "Key mismatch");
223    }
224
225    /// Test that the validity of a valid, signed record is correct
226    #[test]
227    fn test_valid_signature() {
228        // Generate a staking keypair
229        let (public_key, private_key) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
230
231        // Create a value. The key is the public key
232        let value = vec![5, 6, 7, 8];
233
234        // Create a record key (as we need to sign both the key and the value)
235        let record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
236
237        // Sign the record and value with the private key
238        let record_value: RecordValue<BLSPubKey> =
239            RecordValue::new_signed(&record_key, value.clone(), &private_key).unwrap();
240
241        // Validate the signed record
242        assert!(
243            record_value.validate(&record_key),
244            "Failed to validate signed record"
245        );
246    }
247
248    /// Test that altering the namespace byte causes a validation failure
249    #[test]
250    fn test_invalid_namespace() {
251        // Generate a staking keypair
252        let (public_key, private_key) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
253
254        // Create a value. The key is the public key
255        let value = vec![5, 6, 7, 8];
256
257        // Create a record key (as we need to sign both the key and the value)
258        let mut record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
259
260        // Sign the record and value with the private key
261        let record_value: RecordValue<BLSPubKey> =
262            RecordValue::new_signed(&record_key, value.clone(), &private_key).unwrap();
263
264        // Alter the namespace
265        record_key.namespace = Namespace::Testing;
266
267        // Validate the signed record
268        assert!(
269            !record_value.validate(&record_key),
270            "Failed to detect invalid namespace"
271        );
272    }
273
274    /// Test that altering the contents of the record key causes a validation failure
275    #[test]
276    fn test_invalid_key() {
277        // Generate a staking keypair
278        let (public_key, private_key) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
279
280        // Create a value. The key is the public key
281        let value = vec![5, 6, 7, 8];
282
283        // Create a record key (as we need to sign both the key and the value)
284        let mut record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
285
286        // Sign the record and value with the private key
287        let record_value: RecordValue<BLSPubKey> =
288            RecordValue::new_signed(&record_key, value.clone(), &private_key).unwrap();
289
290        // Set the key to a different one
291        record_key.key = BLSPubKey::generated_from_seed_indexed([1; 32], 1338)
292            .0
293            .to_bytes();
294
295        // Validate the signed record
296        assert!(
297            !record_value.validate(&record_key),
298            "Failed to detect invalid record key"
299        );
300    }
301
302    /// Test that unsigned records are always valid
303    #[test]
304    fn test_unsigned_record_is_valid() {
305        // Create a value
306        let value = vec![5, 6, 7, 8];
307
308        // Create a record key
309        let record_key = RecordKey::new(Namespace::TestingUnauthenticated, vec![1, 2, 3, 4]);
310
311        // Create an unsigned record
312        let record_value: RecordValue<BLSPubKey> = RecordValue::new(value.clone());
313
314        // Validate the unsigned record
315        assert!(
316            record_value.validate(&record_key),
317            "Failed to validate unsigned record"
318        );
319    }
320
321    /// Test that unauthenticated namespaces do not require validation for unsigned records
322    #[test]
323    fn test_unauthenticated_namespace() {
324        // Generate a staking keypair
325        let (public_key, _) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
326
327        // Create a record key (as we need to sign both the key and the value)
328        let record_key = RecordKey::new(Namespace::TestingUnauthenticated, public_key.to_bytes());
329
330        // Created an unsigned record
331        let record_value: RecordValue<BLSPubKey> = RecordValue::new(vec![5, 6, 7, 8]);
332
333        // Validate it
334        assert!(
335            record_value.validate(&record_key),
336            "Failed to validate unsigned record in unauthenticated namespace"
337        );
338    }
339
340    /// Test that authenticated namespaces do require validation for unsigned records
341    #[test]
342    fn test_authenticated_namespace() {
343        // Generate a staking keypair
344        let (public_key, _) = BLSPubKey::generated_from_seed_indexed([1; 32], 1337);
345
346        // Create a record key (as we need to sign both the key and the value)
347        let record_key = RecordKey::new(Namespace::Lookup, public_key.to_bytes());
348
349        // Created an unsigned record
350        let record_value: RecordValue<BLSPubKey> = RecordValue::new(vec![5, 6, 7, 8]);
351
352        // Validate it
353        assert!(
354            !record_value.validate(&record_key),
355            "Failed to detect invalid unsigned record"
356        );
357    }
358}