175 lines
5.4 KiB
Rust
175 lines
5.4 KiB
Rust
//! Sprout Shielded Payment Address types.
|
|
|
|
use std::{fmt, io};
|
|
|
|
use bs58;
|
|
|
|
#[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
|
|
///
|
|
/// In Bitcoin a single byte is used for the version field identifying
|
|
/// the address type. In Zcash two bytes are used. For addresses on
|
|
/// the production network, this and the encoded length cause the first
|
|
/// two characters of the Base58Check encoding to be fixed as “t3” for
|
|
/// P2SH addresses, and as “t1” for P2PKH addresses. (This does not
|
|
/// imply that a transparent Zcash address can be parsed identically
|
|
/// to a Bitcoin address just by removing the “t”.)
|
|
///
|
|
/// https://zips.z.cash/protocol/protocol.pdf#transparentaddrencoding
|
|
#[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 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.debug_tuple("SproutShieldedAddress")
|
|
.field(&bs58::encode(bytes.get_ref()).with_check().into_string())
|
|
.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<T: ?Sized + AsRef<[u8]>> From<&T> for SproutShieldedAddress {
|
|
fn from(s: &T) -> Self {
|
|
let bytes = &bs58::decode(s).with_check(None).into_vec().unwrap();
|
|
|
|
return Self::zcash_deserialize(&bytes[..]).expect("sprout z-addr should deserialize");
|
|
}
|
|
}
|
|
|
|
#[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)| {
|
|
return 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 zc_addr = SproutShieldedAddress::from(
|
|
"zcU1Cd6zYyZCd2VJF8yKgmzjxdiiU1rgTTjEwoN1CGUWCziPkUTXUjXmX7TMqdMNsTfuiGN1jQoVN4kGxUR4sAPN4XZ7pxb"
|
|
);
|
|
|
|
assert_eq!(
|
|
format!("{:?}", zc_addr),
|
|
"SproutShieldedAddress { network: Mainnet, paying_key: PayingKey(\"972caa450769480a995064693db07e0302afe6c3a737e8cc083215dfdfbea3a7\"), transmission_key: \"92c223a94d39e539b85fad3debadc980b4c64294ab8a66d04ca80be3dd7da763\" }"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
proptest! {
|
|
|
|
#[test]
|
|
fn sprout_address_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];
|
|
}
|
|
}
|