Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f077979
Orchard key components
str4d Mar 5, 2021
ceac39d
Implement ZIP 32 diversifier derivation
str4d Mar 5, 2021
eaa7158
Use reddsa to instantiate orchard::redpallas
str4d Mar 5, 2021
2750170
Use orchard::redpallas types in orchard::keys implementation
str4d Mar 6, 2021
5772c71
Add doctest example to orchard::Address that exercises key derivation
str4d Mar 6, 2021
a61be5d
Fix typo in documentation
str4d Mar 6, 2021
71542f7
Add internal DiversifiedTransmissionKey type
str4d Mar 6, 2021
9455158
Use protocol spec URL anchors as link handles
str4d Mar 6, 2021
57c6492
Add internal CommitIvkRandomness type
str4d Mar 6, 2021
cfaa61a
Remove unnecessary conversions for DiversifierIndex
str4d Mar 8, 2021
26701c3
Fix commit_ivk specification
str4d Mar 8, 2021
307787e
Use spec name for SpendValidatingKey
str4d Mar 8, 2021
bf5fb7a
Add missing spec links to key docs
str4d Mar 8, 2021
cef44f5
Fix intra-crate doc links
str4d Mar 8, 2021
2462bb2
Use [u8; 64] as the output of prf_expand to match the spec
str4d Mar 8, 2021
f7cad77
Add clarifying note about nomenclature
str4d Mar 8, 2021
e98f324
Ensure diversify_hash does not return the identity
str4d Mar 15, 2021
e0b40cb
FullViewingKey::address_at(impl Into<DiversifierIndex>)
str4d Mar 15, 2021
46bf89c
Update ivk derivation to match latest protocol spec draft
str4d Mar 15, 2021
3c8befa
Remove TODO from extract_p
str4d Mar 15, 2021
8e55b46
Deduplicate default address generation
str4d Mar 15, 2021
e041726
Make address generation infallible again
str4d Mar 17, 2021
42ea809
Update protocol spec references
str4d Mar 17, 2021
861eec1
Document sinsemilla::Pad
str4d Mar 17, 2021
51fd94d
Fix section numbers after spec changes
str4d Mar 18, 2021
05e86a4
Reuse the hasher inside diversify_hash
str4d Mar 18, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ publish = false
rustdoc-args = [ "--html-in-header", "katex-header.html" ]

[dependencies]
aes = "0.6"
bitvec = "0.20"
blake2b_simd = "0.5"
ff = "0.9"
fpe = "0.4"
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"
Expand Down
19 changes: 17 additions & 2 deletions src/address.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
use crate::keys::Diversifier;
use crate::keys::{DiversifiedTransmissionKey, Diversifier};

/// A shielded payment address.
///
/// # Examples
///
/// ```
/// use orchard::keys::{SpendingKey, FullViewingKey};
///
/// let sk = SpendingKey::from_bytes([7; 32]).unwrap();
/// let address = FullViewingKey::from(&sk).default_address();
/// ```
#[derive(Debug)]
pub struct Address {
d: Diversifier,
pk_d: (),
pk_d: DiversifiedTransmissionKey,
}

impl Address {
Copy link
Contributor

@dconnolly dconnolly Mar 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An idea: From/Into's alongside the more descriptive type impl methods defined, where the From/Into's call those, so there is no duplication of logic. I push on this because of the orphan rule, otherwise we could just impl all these std traits ourselves: if they aren't defined in here, we'd have to wrap some orchard types to achieve them. :/ I can make code snippet suggestions for them as needed, eg the one above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 this is how I like to write typeclass instances in Haskell, and the analogy to traits is apt: write a function that is typed to whatever you're working on, and then use that function to provide for the generalized dispatch via the trait/typeclass.

pub(crate) fn from_parts(d: Diversifier, pk_d: DiversifiedTransmissionKey) -> Self {
Address { d, pk_d }
}
}
4 changes: 4 additions & 0 deletions src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! Constants used in the Orchard protocol.
/// $\ell^\mathsf{Orchard}_\mathsf{scalar}$
pub(crate) const L_ORCHARD_SCALAR: usize = 255;
216 changes: 185 additions & 31 deletions src/keys.rs
Original file line number Diff line number Diff line change
@@ -1,70 +1,208 @@
//! Key structures for Orchard.

use crate::address::Address;
use std::convert::TryInto;
use std::mem;

use aes::Aes256;
use fpe::ff1::{BinaryNumeralString, FF1};
use group::GroupEncoding;
use halo2::{arithmetic::FieldExt, pasta::pallas};
use subtle::CtOption;

use crate::{
address::Address,
primitives::redpallas::{self, SpendAuth},
spec::{
commit_ivk, diversify_hash, 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][orchardkeycomponents].
///
/// [orchardkeycomponents]: 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 spend authorizing key, used to create spend authorization signatures.
///
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)]
pub(crate) struct SpendAuthorizingKey;
pub(crate) struct SpendAuthorizingKey(redpallas::SigningKey<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(SpendValidatingKey::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.
/// A key used to validate spend authorization signatures.
///
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)]
pub(crate) struct AuthorizingKey;
pub(crate) struct SpendValidatingKey(redpallas::VerificationKey<SpendAuth>);

