diff --git a/zebra-chain/src/keys/sprout.rs b/zebra-chain/src/keys/sprout.rs index 31e1c41b..10ccc0e9 100644 --- a/zebra-chain/src/keys/sprout.rs +++ b/zebra-chain/src/keys/sprout.rs @@ -40,6 +40,9 @@ fn prf_addr(x: [u8; 32], t: u8) -> [u8; 32] { let mut block = [0u8; 64]; block[0..32].copy_from_slice(&x[..]); + // The first four bits –i.e. the most signicant four bits of the + // first byte– are used to separate distinct uses + // ofSHA256Compress, ensuring that the functions are independent. block[0] |= 0b1100_0000; block[32] = t; @@ -107,19 +110,6 @@ impl fmt::Display for SpendingKey { } } -impl std::str::FromStr for SpendingKey { - 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")), - } - } -} - 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 @@ -134,6 +124,25 @@ impl From<[u8; 32]> for SpendingKey { } } +impl From for [u8; 32] { + fn from(spending_key: SpendingKey) -> [u8; 32] { + spending_key.bytes + } +} + +impl std::str::FromStr for SpendingKey { + 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")), + } + } +} + impl SpendingKey { /// Generate a new _SpendingKey_ with the high 4 bits of the first /// byte set to zero (ie, 256 random bits, clamped to 252). diff --git a/zebra-chain/src/notes/sprout.rs b/zebra-chain/src/notes/sprout.rs index e24b3d47..067c5de5 100644 --- a/zebra-chain/src/notes/sprout.rs +++ b/zebra-chain/src/notes/sprout.rs @@ -4,11 +4,12 @@ use std::{fmt, io}; +use byteorder::{ByteOrder, LittleEndian}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use crate::{ - keys::sprout::PayingKey, + keys::sprout::{PayingKey, SpendingKey}, serde_helpers, serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, types::amount::{Amount, NonNegative}, @@ -16,6 +17,32 @@ use crate::{ use super::memo::Memo; +/// PRF^nf is used to derive a Sprout nullifer from the receiver's +/// spending key a_sk and a nullifier seed ρ, instantiated using the +/// SHA-256 compression function. +/// +/// https://zips.z.cash/protocol/protocol.pdf#abstractprfs +/// https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers +fn prf_nf(a_sk: [u8; 32], rho: [u8; 32]) -> [u8; 32] { + let mut state = [0u32; 8]; + let mut block = [0u8; 64]; + + block[0..32].copy_from_slice(&a_sk[..]); + // The first four bits –i.e. the most signicant four bits of the + // first byte– are used to separate distinct uses + // ofSHA256Compress, ensuring that the functions are independent. + block[0] |= 0b1110_0000; + + block[32..].copy_from_slice(&rho[..]); + + sha2::compress256(&mut state, &block); + + let mut derived_bytes = [0u8; 32]; + LittleEndian::write_u32_into(&state, &mut derived_bytes); + + derived_bytes +} + /// Nullifier seed, named rho in the [spec][ps]. /// /// [ps]: https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents @@ -28,14 +55,26 @@ impl AsRef<[u8]> for NullifierSeed { } } -/// A Nullifier Set for Sprout transactions +impl From<[u8; 32]> for NullifierSeed { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +/// A Nullifier for Sprout transactions #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] pub struct Nullifier([u8; 32]); -impl From<[u8; 32]> for Nullifier { - fn from(buf: [u8; 32]) -> Self { - Self(buf) +impl From for [u8; 32] { + fn from(rho: NullifierSeed) -> Self { + rho.0 + } +} + +impl From<(SpendingKey, NullifierSeed)> for Nullifier { + fn from((a_sk, rho): (SpendingKey, NullifierSeed)) -> Self { + Self(prf_nf(a_sk.into(), rho.into())) } }