//! Sprout Shielded Payment Address types. use std::{fmt, io}; #[cfg(test)] use proptest::{arbitrary::Arbitrary, array, prelude::*}; use crate::{ keys::sprout, serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, Network, }; /// Magic numbers used to identify what networks Sprout Shielded /// Addresses are associated with. mod magics { pub const MAINNET: [u8; 2] = [0x16, 0x9A]; pub const TESTNET: [u8; 2] = [0x16, 0xB6]; } /// Sprout Shielded Payment Addresses /// /// https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding #[derive(Copy, Clone)] pub struct SproutShieldedAddress { network: Network, paying_key: sprout::PayingKey, transmission_key: sprout::TransmissionKey, } impl fmt::Debug for SproutShieldedAddress { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("SproutShieldedAddress") .field("network", &self.network) .field("paying_key", &self.paying_key) // Because x25519_dalek::PublicKey doesn't impl Debug. .field( "transmission_key", &hex::encode(&self.transmission_key.as_bytes()), ) .finish() } } impl PartialEq for SproutShieldedAddress { fn eq(&self, other: &Self) -> bool { self.network == other.network && self.paying_key.0 == other.paying_key.0 && self.transmission_key.as_bytes() == other.transmission_key.as_bytes() } } impl Eq for SproutShieldedAddress {} impl ZcashSerialize for SproutShieldedAddress { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { match self.network { Network::Mainnet => writer.write_all(&magics::MAINNET[..])?, _ => writer.write_all(&magics::TESTNET[..])?, } writer.write_all(&self.paying_key.0[..])?; writer.write_all(self.transmission_key.as_bytes())?; Ok(()) } } impl ZcashDeserialize for SproutShieldedAddress { fn zcash_deserialize(mut reader: R) -> Result { let mut version_bytes = [0; 2]; reader.read_exact(&mut version_bytes)?; let network = match version_bytes { magics::MAINNET => Network::Mainnet, magics::TESTNET => Network::Testnet, _ => panic!(SerializationError::Parse( "bad sprout shielded addr version/type", )), }; Ok(SproutShieldedAddress { network, paying_key: sprout::PayingKey(reader.read_32_bytes()?), transmission_key: sprout::TransmissionKey::from(reader.read_32_bytes()?), }) } } impl fmt::Display for SproutShieldedAddress { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut bytes = io::Cursor::new(Vec::new()); let _ = self.zcash_serialize(&mut bytes); f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string()) } } impl std::str::FromStr for SproutShieldedAddress { type Err = SerializationError; fn from_str(s: &str) -> Result { let result = &bs58::decode(s).with_check(None).into_vec(); match result { Ok(bytes) => Self::zcash_deserialize(&bytes[..]), Err(_) => Err(SerializationError::Parse("bs58 decoding error")), } } } #[cfg(test)] impl Arbitrary for SproutShieldedAddress { type Parameters = (); fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ( any::(), array::uniform32(any::()), array::uniform32(any::()), ) .prop_map(|(network, paying_key_bytes, transmission_key_bytes)| { Self { network, paying_key: sprout::PayingKey(paying_key_bytes), transmission_key: sprout::TransmissionKey::from(transmission_key_bytes), } }) .boxed() } type Strategy = BoxedStrategy; } #[cfg(test)] mod tests { use super::*; #[test] fn from_string_debug() { let string = "zcU1Cd6zYyZCd2VJF8yKgmzjxdiiU1rgTTjEwoN1CGUWCziPkUTXUjXmX7TMqdMNsTfuiGN1jQoVN4kGxUR4sAPN4XZ7pxb"; let zc_addr = string.parse::().unwrap(); assert_eq!(string, zc_addr.to_string()); } } #[cfg(test)] proptest! { #[test] fn zcash_de_serialize_roundtrip(zaddr in any::()) { let mut data = Vec::new(); zaddr.zcash_serialize(&mut data).expect("sprout z-addr should serialize"); let zaddr2 = SproutShieldedAddress::zcash_deserialize(&data[..]) .expect("randomized sprout z-addr should deserialize"); prop_assert_eq![zaddr, zaddr2]; } #[test] fn zcash_base58check_roundtrip(zaddr in any::()) { let string = zaddr.to_string(); let zaddr2 = string.parse::().unwrap(); prop_assert_eq![zaddr, zaddr2]; } }