From f0779792bc5cf5f90b3a96e1213913c8e5452264 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 5 Mar 2021 23:25:45 +0000 Subject: [PATCH] Orchard key components --- Cargo.toml | 7 ++ src/address.rs | 10 +- src/constants.rs | 4 + src/keys.rs | 174 +++++++++++++++++++++++++++++------ src/lib.rs | 2 + src/primitives/sinsemilla.rs | 26 ++---- src/spec.rs | 102 ++++++++++++++++++++ 7 files changed, 276 insertions(+), 49 deletions(-) create mode 100644 src/constants.rs create mode 100644 src/spec.rs diff --git a/Cargo.toml b/Cargo.toml index a292c2d6..9c19eb44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,16 @@ publish = false rustdoc-args = [ "--html-in-header", "katex-header.html" ] [dependencies] +blake2b_simd = "0.5" +ff = "0.9" group = "0.9" halo2 = { git = "https://github.com/zcash/halo2.git", branch = "main" } nonempty = "0.6" +subtle = "2.3" + +[dependencies.reddsa] +git = "https://github.com/str4d/redjubjub.git" +rev = "f8ff124a52d86e122e0705e8e9272f2099fe4c46" [dev-dependencies] criterion = "0.3" diff --git a/src/address.rs b/src/address.rs index 63646573..94b57f0b 100644 --- a/src/address.rs +++ b/src/address.rs @@ -1,8 +1,16 @@ +use halo2::pasta::pallas; + use crate::keys::Diversifier; /// A shielded payment address. #[derive(Debug)] pub struct Address { d: Diversifier, - pk_d: (), + pk_d: pallas::Point, +} + +impl Address { + pub(crate) fn from_parts(d: Diversifier, pk_d: pallas::Point) -> Self { + Address { d, pk_d } + } } diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 00000000..16bd8701 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,4 @@ +//! Constants used in the Orchard protocol. + +/// $\ell^\mathsf{Orchard}_\mathsf{scalar}$ +pub(crate) const L_ORCHARD_SCALAR: usize = 255; diff --git a/src/keys.rs b/src/keys.rs index cc2c6326..2049da28 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,41 +1,94 @@ //! Key structures for Orchard. -use crate::address::Address; +use std::convert::TryInto; +use std::mem; + +use group::GroupEncoding; +use halo2::{arithmetic::FieldExt, pasta::pallas}; +use subtle::CtOption; + +use crate::{ + address::Address, + spec::{ + commit_ivk, diversify_hash, extract_p, ka_orchard, prf_expand, prf_expand_vec, to_base, + to_scalar, + }, +}; /// A spending key, from which all key material is derived. /// -/// TODO: In Sapling we never actually used this, instead deriving everything via ZIP 32, -/// so that we could maintain Bitcoin-like HD keys with properties like non-hardened -/// derivation. If we decide that we don't actually require non-hardened derivation, then -/// we could greatly simplify the HD structure and use this struct directly. +/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][§4.2.3]. +/// +/// [§4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Debug)] -pub struct SpendingKey; +pub struct SpendingKey([u8; 32]); +impl SpendingKey { + /// Constructs an Orchard spending key from uniformly-random bytes. + /// + /// Returns `None` if the bytes do not correspond to a valid Orchard spending key. + pub fn from_bytes(sk: [u8; 32]) -> CtOption { + let sk = SpendingKey(sk); + // If ask = 0, discard this key. + let ask = SpendAuthorizingKey::derive_inner(&sk); + CtOption::new(sk, !ask.ct_is_zero()) + } +} + +/// A spending authorizing key, used to create spend authorization signatures. +/// +/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][§4.2.3]. +/// +/// [§4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Debug)] -pub(crate) struct SpendAuthorizingKey; +pub(crate) struct SpendAuthorizingKey(reddsa::SigningKey); + +impl SpendAuthorizingKey { + /// Derives ask from sk. Internal use only, does not enforce all constraints. + fn derive_inner(sk: &SpendingKey) -> pallas::Scalar { + to_scalar(prf_expand(&sk.0, &[0x06])) + } +} impl From<&SpendingKey> for SpendAuthorizingKey { - fn from(_: &SpendingKey) -> Self { - todo!() + fn from(sk: &SpendingKey) -> Self { + let ask = Self::derive_inner(sk); + // SpendingKey cannot be constructed such that this assertion would fail. + assert!(!bool::from(ask.ct_is_zero())); + // TODO: Add TryFrom for SpendAuthorizingKey. + let ret = SpendAuthorizingKey(ask.to_bytes().try_into().unwrap()); + // If the last bit of repr_P(ak) is 1, negate ask. + if (<[u8; 32]>::from(AuthorizingKey::from(&ret).0)[31] >> 7) == 1 { + SpendAuthorizingKey((-ask).to_bytes().try_into().unwrap()) + } else { + ret + } } } /// TODO: This is its protocol spec name for Sapling, but I'd prefer a different name. #[derive(Debug)] -pub(crate) struct AuthorizingKey; +pub(crate) struct AuthorizingKey(reddsa::VerificationKey); impl From<&SpendAuthorizingKey> for AuthorizingKey { - fn from(_: &SpendAuthorizingKey) -> Self { - todo!() + fn from(ask: &SpendAuthorizingKey) -> Self { + AuthorizingKey((&ask.0).into()) } } +/// A key used to derive [`Nullifier`]s from [`Note`]s. +/// +/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][§4.2.3]. +/// +/// [`Nullifier`]: crate::note::Nullifier; +/// [`Note`]: crate::note::Note; +/// [§4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Debug)] -pub(crate) struct NullifierDerivingKey; +pub(crate) struct NullifierDerivingKey(pallas::Base); impl From<&SpendingKey> for NullifierDerivingKey { - fn from(_: &SpendingKey) -> Self { - todo!() + fn from(sk: &SpendingKey) -> Self { + NullifierDerivingKey(to_base(prf_expand(&sk.0, &[0x07]))) } } @@ -43,28 +96,91 @@ impl From<&SpendingKey> for NullifierDerivingKey { /// /// This key is useful anywhere you need to maintain accurate balance, but do not want the /// ability to spend funds (such as a view-only wallet). -/// -/// TODO: Should we just define the FVK to include extended stuff like the diversifier key? #[derive(Debug)] pub struct FullViewingKey { ak: AuthorizingKey, nk: NullifierDerivingKey, - rivk: (), + rivk: pallas::Scalar, } impl From<&SpendingKey> for FullViewingKey { - fn from(_: &SpendingKey) -> Self { - todo!() + fn from(sk: &SpendingKey) -> Self { + FullViewingKey { + ak: (&SpendAuthorizingKey::from(sk)).into(), + nk: sk.into(), + rivk: to_scalar(prf_expand(&sk.0, &[0x08])), + } } } impl FullViewingKey { + /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][§4.2.3]. + /// + /// [§4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents + fn derive_dk_ovk(&self) -> (DiversifierKey, OutgoingViewingKey) { + let k = self.rivk.to_bytes(); + let b = [self.ak.0.into(), self.nk.0.to_bytes()]; + let r = prf_expand_vec(&k, &[&[0x82], &b[0][..], &b[1][..]]); + ( + DiversifierKey(r.as_bytes()[..32].try_into().unwrap()), + OutgoingViewingKey(r.as_bytes()[32..].try_into().unwrap()), + ) + } + + /// Returns the default payment address for this key. + pub fn default_address(&self) -> Address { + self.address(DiversifierKey::from(self).default_diversifier()) + } + /// Returns the payment address for this key corresponding to the given diversifier. pub fn address(&self, d: Diversifier) -> Address { IncomingViewingKey::from(self).address(d) } } +/// A key that provides the capability to derive a sequence of diversifiers. +#[derive(Debug)] +pub struct DiversifierKey([u8; 32]); + +impl From<&FullViewingKey> for DiversifierKey { + fn from(fvk: &FullViewingKey) -> Self { + fvk.derive_dk_ovk().0 + } +} + +/// The index for a particular diversifier. +#[derive(Clone, Copy, Debug)] +pub struct DiversifierIndex([u8; 11]); + +macro_rules! di_from { + ($n:ident) => { + impl From<$n> for DiversifierIndex { + fn from(j: $n) -> Self { + let mut j_bytes = [0; 11]; + j_bytes[..mem::size_of::<$n>()].copy_from_slice(&j.to_le_bytes()); + DiversifierIndex(j_bytes) + } + } + }; +} +di_from!(u8); +di_from!(u16); +di_from!(u32); +di_from!(u64); +di_from!(usize); + +impl DiversifierKey { + /// Returns the diversifier at index 0. + pub fn default_diversifier(&self) -> Diversifier { + self.get(0u8) + } + + /// Returns the diversifier at the given index. + pub fn get(&self, _: impl Into) -> Diversifier { + todo!() + } +} + /// A diversifier that can be used to derive a specific [`Address`] from a /// [`FullViewingKey`] or [`IncomingViewingKey`]. #[derive(Debug)] @@ -79,18 +195,20 @@ pub struct Diversifier([u8; 11]); /// This key is not suitable for use on its own in a wallet, as it cannot maintain /// accurate balance. You should use a [`FullViewingKey`] instead. #[derive(Debug)] -pub struct IncomingViewingKey; +pub struct IncomingViewingKey(pallas::Scalar); impl From<&FullViewingKey> for IncomingViewingKey { - fn from(_: &FullViewingKey) -> Self { - todo!() + fn from(fvk: &FullViewingKey) -> Self { + let ak = pallas::Point::from_bytes(&fvk.ak.0.into()).unwrap(); + IncomingViewingKey(commit_ivk(&extract_p(&ak), &fvk.nk.0, &fvk.rivk)) } } impl IncomingViewingKey { /// Returns the payment address for this key corresponding to the given diversifier. - pub fn address(&self, _: Diversifier) -> Address { - todo!() + pub fn address(&self, d: Diversifier) -> Address { + let g_d = diversify_hash(&d.0); + Address::from_parts(d, ka_orchard(&self.0, &g_d)) } } @@ -100,10 +218,10 @@ impl IncomingViewingKey { /// This key is not suitable for use on its own in a wallet, as it cannot maintain /// accurate balance. You should use a [`FullViewingKey`] instead. #[derive(Debug)] -pub struct OutgoingViewingKey; +pub struct OutgoingViewingKey([u8; 32]); impl From<&FullViewingKey> for OutgoingViewingKey { - fn from(_: &FullViewingKey) -> Self { - todo!() + fn from(fvk: &FullViewingKey) -> Self { + fvk.derive_dk_ovk().1 } } diff --git a/src/lib.rs b/src/lib.rs index 737a65a5..52517637 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,9 +10,11 @@ mod address; pub mod bundle; mod circuit; +mod constants; pub mod keys; mod note; pub mod primitives; +mod spec; mod tree; pub mod value; diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index 7f80f242..9d239516 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -1,10 +1,9 @@ //! The Sinsemilla hash function and commitment scheme. -use group::{Curve, Group}; -use halo2::{ - arithmetic::{CurveAffine, CurveExt}, - pasta::pallas, -}; +use group::Group; +use halo2::{arithmetic::CurveExt, pasta::pallas}; + +use crate::spec::extract_p; const GROUP_HASH_Q: &str = "z.cash:SinsemillaQ"; const GROUP_HASH_S: &str = "z.cash:SinsemillaS"; @@ -65,19 +64,6 @@ impl> Iterator for Pad { } } -/// Hash extractor for Pallas, from [§ 5.4.8.7]. -/// -/// [§ 5.4.8.7]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas -fn extract(point: &pallas::Point) -> pallas::Base { - // TODO: Should we return the actual bits in a Vec, or allow the caller to use - // PrimeField::to_le_bits on the returned pallas::Base? - if let Some((x, _)) = point.to_affine().get_xy().into() { - x - } else { - pallas::Base::zero() - } -} - #[allow(non_snake_case)] fn Q(domain_prefix: &str) -> pallas::Point { pallas::Point::hash_to_curve(GROUP_HASH_Q)(domain_prefix.as_bytes()) @@ -102,7 +88,7 @@ pub(crate) fn hash_to_point(domain_prefix: &str, msg: impl Iterator /// /// [§ 5.4.1.9]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash pub(crate) fn hash(domain_prefix: &str, msg: impl Iterator) -> pallas::Base { - extract(&hash_to_point(domain_prefix, msg)) + extract_p(&hash_to_point(domain_prefix, msg)) } /// `SinsemillaCommit` from [§ 5.4.7.4]. @@ -130,7 +116,7 @@ pub(crate) fn short_commit( msg: impl Iterator, r: &pallas::Scalar, ) -> pallas::Base { - extract(&commit(domain_prefix, msg, r)) + extract_p(&commit(domain_prefix, msg, r)) } #[cfg(test)] diff --git a/src/spec.rs b/src/spec.rs new file mode 100644 index 00000000..b6bba37a --- /dev/null +++ b/src/spec.rs @@ -0,0 +1,102 @@ +//! Helper functions defined in the Zcash Protocol Specification. + +use std::iter; + +use blake2b_simd::{Hash, Params}; +use ff::PrimeField; +use group::Curve; +use halo2::{ + arithmetic::{CurveAffine, CurveExt, FieldExt}, + pasta::pallas, +}; + +use crate::{constants::L_ORCHARD_SCALAR, primitives::sinsemilla}; + +const PRF_EXPAND_PERSONALIZATION: &[u8; 16] = b"Zcash_ExpandSeed"; + +/// $\mathsf{ToBase}^\mathsf{Orchard}(x) := LEOS2IP_{\ell_\mathsf{PRFexpand}}(x) (mod q_P)$ +/// +/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][§4.2.3]. +/// +/// [§4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents +pub(crate) fn to_base(hash: Hash) -> pallas::Base { + pallas::Base::from_bytes_wide(hash.as_array()) +} + +/// $\mathsf{ToScalar}^\mathsf{Orchard}(x) := LEOS2IP_{\ell_\mathsf{PRFexpand}}(x) (mod r_P)$ +/// +/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][§4.2.3]. +/// +/// [§4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents +pub(crate) fn to_scalar(hash: Hash) -> pallas::Scalar { + pallas::Scalar::from_bytes_wide(hash.as_array()) +} + +/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][§4.2.3]. +/// +/// [§4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents +pub(crate) fn commit_ivk( + ak: &pallas::Base, + nk: &pallas::Base, + rivk: &pallas::Scalar, +) -> pallas::Scalar { + let ivk = sinsemilla::short_commit( + "z.cash:Orchard-CommitIvk", + iter::empty() + .chain(ak.to_le_bits().iter().by_val().take(L_ORCHARD_SCALAR)) + .chain(nk.to_le_bits().iter().by_val().take(L_ORCHARD_SCALAR)), + rivk, + ); + + // Convert from pallas::Base to pallas::Scalar. This requires no modular reduction + // because Pallas' base field is smaller than its scalar field. + pallas::Scalar::from_repr(ivk.to_repr()).unwrap() +} + +/// Defined in [Zcash Protocol Spec § 5.4.1.6: DiversifyHash^Sapling and DiversifyHash^Orchard Hash Functions][§5.4.1.6]. +/// +/// [§5.4.1.6]: https://zips.z.cash/protocol/nu5.pdf#concretediversifyhash +pub(crate) fn diversify_hash(d: &[u8; 11]) -> pallas::Point { + pallas::Point::hash_to_curve("z.cash:Orchard-gd")(d) +} + +/// $PRF^\mathsf{expand}(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t)$ +/// +/// Defined in [Zcash Protocol Spec § 5.4.2: Pseudo Random Functions][§5.4.2]. +/// +/// [§5.4.2]: https://zips.z.cash/protocol/orchard.pdf#concreteprfs +pub(crate) fn prf_expand(sk: &[u8], t: &[u8]) -> Hash { + prf_expand_vec(sk, &[t]) +} + +pub(crate) fn prf_expand_vec(sk: &[u8], ts: &[&[u8]]) -> Hash { + let mut h = Params::new() + .hash_length(64) + .personal(PRF_EXPAND_PERSONALIZATION) + .to_state(); + h.update(sk); + for t in ts { + h.update(t); + } + h.finalize() +} + +/// Defined in [Zcash Protocol Spec § 5.4.4.5: Orchard Key Agreement][§5.4.4.5]. +/// +/// [§5.4.4.5]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement +pub(crate) fn ka_orchard(sk: &pallas::Scalar, b: &pallas::Point) -> pallas::Point { + b * sk +} + +/// Hash extractor for Pallas, from [§ 5.4.8.7]. +/// +/// [§ 5.4.8.7]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas +pub(crate) fn extract_p(point: &pallas::Point) -> pallas::Base { + // TODO: Should we return the actual bits in a Vec, or allow the caller to use + // PrimeField::to_le_bits on the returned pallas::Base? + if let Some((x, _)) = point.to_affine().get_xy().into() { + x + } else { + pallas::Base::zero() + } +}