2020-03-13 14:38:42 -07:00
|
|
|
|
//! Sprout key types
|
2020-03-13 15:58:02 -07:00
|
|
|
|
//!
|
|
|
|
|
//! "The receiving key sk_enc, the incoming viewing key ivk = (apk,
|
|
|
|
|
//! sk_enc), and the shielded payment address addr_pk = (a_pk, pk_enc) are
|
|
|
|
|
//! derived from a_sk, as described in [‘Sprout Key Components’][ps]
|
|
|
|
|
//!
|
|
|
|
|
//! [ps]: https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents
|
2020-03-13 14:38:42 -07:00
|
|
|
|
|
2020-04-14 21:29:15 -07:00
|
|
|
|
use std::{fmt, io};
|
2020-03-26 18:32:21 -07:00
|
|
|
|
|
2020-03-24 01:49:27 -07:00
|
|
|
|
use byteorder::{ByteOrder, LittleEndian};
|
2020-03-27 02:30:16 -07:00
|
|
|
|
use rand_core::{CryptoRng, RngCore};
|
2020-04-14 21:29:15 -07:00
|
|
|
|
use sha2;
|
2020-03-24 01:49:27 -07:00
|
|
|
|
|
2020-02-25 15:14:59 -08:00
|
|
|
|
#[cfg(test)]
|
2020-04-14 21:29:15 -07:00
|
|
|
|
use proptest::{array, prelude::*};
|
2020-02-25 15:14:59 -08:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
use proptest_derive::Arbitrary;
|
|
|
|
|
|
2020-04-14 21:29:15 -07:00
|
|
|
|
use crate::{
|
|
|
|
|
serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize},
|
|
|
|
|
Network,
|
|
|
|
|
};
|
2020-03-18 17:26:07 -07:00
|
|
|
|
|
2020-04-14 23:44:55 -07:00
|
|
|
|
/// Magic numbers used to identify what networks Sprout Shielded
|
|
|
|
|
/// Addresses are associated with.
|
|
|
|
|
mod sk_magics {
|
|
|
|
|
pub const MAINNET: [u8; 2] = [0xAB, 0x36];
|
|
|
|
|
pub const TESTNET: [u8; 2] = [0xAC, 0x08];
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-02 18:43:10 -07:00
|
|
|
|
/// PRF^addr is used to derive a Sprout shielded payment address from
|
|
|
|
|
/// a spending key, and instantiated using the SHA-256 compression
|
|
|
|
|
/// function.
|
|
|
|
|
///
|
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#abstractprfs
|
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents
|
|
|
|
|
fn prf_addr(x: [u8; 32], t: u8) -> [u8; 32] {
|
|
|
|
|
let mut state = [0u32; 8];
|
|
|
|
|
let mut block = [0u8; 64];
|
|
|
|
|
|
|
|
|
|
block[0..32].copy_from_slice(&x[..]);
|
|
|
|
|
block[0] |= 0b1100_0000;
|
|
|
|
|
|
|
|
|
|
block[32] = t;
|
|
|
|
|
|
|
|
|
|
sha2::compress256(&mut state, &block);
|
|
|
|
|
|
|
|
|
|
let mut derived_bytes = [0u8; 32];
|
|
|
|
|
LittleEndian::write_u32_into(&state, &mut derived_bytes);
|
|
|
|
|
|
|
|
|
|
return derived_bytes;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-18 17:26:07 -07:00
|
|
|
|
/// Our root secret key of the Sprout key derivation tree.
|
|
|
|
|
///
|
2020-03-13 15:58:02 -07:00
|
|
|
|
/// All other Sprout key types derive from the SpendingKey value.
|
2020-03-18 17:26:07 -07:00
|
|
|
|
/// Actually 252 bits.
|
2020-03-24 01:49:27 -07:00
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
2020-04-14 23:44:55 -07:00
|
|
|
|
#[cfg_attr(test, derive(Arbitrary))]
|
|
|
|
|
pub struct SpendingKey {
|
2020-04-15 00:01:48 -07:00
|
|
|
|
/// What would normally be the value inside a tuple struct.
|
2020-04-14 23:44:55 -07:00
|
|
|
|
pub bytes: [u8; 32],
|
2020-04-15 00:01:48 -07:00
|
|
|
|
/// The Zcash network with which this key is associated.
|
2020-04-14 23:44:55 -07:00
|
|
|
|
pub network: Network,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ZcashSerialize for SpendingKey {
|
|
|
|
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
|
|
|
|
match self.network {
|
|
|
|
|
Network::Mainnet => writer.write_all(&sk_magics::MAINNET[..])?,
|
|
|
|
|
_ => writer.write_all(&sk_magics::TESTNET[..])?,
|
|
|
|
|
}
|
|
|
|
|
writer.write_all(&self.bytes[..])?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ZcashDeserialize for SpendingKey {
|
|
|
|
|
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 {
|
|
|
|
|
sk_magics::MAINNET => Network::Mainnet,
|
|
|
|
|
sk_magics::TESTNET => Network::Testnet,
|
|
|
|
|
_ => panic!(SerializationError::Parse(
|
|
|
|
|
"bad sprout shielded addr version/type",
|
|
|
|
|
)),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(SpendingKey {
|
|
|
|
|
network,
|
|
|
|
|
bytes: reader.read_32_bytes()?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for SpendingKey {
|
|
|
|
|
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 SpendingKey {
|
|
|
|
|
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")),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<[u8; 32]> for SpendingKey {
|
|
|
|
|
/// Generate a _SpendingKey_ from existing bytes, with the high 4
|
|
|
|
|
/// bits of the first byte set to zero (ie, 256 bits clamped to
|
|
|
|
|
/// 252).
|
|
|
|
|
fn from(mut bytes: [u8; 32]) -> SpendingKey {
|
|
|
|
|
bytes[0] &= 0b0000_1111; // Force the 4 high-order bits to zero.
|
|
|
|
|
|
|
|
|
|
SpendingKey {
|
|
|
|
|
bytes,
|
|
|
|
|
network: Network::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-13 15:58:02 -07:00
|
|
|
|
|
2020-03-27 02:30:16 -07:00
|
|
|
|
impl SpendingKey {
|
2020-03-27 23:01:10 -07:00
|
|
|
|
/// Generate a new _SpendingKey_ with the high 4 bits of the first
|
|
|
|
|
/// byte set to zero (ie, 256 random bits, clamped to 252).
|
2020-03-27 02:30:16 -07:00
|
|
|
|
pub fn new<T>(csprng: &mut T) -> Self
|
|
|
|
|
where
|
|
|
|
|
T: RngCore + CryptoRng,
|
|
|
|
|
{
|
|
|
|
|
let mut bytes = [0u8; 32];
|
|
|
|
|
csprng.fill_bytes(&mut bytes);
|
|
|
|
|
|
|
|
|
|
Self::from(bytes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-18 11:57:23 -07:00
|
|
|
|
/// Derived from a _SpendingKey_.
|
|
|
|
|
pub type ReceivingKey = x25519_dalek::StaticSecret;
|
2020-03-13 15:58:02 -07:00
|
|
|
|
|
2020-03-18 17:26:07 -07:00
|
|
|
|
impl From<SpendingKey> for ReceivingKey {
|
2020-03-27 02:30:16 -07:00
|
|
|
|
/// For this invocation of SHA256Compress as PRF^addr, t=0, which
|
2020-03-24 02:09:28 -07:00
|
|
|
|
/// is populated by default in an empty block of all zeros to
|
|
|
|
|
/// start.
|
|
|
|
|
///
|
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents
|
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#concreteprfs
|
2020-03-18 17:26:07 -07:00
|
|
|
|
fn from(spending_key: SpendingKey) -> ReceivingKey {
|
2020-04-02 18:43:10 -07:00
|
|
|
|
let derived_bytes = prf_addr(spending_key.bytes, 0);
|
2020-03-24 01:49:27 -07:00
|
|
|
|
|
|
|
|
|
ReceivingKey::from(derived_bytes)
|
2020-03-18 17:26:07 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-18 11:57:23 -07:00
|
|
|
|
/// Derived from a _SpendingKey_.
|
2020-03-26 18:34:35 -07:00
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
2020-03-17 15:49:15 -07:00
|
|
|
|
pub struct PayingKey(pub [u8; 32]);
|
2020-03-13 15:58:02 -07:00
|
|
|
|
|
2020-03-26 18:34:35 -07:00
|
|
|
|
impl fmt::Debug for PayingKey {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
f.debug_tuple("PayingKey")
|
|
|
|
|
.field(&hex::encode(&self.0))
|
|
|
|
|
.finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-24 02:09:28 -07:00
|
|
|
|
impl From<SpendingKey> for PayingKey {
|
2020-03-27 02:30:16 -07:00
|
|
|
|
/// For this invocation of SHA256Compress as PRF^addr, t=1.
|
2020-03-24 02:09:28 -07:00
|
|
|
|
///
|
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents
|
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#concreteprfs
|
|
|
|
|
fn from(spending_key: SpendingKey) -> PayingKey {
|
2020-04-02 18:43:10 -07:00
|
|
|
|
let derived_bytes = prf_addr(spending_key.bytes, 1);
|
2020-03-24 02:09:28 -07:00
|
|
|
|
|
|
|
|
|
PayingKey(derived_bytes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-18 11:57:23 -07:00
|
|
|
|
/// Derived from a _ReceivingKey_.
|
2020-03-17 15:49:15 -07:00
|
|
|
|
pub type TransmissionKey = x25519_dalek::PublicKey;
|
2020-03-13 15:58:02 -07:00
|
|
|
|
|
2020-04-14 23:44:55 -07:00
|
|
|
|
/// Magic numbers used to identify with what networks Sprout Incoming
|
|
|
|
|
/// Viewing Keys are associated.
|
2020-04-14 21:29:15 -07:00
|
|
|
|
mod ivk_magics {
|
|
|
|
|
pub const MAINNET: [u8; 3] = [0xA8, 0xAB, 0xD3];
|
|
|
|
|
pub const TESTNET: [u8; 3] = [0xA8, 0xAC, 0x0C];
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-26 19:20:20 -07:00
|
|
|
|
/// The recipient’s possession of the associated incoming viewing key
|
|
|
|
|
/// is used to reconstruct the original note and memo field.
|
2020-03-13 15:58:02 -07:00
|
|
|
|
pub struct IncomingViewingKey {
|
2020-04-14 21:29:15 -07:00
|
|
|
|
network: Network,
|
2020-03-13 15:58:02 -07:00
|
|
|
|
paying_key: PayingKey,
|
|
|
|
|
receiving_key: ReceivingKey,
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 21:29:15 -07:00
|
|
|
|
// Can't derive PartialEq because ReceivingKey aka
|
|
|
|
|
// x25519_dalek::StaticSecret does not impl it.
|
|
|
|
|
impl PartialEq for IncomingViewingKey {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.network == other.network
|
|
|
|
|
&& self.paying_key.0 == other.paying_key.0
|
|
|
|
|
&& self.receiving_key.to_bytes() == other.receiving_key.to_bytes()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Debug for IncomingViewingKey {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
f.debug_struct("IncomingViewingKey")
|
|
|
|
|
.field("network", &self.network)
|
|
|
|
|
.field("paying_key", &hex::encode(&self.paying_key.0))
|
|
|
|
|
.field(
|
|
|
|
|
"receiving_key",
|
|
|
|
|
&hex::encode(&self.receiving_key.to_bytes()),
|
|
|
|
|
)
|
|
|
|
|
.finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ZcashSerialize for IncomingViewingKey {
|
|
|
|
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
|
|
|
|
match self.network {
|
|
|
|
|
Network::Mainnet => writer.write_all(&ivk_magics::MAINNET[..])?,
|
|
|
|
|
_ => writer.write_all(&ivk_magics::TESTNET[..])?,
|
|
|
|
|
}
|
|
|
|
|
writer.write_all(&self.paying_key.0[..])?;
|
|
|
|
|
writer.write_all(&self.receiving_key.to_bytes())?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ZcashDeserialize for IncomingViewingKey {
|
|
|
|
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
|
|
|
|
let mut version_bytes = [0; 3];
|
|
|
|
|
reader.read_exact(&mut version_bytes)?;
|
|
|
|
|
|
|
|
|
|
let network = match version_bytes {
|
|
|
|
|
ivk_magics::MAINNET => Network::Mainnet,
|
|
|
|
|
ivk_magics::TESTNET => Network::Testnet,
|
|
|
|
|
_ => panic!(SerializationError::Parse(
|
2020-04-14 23:44:55 -07:00
|
|
|
|
"bad sprout incoming viewing key network",
|
2020-04-14 21:29:15 -07:00
|
|
|
|
)),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(IncomingViewingKey {
|
|
|
|
|
network,
|
|
|
|
|
paying_key: PayingKey(reader.read_32_bytes()?),
|
|
|
|
|
receiving_key: ReceivingKey::from(reader.read_32_bytes()?),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for IncomingViewingKey {
|
|
|
|
|
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 IncomingViewingKey {
|
|
|
|
|
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 IncomingViewingKey {
|
|
|
|
|
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, receiving_key_bytes)| {
|
|
|
|
|
return Self {
|
|
|
|
|
network,
|
|
|
|
|
paying_key: PayingKey(paying_key_bytes),
|
|
|
|
|
receiving_key: ReceivingKey::from(receiving_key_bytes),
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.boxed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Strategy = BoxedStrategy<Self>;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-24 01:49:27 -07:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
|
2020-03-27 02:30:16 -07:00
|
|
|
|
use rand_core::OsRng;
|
2020-03-24 01:49:27 -07:00
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
2020-03-24 02:13:25 -07:00
|
|
|
|
// TODO: test vectors, not just random data
|
2020-03-24 02:09:28 -07:00
|
|
|
|
fn derive_keys() {
|
2020-03-27 02:30:16 -07:00
|
|
|
|
let spending_key = SpendingKey::new(&mut OsRng);
|
2020-03-24 01:49:27 -07:00
|
|
|
|
|
|
|
|
|
println!("{:?}", spending_key);
|
|
|
|
|
|
|
|
|
|
let receiving_key = ReceivingKey::from(spending_key);
|
|
|
|
|
|
2020-04-14 23:44:55 -07:00
|
|
|
|
let _transmission_key = TransmissionKey::from(&receiving_key);
|
2020-03-24 01:49:27 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-25 15:14:59 -08:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
proptest! {
|
|
|
|
|
|
2020-04-14 23:44:55 -07:00
|
|
|
|
#[test]
|
|
|
|
|
fn spending_key_roundtrip(sk in any::<SpendingKey>()) {
|
|
|
|
|
|
|
|
|
|
let mut data = Vec::new();
|
|
|
|
|
|
|
|
|
|
sk.zcash_serialize(&mut data).expect("sprout spending keyshould serialize");
|
|
|
|
|
|
|
|
|
|
let sk2 = SpendingKey::zcash_deserialize(&data[..]).expect("randomized sprout spending key should deserialize");
|
|
|
|
|
|
|
|
|
|
prop_assert_eq![sk, sk2];
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn spending_key_string_roundtrip(sk in any::<SpendingKey>()) {
|
|
|
|
|
|
|
|
|
|
let string = sk.to_string();
|
|
|
|
|
|
|
|
|
|
let sk2 = string.parse::<SpendingKey>().unwrap();
|
|
|
|
|
|
|
|
|
|
prop_assert_eq![sk, sk2];
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 21:29:15 -07:00
|
|
|
|
#[test]
|
|
|
|
|
fn incoming_viewing_key_roundtrip(ivk in any::<IncomingViewingKey>()) {
|
|
|
|
|
|
|
|
|
|
let mut data = Vec::new();
|
|
|
|
|
|
|
|
|
|
ivk.zcash_serialize(&mut data).expect("t-addr should serialize");
|
|
|
|
|
|
|
|
|
|
let ivk2 = IncomingViewingKey::zcash_deserialize(&data[..]).expect("randomized ivk should deserialize");
|
|
|
|
|
|
|
|
|
|
prop_assert_eq![ivk, ivk2];
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn incoming_viewing_key_string_roundtrip(ivk in any::<IncomingViewingKey>()) {
|
|
|
|
|
|
|
|
|
|
let string = ivk.to_string();
|
|
|
|
|
|
|
|
|
|
let ivk2 = string.parse::<IncomingViewingKey>().unwrap();
|
|
|
|
|
|
|
|
|
|
prop_assert_eq![ivk, ivk2];
|
|
|
|
|
|
|
|
|
|
}
|
2020-02-25 15:14:59 -08:00
|
|
|
|
}
|