rustywallet_lightning/
node.rs

1//! Node identity and key derivation.
2//!
3//! This module provides types for deriving Lightning node identity
4//! from an HD seed.
5
6use crate::error::LightningError;
7use secp256k1::{PublicKey, Secp256k1, SecretKey};
8use sha2::{Digest, Sha256};
9use std::fmt;
10
11/// Lightning node identity derived from an HD seed.
12///
13/// The node identity includes the node's public key (node ID) and
14/// the secret key used for signing.
15pub struct NodeIdentity {
16    /// Node secret key
17    secret_key: SecretKey,
18    /// Node public key (node ID)
19    public_key: PublicKey,
20}
21
22impl NodeIdentity {
23    /// Derive node identity from an HD seed (64 bytes).
24    ///
25    /// Uses a deterministic derivation from the seed.
26    pub fn from_seed(seed: &[u8]) -> Result<Self, LightningError> {
27        if seed.len() != 64 {
28            return Err(LightningError::KeyDerivationError(format!(
29                "Expected 64 byte seed, got {}",
30                seed.len()
31            )));
32        }
33
34        // Derive using a deterministic method
35        // We use SHA256(seed || "lightning-node-key") as the secret key
36        let mut hasher = Sha256::new();
37        hasher.update(seed);
38        hasher.update(b"lightning-node-key");
39        let result = hasher.finalize();
40
41        let secp = Secp256k1::new();
42        let secret_key = SecretKey::from_slice(&result)
43            .map_err(|e| LightningError::KeyDerivationError(e.to_string()))?;
44        let public_key = PublicKey::from_secret_key(&secp, &secret_key);
45
46        Ok(Self {
47            secret_key,
48            public_key,
49        })
50    }
51
52    /// Create from an existing secret key.
53    pub fn from_secret_key(secret_key: SecretKey) -> Self {
54        let secp = Secp256k1::new();
55        let public_key = PublicKey::from_secret_key(&secp, &secret_key);
56        Self {
57            secret_key,
58            public_key,
59        }
60    }
61
62    /// Get the node ID (compressed public key).
63    pub fn node_id(&self) -> NodeId {
64        NodeId(self.public_key.serialize())
65    }
66
67    /// Get the public key.
68    pub fn public_key(&self) -> &PublicKey {
69        &self.public_key
70    }
71
72    /// Get the secret key (use with caution).
73    pub fn secret_key(&self) -> &SecretKey {
74        &self.secret_key
75    }
76
77    /// Sign a message with the node's secret key.
78    pub fn sign(&self, message: &[u8]) -> Result<[u8; 64], LightningError> {
79        use secp256k1::Message;
80
81        let secp = Secp256k1::new();
82        
83        // Hash the message
84        let mut hasher = Sha256::new();
85        hasher.update(message);
86        let hash = hasher.finalize();
87
88        let msg = Message::from_digest_slice(&hash)
89            .map_err(|e| LightningError::SignatureError(e.to_string()))?;
90
91        let sig = secp.sign_ecdsa(&msg, &self.secret_key);
92        Ok(sig.serialize_compact())
93    }
94}
95
96impl fmt::Debug for NodeIdentity {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        f.debug_struct("NodeIdentity")
99            .field("node_id", &self.node_id())
100            .field("secret_key", &"[REDACTED]")
101            .finish()
102    }
103}
104
105/// A 33-byte compressed public key representing a node ID.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107pub struct NodeId([u8; 33]);
108
109impl NodeId {
110    /// Create from bytes.
111    pub fn from_bytes(bytes: [u8; 33]) -> Self {
112        Self(bytes)
113    }
114
115    /// Create from hex string.
116    pub fn from_hex(hex: &str) -> Result<Self, LightningError> {
117        let bytes = hex::decode(hex)
118            .map_err(|e| LightningError::InvalidNodeId(e.to_string()))?;
119
120        if bytes.len() != 33 {
121            return Err(LightningError::InvalidNodeId(format!(
122                "Expected 33 bytes, got {}",
123                bytes.len()
124            )));
125        }
126
127        let mut arr = [0u8; 33];
128        arr.copy_from_slice(&bytes);
129        Ok(Self(arr))
130    }
131
132    /// Get the raw bytes.
133    pub fn as_bytes(&self) -> &[u8; 33] {
134        &self.0
135    }
136
137    /// Convert to hex string.
138    pub fn to_hex(&self) -> String {
139        hex::encode(self.0)
140    }
141
142    /// Get the public key.
143    pub fn to_public_key(&self) -> Result<PublicKey, LightningError> {
144        PublicKey::from_slice(&self.0)
145            .map_err(|e| LightningError::InvalidNodeId(e.to_string()))
146    }
147}
148
149impl fmt::Display for NodeId {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(f, "{}", self.to_hex())
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    fn random_seed() -> [u8; 64] {
160        use rand::RngCore;
161        let mut seed = [0u8; 64];
162        rand::rngs::OsRng.fill_bytes(&mut seed);
163        seed
164    }
165
166    #[test]
167    fn test_node_identity_from_seed() {
168        let seed = random_seed();
169        let identity = NodeIdentity::from_seed(&seed).unwrap();
170
171        // Node ID should be 33 bytes (compressed pubkey)
172        assert_eq!(identity.node_id().as_bytes().len(), 33);
173    }
174
175    #[test]
176    fn test_deterministic_derivation() {
177        let seed = random_seed();
178        let identity1 = NodeIdentity::from_seed(&seed).unwrap();
179        let identity2 = NodeIdentity::from_seed(&seed).unwrap();
180
181        assert_eq!(identity1.node_id(), identity2.node_id());
182    }
183
184    #[test]
185    fn test_different_seeds_different_ids() {
186        let seed1 = random_seed();
187        let seed2 = random_seed();
188
189        let identity1 = NodeIdentity::from_seed(&seed1).unwrap();
190        let identity2 = NodeIdentity::from_seed(&seed2).unwrap();
191
192        assert_ne!(identity1.node_id(), identity2.node_id());
193    }
194
195    #[test]
196    fn test_node_id_hex_roundtrip() {
197        let seed = random_seed();
198        let identity = NodeIdentity::from_seed(&seed).unwrap();
199        let node_id = identity.node_id();
200
201        let hex = node_id.to_hex();
202        let recovered = NodeId::from_hex(&hex).unwrap();
203
204        assert_eq!(node_id, recovered);
205    }
206
207    #[test]
208    fn test_sign_message() {
209        let seed = random_seed();
210        let identity = NodeIdentity::from_seed(&seed).unwrap();
211
212        let message = b"Hello, Lightning!";
213        let signature = identity.sign(message).unwrap();
214
215        assert_eq!(signature.len(), 64);
216    }
217}