cleanup(cryptography): Remove unused shielded key and address code (#5476)

* Remove unused and buggy Sprout key and address code

* Remove unused, buggy Sapling address, key, and commitment code

* Delete unused Orchard key code

* Move almost all the buggy Orchard key code into a test-only module

* Remove Orchard keys and addresses that aren't used in production code

* Remove unused prf_expand() function and unimplemented poseidon_hash() function

* Remove unused Orchard key types

* Remove unused sinsemilla commit code

* Update zebra-chain/src/sprout/keys.rs

* Update zebra-chain/src/sprout/keys.rs

Co-authored-by: Deirdre Connolly <durumcrustulum@gmail.com>
This commit is contained in:
teor 2022-10-30 06:59:55 +10:00 committed by GitHub
parent 9cb3dbba9b
commit 161bb80ce8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 69 additions and 3004 deletions

View File

@ -4,11 +4,12 @@
mod action;
mod address;
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
mod commitment;
mod note;
mod sinsemilla;
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
#[cfg(test)]
mod tests;

View File

@ -24,39 +24,3 @@ impl fmt::Debug for Address {
.finish()
}
}
#[cfg(test)]
mod tests {
use rand_core::OsRng;
use crate::parameters::Network;
use super::*;
#[test]
fn derive_keys_and_addresses() {
let _init_guard = zebra_test::init();
let network = Network::Mainnet;
let spending_key = keys::SpendingKey::new(&mut OsRng, network);
let full_viewing_key = keys::FullViewingKey::from(spending_key);
// Default diversifier, where index = 0.
let diversifier_key = keys::DiversifierKey::from(full_viewing_key);
// This should fail with negligible probability.
let incoming_viewing_key = keys::IncomingViewingKey::try_from(full_viewing_key)
.expect("a valid incoming viewing key");
let diversifier = keys::Diversifier::from(diversifier_key);
let transmission_key = keys::TransmissionKey::from((incoming_viewing_key, diversifier));
let _orchard_shielded_address = Address {
diversifier,
transmission_key,
};
}
}

View File

