Skip to main content

hotshot_libp2p_networking/network/behaviours/dht/
record.rs

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