hive_btle/security/
identity.rs

1// Copyright (c) 2025-2026 (r)evolve - Revolve Team LLC
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Device identity management using Ed25519 signatures
17//!
18//! Each device in a HIVE mesh has a cryptographic identity consisting of:
19//! - An Ed25519 signing keypair (private key stored securely)
20//! - A derived NodeId (first 4 bytes of BLAKE3 hash of public key)
21//!
22//! This enables:
23//! - Cryptographic binding between node_id and device
24//! - Document signing to prove authorship
25//! - Identity attestation to prevent impersonation
26//!
27//! # Example
28//!
29//! ```
30//! use hive_btle::security::DeviceIdentity;
31//!
32//! // Generate a new identity
33//! let identity = DeviceIdentity::generate();
34//!
35//! // Get the derived node_id
36//! let node_id = identity.node_id();
37//!
38//! // Sign a message
39//! let message = b"Hello, mesh!";
40//! let signature = identity.sign(message);
41//!
42//! // Verify signature with public key
43//! assert!(identity.verify(message, &signature));
44//! ```
45
46#[cfg(not(feature = "std"))]
47use alloc::vec::Vec;
48
49use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
50use rand_core::OsRng;
51
52use crate::NodeId;
53
54/// Errors that can occur during identity operations
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub enum IdentityError {
57    /// Invalid signature format or verification failed
58    InvalidSignature,
59    /// Invalid public key format
60    InvalidPublicKey,
61    /// Invalid private key format
62    InvalidPrivateKey,
63    /// Serialization/deserialization error
64    SerializationError,
65}
66
67impl core::fmt::Display for IdentityError {
68    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69        match self {
70            Self::InvalidSignature => write!(f, "invalid signature"),
71            Self::InvalidPublicKey => write!(f, "invalid public key"),
72            Self::InvalidPrivateKey => write!(f, "invalid private key"),
73            Self::SerializationError => write!(f, "serialization error"),
74        }
75    }
76}
77
78#[cfg(feature = "std")]
79impl std::error::Error for IdentityError {}
80
81/// A device's cryptographic identity
82///
83/// Contains an Ed25519 signing key and derives a unique NodeId from it.
84/// The private key should be stored securely (platform secure enclave if available).
85pub struct DeviceIdentity {
86    /// Ed25519 signing key (contains both private and public key)
87    signing_key: SigningKey,
88}
89
90impl DeviceIdentity {
91    /// Generate a new random device identity
92    ///
93    /// Uses the platform's cryptographically secure random number generator.
94    pub fn generate() -> Self {
95        let signing_key = SigningKey::generate(&mut OsRng);
96        Self { signing_key }
97    }
98
99    /// Create identity from existing private key bytes
100    ///
101    /// # Arguments
102    /// * `private_key` - 32-byte Ed25519 private key
103    ///
104    /// # Returns
105    /// * `Ok(DeviceIdentity)` - If key is valid
106    /// * `Err(IdentityError)` - If key format is invalid
107    pub fn from_private_key(private_key: &[u8; 32]) -> Result<Self, IdentityError> {
108        let signing_key = SigningKey::from_bytes(private_key);
109        Ok(Self { signing_key })
110    }
111
112    /// Get the private key bytes for secure storage
113    ///
114    /// **Security**: This exposes the private key. Only use for persisting
115    /// to secure storage (keychain, secure enclave, encrypted NVS).
116    pub fn private_key_bytes(&self) -> [u8; 32] {
117        self.signing_key.to_bytes()
118    }
119
120    /// Get the public key bytes
121    ///
122    /// This can be shared freely to allow others to verify signatures.
123    pub fn public_key(&self) -> [u8; 32] {
124        self.signing_key.verifying_key().to_bytes()
125    }
126
127    /// Get the verifying key for signature verification
128    pub fn verifying_key(&self) -> VerifyingKey {
129        self.signing_key.verifying_key()
130    }
131
132    /// Derive the NodeId from the public key
133    ///
134    /// The NodeId is the first 4 bytes of the BLAKE3 hash of the public key,
135    /// interpreted as a little-endian u32. This provides:
136    /// - Deterministic derivation (same key = same node_id)
137    /// - Collision resistance (BLAKE3 is cryptographically secure)
138    /// - Compact representation (4 bytes vs 32 bytes)
139    pub fn node_id(&self) -> NodeId {
140        let public_key = self.public_key();
141        let hash = blake3::hash(&public_key);
142        let hash_bytes = hash.as_bytes();
143
144        // First 4 bytes as little-endian u32
145        let id = u32::from_le_bytes([hash_bytes[0], hash_bytes[1], hash_bytes[2], hash_bytes[3]]);
146
147        NodeId::new(id)
148    }
149
150    /// Sign a message
151    ///
152    /// # Arguments
153    /// * `message` - Arbitrary bytes to sign
154    ///
155    /// # Returns
156    /// 64-byte Ed25519 signature
157    pub fn sign(&self, message: &[u8]) -> [u8; 64] {
158        let signature = self.signing_key.sign(message);
159        signature.to_bytes()
160    }
161
162    /// Verify a signature made by this identity
163    ///
164    /// # Arguments
165    /// * `message` - Original message that was signed
166    /// * `signature` - 64-byte signature to verify
167    ///
168    /// # Returns
169    /// `true` if signature is valid, `false` otherwise
170    pub fn verify(&self, message: &[u8], signature: &[u8; 64]) -> bool {
171        let sig = Signature::from_bytes(signature);
172        self.signing_key
173            .verifying_key()
174            .verify(message, &sig)
175            .is_ok()
176    }
177
178    /// Create an identity attestation
179    ///
180    /// An attestation proves that the holder of this identity controls
181    /// the claimed node_id at a specific point in time.
182    pub fn create_attestation(&self, timestamp_ms: u64) -> IdentityAttestation {
183        let node_id = self.node_id();
184        let public_key = self.public_key();
185
186        // Sign: node_id || public_key || timestamp
187        let mut message = Vec::with_capacity(4 + 32 + 8);
188        message.extend_from_slice(&node_id.as_u32().to_le_bytes());
189        message.extend_from_slice(&public_key);
190        message.extend_from_slice(&timestamp_ms.to_le_bytes());
191
192        let signature = self.sign(&message);
193
194        IdentityAttestation {
195            node_id,
196            public_key,
197            timestamp_ms,
198            signature,
199        }
200    }
201}
202
203impl Clone for DeviceIdentity {
204    fn clone(&self) -> Self {
205        Self {
206            signing_key: SigningKey::from_bytes(&self.signing_key.to_bytes()),
207        }
208    }
209}
210
211impl core::fmt::Debug for DeviceIdentity {
212    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
213        f.debug_struct("DeviceIdentity")
214            .field("node_id", &self.node_id())
215            .field("public_key", &hex_short(&self.public_key()))
216            .field("private_key", &"[REDACTED]")
217            .finish()
218    }
219}
220
221/// An identity attestation proving ownership of a node_id
222///
223/// Used during mesh joining and periodic re-attestation.
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct IdentityAttestation {
226    /// The claimed node_id
227    pub node_id: NodeId,
228    /// Public key (node_id is derived from this)
229    pub public_key: [u8; 32],
230    /// Timestamp when attestation was created (milliseconds since epoch)
231    pub timestamp_ms: u64,
232    /// Signature over (node_id || public_key || timestamp)
233    pub signature: [u8; 64],
234}
235
236impl IdentityAttestation {
237    /// Verify this attestation is valid
238    ///
239    /// Checks:
240    /// 1. Signature is valid for the public key
241    /// 2. node_id correctly derives from public key
242    ///
243    /// Does NOT check timestamp freshness (caller should do that).
244    pub fn verify(&self) -> bool {
245        // Verify node_id derives from public_key
246        let hash = blake3::hash(&self.public_key);
247        let hash_bytes = hash.as_bytes();
248        let expected_id =
249            u32::from_le_bytes([hash_bytes[0], hash_bytes[1], hash_bytes[2], hash_bytes[3]]);
250
251        if self.node_id.as_u32() != expected_id {
252            return false;
253        }
254
255        // Verify signature
256        let verifying_key = match VerifyingKey::from_bytes(&self.public_key) {
257            Ok(k) => k,
258            Err(_) => return false,
259        };
260
261        let signature = Signature::from_bytes(&self.signature);
262
263        // Reconstruct signed message
264        let mut message = Vec::with_capacity(4 + 32 + 8);
265        message.extend_from_slice(&self.node_id.as_u32().to_le_bytes());
266        message.extend_from_slice(&self.public_key);
267        message.extend_from_slice(&self.timestamp_ms.to_le_bytes());
268
269        verifying_key.verify(&message, &signature).is_ok()
270    }
271
272    /// Encode attestation to bytes for wire transmission
273    ///
274    /// Format: node_id (4) || public_key (32) || timestamp (8) || signature (64) = 108 bytes
275    pub fn encode(&self) -> Vec<u8> {
276        let mut buf = Vec::with_capacity(108);
277        buf.extend_from_slice(&self.node_id.as_u32().to_le_bytes());
278        buf.extend_from_slice(&self.public_key);
279        buf.extend_from_slice(&self.timestamp_ms.to_le_bytes());
280        buf.extend_from_slice(&self.signature);
281        buf
282    }
283
284    /// Decode attestation from bytes
285    ///
286    /// Returns None if data is not exactly 108 bytes.
287    pub fn decode(data: &[u8]) -> Option<Self> {
288        if data.len() != 108 {
289            return None;
290        }
291
292        let node_id = NodeId::new(u32::from_le_bytes([data[0], data[1], data[2], data[3]]));
293
294        let mut public_key = [0u8; 32];
295        public_key.copy_from_slice(&data[4..36]);
296
297        let timestamp_ms = u64::from_le_bytes([
298            data[36], data[37], data[38], data[39], data[40], data[41], data[42], data[43],
299        ]);
300
301        let mut signature = [0u8; 64];
302        signature.copy_from_slice(&data[44..108]);
303
304        Some(Self {
305            node_id,
306            public_key,
307            timestamp_ms,
308            signature,
309        })
310    }
311}
312
313/// Verify a signature from a known public key
314///
315/// Utility function for verifying signatures without a full DeviceIdentity.
316pub fn verify_signature(public_key: &[u8; 32], message: &[u8], signature: &[u8; 64]) -> bool {
317    let verifying_key = match VerifyingKey::from_bytes(public_key) {
318        Ok(k) => k,
319        Err(_) => return false,
320    };
321
322    let sig = Signature::from_bytes(signature);
323
324    verifying_key.verify(message, &sig).is_ok()
325}
326
327/// Derive NodeId from a public key
328///
329/// Utility function for deriving node_id without a full DeviceIdentity.
330pub fn node_id_from_public_key(public_key: &[u8; 32]) -> NodeId {
331    let hash = blake3::hash(public_key);
332    let hash_bytes = hash.as_bytes();
333
334    let id = u32::from_le_bytes([hash_bytes[0], hash_bytes[1], hash_bytes[2], hash_bytes[3]]);
335
336    NodeId::new(id)
337}
338
339// Helper for debug output
340fn hex_short(bytes: &[u8]) -> String {
341    if bytes.len() <= 4 {
342        hex::encode(bytes)
343    } else {
344        format!(
345            "{}..{}",
346            hex::encode(&bytes[..2]),
347            hex::encode(&bytes[bytes.len() - 2..])
348        )
349    }
350}
351
352// Need hex for debug output
353mod hex {
354    pub fn encode(bytes: &[u8]) -> String {
355        bytes.iter().map(|b| format!("{:02x}", b)).collect()
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362
363    #[test]
364    fn test_generate_identity() {
365        let identity = DeviceIdentity::generate();
366
367        // node_id should be non-zero (extremely unlikely to be zero)
368        assert_ne!(identity.node_id().as_u32(), 0);
369
370        // Public key should be 32 bytes
371        assert_eq!(identity.public_key().len(), 32);
372    }
373
374    #[test]
375    fn test_identity_from_private_key() {
376        let identity1 = DeviceIdentity::generate();
377        let private_key = identity1.private_key_bytes();
378
379        let identity2 = DeviceIdentity::from_private_key(&private_key).unwrap();
380
381        // Same private key = same public key = same node_id
382        assert_eq!(identity1.public_key(), identity2.public_key());
383        assert_eq!(identity1.node_id(), identity2.node_id());
384    }
385
386    #[test]
387    fn test_node_id_deterministic() {
388        let identity = DeviceIdentity::generate();
389
390        // Multiple calls return same node_id
391        let id1 = identity.node_id();
392        let id2 = identity.node_id();
393        assert_eq!(id1, id2);
394    }
395
396    #[test]
397    fn test_different_identities_different_node_ids() {
398        let identity1 = DeviceIdentity::generate();
399        let identity2 = DeviceIdentity::generate();
400
401        // Different identities should have different node_ids
402        // (collision probability is ~1 in 4 billion)
403        assert_ne!(identity1.node_id(), identity2.node_id());
404    }
405
406    #[test]
407    fn test_sign_verify() {
408        let identity = DeviceIdentity::generate();
409        let message = b"Test message for signing";
410
411        let signature = identity.sign(message);
412        assert!(identity.verify(message, &signature));
413    }
414
415    #[test]
416    fn test_verify_wrong_message() {
417        let identity = DeviceIdentity::generate();
418        let message = b"Original message";
419        let wrong_message = b"Wrong message";
420
421        let signature = identity.sign(message);
422        assert!(!identity.verify(wrong_message, &signature));
423    }
424
425    #[test]
426    fn test_verify_wrong_key() {
427        let identity1 = DeviceIdentity::generate();
428        let identity2 = DeviceIdentity::generate();
429        let message = b"Test message";
430
431        let signature = identity1.sign(message);
432        assert!(!identity2.verify(message, &signature));
433    }
434
435    #[test]
436    fn test_attestation_create_verify() {
437        let identity = DeviceIdentity::generate();
438        let timestamp = 1705680000000u64; // Some timestamp
439
440        let attestation = identity.create_attestation(timestamp);
441
442        assert!(attestation.verify());
443        assert_eq!(attestation.node_id, identity.node_id());
444        assert_eq!(attestation.public_key, identity.public_key());
445        assert_eq!(attestation.timestamp_ms, timestamp);
446    }
447
448    #[test]
449    fn test_attestation_encode_decode() {
450        let identity = DeviceIdentity::generate();
451        let attestation = identity.create_attestation(1705680000000);
452
453        let encoded = attestation.encode();
454        assert_eq!(encoded.len(), 108);
455
456        let decoded = IdentityAttestation::decode(&encoded).unwrap();
457        assert_eq!(decoded, attestation);
458        assert!(decoded.verify());
459    }
460
461    #[test]
462    fn test_attestation_tampered() {
463        let identity = DeviceIdentity::generate();
464        let mut attestation = identity.create_attestation(1705680000000);
465
466        // Tamper with timestamp
467        attestation.timestamp_ms += 1;
468
469        // Verification should fail
470        assert!(!attestation.verify());
471    }
472
473    #[test]
474    fn test_node_id_from_public_key() {
475        let identity = DeviceIdentity::generate();
476        let public_key = identity.public_key();
477
478        let derived_id = node_id_from_public_key(&public_key);
479        assert_eq!(derived_id, identity.node_id());
480    }
481
482    #[test]
483    fn test_verify_signature_utility() {
484        let identity = DeviceIdentity::generate();
485        let message = b"Test with utility function";
486
487        let signature = identity.sign(message);
488        let public_key = identity.public_key();
489
490        assert!(verify_signature(&public_key, message, &signature));
491    }
492
493    #[test]
494    fn test_identity_clone() {
495        let identity1 = DeviceIdentity::generate();
496        let identity2 = identity1.clone();
497
498        assert_eq!(identity1.public_key(), identity2.public_key());
499        assert_eq!(identity1.node_id(), identity2.node_id());
500
501        // Both can sign and verify each other
502        let message = b"Clone test";
503        let sig1 = identity1.sign(message);
504        let sig2 = identity2.sign(message);
505
506        assert!(identity1.verify(message, &sig2));
507        assert!(identity2.verify(message, &sig1));
508    }
509
510    #[test]
511    fn test_debug_redacts_private_key() {
512        let identity = DeviceIdentity::generate();
513        let debug_str = format!("{:?}", identity);
514
515        assert!(debug_str.contains("REDACTED"));
516        assert!(debug_str.contains("node_id"));
517    }
518}