silent_payments_core/
keys.rs1use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
11
12use crate::error::CryptoError;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub struct ScanPublicKey(PublicKey);
24
25impl ScanPublicKey {
26 pub fn as_inner(&self) -> &PublicKey {
28 &self.0
29 }
30}
31
32impl From<PublicKey> for ScanPublicKey {
33 fn from(key: PublicKey) -> Self {
34 Self(key)
35 }
36}
37
38impl From<ScanPublicKey> for PublicKey {
39 fn from(key: ScanPublicKey) -> Self {
40 key.0
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct SpendPublicKey(PublicKey);
50
51impl SpendPublicKey {
52 pub fn as_inner(&self) -> &PublicKey {
54 &self.0
55 }
56}
57
58impl From<PublicKey> for SpendPublicKey {
59 fn from(key: PublicKey) -> Self {
60 Self(key)
61 }
62}
63
64impl From<SpendPublicKey> for PublicKey {
65 fn from(key: SpendPublicKey) -> Self {
66 key.0
67 }
68}
69
70pub struct ScanSecretKey(SecretKey);
79
80impl ScanSecretKey {
81 pub fn as_inner(&self) -> &SecretKey {
83 &self.0
84 }
85
86 pub fn from_slice(data: &[u8]) -> Result<Self, CryptoError> {
91 SecretKey::from_slice(data)
92 .map(Self)
93 .map_err(|_| CryptoError::InvalidSecretKey)
94 }
95
96 pub fn from_secret_key(sk: SecretKey) -> Self {
98 Self(sk)
99 }
100
101 pub fn public_key(&self, secp: &Secp256k1<bitcoin::secp256k1::All>) -> ScanPublicKey {
103 ScanPublicKey(self.0.public_key(secp))
104 }
105}
106
107impl PartialEq for ScanSecretKey {
108 fn eq(&self, other: &Self) -> bool {
109 self.0 == other.0
111 }
112}
113
114impl Eq for ScanSecretKey {}
115
116impl std::fmt::Debug for ScanSecretKey {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 f.write_str("ScanSecretKey(***)")
119 }
120}
121
122impl Drop for ScanSecretKey {
123 fn drop(&mut self) {
124 self.0.non_secure_erase();
126 }
127}
128
129pub struct SpendSecretKey(SecretKey);
134
135impl SpendSecretKey {
136 pub fn as_inner(&self) -> &SecretKey {
138 &self.0
139 }
140
141 pub fn from_slice(data: &[u8]) -> Result<Self, CryptoError> {
146 SecretKey::from_slice(data)
147 .map(Self)
148 .map_err(|_| CryptoError::InvalidSecretKey)
149 }
150
151 pub fn from_secret_key(sk: SecretKey) -> Self {
153 Self(sk)
154 }
155
156 pub fn public_key(&self, secp: &Secp256k1<bitcoin::secp256k1::All>) -> SpendPublicKey {
158 SpendPublicKey(self.0.public_key(secp))
159 }
160}
161
162impl PartialEq for SpendSecretKey {
163 fn eq(&self, other: &Self) -> bool {
164 self.0 == other.0
166 }
167}
168
169impl Eq for SpendSecretKey {}
170
171impl std::fmt::Debug for SpendSecretKey {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 f.write_str("SpendSecretKey(***)")
174 }
175}
176
177impl Drop for SpendSecretKey {
178 fn drop(&mut self) {
179 self.0.non_secure_erase();
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 const VALID_KEY_BYTES: [u8; 32] = [
190 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
191 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
192 0x1f, 0x20,
193 ];
194
195 #[test]
196 fn scan_secret_key_from_valid_slice() {
197 let sk = ScanSecretKey::from_slice(&VALID_KEY_BYTES);
198 assert!(
199 sk.is_ok(),
200 "from_slice should succeed with valid 32-byte key"
201 );
202 }
203
204 #[test]
205 fn scan_secret_key_rejects_all_zeros() {
206 let zeros = [0u8; 32];
207 let result = ScanSecretKey::from_slice(&zeros);
208 assert!(
209 matches!(result, Err(CryptoError::InvalidSecretKey)),
210 "from_slice should reject all-zero key"
211 );
212 }
213
214 #[test]
215 fn scan_secret_key_debug_hides_material() {
216 let sk = ScanSecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
217 let debug_str = format!("{:?}", sk);
218 assert_eq!(debug_str, "ScanSecretKey(***)");
219 assert!(
221 !debug_str.contains("0102"),
222 "Debug output must not contain key material"
223 );
224 }
225
226 #[test]
227 fn scan_secret_key_equality() {
228 let sk1 = ScanSecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
229 let sk2 = ScanSecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
230 assert_eq!(sk1, sk2, "keys from same bytes should be equal");
231 }
232
233 #[test]
234 fn scan_secret_key_derives_public_key() {
235 let secp = Secp256k1::new();
236 let sk = ScanSecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
237 let pk = sk.public_key(&secp);
238
239 let raw_sk = SecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
241 let expected_pk = raw_sk.public_key(&secp);
242 assert_eq!(
243 *pk.as_inner(),
244 expected_pk,
245 "public_key() should derive the correct public key"
246 );
247 }
248
249 #[test]
250 fn scan_secret_key_drop_runs_without_panic() {
251 {
253 let _sk = ScanSecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
254 }
255 }
257
258 #[test]
259 fn spend_secret_key_from_valid_slice() {
260 let sk = SpendSecretKey::from_slice(&VALID_KEY_BYTES);
261 assert!(
262 sk.is_ok(),
263 "from_slice should succeed with valid 32-byte key"
264 );
265 }
266
267 #[test]
268 fn spend_secret_key_rejects_all_zeros() {
269 let zeros = [0u8; 32];
270 let result = SpendSecretKey::from_slice(&zeros);
271 assert!(
272 matches!(result, Err(CryptoError::InvalidSecretKey)),
273 "from_slice should reject all-zero key"
274 );
275 }
276
277 #[test]
278 fn spend_secret_key_debug_hides_material() {
279 let sk = SpendSecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
280 let debug_str = format!("{:?}", sk);
281 assert_eq!(debug_str, "SpendSecretKey(***)");
282 }
283
284 #[test]
285 fn spend_secret_key_derives_public_key() {
286 let secp = Secp256k1::new();
287 let sk = SpendSecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
288 let pk = sk.public_key(&secp);
289
290 let raw_sk = SecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
291 let expected_pk = raw_sk.public_key(&secp);
292 assert_eq!(*pk.as_inner(), expected_pk);
293 }
294
295 #[test]
296 fn public_key_from_and_into_conversions() {
297 let secp = Secp256k1::new();
298 let raw_sk = SecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
299 let raw_pk = raw_sk.public_key(&secp);
300
301 let scan_pk = ScanPublicKey::from(raw_pk);
303 assert_eq!(*scan_pk.as_inner(), raw_pk);
304
305 let recovered: PublicKey = scan_pk.into();
307 assert_eq!(recovered, raw_pk);
308
309 let spend_pk = SpendPublicKey::from(raw_pk);
311 assert_eq!(*spend_pk.as_inner(), raw_pk);
312 let recovered: PublicKey = spend_pk.into();
313 assert_eq!(recovered, raw_pk);
314 }
315
316 #[test]
317 fn from_secret_key_constructor() {
318 let raw_sk = SecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
319 let scan_sk = ScanSecretKey::from_secret_key(raw_sk);
320 assert_eq!(
321 *scan_sk.as_inner(),
322 SecretKey::from_slice(&VALID_KEY_BYTES).unwrap()
323 );
324
325 let raw_sk = SecretKey::from_slice(&VALID_KEY_BYTES).unwrap();
326 let spend_sk = SpendSecretKey::from_secret_key(raw_sk);
327 assert_eq!(
328 *spend_sk.as_inner(),
329 SecretKey::from_slice(&VALID_KEY_BYTES).unwrap()
330 );
331 }
332}