nula_core/key/
secret_key.rs1use std::fmt;
18use std::str::FromStr;
19
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21use thiserror::Error;
22
23use crate::util::hex::{self, HexError};
24use crate::util::rng::{self, RngError};
25
26pub const SECRET_KEY_SIZE: usize = 32;
28
29#[derive(Debug, Clone, Copy, Error)]
31#[non_exhaustive]
32pub enum SecretKeyError {
33 #[error("invalid hex encoding: {0}")]
35 Hex(#[from] HexError),
36 #[error("invalid length: expected {SECRET_KEY_SIZE} bytes, got {0}")]
38 InvalidLength(usize),
39 #[error("not a valid secp256k1 scalar")]
41 InvalidScalar,
42 #[error("entropy unavailable: {0}")]
44 Rng(#[from] RngError),
45}
46
47#[derive(Clone, PartialEq, Eq)]
63#[allow(
64 missing_copy_implementations,
65 reason = "do not copy secret key material implicitly; clone explicitly"
66)]
67pub struct SecretKey(secp256k1::SecretKey);
68
69impl SecretKey {
70 pub fn from_byte_array(bytes: [u8; SECRET_KEY_SIZE]) -> Result<Self, SecretKeyError> {
77 secp256k1::SecretKey::from_byte_array(bytes)
78 .map(Self)
79 .map_err(|_| SecretKeyError::InvalidScalar)
80 }
81
82 pub fn from_slice(bytes: &[u8]) -> Result<Self, SecretKeyError> {
90 let array: [u8; SECRET_KEY_SIZE] = bytes
91 .try_into()
92 .map_err(|_| SecretKeyError::InvalidLength(bytes.len()))?;
93 Self::from_byte_array(array)
94 }
95
96 pub fn parse<S>(input: S) -> Result<Self, SecretKeyError>
102 where
103 S: AsRef<str>,
104 {
105 let bytes = hex::decode(input.as_ref())?;
106 Self::from_slice(&bytes)
107 }
108
109 pub fn generate() -> Result<Self, SecretKeyError> {
117 let bytes: [u8; SECRET_KEY_SIZE] = rng::random_bytes()?;
118 Self::from_byte_array(bytes)
119 }
120
121 #[must_use]
123 pub fn to_byte_array(&self) -> [u8; SECRET_KEY_SIZE] {
124 self.0.secret_bytes()
125 }
126
127 #[must_use]
129 pub fn to_hex(&self) -> String {
130 hex::encode(self.0.secret_bytes())
131 }
132
133 #[must_use]
137 pub const fn as_inner(&self) -> &secp256k1::SecretKey {
138 &self.0
139 }
140}
141
142impl fmt::Debug for SecretKey {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 f.debug_tuple("SecretKey").field(&"<redacted>").finish()
145 }
146}
147
148impl Drop for SecretKey {
149 fn drop(&mut self) {
150 self.0.non_secure_erase();
154 }
155}
156
157impl FromStr for SecretKey {
158 type Err = SecretKeyError;
159
160 fn from_str(s: &str) -> Result<Self, Self::Err> {
161 Self::parse(s)
162 }
163}
164
165impl Serialize for SecretKey {
166 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
167 where
168 S: Serializer,
169 {
170 serializer.collect_str(&self.to_hex())
171 }
172}
173
174impl<'de> Deserialize<'de> for SecretKey {
175 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
176 where
177 D: Deserializer<'de>,
178 {
179 let raw = <&str>::deserialize(deserializer)?;
180 Self::parse(raw).map_err(serde::de::Error::custom)
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use hex_literal::hex;
187
188 use super::*;
189
190 const VALID_SECRET: [u8; 32] =
191 hex!("0000000000000000000000000000000000000000000000000000000000000001");
192
193 #[test]
194 fn from_byte_array_valid() {
195 let sk = SecretKey::from_byte_array(VALID_SECRET).unwrap();
196 assert_eq!(sk.to_byte_array(), VALID_SECRET);
197 }
198
199 #[test]
200 fn from_byte_array_zero_is_invalid() {
201 let zero = [0_u8; 32];
202 let err = SecretKey::from_byte_array(zero).unwrap_err();
203 assert!(matches!(err, SecretKeyError::InvalidScalar));
204 }
205
206 #[test]
207 fn from_slice_wrong_length() {
208 let err = SecretKey::from_slice(&[0_u8; 16]).unwrap_err();
209 assert!(matches!(err, SecretKeyError::InvalidLength(16)));
210 }
211
212 #[test]
213 fn parse_round_trip() {
214 let sk = SecretKey::from_byte_array(VALID_SECRET).unwrap();
215 let hex_str = sk.to_hex();
216 assert_eq!(hex_str.len(), 64);
217 assert!(hex_str.chars().all(|c| c.is_ascii_hexdigit()));
218 let parsed = SecretKey::parse(&hex_str).unwrap();
219 assert_eq!(parsed, sk);
220 }
221
222 #[test]
223 fn generate_distinct() {
224 let lhs = SecretKey::generate().unwrap();
225 let rhs = SecretKey::generate().unwrap();
226 assert_ne!(lhs, rhs);
227 }
228
229 #[test]
230 fn debug_redacts() {
231 let sk = SecretKey::from_byte_array(VALID_SECRET).unwrap();
232 let dbg = format!("{sk:?}");
233 assert!(dbg.contains("redacted"));
234 assert!(!dbg.contains(&sk.to_hex()));
235 }
236
237 #[test]
238 fn serde_round_trip() {
239 let sk = SecretKey::from_byte_array(VALID_SECRET).unwrap();
240 let json = serde_json::to_string(&sk).unwrap();
241 let parsed: SecretKey = serde_json::from_str(&json).unwrap();
242 assert_eq!(parsed, sk);
243 }
244
245 #[test]
246 fn serde_rejects_short_hex() {
247 let result: Result<SecretKey, _> = serde_json::from_str("\"abcdef\"");
248 assert!(result.is_err());
249 }
250
251 #[test]
252 fn from_str_works() {
253 let sk: SecretKey = "0000000000000000000000000000000000000000000000000000000000000001"
254 .parse()
255 .unwrap();
256 assert_eq!(sk.to_byte_array(), VALID_SECRET);
257 }
258
259 #[test]
260 fn drop_runs_non_secure_erase() {
261 let _ = SecretKey::from_byte_array(VALID_SECRET).unwrap();
267 }
268}