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:
parent
9cb3dbba9b
commit
161bb80ce8
|
@ -4,11 +4,12 @@
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
mod address;
|
mod address;
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
|
||||||
mod arbitrary;
|
|
||||||
mod commitment;
|
mod commitment;
|
||||||
mod note;
|
mod note;
|
||||||
mod sinsemilla;
|
mod sinsemilla;
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
|
mod arbitrary;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
|
|
@ -24,39 +24,3 @@ impl fmt::Debug for Address {
|
||||||
.finish()
|
.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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
//! Randomised data generation for Orchard types.
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use group::{ff::PrimeField, prime::PrimeCurveAffine};
|
use group::{ff::PrimeField, prime::PrimeCurveAffine};
|
||||||
use halo2::{arithmetic::FieldExt, pasta::pallas};
|
use halo2::{arithmetic::FieldExt, pasta::pallas};
|
||||||
use proptest::{arbitrary::any, array, collection::vec, prelude::*};
|
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 crate::primitives::redpallas::{Signature, SpendAuth, VerificationKey, VerificationKeyBytes};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
keys, note, tree, Action, Address, AuthorizedAction, Diversifier, Flags, NoteCommitment,
|
keys::*, note, tree, Action, AuthorizedAction, Flags, NoteCommitment, ValueCommitment,
|
||||||
ValueCommitment,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
convert::{TryFrom, TryInto},
|
|
||||||
marker::PhantomData,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Arbitrary for Action {
|
impl Arbitrary for Action {
|
||||||
|
@ -29,7 +27,7 @@ impl Arbitrary for Action {
|
||||||
nullifier,
|
nullifier,
|
||||||
rk,
|
rk,
|
||||||
cm_x: NoteCommitment(pallas::Affine::identity()).extract_x(),
|
cm_x: NoteCommitment(pallas::Affine::identity()).extract_x(),
|
||||||
ephemeral_key: keys::EphemeralPublicKey(pallas::Affine::generator()),
|
ephemeral_key: EphemeralPublicKey(pallas::Affine::generator()),
|
||||||
enc_ciphertext,
|
enc_ciphertext,
|
||||||
out_ciphertext,
|
out_ciphertext,
|
||||||
})
|
})
|
||||||
|
@ -128,40 +126,3 @@ impl Arbitrary for tree::Root {
|
||||||
|
|
||||||
type Strategy = BoxedStrategy<Self>;
|
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>;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
//! Note and value commitments.
|
//! 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 group::{ff::PrimeField, prime::PrimeCurveAffine, GroupEncoding};
|
||||||
use halo2::{
|
use halo2::{
|
||||||
arithmetic::{Coordinates, CurveAffine, FieldExt},
|
arithmetic::{Coordinates, CurveAffine, FieldExt},
|
||||||
|
@ -18,11 +17,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::sinsemilla::*;
|
||||||
keys::prf_expand,
|
|
||||||
note::{Note, Psi, SeedRandomness},
|
|
||||||
sinsemilla::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Generates a random scalar from the scalar field 𝔽_{q_P}.
|
/// Generates a random scalar from the scalar field 𝔽_{q_P}.
|
||||||
///
|
///
|
||||||
|
@ -41,18 +36,6 @@ where
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct CommitmentRandomness(pallas::Scalar);
|
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.
|
/// Note commitments for the output notes.
|
||||||
#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
|
||||||
pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine);
|
pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine);
|
||||||
|
@ -103,51 +86,6 @@ impl TryFrom<[u8; 32]> for NoteCommitment {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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.
|
/// Extract the x coordinate of the note commitment.
|
||||||
pub fn extract_x(&self) -> pallas::Base {
|
pub fn extract_x(&self) -> pallas::Base {
|
||||||
extract_p(self.0.into())
|
extract_p(self.0.into())
|
||||||
|
@ -320,20 +258,6 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn add() {
|
fn add() {
|
||||||
let _init_guard = zebra_test::init();
|
let _init_guard = zebra_test::init();
|
||||||
|
|
|
@ -1,80 +1,24 @@
|
||||||
//! Orchard key types.
|
//! Orchard key types.
|
||||||
//!
|
//!
|
||||||
|
//! Unused key types are not implemented, see PR #5476.
|
||||||
|
//!
|
||||||
//! <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
|
//! <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
|
||||||
#![allow(clippy::fallible_impl_from)]
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
use std::{fmt, io};
|
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 group::{ff::PrimeField, prime::PrimeCurveAffine, Group, GroupEncoding};
|
||||||
use halo2::{
|
use halo2::{
|
||||||
arithmetic::{Coordinates, CurveAffine, Field, FieldExt},
|
arithmetic::{Coordinates, CurveAffine},
|
||||||
pasta::pallas,
|
pasta::pallas,
|
||||||
};
|
};
|
||||||
use rand_core::{CryptoRng, RngCore};
|
use rand_core::{CryptoRng, RngCore};
|
||||||
use subtle::{Choice, ConstantTimeEq};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::serialization::{
|
||||||
parameters::Network,
|
|
||||||
primitives::redpallas::{self, SpendAuth},
|
|
||||||
serialization::{
|
|
||||||
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
|
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::sinsemilla::*;
|
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.
|
/// Used to derive a diversified base point from a diversifier value.
|
||||||
///
|
///
|
||||||
/// DiversifyHash^Orchard(d) := {︃ GroupHash^P("z.cash:Orchard-gd",""), if P = 0_P
|
/// 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].
|
/// A _diversifier_, as described in [protocol specification §4.2.3][ps].
|
||||||
///
|
///
|
||||||
/// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
/// [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] {
|
impl From<Diversifier> for [u8; 11] {
|
||||||
fn from(d: Diversifier) -> [u8; 11] {
|
fn from(d: Diversifier) -> [u8; 11] {
|
||||||
d.0
|
d.0
|
||||||
|
@ -982,99 +149,18 @@ impl fmt::Debug for TransmissionKey {
|
||||||
|
|
||||||
impl Eq 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] {
|
impl From<TransmissionKey> for [u8; 32] {
|
||||||
fn from(pk_d: TransmissionKey) -> [u8; 32] {
|
fn from(pk_d: TransmissionKey) -> [u8; 32] {
|
||||||
pk_d.0.to_bytes()
|
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 {
|
impl PartialEq<[u8; 32]> for TransmissionKey {
|
||||||
fn eq(&self, other: &[u8; 32]) -> bool {
|
fn eq(&self, other: &[u8; 32]) -> bool {
|
||||||
&self.0.to_bytes() == other
|
&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.
|
/// An ephemeral public key for Orchard key agreement.
|
||||||
///
|
///
|
||||||
/// <https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement>
|
/// <https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement>
|
||||||
|
|
|
@ -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));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +1,22 @@
|
||||||
//! Orchard notes
|
//! Orchard notes
|
||||||
|
|
||||||
#![allow(clippy::unit_arg)]
|
|
||||||
|
|
||||||
use group::{ff::PrimeField, GroupEncoding};
|
use group::{ff::PrimeField, GroupEncoding};
|
||||||
use halo2::{arithmetic::FieldExt, pasta::pallas};
|
use halo2::pasta::pallas;
|
||||||
use rand_core::{CryptoRng, RngCore};
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
use crate::amount::{Amount, NonNegative};
|
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 ciphertexts;
|
||||||
mod nullifiers;
|
mod nullifiers;
|
||||||
|
|
||||||
pub use ciphertexts::{EncryptedNote, WrappedNoteKey};
|
pub use ciphertexts::{EncryptedNote, WrappedNoteKey};
|
||||||
pub use nullifiers::Nullifier;
|
pub use nullifiers::Nullifier;
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
|
mod arbitrary;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
/// A random seed (rseed) used in the Orchard note creation.
|
/// A random seed (rseed) used in the Orchard note creation.
|
||||||
pub struct SeedRandomness(pub(crate) [u8; 32]);
|
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
|
/// A Note represents that a value is spendable by the recipient who holds the
|
||||||
/// spending key corresponding to a given shielded payment address.
|
/// spending key corresponding to a given shielded payment address.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,46 +1,11 @@
|
||||||
#![allow(clippy::unit_arg)]
|
//! Orchard nullifier types and conversions.
|
||||||
|
|
||||||
use std::{
|
use std::hash::{Hash, Hasher};
|
||||||
convert::TryFrom,
|
|
||||||
hash::{Hash, Hasher},
|
|
||||||
};
|
|
||||||
|
|
||||||
use halo2::pasta::{group::ff::PrimeField, pallas};
|
use halo2::pasta::{group::ff::PrimeField, pallas};
|
||||||
|
|
||||||
use crate::serialization::{serde_helpers, SerializationError};
|
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
|
/// A Nullifier for Orchard transactions
|
||||||
#[derive(Clone, Copy, Debug, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Eq, Serialize, Deserialize)]
|
||||||
pub struct Nullifier(#[serde(with = "serde_helpers::Base")] pub(crate) pallas::Base);
|
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] {
|
impl From<Nullifier> for [u8; 32] {
|
||||||
fn from(n: Nullifier) -> Self {
|
fn from(n: Nullifier) -> Self {
|
||||||
n.0.into()
|
n.0.into()
|
||||||
|
|
|
@ -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))
|
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
|
// TODO: test the above correctness and compatibility with the zcash-hackworks test vectors
|
||||||
// https://github.com/ZcashFoundation/zebra/issues/2079
|
// https://github.com/ZcashFoundation/zebra/issues/2079
|
||||||
// https://github.com/zcash-hackworks/zcash-test-vectors/pulls
|
// https://github.com/zcash-hackworks/zcash-test-vectors/pulls
|
||||||
|
|
|
@ -10,23 +10,20 @@
|
||||||
//! points occur, and non-canonical point encodings are rejected. This is
|
//! points occur, and non-canonical point encodings are rejected. This is
|
||||||
//! enforced by the jubjub crate, which is also used by the redjubjub crate.
|
//! 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 commitment;
|
||||||
mod note;
|
mod note;
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
|
mod arbitrary;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
// XXX clean up these modules
|
|
||||||
|
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
pub mod output;
|
pub mod output;
|
||||||
pub mod shielded_data;
|
pub mod shielded_data;
|
||||||
pub mod spend;
|
pub mod spend;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
|
|
||||||
pub use address::Address;
|
|
||||||
pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment};
|
pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment};
|
||||||
pub use keys::Diversifier;
|
pub use keys::Diversifier;
|
||||||
pub use note::{EncryptedNote, Note, Nullifier, WrappedNoteKey};
|
pub use note::{EncryptedNote, Note, Nullifier, WrappedNoteKey};
|
||||||
|
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,5 @@
|
||||||
//! Note and value commitments.
|
//! Note and value commitments.
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_vectors;
|
|
||||||
|
|
||||||
pub mod pedersen_hashes;
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
fmt, io,
|
fmt, io,
|
||||||
|
@ -24,6 +19,11 @@ use crate::{
|
||||||
|
|
||||||
use super::keys::{find_group_hash, Diversifier, TransmissionKey};
|
use super::keys::{find_group_hash, Diversifier, TransmissionKey};
|
||||||
|
|
||||||
|
pub mod pedersen_hashes;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_vectors;
|
||||||
|
|
||||||
use pedersen_hashes::*;
|
use pedersen_hashes::*;
|
||||||
|
|
||||||
/// Generates a random scalar from the scalar field 𝔽_{r_𝕁}.
|
/// Generates a random scalar from the scalar field 𝔽_{r_𝕁}.
|
||||||
|
|
|
@ -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()
|
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
|
/// Construct a 'windowed' Pedersen commitment by reusing a Pederson hash
|
||||||
/// construction, and adding a randomized point on the Jubjub curve.
|
/// construction, and adding a randomized point on the Jubjub curve.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
// Test vector data generated from
|
//! Test vector data generated from
|
||||||
// https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_pedersen.py
|
//! https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_pedersen.py
|
||||||
//
|
//!
|
||||||
// These vectors in particular correspond to the Personalization::NoteCommitment
|
//! These vectors in particular correspond to the Personalization::NoteCommitment
|
||||||
// enum variant from the original source.
|
//! enum variant from the original source.
|
||||||
//
|
//!
|
||||||
// The Python hex-encoded outputs for these test vectors were output in
|
//! 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
|
//! 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
|
//! 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.
|
//! and that's what they compare in their unit tests, not the bytes.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use bitvec::prelude::*;
|
use bitvec::prelude::*;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
|
@ -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),
|
//! "The spend authorizing key ask, proof authorizing key (ak, nsk),
|
||||||
//! full viewing key (ak, nk, ovk), incoming viewing key ivk, and each
|
//! 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
|
//! [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
|
||||||
//! [3.1]: https://zips.z.cash/protocol/protocol.pdf#addressesandkeys
|
//! [3.1]: https://zips.z.cash/protocol/protocol.pdf#addressesandkeys
|
||||||
#![allow(clippy::unit_arg)]
|
|
||||||
#![allow(clippy::fallible_impl_from)]
|
|
||||||
|
|
||||||
#[cfg(test)]
|
use std::{fmt, io};
|
||||||
mod test_vectors;
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
fmt,
|
|
||||||
io::{self, Write},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bech32::{self, FromBase32, ToBase32, Variant};
|
|
||||||
use rand_core::{CryptoRng, RngCore};
|
use rand_core::{CryptoRng, RngCore};
|
||||||
use subtle::{Choice, ConstantTimeEq};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parameters::Network,
|
|
||||||
primitives::redjubjub::{self, SpendAuth},
|
primitives::redjubjub::{self, SpendAuth},
|
||||||
serialization::{
|
serialization::{
|
||||||
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
|
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_vectors;
|
||||||
|
|
||||||
/// The [Randomness Beacon][1] ("URS").
|
/// The [Randomness Beacon][1] ("URS").
|
||||||
///
|
///
|
||||||
/// First 64 bytes of the BLAKE2s input during JubJub group hash. 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] =
|
pub(super) const RANDOMNESS_BEACON_URS: &[u8; 64] =
|
||||||
b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0";
|
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_
|
/// GroupHash into Jubjub, aka _GroupHash_URS_
|
||||||
///
|
///
|
||||||
/// Produces a random point in the Jubjub curve. The point is
|
/// 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.
|
/// Used to derive a diversified base point from a diversifier value.
|
||||||
///
|
///
|
||||||
/// <https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash>
|
/// <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)
|
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].
|
/// A _Diversifier_, as described in [protocol specification §4.2.2][ps].
|
||||||
///
|
///
|
||||||
/// Combined with an _IncomingViewingKey_, produces a _diversified
|
/// 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 {
|
impl PartialEq<[u8; 11]> for Diversifier {
|
||||||
fn eq(&self, other: &[u8; 11]) -> bool {
|
fn eq(&self, other: &[u8; 11]) -> bool {
|
||||||
self.0 == *other
|
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 {
|
impl PartialEq<[u8; 32]> for TransmissionKey {
|
||||||
fn eq(&self, other: &[u8; 32]) -> bool {
|
fn eq(&self, other: &[u8; 32]) -> bool {
|
||||||
&self.0.to_bytes() == other
|
&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.
|
/// An [ephemeral public key][1] for Sapling key agreement.
|
||||||
///
|
///
|
||||||
/// Public keys containing points of small order are not allowed.
|
/// Public keys containing points of small order are not allowed.
|
||||||
|
|
|
@ -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 struct TestVector {
|
||||||
pub sk: [u8; 32],
|
pub sk: [u8; 32],
|
||||||
|
|
|
@ -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];
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
//! Sapling notes
|
//! Sapling notes
|
||||||
|
|
||||||
#![allow(clippy::unit_arg)]
|
|
||||||
|
|
||||||
mod ciphertexts;
|
mod ciphertexts;
|
||||||
mod nullifiers;
|
mod nullifiers;
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,4 @@
|
||||||
#![allow(clippy::unit_arg)]
|
//! Sapling nullifiers.
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A Nullifier for Sapling transactions
|
/// A Nullifier for Sapling transactions
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
#[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] {
|
impl From<Nullifier> for [u8; 32] {
|
||||||
fn from(n: Nullifier) -> Self {
|
fn from(n: Nullifier) -> Self {
|
||||||
n.0
|
n.0
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
//! Sprout-related functionality.
|
//! Sprout-related functionality.
|
||||||
|
|
||||||
|
mod joinsplit;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
mod arbitrary;
|
mod arbitrary;
|
||||||
mod joinsplit;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
// XXX clean up these modules
|
|
||||||
|
|
||||||
pub mod address;
|
|
||||||
pub mod commitment;
|
pub mod commitment;
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
pub mod note;
|
pub mod note;
|
||||||
|
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
//! "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
|
//! 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]
|
//! derived from a_sk, as described in ['Sprout Key Components'][ps]
|
||||||
//!
|
//!
|
||||||
//! [ps]: https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents
|
//! [ps]: https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents
|
||||||
#![allow(clippy::unit_arg)]
|
|
||||||
|
|
||||||
use std::{fmt, io};
|
use std::fmt;
|
||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
/// A Sprout _paying key_.
|
||||||
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.
|
|
||||||
///
|
///
|
||||||
/// <https://zips.z.cash/protocol/protocol.pdf#abstractprfs>
|
/// Derived from a Sprout _spending key.
|
||||||
/// <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_.
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
any(test, feature = "proptest-impl"),
|
any(test, feature = "proptest-impl"),
|
||||||
|
@ -200,205 +33,3 @@ impl fmt::Debug for PayingKey {
|
||||||
.finish()
|
.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];
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
//! Sprout notes
|
//! Sprout notes
|
||||||
|
|
||||||
#![allow(clippy::unit_arg)]
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
|
||||||
mod arbitrary;
|
|
||||||
mod ciphertexts;
|
mod ciphertexts;
|
||||||
mod mac;
|
mod mac;
|
||||||
mod nullifiers;
|
mod nullifiers;
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
|
mod arbitrary;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
amount::{Amount, NonNegative},
|
amount::{Amount, NonNegative},
|
||||||
transaction::Memo,
|
transaction::Memo,
|
||||||
|
|
|
@ -1,34 +1,6 @@
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
//! Sprout nullifiers.
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
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].
|
/// 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] {
|
impl From<Nullifier> for [u8; 32] {
|
||||||
fn from(n: Nullifier) -> Self {
|
fn from(n: Nullifier) -> Self {
|
||||||
n.0
|
n.0
|
||||||
|
|
Loading…
Reference in New Issue