@ -1,3 +1,7 @@
//! Randomised data generation for Orchard types.
use std::marker::PhantomData;
use group::{ff::PrimeField, prime::PrimeCurveAffine};
use halo2::{arithmetic::FieldExt, pasta::pallas};
use proptest::{arbitrary::any, array, collection::vec, prelude::*};
@ -5,13 +9,7 @@ use proptest::{arbitrary::any, array, collection::vec, prelude::*};
use crate::primitives::redpallas::{Signature, SpendAuth, VerificationKey, VerificationKeyBytes};
use super::{
keys, note, tree, Action, Address, AuthorizedAction, Diversifier, Flags, NoteCommitment,
ValueCommitment,
};
use std::{
convert::{TryFrom, TryInto},
marker::PhantomData,
keys::*, note, tree, Action, AuthorizedAction, Flags, NoteCommitment, ValueCommitment,
};
impl Arbitrary for Action {
@ -29,7 +27,7 @@ impl Arbitrary for Action {
nullifier,
rk,
cm_x: NoteCommitment(pallas::Affine::identity()).extract_x(),
ephemeral_key: keys::EphemeralPublicKey(pallas::Affine::generator()),
ephemeral_key: EphemeralPublicKey(pallas::Affine::generator()),
enc_ciphertext,
out_ciphertext,
})
@ -128,40 +126,3 @@ impl Arbitrary for tree::Root {
type Strategy = BoxedStrategy<Self>;
}
impl Arbitrary for keys::TransmissionKey {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(any::<keys::SpendingKey>())
.prop_map(|spending_key| {
let full_viewing_key = keys::FullViewingKey::from(spending_key);
let diversifier_key = keys::DiversifierKey::from(full_viewing_key);
let diversifier = Diversifier::from(diversifier_key);
let incoming_viewing_key = keys::IncomingViewingKey::try_from(full_viewing_key)
.expect("a valid incoming viewing key");
Self::from((incoming_viewing_key, diversifier))
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
impl Arbitrary for Address {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(any::<keys::Diversifier>(), any::<keys::TransmissionKey>())
.prop_map(|(diversifier, transmission_key)| Self {
diversifier,
transmission_key,
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}

View File

@ -1,8 +1,7 @@
//! Note and value commitments.
use std::{convert::TryFrom, fmt, io};
use std::{fmt, io};
use bitvec::prelude::*;
use group::{ff::PrimeField, prime::PrimeCurveAffine, GroupEncoding};
use halo2::{
arithmetic::{Coordinates, CurveAffine, FieldExt},
@ -18,11 +17,7 @@ use crate::{
},
};
use super::{
keys::prf_expand,
note::{Note, Psi, SeedRandomness},
sinsemilla::*,
};
use super::sinsemilla::*;
/// Generates a random scalar from the scalar field 𝔽_{q_P}.
///
@ -41,18 +36,6 @@ where
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CommitmentRandomness(pallas::Scalar);
impl From<SeedRandomness> for CommitmentRandomness {
/// rcm = ToScalar^Orchard((PRF^expand_rseed (\[5\]))
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardsend>
fn from(rseed: SeedRandomness) -> Self {
Self(pallas::Scalar::from_bytes_wide(&prf_expand(
rseed.0,
vec![&[5]],
)))
}
}
/// Note commitments for the output notes.
#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine);
@ -103,51 +86,6 @@ impl TryFrom<[u8; 32]> for NoteCommitment {
}
impl NoteCommitment {
/// Generate a new _NoteCommitment_.
///
/// Unlike in Sapling, the definition of an Orchard _note_ includes the ρ
/// field; the _note_'s position in the _note commitment tree_ does not need
/// to be known in order to compute this value.
///
/// NoteCommit^Orchard_rcm(repr_P(gd),repr_P(pkd), v, ρ, ψ) :=
///
/// <https://zips.z.cash/protocol/nu5.pdf#concretewindowedcommit>
#[allow(non_snake_case)]
#[allow(clippy::unwrap_in_result)]
pub fn new(note: Note) -> Option<Self> {
// s as in the argument name for WindowedPedersenCommit_r(s)
let mut s: BitVec<u8, Lsb0> = BitVec::new();
// Prefix
s.append(&mut bitvec![1; 6]);
// The `TryFrom<Diversifier>` impls for the `pallas::*Point`s handles
// calling `DiversifyHash` implicitly.
// _diversified base_
let g_d_bytes = pallas::Affine::try_from(note.address.diversifier)
.ok()?
.to_bytes();
let pk_d_bytes: [u8; 32] = note.address.transmission_key.into();
let v_bytes = note.value.to_bytes();
let rho_bytes: [u8; 32] = note.rho.into();
let psi_bytes: [u8; 32] = Psi::from(note.rseed).into();
// g*d || pk*d || I2LEBSP_64(v) || I2LEBSP_l^Orchard_Base(ρ) || I2LEBSP_l^Orchard_base(ψ)
s.extend(g_d_bytes);
s.extend(pk_d_bytes);
s.extend(v_bytes);
s.extend(rho_bytes);
s.extend(psi_bytes);
let rcm = CommitmentRandomness::from(note.rseed);
Some(NoteCommitment::from(
sinsemilla_commit(rcm.0, b"z.cash:Orchard-NoteCommit", &s)
.expect("valid orchard note commitment, not ⊥ "),
))
}
/// Extract the x coordinate of the note commitment.
pub fn extract_x(&self) -> pallas::Base {
extract_p(self.0.into())
@ -320,20 +258,6 @@ mod tests {
use super::*;
// #[test]
// fn sinsemilla_hash_to_point_test_vectors() {
// let _init_guard = zebra_test::init();
// const D: [u8; 8] = *b"Zcash_PH";
// for test_vector in test_vectors::TEST_VECTORS.iter() {
// let result =
// pallas::Affine::from(sinsemilla_hash_to_point(D, &test_vector.input_bits.clone()));
// assert_eq!(result, test_vector.output_point);
// }
// }
#[test]
fn add() {
let _init_guard = zebra_test::init();

View File

@ -1,80 +1,24 @@
//! Orchard key types.
//!
//! Unused key types are not implemented, see PR #5476.
//!
//! <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
#![allow(clippy::fallible_impl_from)]
#[cfg(test)]
mod tests;
use std::{fmt, io};
use aes::Aes256;
use bech32::{self, ToBase32, Variant};
use bitvec::prelude::*;
use fpe::ff1::{BinaryNumeralString, FF1};
use group::{ff::PrimeField, prime::PrimeCurveAffine, Group, GroupEncoding};
use halo2::{
arithmetic::{Coordinates, CurveAffine, Field, FieldExt},
arithmetic::{Coordinates, CurveAffine},
pasta::pallas,
};
use rand_core::{CryptoRng, RngCore};
use subtle::{Choice, ConstantTimeEq};
use crate::{
parameters::Network,
primitives::redpallas::{self, SpendAuth},
serialization::{
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
},
use crate::serialization::{
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
};
use super::sinsemilla::*;
/// PRP^d_K(d) := FF1-AES256_K("", d)
///
/// "Let FF1-AES256_K(tweak, x) be the FF1 format-preserving encryption
/// algorithm using AES with a 256-bit key K, and parameters radix = 2, minlen =
/// 88, maxlen = 88. It will be used only with the empty string "" as the
/// tweak. x is a sequence of 88 bits, as is the output."
///
/// <https://zips.z.cash/protocol/nu5.pdf#concreteprps>
#[allow(non_snake_case)]
fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] {
let radix = 2;
let tweak = b"";
let ff = FF1::<Aes256>::new(&K, radix).expect("valid radix");
ff.encrypt(tweak, &BinaryNumeralString::from_bytes_le(&d))
.unwrap()
.to_bytes_le()
.try_into()
.unwrap()
}
/// Invokes Blake2b-512 as PRF^expand with parameter t.
///
/// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t)
///
/// <https://zips.z.cash/protocol/nu5.pdf#concreteprfs>
// TODO: This is basically a duplicate of the one in our sapling module, its
// definition in the draft Nu5 spec is incomplete so I'm putting it here in case
// it changes.
pub fn prf_expand(sk: [u8; 32], t: Vec<&[u8]>) -> [u8; 64] {
let mut state = blake2b_simd::Params::new()
.hash_length(64)
.personal(b"Zcash_ExpandSeed")
.to_state();
state.update(&sk[..]);
for t_i in t {
state.update(t_i);
}
*state.finalize().as_array()
}
/// Used to derive a diversified base point from a diversifier value.
///
/// DiversifyHash^Orchard(d) := { GroupHash^P("z.cash:Orchard-gd",""), if P = 0_P
@ -93,773 +37,6 @@ fn diversify_hash(d: &[u8]) -> pallas::Point {
}
}
/// Magic human-readable strings used to identify what networks Orchard spending
/// keys are associated with when encoded/decoded with bech32.
///
/// [orchardspendingkeyencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardspendingkeyencoding
mod sk_hrp {
pub const MAINNET: &str = "secret-orchard-sk-main";
pub const TESTNET: &str = "secret-orchard-sk-test";
}
/// A spending key, as described in [protocol specification §4.2.3][ps].
///
/// Our root secret key of the Orchard key derivation tree. All other Orchard
/// key types derive from the [`SpendingKey`] value.
///
/// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Copy, Clone, Debug)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(proptest_derive::Arbitrary)
)]
pub struct SpendingKey {
network: Network,
bytes: [u8; 32],
}
impl ConstantTimeEq for SpendingKey {
/// Check whether two `SpendingKey`s are equal, runtime independent of the
/// value of the secret.
///
/// # Note
///
/// This function short-circuits if the networks of the keys are different.
/// Otherwise, it should execute in time independent of the `bytes` value.
fn ct_eq(&self, other: &Self) -> Choice {
if self.network != other.network {
return Choice::from(0);
}
self.bytes.ct_eq(&other.bytes)
}
}
impl fmt::Display for SpendingKey {
#[allow(clippy::unwrap_in_result)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let hrp = match self.network {
Network::Mainnet => sk_hrp::MAINNET,
Network::Testnet => sk_hrp::TESTNET,
};
bech32::encode_to_fmt(f, hrp, &self.bytes.to_base32(), Variant::Bech32)
.expect("hrp is valid")
}
}
impl From<SpendingKey> for [u8; 32] {
fn from(sk: SpendingKey) -> Self {
sk.bytes
}
}
impl Eq for SpendingKey {}
impl PartialEq for SpendingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl SpendingKey {
/// Generate a new `SpendingKey`.
///
/// When generating, we check that the corresponding `SpendAuthorizingKey`
/// is not zero, else fail.
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
pub fn new<T>(csprng: &mut T, network: Network) -> Self
where
T: RngCore + CryptoRng,
{
loop {
let mut bytes = [0u8; 32];
csprng.fill_bytes(&mut bytes);
let sk = Self::from_bytes(bytes, network);
// "if ask = 0, discard this key and repeat with a new sk"
if SpendAuthorizingKey::from(sk).0.is_zero().into() {
continue;
}
// "if ivk ∈ {0, ⊥}, discard this key and repeat with a new sk"
if IncomingViewingKey::try_from(FullViewingKey::from(sk)).is_err() {
continue;
}
break sk;
}
}
/// Generate a `SpendingKey` from existing bytes.
pub fn from_bytes(bytes: [u8; 32], network: Network) -> Self {
Self { network, bytes }
}
}
/// A Spend authorizing key (_ask_), as described in [protocol specification
/// §4.2.3][orchardkeycomponents].
///
/// Used to generate _spend authorization randomizers_ to sign each _Action
/// Description_ that spends notes, proving ownership of notes.
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Copy, Clone, Eq)]
pub struct SpendAuthorizingKey(pub(crate) pallas::Scalar);
impl ConstantTimeEq for SpendAuthorizingKey {
/// Check whether two `SpendAuthorizingKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_repr().ct_eq(&other.0.to_repr())
}
}
impl fmt::Debug for SpendAuthorizingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("SpendAuthorizingKey")
.field(&hex::encode(<[u8; 32]>::from(*self)))
.finish()
}
}
impl From<SpendAuthorizingKey> for [u8; 32] {
fn from(sk: SpendAuthorizingKey) -> Self {
sk.0.to_repr()
}
}
impl From<SpendingKey> for SpendAuthorizingKey {
/// Invokes Blake2b-512 as _PRF^expand_, t=6, to derive a
/// `SpendAuthorizingKey` from a `SpendingKey`.
///
/// ask := ToScalar^Orchard(PRF^expand(sk, \[6\]))
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
/// <https://zips.z.cash/protocol/nu5.pdf#concreteprfs>
fn from(spending_key: SpendingKey) -> SpendAuthorizingKey {
// Handles ToScalar^Orchard
let ask = pallas::Scalar::from_bytes_wide(&prf_expand(spending_key.bytes, vec![&[6]]));
// let ak^P = SpendAuthSig^Orchard.DerivePublic(ask)...
let ak_bytes: [u8; 32] = SpendValidatingKey::from(SpendAuthorizingKey(ask)).into();
// if the last bit (that is, the 𝑦˜ bit) of repr_P (ak^P ) is 1:
// set ask ← ask
match (ak_bytes[31] >> 7) == 0b0000_0001 {
true => Self(-ask),
false => Self(ask),
}
}
}
impl PartialEq for SpendAuthorizingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for SpendAuthorizingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_repr().ct_eq(other).unwrap_u8() == 1u8
}
}
/// A Spend validating key, as described in [protocol specification
/// §4.2.3][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Copy, Clone, Debug)]
pub struct SpendValidatingKey(pub(crate) redpallas::VerificationKey<SpendAuth>);
impl Eq for SpendValidatingKey {}
impl From<[u8; 32]> for SpendValidatingKey {
fn from(bytes: [u8; 32]) -> Self {
Self(redpallas::VerificationKey::try_from(bytes).unwrap())
}
}
impl From<SpendValidatingKey> for [u8; 32] {
fn from(ak: SpendValidatingKey) -> [u8; 32] {
ak.0.into()
}
}
impl From<SpendAuthorizingKey> for SpendValidatingKey {
fn from(ask: SpendAuthorizingKey) -> Self {
let sk = redpallas::SigningKey::<SpendAuth>::try_from(<[u8; 32]>::from(ask)).expect(
"a scalar converted to byte array and then converted back to a scalar should not fail",
);
Self(redpallas::VerificationKey::from(&sk))
}
}
impl PartialEq for SpendValidatingKey {
fn eq(&self, other: &Self) -> bool {
// XXX: These redpallas::VerificationKey(Bytes) fields are pub(crate)
self.0.bytes.bytes == other.0.bytes.bytes
}
}
impl PartialEq<[u8; 32]> for SpendValidatingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
// XXX: These redpallas::VerificationKey(Bytes) fields are pub(crate)
self.0.bytes.bytes == *other
}
}
/// A Orchard nullifier deriving key, as described in [protocol specification
/// §4.2.3][orchardkeycomponents].
///
/// Used to create a _Nullifier_ per note.
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Copy, Clone)]
pub struct NullifierDerivingKey(pub(crate) pallas::Base);
impl ConstantTimeEq for NullifierDerivingKey {
/// Check whether two `NullifierDerivingKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_repr().ct_eq(&other.0.to_repr())
}
}
impl fmt::Debug for NullifierDerivingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("NullifierDerivingKey")
.field(&hex::encode(self.0.to_repr()))
.finish()
}
}
impl Eq for NullifierDerivingKey {}
impl From<NullifierDerivingKey> for [u8; 32] {
fn from(nk: NullifierDerivingKey) -> [u8; 32] {
nk.0.to_repr()
}
}
impl From<&NullifierDerivingKey> for [u8; 32] {
fn from(nk: &NullifierDerivingKey) -> [u8; 32] {
nk.0.to_repr()
}
}
impl From<NullifierDerivingKey> for pallas::Base {
fn from(nk: NullifierDerivingKey) -> pallas::Base {
nk.0
}
}
impl From<[u8; 32]> for NullifierDerivingKey {
fn from(bytes: [u8; 32]) -> Self {
Self(pallas::Base::from_repr(bytes).unwrap())
}
}
impl From<SpendingKey> for NullifierDerivingKey {
/// nk = ToBase^Orchard(PRF^expand_sk (\[7\]))
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
fn from(sk: SpendingKey) -> Self {
Self(pallas::Base::from_bytes_wide(&prf_expand(
sk.into(),
vec![&[7]],
)))
}
}
impl PartialEq for NullifierDerivingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for NullifierDerivingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_repr().ct_eq(other).unwrap_u8() == 1u8
}
}
/// Commit^ivk randomness.
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
// XXX: Should this be replaced by commitment::CommitmentRandomness?
#[derive(Copy, Clone)]
pub struct IvkCommitRandomness(pub(crate) pallas::Scalar);
impl ConstantTimeEq for IvkCommitRandomness {
/// Check whether two `IvkCommitRandomness`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_repr().ct_eq(&other.0.to_repr())
}
}
impl fmt::Debug for IvkCommitRandomness {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("IvkCommitRandomness")
.field(&hex::encode(self.0.to_repr()))
.finish()
}
}
impl Eq for IvkCommitRandomness {}
impl From<SpendingKey> for IvkCommitRandomness {
/// rivk = ToScalar^Orchard(PRF^expand_sk (\[8\]))
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
fn from(sk: SpendingKey) -> Self {
let scalar = pallas::Scalar::from_bytes_wide(&prf_expand(sk.into(), vec![&[8]]));
Self(scalar)
}
}
impl From<IvkCommitRandomness> for [u8; 32] {
fn from(rivk: IvkCommitRandomness) -> Self {
rivk.0.into()
}
}
impl From<IvkCommitRandomness> for pallas::Scalar {
fn from(rivk: IvkCommitRandomness) -> Self {
rivk.0
}
}
impl PartialEq for IvkCommitRandomness {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for IvkCommitRandomness {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_repr().ct_eq(other).unwrap_u8() == 1u8
}
}
impl TryFrom<[u8; 32]> for IvkCommitRandomness {
type Error = &'static str;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
let possible_scalar = pallas::Scalar::from_repr(bytes);
if possible_scalar.is_some().into() {
Ok(Self(possible_scalar.unwrap()))
} else {
Err("Invalid pallas::Scalar value")
}
}
}
/// _Full viewing keys_
///
/// Allows recognizing both incoming and outgoing notes without having
/// spend authority.
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardfullviewingkeyencoding>
#[derive(Copy, Clone)]
pub struct FullViewingKey {
spend_validating_key: SpendValidatingKey,
nullifier_deriving_key: NullifierDerivingKey,
ivk_commit_randomness: IvkCommitRandomness,
}
impl FullViewingKey {
/// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[allow(non_snake_case)]
pub fn to_R(self) -> [u8; 64] {
// let K = I2LEBSP_l_sk(rivk)
let K: [u8; 32] = self.ivk_commit_randomness.into();
let mut t: Vec<&[u8]> = vec![&[0x82u8]];
let ak_bytes = <[u8; 32]>::from(self.spend_validating_key);
t.push(&ak_bytes);
let nk_bytes = <[u8; 32]>::from(self.nullifier_deriving_key);
t.push(&nk_bytes);
// let R = PRF^expand_K( [0x82] || I2LEOSP256(ak) || I2LEOSP256(nk) )
prf_expand(K, t)
}
}
impl ConstantTimeEq for FullViewingKey {
/// Check whether two `FullViewingKey`s are equal, runtime independent of
/// the value of the secrets.
///
/// # Note
///
/// This function short-circuits if the spend validating keys
/// are different. Otherwise, it should execute in time independent of the
/// secret component values.
fn ct_eq(&self, other: &Self) -> Choice {
if self.spend_validating_key != other.spend_validating_key {
return Choice::from(0);
}
// Uses std::ops::BitAnd
self.nullifier_deriving_key
.ct_eq(&other.nullifier_deriving_key)
& self
.ivk_commit_randomness
.ct_eq(&other.ivk_commit_randomness)
}
}
impl fmt::Debug for FullViewingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("FullViewingKey")
.field("spend_validating_key", &self.spend_validating_key)
.field("nullifier_deriving_key", &self.nullifier_deriving_key)
.field("ivk_commit_randomness", &self.ivk_commit_randomness)
.finish()
}
}
impl fmt::Display for FullViewingKey {
/// The _raw encoding_ of an **Orchard** _full viewing key_.
///
/// <https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding>
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&hex::encode(<[u8; 96]>::from(*self)))
}
}
impl From<FullViewingKey> for [u8; 96] {
fn from(fvk: FullViewingKey) -> [u8; 96] {
let mut bytes = [0u8; 96];
bytes[..32].copy_from_slice(&<[u8; 32]>::from(fvk.spend_validating_key));
bytes[32..64].copy_from_slice(&<[u8; 32]>::from(fvk.nullifier_deriving_key));
bytes[64..].copy_from_slice(&<[u8; 32]>::from(fvk.ivk_commit_randomness));
bytes
}
}
impl From<SpendingKey> for FullViewingKey {
/// Derive a _full viewing key_ from a existing _spending key_.
///
/// <https://zips.z.cash/protocol/nu5.pdf#addressesandkeys>
/// <https://zips.z.cash/protocol/nu5.pdf#orchardfullviewingkeyencoding>
fn from(sk: SpendingKey) -> FullViewingKey {
let spend_authorizing_key = SpendAuthorizingKey::from(sk);
Self {
spend_validating_key: SpendValidatingKey::from(spend_authorizing_key),
nullifier_deriving_key: NullifierDerivingKey::from(sk),
ivk_commit_randomness: IvkCommitRandomness::from(sk),
}
}
}
impl PartialEq for FullViewingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
/// An _incoming viewing key_, as described in [protocol specification
/// §4.2.3][ps].
///
/// Used to decrypt incoming notes without spending them.
///
/// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Copy, Clone)]
pub struct IncomingViewingKey {
dk: DiversifierKey,
// TODO: refine type, so that IncomingViewingkey.ivk cannot be 0
ivk: pallas::Scalar,
}
impl ConstantTimeEq for IncomingViewingKey {
/// Check whether two `IncomingViewingKey`s are equal, runtime independent
/// of the value of the secret.
///
/// # Note
///
/// This function should execute in time independent of the `dk` and `ivk` values.
fn ct_eq(&self, other: &Self) -> Choice {
<[u8; 64]>::from(*self).ct_eq(&<[u8; 64]>::from(*other))
}
}
impl fmt::Debug for IncomingViewingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("IncomingViewingKey")
.field("dk", &self.dk)
.field("ivk", &self.ivk)
.finish()
}
}
impl fmt::Display for IncomingViewingKey {
/// The _raw encoding_ of an **Orchard** _incoming viewing key_.
///
/// <https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding>
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&hex::encode(<[u8; 64]>::from(*self)))
}
}
impl Eq for IncomingViewingKey {}
impl From<IncomingViewingKey> for [u8; 64] {
fn from(ivk: IncomingViewingKey) -> [u8; 64] {
let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(&<[u8; 32]>::from(ivk.dk));
bytes[32..].copy_from_slice(&<[u8; 32]>::from(ivk.ivk));
bytes
}
}
impl TryFrom<[u8; 64]> for IncomingViewingKey {
type Error = &'static str;
/// Convert an array of bytes into a [`IncomingViewingKey`].
///
/// Returns an error if the encoding is malformed or if it [encodes the scalar additive
/// identity, 0][1].
///
/// > ivk MUST be in the range {1 .. 𝑞P - 1}
///
/// [1]: https://zips.z.cash/protocol/protocol.pdf#orchardinviewingkeyencoding
fn try_from(bytes: [u8; 64]) -> Result<Self, Self::Error> {
let mut dk_bytes = [0u8; 32];
dk_bytes.copy_from_slice(&bytes[..32]);
let dk = DiversifierKey::from(dk_bytes);
let mut ivk_bytes = [0u8; 32];
ivk_bytes.copy_from_slice(&bytes[32..]);
let possible_scalar = pallas::Scalar::from_repr(ivk_bytes);
if possible_scalar.is_some().into() {
let scalar = possible_scalar.unwrap();
if scalar.is_zero().into() {
Err("pallas::Scalar value for Orchard IncomingViewingKey is 0")
} else {
Ok(Self {
dk,
ivk: possible_scalar.unwrap(),
})
}
} else {
Err("Invalid pallas::Scalar value for Orchard IncomingViewingKey")
}
}
}
impl TryFrom<FullViewingKey> for IncomingViewingKey {
type Error = &'static str;
/// Commit^ivk_rivk(ak, nk) :=
/// SinsemillaShortCommit_rcm(
/// "z.cash:Orchard-CommitIvk",
/// I2LEBSP_l^Orchard_base(ak) || I2LEBSP_l^Orchard_base(nk)
/// ) mod r_P
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
/// <https://zips.z.cash/protocol/nu5.pdf#concreteprfs>
#[allow(non_snake_case)]
#[allow(clippy::unwrap_in_result)]
fn try_from(fvk: FullViewingKey) -> Result<Self, Self::Error> {
let mut M: BitVec<u8, Lsb0> = BitVec::new();
// I2LEBSP_l^Orchard_base(ak)
let ak_bytes =
extract_p(pallas::Point::from_bytes(&fvk.spend_validating_key.into()).unwrap())
.to_repr();
M.extend_from_bitslice(&BitArray::<_, Lsb0>::from(ak_bytes)[0..255]);
// I2LEBSP_l^Orchard_base(nk)
let nk_bytes: [u8; 32] = fvk.nullifier_deriving_key.into();
M.extend_from_bitslice(&BitArray::<_, Lsb0>::from(nk_bytes)[0..255]);
// Commit^ivk_rivk
// rivk needs to be 255 bits long
let commit_x = sinsemilla_short_commit(
fvk.ivk_commit_randomness.into(),
b"z.cash:Orchard-CommitIvk",
&M,
)
.expect("deriving orchard commit^ivk should not output ⊥ ");
let ivk_ctoption = pallas::Scalar::from_repr(commit_x.into());
// if ivk ∈ {0, ⊥}, discard this key
// [`Scalar::is_zero()`] is constant-time under the hood, and ivk is mod r_P
if ivk_ctoption.is_some().into() && !<bool>::from(ivk_ctoption.unwrap().is_zero()) {
Ok(Self {
dk: fvk.into(),
ivk: ivk_ctoption.unwrap(),
})
} else {
Err("generated ivk is the additive identity 0, invalid")
}
}
}
impl PartialEq for IncomingViewingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
/// An outgoing viewing key, as described in [protocol specification
/// §4.2.3][ps].
///
/// Used to decrypt outgoing notes without spending them.
///
/// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Copy, Clone)]
pub struct OutgoingViewingKey(pub(crate) [u8; 32]);
impl ConstantTimeEq for OutgoingViewingKey {
/// Check whether two `OutgoingViewingKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl fmt::Debug for OutgoingViewingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("OutgoingViewingKey")
.field(&hex::encode(self.0))
.finish()
}
}
impl Eq for OutgoingViewingKey {}
impl From<[u8; 32]> for OutgoingViewingKey {
/// Generate an `OutgoingViewingKey` from existing bytes.
fn from(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
impl From<OutgoingViewingKey> for [u8; 32] {
fn from(ovk: OutgoingViewingKey) -> [u8; 32] {
ovk.0
}
}
impl From<FullViewingKey> for OutgoingViewingKey {
/// Derive an `OutgoingViewingKey` from a `FullViewingKey`.
///
/// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[allow(non_snake_case)]
fn from(fvk: FullViewingKey) -> OutgoingViewingKey {
let R = fvk.to_R();
// let ovk be the remaining [32] bytes of R [which is 64 bytes]
let ovk_bytes: [u8; 32] = R[32..64].try_into().expect("32 byte array");
Self::from(ovk_bytes)
}
}
impl PartialEq for OutgoingViewingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for OutgoingViewingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.ct_eq(other).unwrap_u8() == 1u8
}
}
/// A _diversifier key_.
///
/// "We define a mechanism for deterministically deriving a sequence of
/// diversifiers, without leaking how many diversified addresses have already
/// been generated for an account. Unlike Sapling, we do so by deriving a
/// _diversifier key_ directly from the _full viewing key_, instead of as part
/// of the _extended spending key_. This means that the _full viewing key_
/// provides the capability to determine the position of a _diversifier_ within
/// the sequence, which matches the capabilities of a Sapling _extended full
/// viewing key_ but simplifies the key structure."
///
/// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
/// [ZIP-32]: https://zips.z.cash/zip-0032#orchard-diversifier-derivation
#[derive(Copy, Clone, Debug)]
pub struct DiversifierKey([u8; 32]);
impl ConstantTimeEq for DiversifierKey {
/// Check whether two `DiversifierKey`s are equal, runtime independent of
/// the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl Eq for DiversifierKey {}
impl From<FullViewingKey> for DiversifierKey {
/// Derives a _diversifier key_ from a `FullViewingKey`.
///
/// 'For each spending key, there is also a default diversified
/// payment address with a “random-looking” diversifier. This
/// allows an implementation that does not expose diversified
/// addresses as a user-visible feature, to use a default address
/// that cannot be distinguished (without knowledge of the
/// spending key) from one with a random diversifier...'
///
/// Derived as specified in section [4.2.3] of the spec, and [ZIP-32].
///
/// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
/// [ZIP-32]: https://zips.z.cash/zip-0032#orchard-diversifier-derivation
#[allow(non_snake_case)]
fn from(fvk: FullViewingKey) -> DiversifierKey {
let R = fvk.to_R();
// "let dk be the first [32] bytes of R"
Self(R[..32].try_into().expect("subslice of R is a valid array"))
}
}
impl From<[u8; 32]> for DiversifierKey {
fn from(bytes: [u8; 32]) -> DiversifierKey {
DiversifierKey(bytes)
}
}
impl From<DiversifierKey> for [u8; 32] {
fn from(dk: DiversifierKey) -> [u8; 32] {
dk.0
}
}
impl PartialEq for DiversifierKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for DiversifierKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.ct_eq(other).unwrap_u8() == 1u8
}
}
/// A _diversifier_, as described in [protocol specification §4.2.3][ps].
///
/// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
@ -884,16 +61,6 @@ impl From<[u8; 11]> for Diversifier {
}
}
impl From<DiversifierKey> for Diversifier {
/// Generates the _default diversifier_, where the index into the
/// `DiversifierKey` is 0.
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
fn from(dk: DiversifierKey) -> Self {
Self(prp_d(dk.into(), [0u8; 11]))
}
}
impl From<Diversifier> for [u8; 11] {
fn from(d: Diversifier) -> [u8; 11] {
d.0
@ -982,99 +149,18 @@ impl fmt::Debug for TransmissionKey {
impl Eq for TransmissionKey {}
impl From<[u8; 32]> for TransmissionKey {
/// Attempts to interpret a byte representation of an affine point, failing
/// if the element is not on the curve or non-canonical.
fn from(bytes: [u8; 32]) -> Self {
Self(pallas::Affine::from_bytes(&bytes).unwrap())
}
}
impl From<TransmissionKey> for [u8; 32] {
fn from(pk_d: TransmissionKey) -> [u8; 32] {
pk_d.0.to_bytes()
}
}
impl From<(IncomingViewingKey, Diversifier)> for TransmissionKey {
/// This includes _KA^Orchard.DerivePublic(ivk, G_d)_, which is just a
/// scalar mult _\[ivk\]G_d_.
///
/// KA^Orchard.DerivePublic(sk, B) := \[sk\] B
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
/// <https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement>
fn from((ivk, d): (IncomingViewingKey, Diversifier)) -> Self {
let g_d = pallas::Point::from(d);
Self(pallas::Affine::from(g_d * ivk.ivk))
}
}
impl PartialEq<[u8; 32]> for TransmissionKey {
fn eq(&self, other: &[u8; 32]) -> bool {
&self.0.to_bytes() == other
}
}
/// An _outgoing cipher key_ for Orchard note encryption/decryption.
///
/// <https://zips.z.cash/protocol/nu5.pdf#saplingandorchardencrypt>
// TODO: derive `OutgoingCipherKey`: https://github.com/ZcashFoundation/zebra/issues/2041
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct OutgoingCipherKey([u8; 32]);
impl fmt::Debug for OutgoingCipherKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("OutgoingCipherKey")
.field(&hex::encode(self.0))
.finish()
}
}
impl From<&OutgoingCipherKey> for [u8; 32] {
fn from(ock: &OutgoingCipherKey) -> [u8; 32] {
ock.0
}
}
// TODO: implement PrivateKey: #2192
/// An _ephemeral private key_ for Orchard key agreement.
///
/// <https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement>
/// <https://zips.z.cash/protocol/nu5.pdf#saplingandorchardencrypt>
// TODO: refine so that the inner `Scalar` != 0
#[derive(Copy, Clone, Debug)]
pub struct EphemeralPrivateKey(pub(crate) pallas::Scalar);
impl ConstantTimeEq for EphemeralPrivateKey {
/// Check whether two `EphemeralPrivateKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_repr().ct_eq(&other.0.to_repr())
}
}
impl Eq for EphemeralPrivateKey {}
impl From<EphemeralPrivateKey> for [u8; 32] {
fn from(esk: EphemeralPrivateKey) -> Self {
esk.0.to_repr()
}
}
impl PartialEq for EphemeralPrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for EphemeralPrivateKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_repr().ct_eq(other).unwrap_u8() == 1u8
}
}
/// An ephemeral public key for Orchard key agreement.
///
/// <https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement>

