Orchard key components

This commit is contained in:
Jack Grigg 2021-03-05 23:25:45 +00:00
parent 35da17944a
commit f0779792bc
7 changed files with 276 additions and 49 deletions

View File

@ -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"

View File

@ -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 }
}
}

4
src/constants.rs Normal file
View File

@ -0,0 +1,4 @@
//! Constants used in the Orchard protocol.
/// $\ell^\mathsf{Orchard}_\mathsf{scalar}$
pub(crate) const L_ORCHARD_SCALAR: usize = 255;

View File

@ -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<Self> {
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<reddsa::orchard::SpendAuth>);
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<S::Scalar> 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<reddsa::orchard::SpendAuth>);
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<DiversifierIndex>) -> 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
}
}

View File

@ -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;

View File

@ -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<I: Iterator<Item = bool>> Iterator for Pad<I> {
}
}
/// 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<Item = bool>
///
/// [§ 5.4.1.9]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash
pub(crate) fn hash(domain_prefix: &str, msg: impl Iterator<Item = bool>) -> 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<Item = bool>,
r: &pallas::Scalar,
) -> pallas::Base {
extract(&commit(domain_prefix, msg, r))
extract_p(&commit(domain_prefix, msg, r))
}
#[cfg(test)]

102
src/spec.rs Normal file
View File

@ -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()
}
}