Zebra/zebra-chain/src/addresses/sprout.rs

175 lines
5.0 KiB
Rust

//! 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<W: io::Write>(&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<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
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<Self, Self::Err> {
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::<Network>(),
array::uniform32(any::<u8>()),
array::uniform32(any::<u8>()),
)
.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<Self>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_string_debug() {
let string = "zcU1Cd6zYyZCd2VJF8yKgmzjxdiiU1rgTTjEwoN1CGUWCziPkUTXUjXmX7TMqdMNsTfuiGN1jQoVN4kGxUR4sAPN4XZ7pxb";
let zc_addr = string.parse::<SproutShieldedAddress>().unwrap();
assert_eq!(string, zc_addr.to_string());
}
}
#[cfg(test)]
proptest! {
#[test]
fn zcash_de_serialize_roundtrip(zaddr in any::<SproutShieldedAddress>()) {
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::<SproutShieldedAddress>()) {
let string = zaddr.to_string();
let zaddr2 = string.parse::<SproutShieldedAddress>().unwrap();
prop_assert_eq![zaddr, zaddr2];
}
}