View File

@ -1,103 +0,0 @@
#![allow(clippy::module_inception)]
use super::*;
use crate::orchard::tests::vectors::KEY_COMPONENTS;
use proptest::prelude::*;
#[test]
fn generate_keys_from_test_vectors() {
let _init_guard = zebra_test::init();
for test_vector in KEY_COMPONENTS.iter() {
let spending_key = SpendingKey::from_bytes(test_vector.sk, Network::Mainnet);
let spend_authorizing_key = SpendAuthorizingKey::from(spending_key);
assert_eq!(spend_authorizing_key, test_vector.ask);
let spend_validating_key = SpendValidatingKey::from(spend_authorizing_key);
assert_eq!(<[u8; 32]>::from(spend_validating_key), test_vector.ak);
let nullifier_deriving_key = NullifierDerivingKey::from(spending_key);
assert_eq!(nullifier_deriving_key, test_vector.nk);
let ivk_commit_randomness = IvkCommitRandomness::from(spending_key);
assert_eq!(ivk_commit_randomness, test_vector.rivk);
let full_viewing_key = FullViewingKey {
spend_validating_key,
nullifier_deriving_key,
ivk_commit_randomness,
};
let diversifier_key = DiversifierKey::from(full_viewing_key);
assert_eq!(diversifier_key, test_vector.dk);
let incoming_viewing_key =
IncomingViewingKey::try_from(full_viewing_key).expect("a valid incoming viewing key");
assert_eq!(<[u8; 32]>::from(incoming_viewing_key.ivk), test_vector.ivk);
let outgoing_viewing_key = OutgoingViewingKey::from(full_viewing_key);
assert_eq!(outgoing_viewing_key, test_vector.ovk);
let diversifier = Diversifier::from(diversifier_key);
assert_eq!(diversifier, test_vector.default_d);
let transmission_key = TransmissionKey::from((incoming_viewing_key, diversifier));
assert_eq!(transmission_key, test_vector.default_pk_d);
}
}
proptest! {
#[test]
#[allow(clippy::clone_on_copy, clippy::cmp_owned)]
fn generate_keys(spending_key in any::<SpendingKey>()) {
let _init_guard = zebra_test::init();
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(spending_key, SpendingKey::from_bytes(spending_key.bytes, spending_key.network));
let spend_authorizing_key = SpendAuthorizingKey::from(spending_key);
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(spend_authorizing_key, spend_authorizing_key.clone());
// ConstantTimeEq not implemented as it's a public value
let spend_validating_key = SpendValidatingKey::from(spend_authorizing_key);
let nullifier_deriving_key = NullifierDerivingKey::from(spending_key);
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(nullifier_deriving_key, nullifier_deriving_key.clone());
let ivk_commit_randomness = IvkCommitRandomness::from(spending_key);
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(ivk_commit_randomness, ivk_commit_randomness.clone());
let full_viewing_key = FullViewingKey {
spend_validating_key,
nullifier_deriving_key,
ivk_commit_randomness,
};
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(full_viewing_key, full_viewing_key.clone());
let diversifier_key = DiversifierKey::from(full_viewing_key);
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(diversifier_key, diversifier_key.clone());
let incoming_viewing_key = IncomingViewingKey::try_from(full_viewing_key).expect("a valid incoming viewing key");
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(incoming_viewing_key, incoming_viewing_key.clone());
let outgoing_viewing_key = OutgoingViewingKey::from(full_viewing_key);
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(outgoing_viewing_key, outgoing_viewing_key.clone());
// ConstantTimeEq not implemented for Diversifier as it's a public value
let diversifier = Diversifier::from(diversifier_key);
// ConstantTimeEq not implemented as it's a public value
let _transmission_key = TransmissionKey::from((incoming_viewing_key, diversifier));
}
}

View File

@ -1,23 +1,22 @@
//! Orchard notes
#![allow(clippy::unit_arg)]
use group::{ff::PrimeField, GroupEncoding};
use halo2::{arithmetic::FieldExt, pasta::pallas};
use halo2::pasta::pallas;
use rand_core::{CryptoRng, RngCore};
use crate::amount::{Amount, NonNegative};
use super::{address::Address, keys::prf_expand, sinsemilla::extract_p};
use super::{address::Address, sinsemilla::extract_p};
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
mod ciphertexts;
mod nullifiers;
pub use ciphertexts::{EncryptedNote, WrappedNoteKey};
pub use nullifiers::Nullifier;
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
#[derive(Clone, Copy, Debug)]
/// A random seed (rseed) used in the Orchard note creation.
pub struct SeedRandomness(pub(crate) [u8; 32]);
@ -81,18 +80,6 @@ impl From<Psi> for [u8; 32] {
}
}
impl From<SeedRandomness> for Psi {
/// rcm = ToScalar^Orchard((PRF^expand_rseed (\[9\]))
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardsend>
fn from(rseed: SeedRandomness) -> Self {
Self(pallas::Base::from_bytes_wide(&prf_expand(
rseed.0,
vec![&[9]],
)))
}
}
/// A Note represents that a value is spendable by the recipient who holds the
/// spending key corresponding to a given shielded payment address.
///

View File

@ -1,46 +1,11 @@
#![allow(clippy::unit_arg)]
//! Orchard nullifier types and conversions.
use std::{
convert::TryFrom,
hash::{Hash, Hasher},
};
use std::hash::{Hash, Hasher};
use halo2::pasta::{group::ff::PrimeField, pallas};
use crate::serialization::{serde_helpers, SerializationError};
use super::super::{
commitment::NoteCommitment,
keys::NullifierDerivingKey,
note::{Note, Psi},
sinsemilla::*,
};
/// A cryptographic permutation, defined in [poseidonhash].
///
/// PoseidonHash(x, y) = f([x, y, 0])_1 (using 1-based indexing).
///
/// [poseidonhash]: https://zips.z.cash/protocol/nu5.pdf#poseidonhash
fn poseidon_hash(_x: pallas::Base, _y: pallas::Base) -> pallas::Base {
// TODO: implement: #2064
unimplemented!("PoseidonHash is not yet implemented (#2064)")
}
/// Used as part of deriving the _nullifier_ for a Orchard _note_.
///
/// PRF^nfOrchard: F_𝑞P × F_𝑞P → F_𝑞P
///
/// Instantiated using the PoseidonHash hash function defined in [§5.4.1.10
/// PoseidonHash Function][poseidonhash]:
///
/// PRF^nfOrchard(nk*, ρ*) := PoseidonHash(nk*, ρ*)
///
/// [concreteprfs]: https://zips.z.cash/protocol/nu5.pdf#concreteprfs
/// [poseidonhash]: https://zips.z.cash/protocol/nu5.pdf#poseidonhash
fn prf_nf(nk: pallas::Base, rho: pallas::Base) -> pallas::Base {
poseidon_hash(nk, rho)
}
/// A Nullifier for Orchard transactions
#[derive(Clone, Copy, Debug, Eq, Serialize, Deserialize)]
pub struct Nullifier(#[serde(with = "serde_helpers::Base")] pub(crate) pallas::Base);
@ -73,33 +38,6 @@ impl PartialEq for Nullifier {
}
}
impl From<(NullifierDerivingKey, Note, NoteCommitment)> for Nullifier {
/// Derive a `Nullifier` for an Orchard _note_.
///
/// nk is the _nullifier deriving key_ associated with the _note_; ρ and ψ
/// are part of the _note_; and cm is the _note commitment_.
///
/// DeriveNullifier_nk(ρ, ψ, cm) = Extract_P( [ (PRF^nfOrchard_nk(ρ) + ψ) mod q_P ] K^Orchard + cm)
///
/// <https://zips.z.cash/protocol/nu5.pdf#commitmentsandnullifiers>
#[allow(non_snake_case)]
// TODO: tidy prf_nf, notes/rho/psi
fn from((nk, note, cm): (NullifierDerivingKey, Note, NoteCommitment)) -> Self {
let K = pallas_group_hash(b"z.cash:Orchard", b"K");
let psi: Psi = note.rseed.into();
// impl Add for pallas::Base reduces by the modulus (q_P)
//
// [ (PRF^nfOrchard_nk(ρ) + ψ) mod q_P ] K^Orchard + cm
let scalar =
pallas::Scalar::from_repr((prf_nf(nk.0, note.rho.0) + psi.0).to_repr()).unwrap();
// Basically a new-gen Pedersen hash?
Nullifier(extract_p((K * scalar) + cm.0))
}
}
impl From<Nullifier> for [u8; 32] {
fn from(n: Nullifier) -> Self {
n.0.into()

View File

@ -159,37 +159,6 @@ pub fn sinsemilla_hash(D: &[u8], M: &BitVec<u8, Lsb0>) -> Option<pallas::Base> {
extract_p_bottom(sinsemilla_hash_to_point(D, M))
}
/// Sinsemilla commit
///
/// We construct Sinsemilla commitments by hashing to a point with Sinsemilla
/// hash, and adding a randomized point on the Pallas curve (with complete
/// addition, vs incomplete addition as used in [`sinsemilla_hash_to_point`]).
///
/// SinsemillaCommit_r(D, M) := SinsemillaHashToPoint(D || "-M", M) + \[r\]GroupHash^P(D || "-r", "")
///
/// <https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit>
#[allow(non_snake_case)]
pub fn sinsemilla_commit(
r: pallas::Scalar,
D: &[u8],
M: &BitVec<u8, Lsb0>,
) -> Option<pallas::Point> {
sinsemilla_hash_to_point(&[D, b"-M"].concat(), M)
.map(|point| point + pallas_group_hash(&[D, b"-r"].concat(), b"") * r)
}
/// SinsemillaShortCommit_r(D, M) := Extract⊥ P(SinsemillaCommit_r(D, M))
///
/// <https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit>
#[allow(non_snake_case)]
pub fn sinsemilla_short_commit(
r: pallas::Scalar,
D: &[u8],
M: &BitVec<u8, Lsb0>,
) -> Option<pallas::Base> {
extract_p_bottom(sinsemilla_commit(r, D, M))
}
// TODO: test the above correctness and compatibility with the zcash-hackworks test vectors
// https://github.com/ZcashFoundation/zebra/issues/2079
// https://github.com/zcash-hackworks/zcash-test-vectors/pulls

View File

@ -10,23 +10,20 @@
//! points occur, and non-canonical point encodings are rejected. This is
//! enforced by the jubjub crate, which is also used by the redjubjub crate.
mod address;
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
mod commitment;
mod note;
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
#[cfg(test)]
mod tests;
// XXX clean up these modules
pub mod keys;
pub mod output;
pub mod shielded_data;
pub mod spend;
pub mod tree;
pub use address::Address;
pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment};
pub use keys::Diversifier;
pub use note::{EncryptedNote, Note, Nullifier, WrappedNoteKey};

View File

@ -1,182 +0,0 @@
//! Shielded addresses.
use std::{
convert::TryFrom,
fmt,
io::{self, Read, Write},
};
use bech32::{self, FromBase32, ToBase32, Variant};
#[cfg(test)]
use proptest::prelude::*;
use crate::{
parameters::Network,
serialization::{ReadZcashExt, SerializationError},
};
use super::keys;
/// Human-Readable Parts for input to bech32 encoding.
mod human_readable_parts {
pub const MAINNET: &str = "zs";
pub const TESTNET: &str = "ztestsapling";
}
/// A Sapling _shielded payment address_.
///
/// Also known as a _diversified payment address_ for Sapling, as
/// defined in [§4.2.2][4.2.2].
///
/// [4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Address {
network: Network,
diversifier: keys::Diversifier,
transmission_key: keys::TransmissionKey,
}
impl fmt::Debug for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("SaplingAddress")
.field("network", &self.network)
.field("diversifier", &self.diversifier)
.field("transmission_key", &self.transmission_key)
.finish()
}
}
impl fmt::Display for Address {
#[allow(clippy::unwrap_in_result)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut bytes = io::Cursor::new(Vec::new());
let _ = bytes.write_all(&<[u8; 11]>::from(self.diversifier));
let _ = bytes.write_all(&<[u8; 32]>::from(self.transmission_key));
let hrp = match self.network {
Network::Mainnet => human_readable_parts::MAINNET,
_ => human_readable_parts::TESTNET,
};
bech32::encode_to_fmt(f, hrp, bytes.get_ref().to_base32(), Variant::Bech32)
.expect("hrp is valid")
}
}
impl std::str::FromStr for Address {
type Err = SerializationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match bech32::decode(s) {
Ok((hrp, bytes, Variant::Bech32)) => {
let mut decoded_bytes =
io::Cursor::new(Vec::<u8>::from_base32(&bytes).map_err(|_| {
SerializationError::Parse("bech32::decode guarantees valid base32")
})?);
let mut diversifier_bytes = [0; 11];
decoded_bytes.read_exact(&mut diversifier_bytes)?;
let transmission_key_bytes = decoded_bytes.read_32_bytes()?;
Ok(Address {
network: match hrp.as_str() {
human_readable_parts::MAINNET => Network::Mainnet,
_ => Network::Testnet,
},
diversifier: keys::Diversifier::from(diversifier_bytes),
transmission_key: keys::TransmissionKey::try_from(transmission_key_bytes)
.map_err(|_| SerializationError::Parse("invalid transmission key bytes"))?,
})
}
_ => Err(SerializationError::Parse("bech32 decoding error")),
}
}
}
#[cfg(test)]
impl Arbitrary for Address {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
any::<Network>(),
any::<keys::Diversifier>(),
any::<keys::TransmissionKey>(),
)
.prop_map(|(network, diversifier, transmission_key)| Self {
network,
diversifier,
transmission_key,
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
#[cfg(test)]
mod tests {
use rand_core::OsRng;
use super::*;
#[test]
fn from_str_display() {
let _init_guard = zebra_test::init();
let zs_addr: Address =
"zs1qqqqqqqqqqqqqqqqqrjq05nyfku05msvu49mawhg6kr0wwljahypwyk2h88z6975u563j8nfaxd"
.parse()
.unwrap();
assert_eq!(
format!("{}", zs_addr),
"zs1qqqqqqqqqqqqqqqqqrjq05nyfku05msvu49mawhg6kr0wwljahypwyk2h88z6975u563j8nfaxd"
);
}
#[test]
fn derive_keys_and_addresses() {
let _init_guard = zebra_test::init();
let spending_key = keys::SpendingKey::new(&mut OsRng);
let spend_authorizing_key = keys::SpendAuthorizingKey::from(spending_key);
let proof_authorizing_key = keys::ProofAuthorizingKey::from(spending_key);
let authorizing_key = keys::AuthorizingKey::from(spend_authorizing_key);
let nullifier_deriving_key = keys::NullifierDerivingKey::from(proof_authorizing_key);
let incoming_viewing_key =
keys::IncomingViewingKey::from((authorizing_key, nullifier_deriving_key));
let diversifier = keys::Diversifier::new(&mut OsRng);
let transmission_key = keys::TransmissionKey::try_from((incoming_viewing_key, diversifier))
.expect("should be a valid transmission key");
let _sapling_shielded_address = Address {
network: Network::Mainnet,
diversifier,
transmission_key,
};
}
}
#[cfg(test)]
proptest! {
#[test]
fn sapling_address_roundtrip(zaddr in any::<Address>()) {
let _init_guard = zebra_test::init();
let string = zaddr.to_string();
let zaddr2 = string.parse::<Address>()
.expect("randomized sapling z-addr should deserialize");
prop_assert_eq![zaddr, zaddr2];
}
}

View File

@ -1,10 +1,5 @@
//! Note and value commitments.
#[cfg(test)]
mod test_vectors;
pub mod pedersen_hashes;
use std::{
convert::{TryFrom, TryInto},
fmt, io,
@ -24,6 +19,11 @@ use crate::{
use super::keys::{find_group_hash, Diversifier, TransmissionKey};
pub mod pedersen_hashes;
#[cfg(test)]
mod test_vectors;
use pedersen_hashes::*;
/// Generates a random scalar from the scalar field 𝔽_{r_𝕁}.

View File

@ -98,22 +98,6 @@ pub fn pedersen_hash(domain: [u8; 8], M: &BitVec<u8, Lsb0>) -> jubjub::Fq {
jubjub::AffinePoint::from(pedersen_hash_to_point(domain, M)).get_u()
}
/// Mixing Pedersen Hash Function
///
/// Used to compute ρ from a note commitment and its position in the note
/// commitment tree. It takes as input a Pedersen commitment P, and hashes it
/// with another input x.
///
/// MixingPedersenHash(P, x) := P + \[x\]FindGroupHash^J^(r)("Zcash_J_", "")
///
/// <https://zips.z.cash/protocol/protocol.pdf#concretemixinghash>
#[allow(non_snake_case)]
pub fn mixing_pedersen_hash(P: jubjub::ExtendedPoint, x: jubjub::Fr) -> jubjub::ExtendedPoint {
const J: [u8; 8] = *b"Zcash_J_";
P + find_group_hash(J, b"") * x
}
/// Construct a 'windowed' Pedersen commitment by reusing a Pederson hash
/// construction, and adding a randomized point on the Jubjub curve.
///

View File

@ -1,13 +1,15 @@
// Test vector data generated from
// https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_pedersen.py
//
// These vectors in particular correspond to the Personalization::NoteCommitment
// enum variant from the original source.
//
// The Python hex-encoded outputs for these test vectors were output in
// big-endian byte order, so to parse them, we reversed their order; in
// librustzcash, they match their Display impl to match the Python hex strings
// and that's what they compare in their unit tests, not the bytes.
//! Test vector data generated from
//! https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_pedersen.py
//!
//! These vectors in particular correspond to the Personalization::NoteCommitment
//! enum variant from the original source.
//!
//! The Python hex-encoded outputs for these test vectors were output in
//! big-endian byte order, so to parse them, we reversed their order; in
//! librustzcash, they match their Display impl to match the Python hex strings
//! and that's what they compare in their unit tests, not the bytes.
#![allow(dead_code)]
use bitvec::prelude::*;
use lazy_static::lazy_static;

View File

@ -1,4 +1,6 @@
//! Key types.
//! Sapling key types.
//!
//! Unused key types are not implemented, see PR #5476.
//!
//! "The spend authorizing key ask, proof authorizing key (ak, nsk),
//! full viewing key (ak, nk, ovk), incoming viewing key ivk, and each
@ -7,32 +9,21 @@
//!
//! [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
//! [3.1]: https://zips.z.cash/protocol/protocol.pdf#addressesandkeys
#![allow(clippy::unit_arg)]
#![allow(clippy::fallible_impl_from)]
#[cfg(test)]
mod test_vectors;
#[cfg(test)]
mod tests;
use std::{fmt, io};
use std::{
fmt,
io::{self, Write},
str::FromStr,
};
use bech32::{self, FromBase32, ToBase32, Variant};
use rand_core::{CryptoRng, RngCore};
use subtle::{Choice, ConstantTimeEq};
use crate::{
parameters::Network,
primitives::redjubjub::{self, SpendAuth},
serialization::{
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
},
};
#[cfg(test)]
mod test_vectors;
/// The [Randomness Beacon][1] ("URS").
///
/// First 64 bytes of the BLAKE2s input during JubJub group hash. URS
@ -46,42 +37,6 @@ use crate::{
pub(super) const RANDOMNESS_BEACON_URS: &[u8; 64] =
b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0";
/// Invokes Blake2b-512 as PRF^expand with parameter t, to derive a
/// SpendAuthorizingKey and ProofAuthorizingKey from SpendingKey.
///
/// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t)
///
/// <https://zips.z.cash/protocol/protocol.pdf#concreteprfs>
fn prf_expand(sk: [u8; 32], t: &[u8]) -> [u8; 64] {
let hash = blake2b_simd::Params::new()
.hash_length(64)
.personal(b"Zcash_ExpandSeed")
.to_state()
.update(&sk[..])
.update(t)
.finalize();
*hash.as_array()
}
/// Invokes Blake2s-256 as _CRH^ivk_, to derive the IncomingViewingKey
/// bytes from an AuthorizingKey and NullifierDerivingKey.
///
/// _CRH^ivk(ak, nk) := BLAKE2s-256("Zcashivk", ak || nk)_
///
/// <https://zips.z.cash/protocol/protocol.pdf#concretecrhivk>
fn crh_ivk(ak: [u8; 32], nk: [u8; 32]) -> [u8; 32] {
let hash = blake2s_simd::Params::new()
.hash_length(32)
.personal(b"Zcashivk")
.to_state()
.update(&ak[..])
.update(&nk[..])
.finalize();
*hash.as_array()
}
/// GroupHash into Jubjub, aka _GroupHash_URS_
///
/// Produces a random point in the Jubjub curve. The point is
@ -144,14 +99,6 @@ pub(super) fn find_group_hash(d: [u8; 8], m: &[u8]) -> jubjub::ExtendedPoint {
}
}
/// Instance of FindGroupHash for JubJub, using personalized by
/// BLAKE2s for picking the proof generation key base point.
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents>
fn zcash_h() -> jubjub::ExtendedPoint {
find_group_hash(*b"Zcash_H_", b"")
}
/// Used to derive a diversified base point from a diversifier value.
///
/// <https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash>
@ -159,551 +106,6 @@ fn diversify_hash(d: [u8; 11]) -> Option<jubjub::ExtendedPoint> {
jubjub_group_hash(*b"Zcash_gd", &d)
}
// TODO: replace with reference to redjubjub or jubjub when merged and
// exported.
type Scalar = jubjub::Fr;
/// Magic human-readable strings used to identify what networks
/// Sapling Spending Keys are associated with when encoded/decoded
/// with bech32.
mod sk_hrp {
pub const MAINNET: &str = "secret-spending-key-main";
pub const TESTNET: &str = "secret-spending-key-test";
}
/// A _Spending Key_, as described in [protocol specification
/// §4.2.2][ps].
///
/// Our root secret key of the Sapling key derivation tree. All other
/// Sapling key types derive from the `SpendingKey` value.
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Copy, Clone, Debug)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(proptest_derive::Arbitrary)
)]
pub struct SpendingKey {
network: Network,
bytes: [u8; 32],
}
impl SpendingKey {
/// Generate a new _SpendingKey_.
pub fn new<T>(csprng: &mut T) -> Self
where
T: RngCore + CryptoRng,
{
let mut bytes = [0u8; 32];
csprng.fill_bytes(&mut bytes);
Self::from(bytes)
}
}
impl ConstantTimeEq for SpendingKey {
/// Check whether two `SpendingKey`s are equal, runtime independent of the
/// value of the secret.
///
/// # Note
///
/// This function short-circuits if the networks of the keys are different.
/// Otherwise, it should execute in time independent of the `bytes` value.
fn ct_eq(&self, other: &Self) -> Choice {
if self.network != other.network {
return Choice::from(0);
}
self.bytes.ct_eq(&other.bytes)
}
}
impl Eq for SpendingKey {}
// TODO: impl a From that accepts a Network?
impl From<[u8; 32]> for SpendingKey {
/// Generate a `SpendingKey` from existing bytes.
fn from(bytes: [u8; 32]) -> Self {
Self {
network: Network::default(),
bytes,
}
}
}
impl fmt::Display for SpendingKey {
#[allow(clippy::unwrap_in_result)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let hrp = match self.network {
Network::Mainnet => sk_hrp::MAINNET,
_ => sk_hrp::TESTNET,
};
bech32::encode_to_fmt(f, hrp, &self.bytes.to_base32(), Variant::Bech32)
.expect("hrp is valid")
}
}
impl FromStr for SpendingKey {
type Err = SerializationError;
#[allow(clippy::unwrap_in_result)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
match bech32::decode(s) {
Ok((hrp, bytes, Variant::Bech32)) => {
let decoded =
Vec::<u8>::from_base32(&bytes).expect("bech32::decode guarantees valid base32");
let mut decoded_bytes = [0u8; 32];
decoded_bytes[..].copy_from_slice(&decoded[0..32]);
Ok(SpendingKey {
network: match hrp.as_str() {
sk_hrp::MAINNET => Network::Mainnet,
_ => Network::Testnet,
},
bytes: decoded_bytes,
})
}
_ => Err(SerializationError::Parse("bech32 decoding error")),
}
}
}
impl PartialEq for SpendingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
/// A _Spend Authorizing Key_, as described in [protocol specification
/// §4.2.2][ps].
///
/// Used to generate _spend authorization randomizers_ to sign each
/// _Spend Description_, proving ownership of notes.
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Copy, Clone)]
pub struct SpendAuthorizingKey(pub(crate) Scalar);
impl ConstantTimeEq for SpendAuthorizingKey {
/// Check whether two `SpendAuthorizingKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_bytes().ct_eq(&other.0.to_bytes())
}
}
impl fmt::Debug for SpendAuthorizingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("SpendAuthorizingKey")
.field(&hex::encode(<[u8; 32]>::from(*self)))
.finish()
}
}
impl Eq for SpendAuthorizingKey {}
impl From<SpendAuthorizingKey> for [u8; 32] {
fn from(sk: SpendAuthorizingKey) -> Self {
sk.0.to_bytes()
}
}
impl From<SpendingKey> for SpendAuthorizingKey {
/// Invokes Blake2b-512 as _PRF^expand_, t=0, to derive a
/// SpendAuthorizingKey from a SpendingKey.
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents>
/// <https://zips.z.cash/protocol/protocol.pdf#concreteprfs>
fn from(spending_key: SpendingKey) -> SpendAuthorizingKey {
let hash_bytes = prf_expand(spending_key.bytes, &[0]);
Self(Scalar::from_bytes_wide(&hash_bytes))
}
}
impl PartialEq for SpendAuthorizingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for SpendAuthorizingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_bytes().ct_eq(other).unwrap_u8() == 1u8
}
}
/// A _Proof Authorizing Key_, as described in [protocol specification
/// §4.2.2][ps].
///
/// Used in the _Spend Statement_ to prove nullifier integrity.
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Copy, Clone)]
pub struct ProofAuthorizingKey(pub(crate) Scalar);
impl ConstantTimeEq for ProofAuthorizingKey {
/// Check whether two `ProofAuthorizingKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_bytes().ct_eq(&other.0.to_bytes())
}
}
impl fmt::Debug for ProofAuthorizingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ProofAuthorizingKey")
.field(&hex::encode(<[u8; 32]>::from(*self)))
.finish()
}
}
impl Eq for ProofAuthorizingKey {}
impl From<ProofAuthorizingKey> for [u8; 32] {
fn from(nsk: ProofAuthorizingKey) -> Self {
nsk.0.to_bytes()
}
}
impl From<SpendingKey> for ProofAuthorizingKey {
/// For this invocation of Blake2b-512 as _PRF^expand_, t=1.
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents>
/// <https://zips.z.cash/protocol/protocol.pdf#concreteprfs>
fn from(spending_key: SpendingKey) -> ProofAuthorizingKey {
let hash_bytes = prf_expand(spending_key.bytes, &[1]);
Self(Scalar::from_bytes_wide(&hash_bytes))
}
}
impl PartialEq for ProofAuthorizingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for ProofAuthorizingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_bytes().ct_eq(other).unwrap_u8() == 1u8
}
}
/// An _Outgoing Viewing Key_, as described in [protocol specification
/// §4.2.2][ps].
///
/// Used to decrypt outgoing notes without spending them.
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct OutgoingViewingKey(pub(crate) [u8; 32]);
impl fmt::Debug for OutgoingViewingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("OutgoingViewingKey")
.field(&hex::encode(self.0))
.finish()
}
}
impl From<[u8; 32]> for OutgoingViewingKey {
/// Generate an _OutgoingViewingKey_ from existing bytes.
fn from(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
impl From<OutgoingViewingKey> for [u8; 32] {
fn from(ovk: OutgoingViewingKey) -> [u8; 32] {
ovk.0
}
}
impl From<SpendingKey> for OutgoingViewingKey {
/// For this invocation of Blake2b-512 as _PRF^expand_, t=2.
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents>
/// <https://zips.z.cash/protocol/protocol.pdf#concreteprfs>
fn from(spending_key: SpendingKey) -> OutgoingViewingKey {
let hash_bytes = prf_expand(spending_key.bytes, &[2]);
let mut bytes = [0u8; 32];
bytes[..].copy_from_slice(&hash_bytes[0..32]);
Self(bytes)
}
}
impl PartialEq<[u8; 32]> for OutgoingViewingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0 == *other
}
}
/// An _Authorizing Key_, as described in [protocol specification
/// §4.2.2][ps].
///
/// Used to validate _Spend Authorization Signatures_, proving
/// ownership of notes.
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Copy, Clone, Debug)]
pub struct AuthorizingKey(pub(crate) redjubjub::VerificationKey<SpendAuth>);
impl Eq for AuthorizingKey {}
impl TryFrom<[u8; 32]> for AuthorizingKey {
type Error = &'static str;
/// Convert an array into an AuthorizingKey.
///
/// Returns an error if the encoding is malformed or if [it does not encode
/// a prime-order point][1]:
///
/// > When decoding this representation, the key MUST be considered invalid
/// > if abst_J returns ⊥ for either ak or nk, or if ak not in J^{(r)*}
///
/// [1]: https://zips.z.cash/protocol/protocol.pdf#saplingfullviewingkeyencoding
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
let affine_point = jubjub::AffinePoint::from_bytes(bytes);
if affine_point.is_none().into() {
return Err("Invalid jubjub::AffinePoint for Sapling AuthorizingKey");
}
if affine_point.unwrap().is_prime_order().into() {
Ok(Self(redjubjub::VerificationKey::try_from(bytes).map_err(
|_e| "Invalid jubjub::AffinePoint for Sapling AuthorizingKey",
)?))
} else {
Err("jubjub::AffinePoint value for Sapling AuthorizingKey is not of prime order")
}
}
}
impl From<AuthorizingKey> for [u8; 32] {
fn from(ak: AuthorizingKey) -> [u8; 32] {
ak.0.into()
}
}
impl From<SpendAuthorizingKey> for AuthorizingKey {
fn from(ask: SpendAuthorizingKey) -> Self {
let sk = redjubjub::SigningKey::<SpendAuth>::try_from(<[u8; 32]>::from(ask)).expect(
"a scalar converted to byte array and then converted back to a scalar should not fail",
);
Self(redjubjub::VerificationKey::from(&sk))
}
}
impl PartialEq for AuthorizingKey {
fn eq(&self, other: &Self) -> bool {
self == &<[u8; 32]>::from(*other)
}
}
impl PartialEq<[u8; 32]> for AuthorizingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
&<[u8; 32]>::from(self.0) == other
}
}
/// A _Nullifier Deriving Key_, as described in [protocol
/// specification §4.2.2][ps].
///
/// Used to create a `Nullifier` per note.
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Copy, Clone)]
pub struct NullifierDerivingKey(pub(crate) jubjub::AffinePoint);
impl ConstantTimeEq for NullifierDerivingKey {
/// Check whether two `NullifierDerivingKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_bytes().ct_eq(&other.0.to_bytes())
}
}
impl fmt::Debug for NullifierDerivingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("NullifierDerivingKey")
.field("u", &hex::encode(self.0.get_u().to_bytes()))
.field("v", &hex::encode(self.0.get_v().to_bytes()))
.finish()
}
}
impl From<[u8; 32]> for NullifierDerivingKey {
fn from(bytes: [u8; 32]) -> Self {
Self(jubjub::AffinePoint::from_bytes(bytes).unwrap())
}
}
impl Eq for NullifierDerivingKey {}
impl From<NullifierDerivingKey> for [u8; 32] {
fn from(nk: NullifierDerivingKey) -> [u8; 32] {
nk.0.to_bytes()
}
}
impl From<&NullifierDerivingKey> for [u8; 32] {
fn from(nk: &NullifierDerivingKey) -> [u8; 32] {
nk.0.to_bytes()
}
}
impl From<ProofAuthorizingKey> for NullifierDerivingKey {
/// Requires JubJub's _FindGroupHash^J("Zcash_H_", "")_, then uses
/// the resulting generator point to scalar multiply the
/// ProofAuthorizingKey into the new NullifierDerivingKey
///
/// <https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/group_hash.rs>
/// <https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents>
/// <https://zips.z.cash/protocol/protocol.pdf#concretegrouphashjubjub>
fn from(nsk: ProofAuthorizingKey) -> Self {
// Should this point, when generated, be fixed for the rest of
// the protocol instance? Since this is kind of hash-and-pray, it
// seems it might not always return the same result?
let generator_point = zcash_h();
// TODO: impl Mul<ExtendedPoint> for Fr, so we can reverse
// this to match the math in the spec / general scalar mult
// notation convention.
Self(jubjub::AffinePoint::from(generator_point * nsk.0))
}
}
impl PartialEq for NullifierDerivingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for NullifierDerivingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_bytes().ct_eq(other).unwrap_u8() == 1u8
}
}
/// Magic human-readable strings used to identify what networks
/// Sapling IncomingViewingKeys are associated with when
/// encoded/decoded with bech32.
mod ivk_hrp {
pub const MAINNET: &str = "zivks";
pub const TESTNET: &str = "zivktestsapling";
}
/// An _Incoming Viewing Key_, as described in [protocol specification
/// §4.2.2][ps].
///
/// Used to decrypt incoming notes without spending them.
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct IncomingViewingKey {
network: Network,
scalar: Scalar,
}
// TODO: impl a From that accepts a Network?
impl fmt::Debug for IncomingViewingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("IncomingViewingKey")
.field(&hex::encode(self.scalar.to_bytes()))
.finish()
}
}
impl fmt::Display for IncomingViewingKey {
#[allow(clippy::unwrap_in_result)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let hrp = match self.network {
Network::Mainnet => ivk_hrp::MAINNET,
_ => ivk_hrp::TESTNET,
};
bech32::encode_to_fmt(f, hrp, &self.scalar.to_bytes().to_base32(), Variant::Bech32)
.expect("hrp is valid")
}
}
impl From<[u8; 32]> for IncomingViewingKey {
/// Generate an _IncomingViewingKey_ from existing bytes.
fn from(mut bytes: [u8; 32]) -> Self {
// Drop the most significant five bits, so it can be interpreted
// as a scalar.
//
// I don't want to put this inside crh_ivk, but does it belong
// inside Scalar/Fr::from_bytes()? That seems the better
// place...
//
// https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/primitives.rs#L86
bytes[31] &= 0b0000_0111;
Self {
// TODO: handle setting the Network better.
network: Network::default(),
scalar: Scalar::from_bytes(&bytes).unwrap(),
}
}
}
impl From<(AuthorizingKey, NullifierDerivingKey)> for IncomingViewingKey {
/// For this invocation of Blake2s-256 as _CRH^ivk_.
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents>
/// <https://zips.z.cash/protocol/protocol.pdf#concreteprfs>
/// <https://zips.z.cash/protocol/protocol.pdf#jubjub>
// TODO: return None if ivk = 0
//
// "If ivk = 0, discard this key and start over with a new
// [spending key]." - [§4.2.2][ps]
//
// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
fn from((ask, nk): (AuthorizingKey, NullifierDerivingKey)) -> Self {
let hash_bytes = crh_ivk(ask.into(), nk.into());
IncomingViewingKey::from(hash_bytes)
}
}
impl FromStr for IncomingViewingKey {
type Err = SerializationError;
#[allow(clippy::unwrap_in_result)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
match bech32::decode(s) {
Ok((hrp, bytes, Variant::Bech32)) => {
let decoded =
Vec::<u8>::from_base32(&bytes).expect("bech32::decode guarantees valid base32");
let mut scalar_bytes = [0u8; 32];
scalar_bytes[..].copy_from_slice(&decoded[0..32]);
Ok(IncomingViewingKey {
network: match hrp.as_str() {
ivk_hrp::MAINNET => Network::Mainnet,
_ => Network::Testnet,
},
scalar: Scalar::from_bytes(&scalar_bytes).unwrap(),
})
}
_ => Err(SerializationError::Parse("bech32 decoding error")),
}
}
}
impl PartialEq<[u8; 32]> for IncomingViewingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.scalar.to_bytes() == *other
}
}
/// A _Diversifier_, as described in [protocol specification §4.2.2][ps].
///
/// Combined with an _IncomingViewingKey_, produces a _diversified
@ -765,35 +167,6 @@ impl TryFrom<Diversifier> for jubjub::ExtendedPoint {
}
}
impl From<SpendingKey> for Diversifier {
/// Derives a [_default diversifier_][4.2.2] from a SpendingKey.
///
/// 'For each spending key, there is also a default diversified
/// payment address with a "random-looking" diversifier. This
/// allows an implementation that does not expose diversified
/// addresses as a user-visible feature, to use a default address
/// that cannot be distinguished (without knowledge of the
/// spending key) from one with a random diversifier...'
///
/// [4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
fn from(sk: SpendingKey) -> Diversifier {
let mut i = 0u8;
loop {
let mut d_bytes = [0u8; 11];
d_bytes[..].copy_from_slice(&prf_expand(sk.bytes, &[3, i])[..11]);
if diversify_hash(d_bytes).is_some() {
break Self(d_bytes);
}
assert!(i < 255);
i += 1;
}
}
}
impl PartialEq<[u8; 11]> for Diversifier {
fn eq(&self, other: &[u8; 11]) -> bool {
self.0 == *other
@ -879,123 +252,12 @@ impl From<TransmissionKey> for [u8; 32] {
}
}
impl TryFrom<(IncomingViewingKey, Diversifier)> for TransmissionKey {
type Error = &'static str;
/// This includes _KA^Sapling.DerivePublic(ivk, G_d)_, which is just a
/// scalar mult _\[ivk\]G_d_.
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents>
/// <https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement>
fn try_from((ivk, d): (IncomingViewingKey, Diversifier)) -> Result<Self, Self::Error> {
let affine_point = jubjub::AffinePoint::from(
diversify_hash(d.0).ok_or("invalid diversifier")? * ivk.scalar,
);
// We need to ensure that the result point is in the prime-order subgroup.
// Since diversify_hash() returns a point in the prime-order subgroup,
// then the result point will also be in the prime-order subgroup
// and there is no need to check anything.
Ok(Self(affine_point))
}
}
impl PartialEq<[u8; 32]> for TransmissionKey {
fn eq(&self, other: &[u8; 32]) -> bool {
&self.0.to_bytes() == other
}
}
/// Magic human-readable strings used to identify what networks
/// Sapling FullViewingKeys are associated with when encoded/decoded
/// with bech32.
mod fvk_hrp {
pub const MAINNET: &str = "zviews";
pub const TESTNET: &str = "zviewtestsapling";
}
/// Full Viewing Keys
///
/// Allows recognizing both incoming and outgoing notes without having
/// spend authority.
///
/// For incoming viewing keys on the production network, the
/// Human-Readable Part is "zviews". For incoming viewing keys on the
/// test network, the Human-Readable Part is "zviewtestsapling".
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingfullviewingkeyencoding>
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct FullViewingKey {
network: Network,
authorizing_key: AuthorizingKey,
nullifier_deriving_key: NullifierDerivingKey,
outgoing_viewing_key: OutgoingViewingKey,
}
// TODO: impl a From that accepts a Network?
impl fmt::Debug for FullViewingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("FullViewingKey")
.field("network", &self.network)
.field("authorizing_key", &self.authorizing_key)
.field("nullifier_deriving_key", &self.nullifier_deriving_key)
.field("outgoing_viewing_key", &self.outgoing_viewing_key)
.finish()
}
}
impl fmt::Display for FullViewingKey {
#[allow(clippy::unwrap_in_result)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut bytes = io::Cursor::new(Vec::new());
let _ = bytes.write_all(&<[u8; 32]>::from(self.authorizing_key));
let _ = bytes.write_all(&<[u8; 32]>::from(self.nullifier_deriving_key));
let _ = bytes.write_all(&<[u8; 32]>::from(self.outgoing_viewing_key));
let hrp = match self.network {
Network::Mainnet => fvk_hrp::MAINNET,
_ => fvk_hrp::TESTNET,
};
bech32::encode_to_fmt(f, hrp, bytes.get_ref().to_base32(), Variant::Bech32)
.expect("hrp is valid")
}
}
impl FromStr for FullViewingKey {
type Err = SerializationError;
#[allow(clippy::unwrap_in_result)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
match bech32::decode(s) {
Ok((hrp, bytes, Variant::Bech32)) => {
let mut decoded_bytes = io::Cursor::new(
Vec::<u8>::from_base32(&bytes).expect("bech32::decode guarantees valid base32"),
);
let authorizing_key_bytes = decoded_bytes.read_32_bytes()?;
let nullifier_deriving_key_bytes = decoded_bytes.read_32_bytes()?;
let outgoing_key_bytes = decoded_bytes.read_32_bytes()?;
Ok(FullViewingKey {
network: match hrp.as_str() {
fvk_hrp::MAINNET => Network::Mainnet,
_ => Network::Testnet,
},
authorizing_key: AuthorizingKey::try_from(authorizing_key_bytes)
.map_err(SerializationError::Parse)?,
nullifier_deriving_key: NullifierDerivingKey::from(
nullifier_deriving_key_bytes,
),
outgoing_viewing_key: OutgoingViewingKey::from(outgoing_key_bytes),
})
}
_ => Err(SerializationError::Parse("bech32 decoding error")),
}
}
}
/// An [ephemeral public key][1] for Sapling key agreement.
///
/// Public keys containing points of small order are not allowed.