impl From<&SpendAuthorizingKey> for AuthorizingKey {
fn from(_: &SpendAuthorizingKey) -> Self {
todo!()
impl From<&SpendAuthorizingKey> for SpendValidatingKey {
fn from(ask: &SpendAuthorizingKey) -> Self {
SpendValidatingKey((&ask.0).into())
}
}

/// A key used to derive [`Nullifier`]s from [`Note`]s.
///
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [`Nullifier`]: crate::note::Nullifier;
/// [`Note`]: crate::note::Note;
/// [orchardkeycomponents]: 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])))
}
}

/// The randomness for $\mathsf{Commit}^\mathsf{ivk}$.
///
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug)]
struct CommitIvkRandomness(pallas::Scalar);

impl From<&SpendingKey> for CommitIvkRandomness {
fn from(sk: &SpendingKey) -> Self {
CommitIvkRandomness(to_scalar(prf_expand(&sk.0, &[0x08])))
}
}

/// A key that provides the capability to view incoming and outgoing transactions.
///
/// 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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A key that provides the capability to view incoming and outgoing transactions.
///
/// 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).
/// An Orchard full viewing key, as defined in [Zcash Protocol Spec §4.2.3: Orchard Full Viewing Keys][orchardfullviewingkeyencoding]
///
/// Provides the capability to view incoming and outgoing transactions. 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).
///
/// [orchardfullviewingkeyencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardfullviewingkeyencoding

Is there a better section in the spec? Also the line wrap might be wonky

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That section of the specification defines the string encoding of fvk, not fvk itself.

I'm not sure yet whether it makes sense to implement string encoding in this crate or the downstream users (zcash_primitives and zebra-chain), since doing so requires knowledge of the network type, which the downstream crates define but the Orchard protocol is agnostic over. It would be nice if the two downstream crates could agree on a network type, but that seems like it might require a larger refactor beyond Orchard.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That section of the specification defines the string encoding of fvk, not fvk itself.

I'm not sure yet whether it makes sense to implement string encoding in this crate or the downstream users (zcash_primitives and zebra-chain), since doing so requires knowledge of the network type, which the downstream crates define but the Orchard protocol is agnostic over. It would be nice if the two downstream crates could agree on a network type, but that seems like it might require a larger refactor beyond Orchard.

Oh very agree. I think there is no better layout/description of the Orchard Full Viewing Key than this section at present, the best other section i can see in version v2021.1.16-10-g928b3d is https://zips.z.cash/protocol/nu5.pdf#addressesandkeys, unless I'm missing something or there are incoming additions. I think this is a holdover from Sapling but.

Copy link
Contributor

@dconnolly dconnolly Mar 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof that may mean we can't just impl FromStr and Display on this in zebra-chain, we'd have to wrap it or transform from this key: https://doc.zebra.zfnd.org/src/zebra_chain/sapling/keys.rs.html#684
That's ok, that's one of the few key types that has any external type impacting them like that for us

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started putting together a zcash_address crate which I think we could use as a common base for encodings. We'd impl FromAddress for orchard::PaymentAddress, for example (only defining the Orchard payment address conversion, for example), which would erase network-specific information where we don't need it, but the zebra-chain address type could retain it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(The above crate is focused on addresses rather than keys, but we could maybe generalise it for keys in a similar fashion. I'm a bit on-the-fence about whether I want key encodings in an address-focused crate, but I also don't want to see everything become a mish-mash of tiny crates...)

///
/// TODO: Should we just define the FVK to include extended stuff like the diversifier key?
#[derive(Debug)]
pub struct FullViewingKey {
ak: AuthorizingKey,
ak: SpendValidatingKey,
nk: NullifierDerivingKey,
rivk: (),
rivk: CommitIvkRandomness,
}

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: sk.into(),
}
}
}

impl FullViewingKey {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
fn derive_dk_ovk(&self) -> (DiversifierKey, OutgoingViewingKey) {
let k = self.rivk.0.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!(u32);
di_from!(u64);
di_from!(usize);

impl DiversifierKey {
/// Returns the diversifier at index 0.
pub fn default_diversifier(&self) -> Diversifier {
self.get(0u32)
}

/// Returns the diversifier at the given index.
pub fn get(&self, j: impl Into<DiversifierIndex>) -> Diversifier {
let ff = FF1::<Aes256>::new(&self.0, 2).expect("valid radix");
let enc = ff
.encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.into().0[..]))
.unwrap();
Diversifier(enc.to_bytes_le().try_into().unwrap())
}
}

/// A diversifier that can be used to derive a specific [`Address`] from a
/// [`FullViewingKey`] or [`IncomingViewingKey`].
#[derive(Debug)]
Expand All @@ -79,18 +217,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(&ak, &fvk.nk.0, &fvk.rivk.0))
}
}

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 pk_d = DiversifiedTransmissionKey::derive(self, &d);
Address::from_parts(d, pk_d)
}
}

Expand All @@ -100,10 +240,24 @@ 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
}
}

/// The diversified transmission key for a given payment address.
#[derive(Debug)]
pub(crate) struct DiversifiedTransmissionKey(pallas::Point);

impl DiversifiedTransmissionKey {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
fn derive(ivk: &IncomingViewingKey, d: &Diversifier) -> Self {
let g_d = diversify_hash(&d.0);
DiversifiedTransmissionKey(ka_orchard(&ivk.0, &g_d))
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading