rustywallet_lightning/
node.rs1use crate::error::LightningError;
7use secp256k1::{PublicKey, Secp256k1, SecretKey};
8use sha2::{Digest, Sha256};
9use std::fmt;
10
11pub struct NodeIdentity {
16 secret_key: SecretKey,
18 public_key: PublicKey,
20}
21
22impl NodeIdentity {
23 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 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 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 pub fn node_id(&self) -> NodeId {
64 NodeId(self.public_key.serialize())
65 }
66
67 pub fn public_key(&self) -> &PublicKey {
69 &self.public_key
70 }
71
72 pub fn secret_key(&self) -> &SecretKey {
74 &self.secret_key
75 }
76
77 pub fn sign(&self, message: &[u8]) -> Result<[u8; 64], LightningError> {
79 use secp256k1::Message;
80
81 let secp = Secp256k1::new();
82
83 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107pub struct NodeId([u8; 33]);
108
109impl NodeId {
110 pub fn from_bytes(bytes: [u8; 33]) -> Self {
112 Self(bytes)
113 }
114
115 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 pub fn as_bytes(&self) -> &[u8; 33] {
134 &self.0
135 }
136
137 pub fn to_hex(&self) -> String {
139 hex::encode(self.0)
140 }
141
142 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 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}