View File

@ -1,4 +1,8 @@
// Generated from https://github.com/zcash-hackworks/zcash-test-vectors/blob/07dc43fd90cd78a0b45b2eb5d2be3ce3c1841603/sapling_key_components.py
//! Sapling key test vectors.
//!
//! Generated from https://github.com/zcash-hackworks/zcash-test-vectors/blob/07dc43fd90cd78a0b45b2eb5d2be3ce3c1841603/sapling_key_components.py
#![allow(dead_code)]
pub struct TestVector {
pub sk: [u8; 32],

View File

@ -1,114 +0,0 @@
#![allow(clippy::module_inception)]
use super::*;
#[cfg(test)]
use proptest::prelude::*;
#[cfg(test)]
impl Arbitrary for TransmissionKey {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(any::<SpendingKey>())
.prop_map(|spending_key| {
let spend_authorizing_key = SpendAuthorizingKey::from(spending_key);
let proof_authorizing_key = ProofAuthorizingKey::from(spending_key);
let authorizing_key = AuthorizingKey::from(spend_authorizing_key);
let nullifier_deriving_key = NullifierDerivingKey::from(proof_authorizing_key);
let incoming_viewing_key =
IncomingViewingKey::from((authorizing_key, nullifier_deriving_key));
let diversifier = Diversifier::from(spending_key);
Self::try_from((incoming_viewing_key, diversifier)).unwrap()
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derive_for_each_test_vector() {
let _init_guard = zebra_test::init();
for test_vector in test_vectors::TEST_VECTORS.iter() {
let spending_key = SpendingKey::from(test_vector.sk);
let spend_authorizing_key = SpendAuthorizingKey::from(spending_key);
assert_eq!(spend_authorizing_key, test_vector.ask);
let proof_authorizing_key = ProofAuthorizingKey::from(spending_key);
assert_eq!(proof_authorizing_key, test_vector.nsk);
let outgoing_viewing_key = OutgoingViewingKey::from(spending_key);
assert_eq!(outgoing_viewing_key, test_vector.ovk);
let authorizing_key = AuthorizingKey::from(spend_authorizing_key);
assert_eq!(authorizing_key, test_vector.ak);
let nullifier_deriving_key = NullifierDerivingKey::from(proof_authorizing_key);
assert_eq!(nullifier_deriving_key, test_vector.nk);
let incoming_viewing_key =
IncomingViewingKey::from((authorizing_key, nullifier_deriving_key));
assert_eq!(incoming_viewing_key, test_vector.ivk);
let diversifier = Diversifier::from(spending_key);
assert_eq!(diversifier, test_vector.default_d);
let transmission_key = TransmissionKey::try_from((incoming_viewing_key, diversifier))
.expect("should be a valid transmission key");
assert_eq!(transmission_key, test_vector.default_pk_d);
let _full_viewing_key = FullViewingKey {
network: Network::default(),
authorizing_key,
nullifier_deriving_key,
outgoing_viewing_key,
};
}
}
}
#[cfg(test)]
proptest! {
#[test]
fn string_roundtrips(spending_key in any::<SpendingKey>()) {
let _init_guard = zebra_test::init();
let sk_string = spending_key.to_string();
let spending_key_2: SpendingKey = sk_string.parse().unwrap();
prop_assert_eq![spending_key, spending_key_2];
let spend_authorizing_key = SpendAuthorizingKey::from(spending_key);
let proof_authorizing_key = ProofAuthorizingKey::from(spending_key);
let outgoing_viewing_key = OutgoingViewingKey::from(spending_key);
let authorizing_key = AuthorizingKey::from(spend_authorizing_key);
let nullifier_deriving_key = NullifierDerivingKey::from(proof_authorizing_key);
let mut incoming_viewing_key =
IncomingViewingKey::from((authorizing_key, nullifier_deriving_key));
incoming_viewing_key.network = spending_key.network;
let ivk_string = incoming_viewing_key.to_string();
let incoming_viewing_key_2: IncomingViewingKey = ivk_string.parse().unwrap();
prop_assert_eq![incoming_viewing_key, incoming_viewing_key_2];
let full_viewing_key = FullViewingKey {
network: spending_key.network,
authorizing_key,
nullifier_deriving_key,
outgoing_viewing_key,
};
let fvk_string = full_viewing_key.to_string();
let full_viewing_key_2: FullViewingKey = fvk_string.parse().unwrap();
prop_assert_eq![full_viewing_key, full_viewing_key_2];
}
}

View File

@ -1,7 +1,5 @@
//! Sapling notes
#![allow(clippy::unit_arg)]
mod ciphertexts;
mod nullifiers;

View File

@ -1,28 +1,4 @@
#![allow(clippy::unit_arg)]
use super::super::{
commitment::{pedersen_hashes::mixing_pedersen_hash, NoteCommitment},
keys::NullifierDerivingKey,
tree::Position,
};
/// Invokes Blake2s-256 as PRF^nfSapling to derive the nullifier for a
/// Sapling note.
///
/// PRF^nfSapling(ρ*) := BLAKE2s-256("Zcash_nf", nk* || ρ*)
///
/// <https://zips.z.cash/protocol/protocol.pdf#concreteprfs>
fn prf_nf(nk: [u8; 32], rho: [u8; 32]) -> [u8; 32] {
let hash = blake2s_simd::Params::new()
.hash_length(32)
.personal(b"Zcash_nf")
.to_state()
.update(&nk[..])
.update(&rho[..])
.finalize();
*hash.as_array()
}
//! Sapling nullifiers.
/// A Nullifier for Sapling transactions
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
@ -38,14 +14,6 @@ impl From<[u8; 32]> for Nullifier {
}
}
impl<'a> From<(NoteCommitment, Position, &'a NullifierDerivingKey)> for Nullifier {
fn from((cm, pos, nk): (NoteCommitment, Position, &'a NullifierDerivingKey)) -> Self {
let rho = jubjub::AffinePoint::from(mixing_pedersen_hash(cm.0.into(), pos.0.into()));
Nullifier(prf_nf(nk.into(), rho.to_bytes()))
}
}
impl From<Nullifier> for [u8; 32] {
fn from(n: Nullifier) -> Self {
n.0

View File

@ -1,14 +1,12 @@
//! Sprout-related functionality.
mod joinsplit;
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
mod joinsplit;
#[cfg(test)]
mod tests;
// XXX clean up these modules
pub mod address;
pub mod commitment;
pub mod keys;
pub mod note;

View File

@ -1,175 +0,0 @@
//! Sprout Shielded Payment Address types.
use std::{fmt, io};
#[cfg(test)]
use proptest::{arbitrary::Arbitrary, array, prelude::*};
use crate::{
parameters::Network,
serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize},
};
use super::keys;
/// Magic numbers used to identify what networks Sprout Shielded
/// Addresses are associated with.
mod magics {
pub const MAINNET: [u8; 2] = [0x16, 0x9A];
pub const TESTNET: [u8; 2] = [0x16, 0xB6];
}
/// Sprout Shielded Payment Addresses
///
/// <https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding>
#[derive(Copy, Clone)]
pub struct SproutShieldedAddress {
network: Network,
paying_key: keys::PayingKey,
transmission_key: keys::TransmissionKey,
}
impl fmt::Debug for SproutShieldedAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("SproutShieldedAddress")
.field("network", &self.network)
.field("paying_key", &self.paying_key)
// Use hex formatting for the transmission key.
.field(
"transmission_key",
&hex::encode(self.transmission_key.as_bytes()),
)
.finish()
}
}
impl PartialEq for SproutShieldedAddress {
fn eq(&self, other: &Self) -> bool {
self.network == other.network
&& self.paying_key.0 == other.paying_key.0
&& self.transmission_key.as_bytes() == other.transmission_key.as_bytes()
}
}
impl Eq for SproutShieldedAddress {}
impl ZcashSerialize for SproutShieldedAddress {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self.network {
Network::Mainnet => writer.write_all(&magics::MAINNET[..])?,
_ => writer.write_all(&magics::TESTNET[..])?,
}
writer.write_all(&self.paying_key.0[..])?;
writer.write_all(self.transmission_key.as_bytes())?;
Ok(())
}
}
impl ZcashDeserialize for SproutShieldedAddress {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let mut version_bytes = [0; 2];
reader.read_exact(&mut version_bytes)?;
let network = match version_bytes {
magics::MAINNET => Network::Mainnet,
magics::TESTNET => Network::Testnet,
_ => panic!("SerializationError: bad sprout shielded addr version/type"),
};
Ok(SproutShieldedAddress {
network,
paying_key: keys::PayingKey(reader.read_32_bytes()?),
transmission_key: keys::TransmissionKey::from(reader.read_32_bytes()?),
})
}
}
impl fmt::Display for SproutShieldedAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut bytes = io::Cursor::new(Vec::new());
let _ = self.zcash_serialize(&mut bytes);
f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string())
}
}
impl std::str::FromStr for SproutShieldedAddress {
type Err = SerializationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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")),
}
}
}
#[cfg(test)]
impl Arbitrary for SproutShieldedAddress {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
any::<Network>(),
array::uniform32(any::<u8>()),
array::uniform32(any::<u8>()),
)
.prop_map(|(network, paying_key_bytes, transmission_key_bytes)| Self {
network,
paying_key: keys::PayingKey(paying_key_bytes),
transmission_key: keys::TransmissionKey::from(transmission_key_bytes),
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_string_debug() {
let _init_guard = zebra_test::init();
let string = "zcU1Cd6zYyZCd2VJF8yKgmzjxdiiU1rgTTjEwoN1CGUWCziPkUTXUjXmX7TMqdMNsTfuiGN1jQoVN4kGxUR4sAPN4XZ7pxb";
let zc_addr = string.parse::<SproutShieldedAddress>().unwrap();
assert_eq!(string, zc_addr.to_string());
}
}
#[cfg(test)]
proptest! {
#[test]
fn zcash_de_serialize_roundtrip(zaddr in any::<SproutShieldedAddress>()) {
let _init_guard = zebra_test::init();
let mut data = Vec::new();
zaddr.zcash_serialize(&mut data).expect("sprout z-addr should serialize");
let zaddr2 = SproutShieldedAddress::zcash_deserialize(&data[..])
.expect("randomized sprout z-addr should deserialize");
prop_assert_eq![zaddr, zaddr2];
}
#[test]
fn zcash_base58check_roundtrip(zaddr in any::<SproutShieldedAddress>()) {
let _init_guard = zebra_test::init();
let string = zaddr.to_string();
let zaddr2 = string.parse::<SproutShieldedAddress>().unwrap();
prop_assert_eq![zaddr, zaddr2];
}
}

View File

@ -1,185 +1,18 @@
//! Sprout key types
//! Sprout key types.
//!
//! Unused key types are not implemented, see PR #5476.
//!
//! "The receiving key sk_enc, the incoming viewing key ivk = (apk,
//! sk_enc), and the shielded payment address addr_pk = (a_pk, pk_enc) are
//! derived from a_sk, as described in ['Sprout Key Components'][ps]
//!
//! [ps]: https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents
#![allow(clippy::unit_arg)]
use std::{fmt, io};
use std::fmt;
use byteorder::{ByteOrder, LittleEndian};
use rand_core::{CryptoRng, RngCore};
use sha2::digest::generic_array::{typenum::U64, GenericArray};
#[cfg(any(test, feature = "proptest-impl"))]
use proptest::{array, prelude::*};
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
use crate::{
parameters::Network,
serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize},
};
/// Magic numbers used to identify with what networks Sprout Spending
/// Keys are associated.
mod sk_magics {
pub const MAINNET: [u8; 2] = [0xAB, 0x36];
pub const TESTNET: [u8; 2] = [0xAC, 0x08];
}
/// PRF^addr is used to derive a Sprout shielded payment address from
/// a spending key, and instantiated using the SHA-256 compression
/// function.
/// A Sprout _paying key_.
///
/// <https://zips.z.cash/protocol/protocol.pdf#abstractprfs>
/// <https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents>
fn prf_addr(x: [u8; 32], t: u8) -> [u8; 32] {
let mut state = [0u32; 8];
let mut block = GenericArray::<u8, U64>::default();
block.as_mut_slice()[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
// of SHA256Compress, ensuring that the functions are independent.
block.as_mut_slice()[0] |= 0b1100_0000;
block.as_mut_slice()[32] = t;
sha2::compress256(&mut state, &[block]);
let mut derived_bytes = [0u8; 32];
LittleEndian::write_u32_into(&state, &mut derived_bytes);
derived_bytes
}
/// Our root secret key of the Sprout key derivation tree.
///
/// All other Sprout key types derive from the SpendingKey value.
/// Actually 252 bits.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct SpendingKey {
/// What would normally be the value inside a tuple struct.
pub bytes: [u8; 32],
/// The Zcash network with which this key is associated.
pub network: Network,
}
impl ZcashSerialize for SpendingKey {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self.network {
Network::Mainnet => writer.write_all(&sk_magics::MAINNET[..])?,
_ => writer.write_all(&sk_magics::TESTNET[..])?,
}
writer.write_all(&self.bytes[..])?;
Ok(())
}
}
impl ZcashDeserialize for SpendingKey {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let mut version_bytes = [0; 2];
reader.read_exact(&mut version_bytes)?;
let network = match version_bytes {
sk_magics::MAINNET => Network::Mainnet,
sk_magics::TESTNET => Network::Testnet,
_ => panic!("SerializationError: bad sprout spending key version/type"),
};
Ok(SpendingKey {
network,
bytes: reader.read_32_bytes()?,
})
}
}
impl fmt::Display for SpendingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut bytes = io::Cursor::new(Vec::new());
let _ = self.zcash_serialize(&mut bytes);
f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string())
}
}
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
/// 252).
fn from(mut bytes: [u8; 32]) -> SpendingKey {
bytes[0] &= 0b0000_1111; // Force the 4 high-order bits to zero.
SpendingKey {
bytes,
network: Network::default(),
}
}
}
impl From<SpendingKey> for [u8; 32] {
fn from(spending_key: SpendingKey) -> [u8; 32] {
spending_key.bytes
}
}
impl<'a> From<&'a SpendingKey> for [u8; 32] {
fn from(spending_key: &'a SpendingKey) -> [u8; 32] {
spending_key.bytes
}
}
impl std::str::FromStr for SpendingKey {
type Err = SerializationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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).
pub fn new<T>(csprng: &mut T) -> Self
where
T: RngCore + CryptoRng,
{
let mut bytes = [0u8; 32];
csprng.fill_bytes(&mut bytes);
Self::from(bytes)
}
}
/// Derived from a _SpendingKey_.
pub type ReceivingKey = x25519_dalek::StaticSecret;
impl From<SpendingKey> for ReceivingKey {
/// For this invocation of SHA256Compress as PRF^addr, t=0, which
/// is populated by default in an empty block of all zeros to
/// start.
///
/// <https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents>
/// <https://zips.z.cash/protocol/protocol.pdf#concreteprfs>
fn from(spending_key: SpendingKey) -> ReceivingKey {
let derived_bytes = prf_addr(spending_key.bytes, 0);
ReceivingKey::from(derived_bytes)
}
}
/// Derived from a _SpendingKey_.
/// Derived from a Sprout _spending key.
#[derive(Copy, Clone, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
@ -200,205 +33,3 @@ impl fmt::Debug for PayingKey {
.finish()
}
}
impl From<SpendingKey> for PayingKey {
/// For this invocation of SHA256Compress as PRF^addr, t=1.
///
/// <https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents>
/// <https://zips.z.cash/protocol/protocol.pdf#concreteprfs>
fn from(spending_key: SpendingKey) -> PayingKey {
let derived_bytes = prf_addr(spending_key.bytes, 1);
PayingKey(derived_bytes)
}
}
/// Derived from a _ReceivingKey_.
pub type TransmissionKey = x25519_dalek::PublicKey;
/// Magic numbers used to identify with what networks Sprout Incoming
/// Viewing Keys are associated.
mod ivk_magics {
pub const MAINNET: [u8; 3] = [0xA8, 0xAB, 0xD3];
pub const TESTNET: [u8; 3] = [0xA8, 0xAC, 0x0C];
}
/// The recipient's possession of the associated incoming viewing key
/// is used to reconstruct the original note and memo field.
pub struct IncomingViewingKey {
network: Network,
paying_key: PayingKey,
receiving_key: ReceivingKey,
}
// Can't derive PartialEq because ReceivingKey aka
// x25519_dalek::StaticSecret does not impl it.
impl PartialEq for IncomingViewingKey {
fn eq(&self, other: &Self) -> bool {
self.network == other.network
&& self.paying_key.0 == other.paying_key.0
&& self.receiving_key.to_bytes() == other.receiving_key.to_bytes()
}
}
impl fmt::Debug for IncomingViewingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("IncomingViewingKey")
.field("network", &self.network)
.field("paying_key", &hex::encode(self.paying_key.0))
.field("receiving_key", &hex::encode(self.receiving_key.to_bytes()))
.finish()
}
}
impl ZcashSerialize for IncomingViewingKey {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self.network {
Network::Mainnet => writer.write_all(&ivk_magics::MAINNET[..])?,
_ => writer.write_all(&ivk_magics::TESTNET[..])?,
}
writer.write_all(&self.paying_key.0[..])?;
writer.write_all(&self.receiving_key.to_bytes())?;
Ok(())
}
}
impl ZcashDeserialize for IncomingViewingKey {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let mut version_bytes = [0; 3];
reader.read_exact(&mut version_bytes)?;
let network = match version_bytes {
ivk_magics::MAINNET => Network::Mainnet,
ivk_magics::TESTNET => Network::Testnet,
_ => panic!("SerializationError: bad sprout incoming viewing key network"),
};
Ok(IncomingViewingKey {
network,
paying_key: PayingKey(reader.read_32_bytes()?),
receiving_key: ReceivingKey::from(reader.read_32_bytes()?),
})
}
}
impl fmt::Display for IncomingViewingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut bytes = io::Cursor::new(Vec::new());
let _ = self.zcash_serialize(&mut bytes);
f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string())
}
}
impl std::str::FromStr for IncomingViewingKey {
type Err = SerializationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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")),
}
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl Arbitrary for IncomingViewingKey {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
any::<Network>(),
array::uniform32(any::<u8>()),
array::uniform32(any::<u8>()),
)
.prop_map(|(network, paying_key_bytes, receiving_key_bytes)| Self {
network,
paying_key: PayingKey(paying_key_bytes),
receiving_key: ReceivingKey::from(receiving_key_bytes),
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
#[cfg(test)]
mod tests {
use rand_core::OsRng;
use super::*;
#[test]
// TODO: test vectors, not just random data
fn derive_keys() {
let _init_guard = zebra_test::init();
let spending_key = SpendingKey::new(&mut OsRng);
let receiving_key = ReceivingKey::from(spending_key);
let _transmission_key = TransmissionKey::from(&receiving_key);
}
}
#[cfg(test)]
proptest! {
#[test]
fn spending_key_roundtrip(sk in any::<SpendingKey>()) {
let _init_guard = zebra_test::init();
let mut data = Vec::new();
sk.zcash_serialize(&mut data).expect("sprout spending key should serialize");
let sk2 = SpendingKey::zcash_deserialize(&data[..]).expect("randomized sprout spending key should deserialize");
prop_assert_eq![sk, sk2];
}
#[test]
fn spending_key_string_roundtrip(sk in any::<SpendingKey>()) {
let _init_guard = zebra_test::init();
let string = sk.to_string();
let sk2 = string.parse::<SpendingKey>().unwrap();
prop_assert_eq![sk, sk2];
}
#[test]
fn incoming_viewing_key_roundtrip(ivk in any::<IncomingViewingKey>()) {
let _init_guard = zebra_test::init();
let mut data = Vec::new();
ivk.zcash_serialize(&mut data).expect("sprout z-addr should serialize");
let ivk2 = IncomingViewingKey::zcash_deserialize(&data[..]).expect("randomized ivk should deserialize");
prop_assert_eq![ivk, ivk2];
}
#[test]
fn incoming_viewing_key_string_roundtrip(ivk in any::<IncomingViewingKey>()) {
let _init_guard = zebra_test::init();
let string = ivk.to_string();
let ivk2 = string.parse::<IncomingViewingKey>().unwrap();
prop_assert_eq![ivk, ivk2];
}
}

View File

@ -1,13 +1,12 @@
//! Sprout notes
#![allow(clippy::unit_arg)]
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
mod ciphertexts;
mod mac;
mod nullifiers;
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
use crate::{
amount::{Amount, NonNegative},
transaction::Memo,

View File

@ -1,34 +1,6 @@
use byteorder::{ByteOrder, LittleEndian};
//! Sprout nullifiers.
use serde::{Deserialize, Serialize};
use sha2::digest::generic_array::{typenum::U64, GenericArray};
use super::super::keys::SpendingKey;
/// 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 = GenericArray::<u8, U64>::default();
block.as_mut_slice()[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
// of SHA256Compress, ensuring that the functions are independent.
block.as_mut_slice()[0] |= 0b1100_0000;
block.as_mut_slice()[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].
///
@ -73,12 +45,6 @@ impl From<[u8; 32]> for Nullifier {
}
}
impl<'a> From<(&'a SpendingKey, NullifierSeed)> for Nullifier {
fn from((a_sk, rho): (&'a SpendingKey, NullifierSeed)) -> Self {
Self(prf_nf(a_sk.into(), rho.into()))
}
}
impl From<Nullifier> for [u8; 32] {
fn from(n: Nullifier) -> Self {
n.0