use core::mem;
use std::io::{self, Read, Write};
use aes::Aes256;
use blake2b_simd::{Hash as Blake2bHash, Params};
use fpe::ff1::{BinaryNumeralString, FF1};
use group::{
ff::{Field, PrimeField},
prime::PrimeCurveAffine,
Curve, GroupEncoding,
};
use pasta_curves::pallas;
use rand::RngCore;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use zcash_note_encryption::EphemeralKeyBytes;
use crate::{
address::Address,
primitives::redpallas::{self, SpendAuth},
spec::{
commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar,
NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand,
},
zip32::{self, ChildIndex, ExtendedSpendingKey},
};
const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF";
const ZIP32_PURPOSE: u32 = 32;
#[derive(Debug, Copy, Clone)]
pub struct SpendingKey([u8; 32]);
impl ConstantTimeEq for SpendingKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.to_bytes().ct_eq(other.to_bytes())
}
}
impl SpendingKey {
pub(crate) fn random(rng: &mut impl RngCore) -> Self {
loop {
let mut bytes = [0; 32];
rng.fill_bytes(&mut bytes);
let sk = SpendingKey::from_bytes(bytes);
if sk.is_some().into() {
break sk.unwrap();
}
}
}
pub fn from_bytes(sk: [u8; 32]) -> CtOption<Self> {
let sk = SpendingKey(sk);
let ask = SpendAuthorizingKey::derive_inner(&sk);
let fvk = (&sk).into();
let external_ivk = KeyAgreementPrivateKey::derive_inner(&fvk);
let internal_ivk = KeyAgreementPrivateKey::derive_inner(&fvk.derive_internal());
CtOption::new(
sk,
!(ask.is_zero() | external_ivk.is_none() | internal_ivk.is_none()),
)
}
pub fn to_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn from_zip32_seed(
seed: &[u8],
coin_type: u32,
account: u32,
) -> Result<Self, zip32::Error> {
let path = &[
ChildIndex::try_from(ZIP32_PURPOSE)?,
ChildIndex::try_from(coin_type)?,
ChildIndex::try_from(account)?,
];
ExtendedSpendingKey::from_path(seed, path).map(|esk| esk.sk())
}
}
#[derive(Clone, Debug)]
pub struct SpendAuthorizingKey(redpallas::SigningKey<SpendAuth>);
impl SpendAuthorizingKey {
fn derive_inner(sk: &SpendingKey) -> pallas::Scalar {
to_scalar(PrfExpand::OrchardAsk.expand(&sk.0))
}
pub fn randomize(&self, randomizer: &pallas::Scalar) -> redpallas::SigningKey<SpendAuth> {
self.0.randomize(randomizer)
}
}
impl From<&SpendingKey> for SpendAuthorizingKey {
fn from(sk: &SpendingKey) -> Self {
let ask = Self::derive_inner(sk);
assert!(!bool::from(ask.is_zero()));
let ret = SpendAuthorizingKey(ask.to_repr().try_into().unwrap());
if (<[u8; 32]>::from(SpendValidatingKey::from(&ret).0)[31] >> 7) == 1 {
SpendAuthorizingKey((-ask).to_repr().try_into().unwrap())
} else {
ret
}
}
}
#[derive(Debug, Clone, PartialOrd, Ord)]
pub struct SpendValidatingKey(redpallas::VerificationKey<SpendAuth>);
impl From<&SpendAuthorizingKey> for SpendValidatingKey {
fn from(ask: &SpendAuthorizingKey) -> Self {
SpendValidatingKey((&ask.0).into())
}
}
impl From<&SpendValidatingKey> for pallas::Point {
fn from(spend_validating_key: &SpendValidatingKey) -> pallas::Point {
pallas::Point::from_bytes(&(&spend_validating_key.0).into()).unwrap()
}
}
impl PartialEq for SpendValidatingKey {
fn eq(&self, other: &Self) -> bool {
<[u8; 32]>::from(&self.0).eq(&<[u8; 32]>::from(&other.0))
}
}
impl Eq for SpendValidatingKey {}
impl SpendValidatingKey {
pub fn randomize(&self, randomizer: &pallas::Scalar) -> redpallas::VerificationKey<SpendAuth> {
self.0.randomize(randomizer)
}
pub(crate) fn to_bytes(&self) -> [u8; 32] {
<[u8; 32]>::from(&self.0)
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
<[u8; 32]>::try_from(bytes)
.ok()
.and_then(|b| {
if b != [0; 32] && b[31] & 0x80 == 0 {
<redpallas::VerificationKey<SpendAuth>>::try_from(b).ok()
} else {
None
}
})
.map(SpendValidatingKey)
}
}
#[derive(Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct NullifierDerivingKey(pallas::Base);
impl NullifierDerivingKey {
pub(crate) fn inner(&self) -> pallas::Base {
self.0
}
}
impl From<&SpendingKey> for NullifierDerivingKey {
fn from(sk: &SpendingKey) -> Self {
NullifierDerivingKey(to_base(PrfExpand::OrchardNk.expand(&sk.0)))
}
}
impl NullifierDerivingKey {
pub(crate) fn prf_nf(&self, rho: pallas::Base) -> pallas::Base {
prf_nf(self.0, rho)
}
pub(crate) fn to_bytes(self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
let nk_bytes = <[u8; 32]>::try_from(bytes).ok()?;
let nk = pallas::Base::from_repr(nk_bytes).map(NullifierDerivingKey);
if nk.is_some().into() {
Some(nk.unwrap())
} else {
None
}
}
}
#[derive(Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct CommitIvkRandomness(pallas::Scalar);
impl From<&SpendingKey> for CommitIvkRandomness {
fn from(sk: &SpendingKey) -> Self {
CommitIvkRandomness(to_scalar(PrfExpand::OrchardRivk.expand(&sk.0)))
}
}
impl CommitIvkRandomness {
pub(crate) fn inner(&self) -> pallas::Scalar {
self.0
}
pub(crate) fn to_bytes(self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
let rivk_bytes = <[u8; 32]>::try_from(bytes).ok()?;
let rivk = pallas::Scalar::from_repr(rivk_bytes).map(CommitIvkRandomness);
if rivk.is_some().into() {
Some(rivk.unwrap())
} else {
None
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Scope {
External,
Internal,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FullViewingKey {
ak: SpendValidatingKey,
nk: NullifierDerivingKey,
rivk: CommitIvkRandomness,
}
impl From<&SpendingKey> for FullViewingKey {
fn from(sk: &SpendingKey) -> Self {
FullViewingKey {
ak: (&SpendAuthorizingKey::from(sk)).into(),
nk: sk.into(),
rivk: sk.into(),
}
}
}
impl From<&ExtendedSpendingKey> for FullViewingKey {
fn from(extsk: &ExtendedSpendingKey) -> Self {
(&extsk.sk()).into()
}
}
impl From<FullViewingKey> for SpendValidatingKey {
fn from(fvk: FullViewingKey) -> Self {
fvk.ak
}
}
impl FullViewingKey {
pub(crate) fn nk(&self) -> &NullifierDerivingKey {
&self.nk
}
pub(crate) fn rivk(&self, scope: Scope) -> CommitIvkRandomness {
match scope {
Scope::External => self.rivk,
Scope::Internal => {
let k = self.rivk.0.to_repr();
let ak = self.ak.to_bytes();
let nk = self.nk.to_bytes();
CommitIvkRandomness(to_scalar(
PrfExpand::OrchardRivkInternal.with_ad_slices(&k, &[&ak, &nk]),
))
}
}
}
fn derive_dk_ovk(&self) -> (DiversifierKey, OutgoingViewingKey) {
let k = self.rivk.0.to_repr();
let b = [(&self.ak.0).into(), self.nk.0.to_repr()];
let r = PrfExpand::OrchardDkOvk.with_ad_slices(&k, &[&b[0][..], &b[1][..]]);
(
DiversifierKey(r[..32].try_into().unwrap()),
OutgoingViewingKey(r[32..].try_into().unwrap()),
)
}
pub fn address_at(&self, j: impl Into<DiversifierIndex>, scope: Scope) -> Address {
self.to_ivk(scope).address_at(j)
}
pub fn address(&self, d: Diversifier, scope: Scope) -> Address {
match scope {
Scope::External => KeyAgreementPrivateKey::from_fvk(self),
Scope::Internal => KeyAgreementPrivateKey::from_fvk(&self.derive_internal()),
}
.address(d)
}
pub fn scope_for_address(&self, address: &Address) -> Option<Scope> {
[Scope::External, Scope::Internal]
.into_iter()
.find(|scope| self.to_ivk(*scope).diversifier_index(address).is_some())
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.to_bytes())
}
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut data = [0u8; 96];
reader.read_exact(&mut data)?;
Self::from_bytes(&data).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Unable to deserialize a valid Orchard FullViewingKey from bytes".to_owned(),
)
})
}
pub fn to_bytes(&self) -> [u8; 96] {
let mut result = [0u8; 96];
result[0..32].copy_from_slice(&<[u8; 32]>::from(self.ak.0.clone()));
result[32..64].copy_from_slice(&self.nk.0.to_repr());
result[64..96].copy_from_slice(&self.rivk.0.to_repr());
result
}
pub fn from_bytes(bytes: &[u8; 96]) -> Option<Self> {
let ak = SpendValidatingKey::from_bytes(&bytes[..32])?;
let nk = NullifierDerivingKey::from_bytes(&bytes[32..64])?;
let rivk = CommitIvkRandomness::from_bytes(&bytes[64..])?;
let fvk = FullViewingKey { ak, nk, rivk };
let _: NonZeroPallasBase = Option::from(KeyAgreementPrivateKey::derive_inner(&fvk))?;
let _: NonZeroPallasBase =
Option::from(KeyAgreementPrivateKey::derive_inner(&fvk.derive_internal()))?;
Some(fvk)
}
fn derive_internal(&self) -> Self {
FullViewingKey {
ak: self.ak.clone(),
nk: self.nk,
rivk: self.rivk(Scope::Internal),
}
}
pub fn to_ivk(&self, scope: Scope) -> IncomingViewingKey {
match scope {
Scope::External => IncomingViewingKey::from_fvk(self),
Scope::Internal => IncomingViewingKey::from_fvk(&self.derive_internal()),
}
}
pub fn to_ovk(&self, scope: Scope) -> OutgoingViewingKey {
match scope {
Scope::External => OutgoingViewingKey::from_fvk(self),
Scope::Internal => OutgoingViewingKey::from_fvk(&self.derive_internal()),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct DiversifierKey([u8; 32]);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiversifierIndex([u8; 11]);
macro_rules! di_from {
($n:ident) => {
impl From<$n> for DiversifierIndex {
fn from(j: $n) -> Self {
let mut j_bytes = [0; 11];
j_bytes[..mem::size_of::<$n>()].copy_from_slice(&j.to_le_bytes());
DiversifierIndex(j_bytes)
}
}
};
}
di_from!(u32);
di_from!(u64);
di_from!(usize);
impl From<[u8; 11]> for DiversifierIndex {
fn from(j_bytes: [u8; 11]) -> Self {
DiversifierIndex(j_bytes)
}
}
impl DiversifierIndex {
pub fn to_bytes(&self) -> &[u8; 11] {
&self.0
}
}
impl DiversifierKey {
pub fn get(&self, j: impl Into<DiversifierIndex>) -> Diversifier {
let ff = FF1::<Aes256>::new(&self.0, 2).expect("valid radix");
let enc = ff
.encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.into().0[..]))
.unwrap();
Diversifier(enc.to_bytes_le().try_into().unwrap())
}
pub fn diversifier_index(&self, d: &Diversifier) -> DiversifierIndex {
let ff = FF1::<Aes256>::new(&self.0, 2).expect("valid radix");
let dec = ff
.decrypt(&[], &BinaryNumeralString::from_bytes_le(d.as_array()))
.unwrap();
DiversifierIndex::from(<[u8; 11]>::try_from(dec.to_bytes_le()).unwrap())
}
pub fn to_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn from_bytes(bytes: [u8; 32]) -> Self {
DiversifierKey(bytes)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Diversifier([u8; 11]);
impl Diversifier {
pub fn from_bytes(d: [u8; 11]) -> Self {
Diversifier(d)
}
pub fn as_array(&self) -> &[u8; 11] {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct KeyAgreementPrivateKey(NonZeroPallasScalar);
impl KeyAgreementPrivateKey {
fn from_fvk(fvk: &FullViewingKey) -> Self {
let ivk = KeyAgreementPrivateKey::derive_inner(fvk).unwrap();
KeyAgreementPrivateKey(ivk.into())
}
}
impl KeyAgreementPrivateKey {
fn derive_inner(fvk: &FullViewingKey) -> CtOption<NonZeroPallasBase> {
let ak = extract_p(&pallas::Point::from_bytes(&(&fvk.ak.0).into()).unwrap());
commit_ivk(&ak, &fvk.nk.0, &fvk.rivk.0)
.and_then(NonZeroPallasBase::from_base)
}
fn address(&self, d: Diversifier) -> Address {
let pk_d = DiversifiedTransmissionKey::derive_inner(self, &d);
Address::from_parts(d, pk_d)
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct IncomingViewingKey {
dk: DiversifierKey,
ivk: KeyAgreementPrivateKey,
}
impl IncomingViewingKey {
fn from_fvk(fvk: &FullViewingKey) -> Self {
IncomingViewingKey {
dk: fvk.derive_dk_ovk().0,
ivk: KeyAgreementPrivateKey::from_fvk(fvk),
}
}
}
impl IncomingViewingKey {
pub fn to_bytes(&self) -> [u8; 64] {
let mut result = [0u8; 64];
result[..32].copy_from_slice(self.dk.to_bytes());
result[32..].copy_from_slice(&self.ivk.0.to_repr());
result
}
pub fn from_bytes(bytes: &[u8; 64]) -> CtOption<Self> {
NonZeroPallasBase::from_bytes(bytes[32..].try_into().unwrap()).map(|ivk| {
IncomingViewingKey {
dk: DiversifierKey(bytes[..32].try_into().unwrap()),
ivk: KeyAgreementPrivateKey(ivk.into()),
}
})
}
pub fn diversifier_index(&self, addr: &Address) -> Option<DiversifierIndex> {
let j = self.dk.diversifier_index(&addr.diversifier());
if &self.address_at(j) == addr {
Some(j)
} else {
None
}
}
pub fn address_at(&self, j: impl Into<DiversifierIndex>) -> Address {
self.address(self.dk.get(j))
}
pub fn address(&self, d: Diversifier) -> Address {
self.ivk.address(d)
}
}
#[derive(Debug, Clone)]
pub struct OutgoingViewingKey([u8; 32]);
impl OutgoingViewingKey {
fn from_fvk(fvk: &FullViewingKey) -> Self {
fvk.derive_dk_ovk().1
}
}
impl From<[u8; 32]> for OutgoingViewingKey {
fn from(ovk: [u8; 32]) -> Self {
OutgoingViewingKey(ovk)
}
}
impl AsRef<[u8; 32]> for OutgoingViewingKey {
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DiversifiedTransmissionKey(NonIdentityPallasPoint);
impl DiversifiedTransmissionKey {
pub(crate) fn inner(&self) -> NonIdentityPallasPoint {
self.0
}
}
impl DiversifiedTransmissionKey {
pub(crate) fn derive(ivk: &IncomingViewingKey, d: &Diversifier) -> Self {
Self::derive_inner(&ivk.ivk, d)
}
fn derive_inner(ivk: &KeyAgreementPrivateKey, d: &Diversifier) -> Self {
let g_d = diversify_hash(d.as_array());
DiversifiedTransmissionKey(ka_orchard(&ivk.0, &g_d))
}
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
NonIdentityPallasPoint::from_bytes(bytes).map(DiversifiedTransmissionKey)
}
pub(crate) fn to_bytes(self) -> [u8; 32] {
self.0.to_bytes()
}
}
impl ConditionallySelectable for DiversifiedTransmissionKey {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
DiversifiedTransmissionKey(NonIdentityPallasPoint::conditional_select(
&a.0, &b.0, choice,
))
}
}
#[derive(Debug)]
pub struct EphemeralSecretKey(pub(crate) NonZeroPallasScalar);
impl ConstantTimeEq for EphemeralSecretKey {
fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.0.ct_eq(&other.0)
}
}
impl EphemeralSecretKey {
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
NonZeroPallasScalar::from_bytes(bytes).map(EphemeralSecretKey)
}
pub(crate) fn derive_public(&self, g_d: NonIdentityPallasPoint) -> EphemeralPublicKey {
EphemeralPublicKey(ka_orchard(&self.0, &g_d))
}
pub(crate) fn agree(&self, pk_d: &DiversifiedTransmissionKey) -> SharedSecret {
SharedSecret(ka_orchard(&self.0, &pk_d.0))
}
}
#[derive(Debug)]
pub struct EphemeralPublicKey(NonIdentityPallasPoint);
impl EphemeralPublicKey {
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
NonIdentityPallasPoint::from_bytes(bytes).map(EphemeralPublicKey)
}
pub(crate) fn to_bytes(&self) -> EphemeralKeyBytes {
EphemeralKeyBytes(self.0.to_bytes())
}
pub(crate) fn agree(&self, ivk: &IncomingViewingKey) -> SharedSecret {
SharedSecret(ka_orchard(&ivk.ivk.0, &self.0))
}
}
#[derive(Debug)]
pub struct SharedSecret(NonIdentityPallasPoint);
impl SharedSecret {
#[cfg(test)]
pub(crate) fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
pub(crate) fn batch_to_affine(
shared_secrets: Vec<Option<Self>>,
) -> impl Iterator<Item = Option<pallas::Affine>> {
let secrets: Vec<_> = shared_secrets
.iter()
.filter_map(|s| s.as_ref().map(|s| *(s.0)))
.collect();
let mut secrets_affine = vec![pallas::Affine::identity(); secrets.len()];
group::Curve::batch_normalize(&secrets, &mut secrets_affine);
let mut secrets_affine = secrets_affine.into_iter();
shared_secrets
.into_iter()
.map(move |s| s.and_then(|_| secrets_affine.next()))
}
pub(crate) fn kdf_orchard(self, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash {
Self::kdf_orchard_inner(self.0.to_affine(), ephemeral_key)
}
pub(crate) fn kdf_orchard_inner(
secret: pallas::Affine,
ephemeral_key: &EphemeralKeyBytes,
) -> Blake2bHash {
Params::new()
.hash_length(32)
.personal(KDF_ORCHARD_PERSONALIZATION)
.to_state()
.update(&secret.to_bytes())
.update(&ephemeral_key.0)
.finalize()
}
}
#[cfg(any(test, feature = "test-dependencies"))]
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
pub mod testing {
use proptest::prelude::*;
use super::{DiversifierIndex, DiversifierKey, EphemeralSecretKey, SpendingKey};
prop_compose! {
pub fn arb_spending_key()(
key in prop::array::uniform32(prop::num::u8::ANY)
.prop_map(SpendingKey::from_bytes)
.prop_filter(
"Values must correspond to valid Orchard spending keys.",
|opt| bool::from(opt.is_some())
)
) -> SpendingKey {
key.unwrap()
}
}
prop_compose! {
pub fn arb_esk()(
esk in prop::array::uniform32(prop::num::u8::ANY)
.prop_map(|b| EphemeralSecretKey::from_bytes(&b))
.prop_filter(
"Values must correspond to valid Orchard ephemeral secret keys.",
|opt| bool::from(opt.is_some())
)
) -> EphemeralSecretKey {
esk.unwrap()
}
}
prop_compose! {
pub(crate) fn arb_diversifier_key()(
dk_bytes in prop::array::uniform32(prop::num::u8::ANY)
) -> DiversifierKey {
DiversifierKey::from_bytes(dk_bytes)
}
}
prop_compose! {
pub fn arb_diversifier_index()(
d_bytes in prop::array::uniform11(prop::num::u8::ANY)
) -> DiversifierIndex {
DiversifierIndex::from(d_bytes)
}
}
}
#[cfg(test)]
mod tests {
use ff::PrimeField;
use proptest::prelude::*;
use super::{
testing::{arb_diversifier_index, arb_diversifier_key, arb_esk, arb_spending_key},
*,
};
use crate::{
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
value::NoteValue,
Note,
};
#[test]
fn spend_validating_key_from_bytes() {
assert!(SpendValidatingKey::from_bytes(&[0; 32]).is_none());
}
#[test]
fn parsers_reject_invalid() {
assert!(bool::from(
EphemeralSecretKey::from_bytes(&[0xff; 32]).is_none()
));
assert!(bool::from(
EphemeralPublicKey::from_bytes(&[0xff; 32]).is_none()
));
}
proptest! {
#[test]
fn key_agreement(
sk in arb_spending_key(),
esk in arb_esk(),
j in arb_diversifier_index(),
) {
let ivk = IncomingViewingKey::from_fvk(&(&sk).into());
let addr = ivk.address_at(j);
let epk = esk.derive_public(addr.g_d());
assert!(bool::from(
esk.agree(addr.pk_d()).0.ct_eq(&epk.agree(&ivk).0)
));
}
}
proptest! {
#[test]
fn diversifier_index(
dk in arb_diversifier_key(),
j in arb_diversifier_index(),
) {
let d = dk.get(j);
assert_eq!(j, dk.diversifier_index(&d));
}
}
#[test]
fn test_vectors() {
for tv in crate::test_vectors::keys::test_vectors() {
let sk = SpendingKey::from_bytes(tv.sk).unwrap();
let ask: SpendAuthorizingKey = (&sk).into();
assert_eq!(<[u8; 32]>::from(&ask.0), tv.ask);
let ak: SpendValidatingKey = (&ask).into();
assert_eq!(<[u8; 32]>::from(ak.0), tv.ak);
let nk: NullifierDerivingKey = (&sk).into();
assert_eq!(nk.0.to_repr(), tv.nk);
let rivk: CommitIvkRandomness = (&sk).into();
assert_eq!(rivk.0.to_repr(), tv.rivk);
let fvk: FullViewingKey = (&sk).into();
assert_eq!(<[u8; 32]>::from(&fvk.ak.0), tv.ak);
assert_eq!(fvk.nk().0.to_repr(), tv.nk);
assert_eq!(fvk.rivk.0.to_repr(), tv.rivk);
let external_ivk = fvk.to_ivk(Scope::External);
assert_eq!(external_ivk.ivk.0.to_repr(), tv.ivk);
let diversifier = Diversifier(tv.default_d);
let addr = fvk.address(diversifier, Scope::External);
assert_eq!(&addr.pk_d().to_bytes(), &tv.default_pk_d);
let rho = Nullifier::from_bytes(&tv.note_rho).unwrap();
let note = Note::from_parts(
addr,
NoteValue::from_raw(tv.note_v),
rho,
RandomSeed::from_bytes(tv.note_rseed, &rho).unwrap(),
)
.unwrap();
let cmx: ExtractedNoteCommitment = note.commitment().into();
assert_eq!(cmx.to_bytes(), tv.note_cmx);
assert_eq!(note.nullifier(&fvk).to_bytes(), tv.note_nf);
let internal_rivk = fvk.rivk(Scope::Internal);
assert_eq!(internal_rivk.0.to_repr(), tv.internal_rivk);
let internal_ivk = fvk.to_ivk(Scope::Internal);
assert_eq!(internal_ivk.ivk.0.to_repr(), tv.internal_ivk);
assert_eq!(internal_ivk.dk.0, tv.internal_dk);
let internal_ovk = fvk.to_ovk(Scope::Internal);
assert_eq!(internal_ovk.0, tv.internal_ovk);
}
}
}