diff --git a/Cargo.lock b/Cargo.lock index 733b84f..07fca47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,38 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "blake2b_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "zcash_spec" version = "0.0.0" +dependencies = [ + "blake2b_simd", +] diff --git a/Cargo.toml b/Cargo.toml index 5b90944..e1835cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ repository = "https://github.com/zcash/zcash_spec" license = "MIT OR Apache-2.0" [dependencies] +blake2b_simd = "1" diff --git a/src/lib.rs b/src/lib.rs index 9dd0e43..7c2864e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,4 @@ #![deny(rustdoc::broken_intra_doc_links)] + +mod prf_expand; +pub use prf_expand::PrfExpand; diff --git a/src/prf_expand.rs b/src/prf_expand.rs new file mode 100644 index 0000000..c13e170 --- /dev/null +++ b/src/prf_expand.rs @@ -0,0 +1,109 @@ +use std::marker::PhantomData; + +use blake2b_simd::Params; + +const PRF_EXPAND_PERSONALIZATION: &[u8; 16] = b"Zcash_ExpandSeed"; + +/// The set of domains in which $PRF^\mathsf{expand}$ is defined. +/// +/// Defined in [Zcash Protocol Spec § 5.4.2: Pseudo Random Functions][concreteprfs]. +/// +/// [concreteprfs]: https://zips.z.cash/protocol/protocol.pdf#concreteprfs +pub struct PrfExpand { + domain_separator: u8, + _input: PhantomData, +} + +impl PrfExpand { + /// Defines a new $PRF^\mathsf{expand}$ domain. + /// + /// Private because we want to ensure that all domains are defined in the same place, + /// to avoid bugs where a domain separator is accidentally reused. + const fn new(domain_separator: u8) -> Self { + Self { + domain_separator, + _input: PhantomData, + } + } + + /// Expands the given secret key in this domain, with additional data concatenated + /// from the given slices. + /// + /// $PRF^\mathsf{expand}(sk, dst, a, b, ...) := BLAKE2b-512("Zcash_ExpandSeed", sk || dst || a || b || ...)$ + /// + /// Defined in [Zcash Protocol Spec § 5.4.2: Pseudo Random Functions][concreteprfs]. + /// + /// [concreteprfs]: https://zips.z.cash/protocol/protocol.pdf#concreteprfs + fn apply(self, sk: &[u8], ts: &[&[u8]]) -> [u8; 64] { + let mut h = Params::new() + .hash_length(64) + .personal(PRF_EXPAND_PERSONALIZATION) + .to_state(); + h.update(sk); + h.update(&[self.domain_separator]); + for t in ts { + h.update(t); + } + *h.finalize().as_array() + } +} + +macro_rules! with_inputs { + ($($arr:ident, $arrlen:ident),*) => { + #[allow(unused_parens)] + impl<$(const $arrlen: usize),*> PrfExpand<($([u8; $arrlen]),*)> { + /// Expands the given secret key in this domain. + pub fn with(self, sk: &[u8], $($arr: &[u8; $arrlen]),*) -> [u8; 64] { + self.apply(sk, &[$($arr),*]) + } + } + }; +} + +impl PrfExpand<()> { + pub const SAPLING_ASK: Self = Self::new(0x00); + pub const SAPLING_NSK: Self = Self::new(0x01); + pub const SAPLING_OVK: Self = Self::new(0x02); + pub const SAPLING_RCM: Self = Self::new(0x04); + pub const SAPLING_ESK: Self = Self::new(0x05); + pub const ORCHARD_ASK: Self = Self::new(0x06); + pub const ORCHARD_NK: Self = Self::new(0x07); + pub const ORCHARD_RIVK: Self = Self::new(0x08); + pub const SAPLING_ZIP32_MASTER_DK: Self = Self::new(0x10); + pub const SAPLING_ZIP32_CHILD_ASK: Self = Self::new(0x13); + pub const SAPLING_ZIP32_CHILD_NSK: Self = Self::new(0x14); + pub const SAPLING_ZIP32_INTERNAL_NSK: Self = Self::new(0x17); + pub const SAPLING_ZIP32_INTERNAL_DK_OVK: Self = Self::new(0x18); +} +with_inputs!(); + +impl PrfExpand<[u8; 1]> { + pub const SAPLING_DEFAULT_DIVERSIFIER: Self = Self::new(0x03); +} +impl PrfExpand<[u8; 32]> { + pub const ORCHARD_ESK: Self = Self::new(0x04); + pub const ORCHARD_RCM: Self = Self::new(0x05); + pub const PSI: Self = Self::new(0x09); + pub const SAPLING_ZIP32_CHILD_OVK: Self = Self::new(0x15); + pub const SAPLING_ZIP32_CHILD_DK: Self = Self::new(0x16); +} +impl PrfExpand<[u8; 33]> { + pub const ZIP316_TRANSPARENT_OVK: Self = Self::new(0xD0); +} +with_inputs!(a, A); + +impl PrfExpand<([u8; 32], [u8; 4])> { + pub const SPROUT_ZIP32_CHILD: Self = Self::new(0x80); + pub const ORCHARD_ZIP32_CHILD: Self = Self::new(0x81); +} +impl PrfExpand<([u8; 32], [u8; 32])> { + pub const ORCHARD_DK_OVK: Self = Self::new(0x82); + pub const ORCHARD_RIVK_INTERNAL: Self = Self::new(0x83); +} +with_inputs!(a, A, b, B); + +impl PrfExpand<([u8; 96], [u8; 32], [u8; 4])> { + pub const SAPLING_ZIP32_CHILD_HARDENED: Self = Self::new(0x11); + pub const SAPLING_ZIP32_CHILD_NON_HARDENED: Self = Self::new(0x12); +} +with_inputs!(a, A, b, B, c, C);