1#[cfg(feature = "transparent-inputs")]
7pub mod gap_limits;
8
9#[cfg(feature = "transparent-key-encoding")]
10use {
11 bip32::{PrivateKey, PrivateKeyBytes},
12 core::array::TryFromSliceError,
13 secp256k1::{PublicKey, SecretKey},
14 secrecy::{ExposeSecret, SecretString, SecretVec, Zeroize, zeroize::ZeroizeOnDrop},
15 zcash_protocol::consensus::NetworkType,
16};
17
18#[cfg(feature = "transparent-key-encoding")]
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub struct DerDecodeError;
22
23#[cfg(feature = "transparent-key-encoding")]
24impl core::fmt::Display for DerDecodeError {
25 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26 write!(f, "Invalid DER encoding for transparent secret key")
27 }
28}
29
30#[cfg(feature = "transparent-key-encoding")]
32#[derive(Debug)]
33pub enum ParseError {
34 InvalidEncoding,
37 Base58(bs58::decode::Error),
39 Bip32(bip32::Error),
41}
42
43#[cfg(feature = "transparent-key-encoding")]
44impl core::fmt::Debug for Key {
45 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 f.debug_struct("Key")
47 .field("secret", &"...")
48 .field("compressed", &self.compressed)
49 .finish()
50 }
51}
52
53#[cfg(feature = "transparent-key-encoding")]
54impl From<bs58::decode::Error> for ParseError {
55 fn from(value: bs58::decode::Error) -> Self {
56 ParseError::Base58(value)
57 }
58}
59
60#[cfg(feature = "transparent-key-encoding")]
61impl From<TryFromSliceError> for ParseError {
62 fn from(_: TryFromSliceError) -> Self {
63 ParseError::InvalidEncoding
64 }
65}
66
67#[cfg(feature = "transparent-key-encoding")]
68impl From<bip32::Error> for ParseError {
69 fn from(value: bip32::Error) -> Self {
70 ParseError::Bip32(value)
71 }
72}
73
74#[cfg(feature = "transparent-key-encoding")]
77pub struct Key {
78 secret: SecretKey,
79 compressed: bool,
80}
81
82#[cfg(feature = "transparent-key-encoding")]
83impl Key {
84 pub fn new(secret: SecretKey, compressed: bool) -> Self {
87 Self { secret, compressed }
88 }
89
90 pub fn decode_base58(network: NetworkType, encoded: &SecretString) -> Result<Self, ParseError> {
94 let decoded = SecretVec::new(
95 bs58::decode(encoded.expose_secret())
96 .with_check(None)
97 .into_vec()?,
98 );
99 let prefix = crate::constants::b58_secret_key_prefix(network);
100 let decoded_len = decoded.expose_secret().len();
101 let compressed =
102 decoded_len == (33 + prefix.len()) && decoded.expose_secret().last() == Some(&1);
103 if (decoded_len == 32 + prefix.len() || compressed)
104 && decoded.expose_secret()[0..prefix.len()] == prefix
105 {
106 let key_end = decoded_len - if compressed { 1 } else { 0 };
107 let bytes = PrivateKeyBytes::try_from(&decoded.expose_secret()[prefix.len()..key_end])?;
108 Ok(Self {
109 secret: SecretKey::from_bytes(&bytes)?,
110 compressed,
111 })
112 } else {
113 Err(ParseError::InvalidEncoding)
114 }
115 }
116
117 pub fn encode_base58(&self, network: NetworkType) -> SecretString {
121 let input = SecretVec::new(
122 crate::constants::b58_secret_key_prefix(network)
123 .iter()
124 .chain(self.secret.to_secret_bytes().iter())
125 .chain(self.compressed.then_some(&1))
126 .copied()
127 .collect(),
128 );
129
130 SecretString::new(
131 bs58::encode(input.expose_secret())
132 .with_check()
133 .into_string(),
134 )
135 }
136
137 pub fn secret(&self) -> &SecretKey {
139 &self.secret
140 }
141
142 pub fn compressed(&self) -> bool {
144 self.compressed
145 }
146
147 pub fn pubkey(&self) -> PublicKey {
149 self.secret.public_key()
150 }
151
152 pub fn der_encode(&self) -> SecretVec<u8> {
154 let keypair = self.secret().keypair();
164
165 if self.compressed {
166 let begin = [0x30, 0x81, 0xD3, 0x02, 0x01, 0x01, 0x04, 0x20];
167 let middle = [
168 0xA0, 0x81, 0x85, 0x30, 0x81, 0x82, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A,
169 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
170 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
171 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFC, 0x2F, 0x30,
172 0x06, 0x04, 0x01, 0x00, 0x04, 0x01, 0x07, 0x04, 0x21, 0x02, 0x79, 0xBE, 0x66, 0x7E,
173 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07, 0x02, 0x9B,
174 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98,
175 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
176 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF,
177 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41, 0x02, 0x01, 0x01, 0xA1, 0x24, 0x03, 0x22,
178 0x00,
179 ];
180 SecretVec::new(
190 begin
191 .iter()
192 .chain(keypair.to_secret_bytes().iter())
193 .chain(middle.iter())
194 .chain(keypair.public_key().serialize().iter())
195 .copied()
196 .collect(),
197 )
198 } else {
199 let begin = [0x30, 0x82, 0x01, 0x13, 0x02, 0x01, 0x01, 0x04, 0x20];
200 let middle = [
201 0xA0, 0x81, 0xA5, 0x30, 0x81, 0xA2, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A,
202 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
203 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
204 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFC, 0x2F, 0x30,
205 0x06, 0x04, 0x01, 0x00, 0x04, 0x01, 0x07, 0x04, 0x41, 0x04, 0x79, 0xBE, 0x66, 0x7E,
206 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07, 0x02, 0x9B,
207 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98,
208 0x48, 0x3A, 0xDA, 0x77, 0x26, 0xA3, 0xC4, 0x65, 0x5D, 0xA4, 0xFB, 0xFC, 0x0E, 0x11,
209 0x08, 0xA8, 0xFD, 0x17, 0xB4, 0x48, 0xA6, 0x85, 0x54, 0x19, 0x9C, 0x47, 0xD0, 0x8F,
210 0xFB, 0x10, 0xD4, 0xB8, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
211 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF,
212 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41, 0x02, 0x01, 0x01,
213 0xA1, 0x44, 0x03, 0x42, 0x00,
214 ];
215 SecretVec::new(
225 begin
226 .iter()
227 .chain(keypair.to_secret_bytes().iter())
228 .chain(middle.iter())
229 .chain(keypair.public_key().serialize_uncompressed().iter())
230 .copied()
231 .collect(),
232 )
233 }
234 }
235
236 pub fn der_decode(encoded: &SecretVec<u8>, compressed: bool) -> Result<Self, DerDecodeError> {
238 let seckey = encoded.expose_secret();
242
243 let seckey = match seckey.split_first() {
249 Some((&0x30, rest)) => Ok(rest),
250 _ => Err(DerDecodeError),
251 }?;
252
253 let (lenb, seckey) = match seckey.split_first() {
265 Some((lenb, seckey)) if lenb & 0x80 != 0 => Ok((usize::from(lenb & !0x80), seckey)),
266 _ => Err(DerDecodeError),
267 }?;
268 if !(1..=2).contains(&lenb) {
269 return Err(DerDecodeError);
270 }
271 if seckey.len() < lenb {
272 return Err(DerDecodeError);
273 }
274
275 let len_low_bits = usize::from(seckey[lenb - 1]);
282 let len_high_bits = if lenb > 1 {
283 usize::from(seckey[lenb - 2]) << 8
284 } else {
285 0
286 };
287 let len = len_low_bits | len_high_bits;
288 let seckey = &seckey[lenb..];
289 if seckey.len() < len {
290 return Err(DerDecodeError);
291 }
292
293 let seckey = match seckey.split_at(3) {
299 (&[0x02, 0x01, 0x01], rest) => Ok(rest),
300 _ => Err(DerDecodeError),
301 }?;
302
303 if seckey.len() < 2 || seckey[0] != 0x04 {
320 return Err(DerDecodeError);
321 }
322 let oslen = usize::from(seckey[1]);
323 let seckey = &seckey[2..];
324 if oslen > 32 || seckey.len() < oslen {
325 return Err(DerDecodeError);
326 }
327 let mut secret_buf = [0u8; 32];
328 secret_buf[(32 - oslen)..].copy_from_slice(&seckey[..oslen]);
329 let secret_key = SecretKey::from_bytes(&secret_buf);
330 secret_buf.zeroize();
331
332 secret_key
333 .map(|secret| Self { secret, compressed })
334 .map_err(|_| DerDecodeError)
335 }
336}
337
338#[cfg(feature = "transparent-key-encoding")]
339impl Zeroize for Key {
340 fn zeroize(&mut self) {
341 self.secret.non_secure_erase();
342 }
343}
344
345#[cfg(feature = "transparent-key-encoding")]
346impl ZeroizeOnDrop for Key {}
347
348#[cfg(all(
349 feature = "transparent-key-encoding",
350 any(test, feature = "test-dependencies")
351))]
352pub mod test_vectors;
353
354#[cfg(all(test, feature = "transparent-key-encoding"))]
355mod tests {
356 use rand::{Rng, RngExt, SeedableRng as _};
357 use rand_chacha::ChaChaRng;
358 use secp256k1::SecretKey;
359 use secrecy::SecretString;
360 use zcash_protocol::consensus::NetworkType;
361 use zcash_script::script::Evaluable;
362
363 use super::{
364 Key,
365 test_vectors::{INVALID, VALID, VectorKind},
366 };
367 use crate::address::Address;
368
369 fn random_secret_key(rng: &mut impl Rng) -> SecretKey {
371 loop {
372 let mut bytes = [0u8; 32];
373 rng.fill_bytes(&mut bytes);
374 if let Ok(secret) = SecretKey::from_secret_bytes(bytes) {
375 return secret;
376 }
377 }
378 }
379
380 #[test]
381 #[cfg(feature = "transparent-key-encoding")]
382 fn der_encoding_roundtrip() {
383 let mut rng = ChaChaRng::from_seed([0u8; 32]);
384 for _ in 0..100 {
385 let secret = random_secret_key(&mut rng);
386 let compressed = rng.random_bool(0.5);
387 let key = Key { secret, compressed };
388
389 let encoded = key.der_encode();
390 let decoded = Key::der_decode(&encoded, compressed).unwrap();
391
392 assert_eq!(key.secret(), decoded.secret());
393 }
394 }
395
396 #[test]
398 fn base58_keys_valid_parse() {
399 for v in VALID {
400 match v.kind {
401 VectorKind::Privkey { is_compressed } => {
402 let secret = &SecretString::new(v.base58_encoding.into());
404 let privkey = Key::decode_base58(v.network, secret).unwrap();
405 assert_eq!(privkey.compressed, is_compressed);
406 assert_eq!(
407 hex::encode(privkey.secret.to_secret_bytes()),
408 v.raw_bytes_hex
409 );
410
411 assert_eq!(
413 zcash_address::ZcashAddress::try_from_encoded(v.base58_encoding),
414 Err(zcash_address::ParseError::NotZcash),
415 );
416 }
417 VectorKind::Pubkey => {
418 let destination =
420 match zcash_address::ZcashAddress::try_from_encoded(v.base58_encoding)
421 .unwrap()
422 .convert_if_network::<Address>(v.network)
423 .unwrap()
424 {
425 Address::Transparent(addr) => addr,
426 _ => panic!("{} is not a transparent address", v.base58_encoding),
427 };
428 let script = destination.script();
429 assert_eq!(hex::encode(script.to_bytes()), v.raw_bytes_hex);
430
431 assert!(
433 Key::decode_base58(v.network, &SecretString::new(v.base58_encoding.into()))
434 .is_err()
435 );
436 }
437 }
438 }
439 }
440
441 #[test]
443 fn base58_keys_invalid() {
444 for &encoded in INVALID {
445 assert!(
446 Key::decode_base58(NetworkType::Main, &SecretString::new(encoded.into())).is_err()
447 );
448 assert_eq!(
449 zcash_address::ZcashAddress::try_from_encoded(encoded),
450 Err(zcash_address::ParseError::NotZcash),
451 );
452 }
453 }
454
455 #[test]
456 #[cfg(feature = "transparent-key-encoding")]
457 fn key_debug_redaction() {
458 let mut rng = ChaChaRng::from_seed([0u8; 32]);
459 let secret = random_secret_key(&mut rng);
460 let key = Key {
461 secret,
462 compressed: true,
463 };
464 let debug_str = format!("{key:?}");
465 assert!(debug_str.contains("secret: \"...\""));
466 assert!(debug_str.contains("compressed: true"));
467 }
468}