From a0424984c6bb65ad900abcee913f7e1c92fd13df Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 30 Mar 2022 19:53:46 +0800 Subject: [PATCH 1/3] Add explicit scoping for viewing keys and addresses Co-authored-by: Jack Grigg Co-authored-by: Daira Hopwood --- CHANGELOG.md | 24 +++++++ benches/circuit.rs | 4 +- benches/note_decryption.rs | 9 +-- benches/small.rs | 6 +- src/address.rs | 8 +-- src/builder.rs | 30 ++++++--- src/circuit.rs | 2 +- src/keys.rs | 127 ++++++++++++++++++++++++++----------- src/note.rs | 4 +- tests/builder.rs | 6 +- 10 files changed, 155 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4477fc1..5e69141e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,30 @@ and this project adheres to Rust's notion of ## [Unreleased] ### Added +- `orchard::keys`: + - `Scope` enum, for distinguishing external and internal scopes for viewing + keys and addresses. + - `FullViewingKey::{to_ivk, to_ovk}`, which each take a `Scope` argument. + - `FullViewingKey::scope_for_address` + +### Changed +- `orchard::builder`: + - `Builder::add_spend` now requires that the `FullViewingKey` matches the + given `Note`, and handles any scoping itself (instead of requiring the + caller to pass the `FullViewingKey` for the correct scope). +- `orchard::keys`: + - `FullViewingKey::{address, address_at}` now each take a `Scope` argument. + +### Removed +- `orchard::keys`: + - `FullViewingKey::derive_internal` + - `impl From<&FullViewingKey> for IncomingViewingKey` (use + `FullViewingKey::to_ivk` instead). + - `impl From<&FullViewingKey> for OutgoingViewingKey` (use + `FullViewingKey::to_ovk` instead). + +## [0.1.0-beta.2] - 2022-03-22 +### Added - `orchard::keys`: - `DiversifierIndex::to_bytes` - `FullViewingKey::derive_internal` diff --git a/benches/circuit.rs b/benches/circuit.rs index e942aaca..3140a90a 100644 --- a/benches/circuit.rs +++ b/benches/circuit.rs @@ -10,7 +10,7 @@ use orchard::{ builder::Builder, bundle::Flags, circuit::{ProvingKey, VerifyingKey}, - keys::{FullViewingKey, SpendingKey}, + keys::{FullViewingKey, Scope, SpendingKey}, value::NoteValue, Anchor, Bundle, }; @@ -20,7 +20,7 @@ fn criterion_benchmark(c: &mut Criterion) { let rng = OsRng; let sk = SpendingKey::from_bytes([7; 32]).unwrap(); - let recipient = FullViewingKey::from(&sk).address_at(0u32); + let recipient = FullViewingKey::from(&sk).address_at(0u32, Scope::External); let vk = VerifyingKey::build(); let pk = ProvingKey::build(); diff --git a/benches/note_decryption.rs b/benches/note_decryption.rs index 5d2cda55..2e2975e5 100644 --- a/benches/note_decryption.rs +++ b/benches/note_decryption.rs @@ -5,7 +5,7 @@ use orchard::{ builder::Builder, bundle::Flags, circuit::ProvingKey, - keys::{FullViewingKey, IncomingViewingKey, SpendingKey}, + keys::{FullViewingKey, Scope, SpendingKey}, note_encryption::{CompactAction, OrchardDomain}, value::NoteValue, Anchor, Bundle, @@ -21,8 +21,8 @@ fn bench_note_decryption(c: &mut Criterion) { let pk = ProvingKey::build(); let fvk = FullViewingKey::from(&SpendingKey::from_bytes([7; 32]).unwrap()); - let valid_ivk = IncomingViewingKey::from(&fvk); - let recipient = fvk.address_at(0u32); + let valid_ivk = fvk.to_ivk(Scope::External); + let recipient = valid_ivk.address_at(0u32); // Compact actions don't have the full AEAD ciphertext, so ZIP 307 trial-decryption // relies on an invalid ivk resulting in random noise for which the note commitment @@ -40,7 +40,8 @@ fn bench_note_decryption(c: &mut Criterion) { .map(|i| { let mut sk = [0; 32]; sk[..4].copy_from_slice(&i.to_le_bytes()); - IncomingViewingKey::from(&FullViewingKey::from(&SpendingKey::from_bytes(sk).unwrap())) + let fvk = FullViewingKey::from(&SpendingKey::from_bytes(sk).unwrap()); + fvk.to_ivk(Scope::External) }) .collect(); diff --git a/benches/small.rs b/benches/small.rs index 2ea43f59..daeec5cb 100644 --- a/benches/small.rs +++ b/benches/small.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use orchard::keys::{FullViewingKey, SpendingKey}; +use orchard::keys::{FullViewingKey, Scope, SpendingKey}; fn key_derivation(c: &mut Criterion) { // Meaningless random spending key. @@ -12,7 +12,9 @@ fn key_derivation(c: &mut Criterion) { let fvk = FullViewingKey::from(&sk); c.bench_function("derive_fvk", |b| b.iter(|| FullViewingKey::from(&sk))); - c.bench_function("default_address", |b| b.iter(|| fvk.address_at(0u32))); + c.bench_function("default_address", |b| { + b.iter(|| fvk.address_at(0u32, Scope::External)) + }); } criterion_group!(benches, key_derivation); diff --git a/src/address.rs b/src/address.rs index 24a41b25..245021c6 100644 --- a/src/address.rs +++ b/src/address.rs @@ -12,10 +12,10 @@ use crate::{ /// # Examples /// /// ``` -/// use orchard::keys::{SpendingKey, FullViewingKey}; +/// use orchard::keys::{SpendingKey, FullViewingKey, Scope}; /// /// let sk = SpendingKey::from_bytes([7; 32]).unwrap(); -/// let address = FullViewingKey::from(&sk).address_at(0u32); +/// let address = FullViewingKey::from(&sk).address_at(0u32, Scope::External); /// ``` #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Address { @@ -73,7 +73,7 @@ pub mod testing { use crate::keys::{ testing::{arb_diversifier_index, arb_spending_key}, - FullViewingKey, + FullViewingKey, Scope, }; use super::Address; @@ -82,7 +82,7 @@ pub mod testing { /// Generates an arbitrary payment address. pub(crate) fn arb_address()(sk in arb_spending_key(), j in arb_diversifier_index()) -> Address { let fvk = FullViewingKey::from(&sk); - fvk.address_at(j) + fvk.address_at(j, Scope::External) } } } diff --git a/src/builder.rs b/src/builder.rs index 4a207c2b..5808c67a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -15,7 +15,8 @@ use crate::{ bundle::{Action, Authorization, Authorized, Bundle, Flags}, circuit::{Circuit, Instance, Proof, ProvingKey}, keys::{ - FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey, SpendingKey, + FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey, + SpendingKey, }, note::{Note, TransmittedNoteCiphertext}, note_encryption::OrchardNoteEncryption, @@ -55,6 +56,7 @@ impl From for Error { struct SpendInfo { dummy_sk: Option, fvk: FullViewingKey, + scope: Scope, note: Note, merkle_path: MerklePath, } @@ -70,6 +72,9 @@ impl SpendInfo { SpendInfo { dummy_sk: Some(sk), fvk, + // We use external scope to avoid unnecessary derivations, because the dummy + // note's spending key is random and thus scoping is irrelevant. + scope: Scope::External, note, merkle_path, } @@ -91,7 +96,7 @@ impl RecipientInfo { /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes fn dummy(rng: &mut impl RngCore) -> Self { let fvk: FullViewingKey = (&SpendingKey::random(rng)).into(); - let recipient = fvk.address_at(0u32); + let recipient = fvk.address_at(0u32, Scope::External); RecipientInfo { ovk: None, @@ -191,7 +196,7 @@ impl ActionInfo { alpha: Some(alpha), ak: Some(ak), nk: Some(*self.spend.fvk.nk()), - rivk: Some(*self.spend.fvk.rivk()), + rivk: Some(self.spend.fvk.rivk(self.spend.scope)), g_d_new_star: Some((*note.recipient().g_d()).to_bytes()), pk_d_new_star: Some(note.recipient().pk_d().to_bytes()), v_new: Some(note.value()), @@ -246,9 +251,15 @@ impl Builder { return Err("All anchors must be equal."); } + // Check if note is internal or external. + let scope = fvk + .scope_for_address(¬e.recipient()) + .ok_or("FullViewingKey does not correspond to the given note")?; + self.spends.push(SpendInfo { dummy_sk: None, fvk, + scope, note, merkle_path, }); @@ -591,10 +602,7 @@ pub mod testing { address::testing::arb_address, bundle::{Authorized, Bundle, Flags}, circuit::ProvingKey, - keys::{ - testing::arb_spending_key, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, - SpendingKey, - }, + keys::{testing::arb_spending_key, FullViewingKey, SpendAuthorizingKey, SpendingKey}, note::testing::arb_note, tree::{Anchor, MerkleHashOrchard, MerklePath}, value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE}, @@ -624,7 +632,6 @@ pub mod testing { /// Create a bundle from the set of arbitrary bundle inputs. fn into_bundle>(mut self) -> Bundle { let fvk = FullViewingKey::from(&self.sk); - let ovk = OutgoingViewingKey::from(&fvk); let flags = Flags::from_parts(true, true); let mut builder = Builder::new(flags, self.anchor); @@ -633,6 +640,9 @@ pub mod testing { } for (addr, value) in self.recipient_amounts.into_iter() { + let scope = fvk.scope_for_address(&addr).unwrap(); + let ovk = fvk.to_ovk(scope); + builder .add_recipient(Some(ovk.clone()), addr, value, None) .unwrap(); @@ -720,7 +730,7 @@ mod tests { bundle::{Authorized, Bundle, Flags}, circuit::ProvingKey, constants::MERKLE_DEPTH_ORCHARD, - keys::{FullViewingKey, SpendingKey}, + keys::{FullViewingKey, Scope, SpendingKey}, tree::EMPTY_ROOTS, value::NoteValue, }; @@ -732,7 +742,7 @@ mod tests { let sk = SpendingKey::random(&mut rng); let fvk = FullViewingKey::from(&sk); - let recipient = fvk.address_at(0u32); + let recipient = fvk.address_at(0u32, Scope::External); let mut builder = Builder::new( Flags::from_parts(true, true), diff --git a/src/circuit.rs b/src/circuit.rs index 39978bae..74aed99d 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -906,7 +906,7 @@ mod tests { let sender_address = spent_note.recipient(); let nk = *fvk.nk(); - let rivk = *fvk.rivk(); + let rivk = fvk.rivk(fvk.scope_for_address(&spent_note.recipient()).unwrap()); let nf_old = spent_note.nullifier(&fvk); let ak: SpendValidatingKey = fvk.into(); let alpha = pallas::Scalar::random(&mut rng); diff --git a/src/keys.rs b/src/keys.rs index 42ce0636..6877a218 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,5 +1,6 @@ //! Key structures for Orchard. +use std::array; use std::convert::{TryFrom, TryInto}; use std::io::{self, Read, Write}; use std::mem; @@ -277,6 +278,24 @@ impl CommitIvkRandomness { } } +/// The scope of a viewing key or address. +/// +/// A "scope" narrows the visibility or usage to a level below "full". +/// +/// Consistent usage of `Scope` enables the user to provide consistent views over a wallet +/// to other people. For example, a user can give an external [`IncomingViewingKey`] to a +/// merchant terminal, enabling it to only detect "real" transactions from customers and +/// not internal transactions from the wallet. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Scope { + /// A scope used for wallet-external operations, namely deriving addresses to give to + /// other users in order to receive funds. + External, + /// A scope used for wallet-internal operations, such as creating change notes, + /// auto-shielding, and note management. + Internal, +} + /// A key that provides the capability to view incoming and outgoing transactions. /// /// This key is useful anywhere you need to maintain accurate balance, but do not want the @@ -319,17 +338,19 @@ impl FullViewingKey { &self.nk } - pub(crate) fn rivk(&self) -> &CommitIvkRandomness { - &self.rivk - } - - pub(crate) fn rivk_internal(&self) -> CommitIvkRandomness { - 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]), - )) + /// Returns either `rivk` or `rivk_internal` based on `scope`. + 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]), + )) + } + } } /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. @@ -346,14 +367,25 @@ impl FullViewingKey { } /// Returns the payment address for this key at the given index. - pub fn address_at(&self, j: impl Into) -> Address { - IncomingViewingKey::from(self).address_at(j) + pub fn address_at(&self, j: impl Into, scope: Scope) -> Address { + self.to_ivk(scope).address_at(j) } /// Returns the payment address for this key corresponding to the given diversifier. - pub fn address(&self, d: Diversifier) -> Address { + pub fn address(&self, d: Diversifier, scope: Scope) -> Address { // Shortcut: we don't need to derive DiversifierKey. - KeyAgreementPrivateKey::from(self).address(d) + match scope { + Scope::External => KeyAgreementPrivateKey::from_fvk(self), + Scope::Internal => KeyAgreementPrivateKey::from_fvk(&self.derive_internal()), + } + .address(d) + } + + /// Returns the scope of the given address, or `None` if the address is not derived + /// from this full viewing key. + pub fn scope_for_address(&self, address: &Address) -> Option { + array::IntoIter::new([Scope::External, Scope::Internal]) + .find(|scope| self.to_ivk(*scope).diversifier_index(address).is_some()) } /// Serializes the full viewing key as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys] @@ -404,14 +436,31 @@ impl FullViewingKey { Some(FullViewingKey { ak, nk, rivk }) } - /// Derives an internal full viewing key from a full viewing key, as specified in [ZIP32][orchardinternalfullviewingkey] + /// Derives an internal full viewing key from a full viewing key, as specified in + /// [ZIP32][orchardinternalfullviewingkey]. Internal use only. /// /// [orchardinternalfullviewingkey]: https://zips.z.cash/zip-0032#orchard-internal-key-derivation - pub fn derive_internal(&self) -> Self { + fn derive_internal(&self) -> Self { FullViewingKey { ak: self.ak.clone(), nk: self.nk, - rivk: self.rivk_internal(), + rivk: self.rivk(Scope::Internal), + } + } + + /// Derives an `IncomingViewingKey` for this full viewing key. + pub fn to_ivk(&self, scope: Scope) -> IncomingViewingKey { + match scope { + Scope::External => IncomingViewingKey::from_fvk(self), + Scope::Internal => IncomingViewingKey::from_fvk(&self.derive_internal()), + } + } + + /// Derives an `OutgoingViewingKey` for this full viewing key. + pub fn to_ovk(&self, scope: Scope) -> OutgoingViewingKey { + match scope { + Scope::External => OutgoingViewingKey::from_fvk(self), + Scope::Internal => OutgoingViewingKey::from_fvk(&self.derive_internal()), } } } @@ -527,8 +576,13 @@ impl Diversifier { #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] struct KeyAgreementPrivateKey(NonZeroPallasScalar); -impl From<&FullViewingKey> for KeyAgreementPrivateKey { - fn from(fvk: &FullViewingKey) -> Self { +impl KeyAgreementPrivateKey { + /// Derives `KeyAgreementPrivateKey` from fvk. + /// + /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. + /// + /// [orchardkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents + fn from_fvk(fvk: &FullViewingKey) -> Self { // KeyAgreementPrivateKey cannot be constructed such that this unwrap would fail. let ivk = KeyAgreementPrivateKey::derive_inner(fvk).unwrap(); KeyAgreementPrivateKey(ivk.into()) @@ -588,11 +642,12 @@ pub struct IncomingViewingKey { ivk: KeyAgreementPrivateKey, } -impl From<&FullViewingKey> for IncomingViewingKey { - fn from(fvk: &FullViewingKey) -> Self { +impl IncomingViewingKey { + /// Helper method. + fn from_fvk(fvk: &FullViewingKey) -> Self { IncomingViewingKey { dk: fvk.derive_dk_ovk().0, - ivk: fvk.into(), + ivk: KeyAgreementPrivateKey::from_fvk(fvk), } } } @@ -653,8 +708,9 @@ impl IncomingViewingKey { #[derive(Debug, Clone)] pub struct OutgoingViewingKey([u8; 32]); -impl From<&FullViewingKey> for OutgoingViewingKey { - fn from(fvk: &FullViewingKey) -> Self { +impl OutgoingViewingKey { + /// Helper method. + fn from_fvk(fvk: &FullViewingKey) -> Self { fvk.derive_dk_ovk().1 } } @@ -934,7 +990,7 @@ mod tests { esk in arb_esk(), j in arb_diversifier_index(), ) { - let ivk = IncomingViewingKey::from(&(&sk).into()); + let ivk = IncomingViewingKey::from_fvk(&(&sk).into()); let addr = ivk.address_at(j); let epk = esk.derive_public(addr.g_d()); @@ -978,12 +1034,12 @@ mod tests { assert_eq!(fvk.nk().0.to_repr(), tv.nk); assert_eq!(fvk.rivk.0.to_repr(), tv.rivk); - let ivk: KeyAgreementPrivateKey = (&fvk).into(); - assert_eq!(ivk.0.to_repr(), tv.ivk); + 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); + 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(); @@ -999,17 +1055,14 @@ mod tests { assert_eq!(note.nullifier(&fvk).to_bytes(), tv.note_nf); - let internal_rivk = fvk.rivk_internal(); + let internal_rivk = fvk.rivk(Scope::Internal); assert_eq!(internal_rivk.0.to_repr(), tv.internal_rivk); - let internal_fvk = fvk.derive_internal(); - assert_eq!(internal_rivk, *internal_fvk.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_ivk: KeyAgreementPrivateKey = (&internal_fvk).into(); - assert_eq!(internal_ivk.0.to_repr(), tv.internal_ivk); - - let (internal_dk, internal_ovk) = internal_fvk.derive_dk_ovk(); - assert_eq!(internal_dk.0, tv.internal_dk); + let internal_ovk = fvk.to_ovk(Scope::Internal); assert_eq!(internal_ovk.0, tv.internal_ovk); } } diff --git a/src/note.rs b/src/note.rs index 75893eef..fa640935 100644 --- a/src/note.rs +++ b/src/note.rs @@ -5,7 +5,7 @@ use rand::RngCore; use subtle::CtOption; use crate::{ - keys::{EphemeralSecretKey, FullViewingKey, SpendingKey}, + keys::{EphemeralSecretKey, FullViewingKey, Scope, SpendingKey}, spec::{to_base, to_scalar, NonZeroPallasScalar, PrfExpand}, value::NoteValue, Address, @@ -155,7 +155,7 @@ impl Note { ) -> (SpendingKey, FullViewingKey, Self) { let sk = SpendingKey::random(rng); let fvk: FullViewingKey = (&sk).into(); - let recipient = fvk.address_at(0u32); + let recipient = fvk.address_at(0u32, Scope::External); let note = Note::new( recipient, diff --git a/tests/builder.rs b/tests/builder.rs index 11d1d1b9..c7496f9f 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -5,7 +5,7 @@ use orchard::{ builder::Builder, bundle::{Authorized, Flags}, circuit::{ProvingKey, VerifyingKey}, - keys::{FullViewingKey, IncomingViewingKey, SpendAuthorizingKey, SpendingKey}, + keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, note::ExtractedNoteCommitment, note_encryption::OrchardDomain, tree::{MerkleHashOrchard, MerklePath}, @@ -36,7 +36,7 @@ fn bundle_chain() { let sk = SpendingKey::from_bytes([0; 32]).unwrap(); let fvk = FullViewingKey::from(&sk); - let recipient = fvk.address_at(0u32); + let recipient = fvk.address_at(0u32, Scope::External); // Create a shielding bundle. let shielding_bundle: Bundle<_, i64> = { @@ -59,7 +59,7 @@ fn bundle_chain() { // Create a shielded bundle spending the previous output. let shielded_bundle: Bundle<_, i64> = { - let ivk = IncomingViewingKey::from(&fvk); + let ivk = fvk.to_ivk(Scope::External); let (note, _, _) = shielding_bundle .actions() .iter() From e550c3d536594395496c5466f67f12c2c1a19d41 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 30 Mar 2022 20:16:45 +0800 Subject: [PATCH 2/3] Check IVK derivations during FullViewingKey::from_bytes. Closes zcash/orchard#303 Co-authored-by: Jack Grigg Co-authored-by: Daira Hopwood --- src/keys.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index 6877a218..b1324a69 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -433,7 +433,12 @@ impl FullViewingKey { let nk = NullifierDerivingKey::from_bytes(&bytes[32..64])?; let rivk = CommitIvkRandomness::from_bytes(&bytes[64..])?; - Some(FullViewingKey { ak, nk, rivk }) + let fvk = FullViewingKey { ak, nk, rivk }; + + // If ivk is 0 or ⊥, this FVK is invalid. + let _: NonZeroPallasBase = Option::from(KeyAgreementPrivateKey::derive_inner(&fvk))?; + + Some(fvk) } /// Derives an internal full viewing key from a full viewing key, as specified in @@ -583,7 +588,7 @@ impl KeyAgreementPrivateKey { /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents fn from_fvk(fvk: &FullViewingKey) -> Self { - // KeyAgreementPrivateKey cannot be constructed such that this unwrap would fail. + // FullViewingKey cannot be constructed such that this unwrap would fail. let ivk = KeyAgreementPrivateKey::derive_inner(fvk).unwrap(); KeyAgreementPrivateKey(ivk.into()) } From eaa0cfdbf6a52a023752ac8e29d10308318424a0 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 30 Mar 2022 20:20:55 +0800 Subject: [PATCH 3/3] Check that the internal IVK can be derived from a spending key Co-authored-by: Jack Grigg Co-authored-by: Daira Hopwood --- src/keys.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index b1324a69..c361d07f 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -73,9 +73,14 @@ impl SpendingKey { // whether ask = 0; the adjustment to potentially negate ask is not // needed. Also, `from` would panic on ask = 0. let ask = SpendAuthorizingKey::derive_inner(&sk); - // If ivk = ⊥, discard this key. - let ivk = KeyAgreementPrivateKey::derive_inner(&(&sk).into()); - CtOption::new(sk, !(ask.is_zero() | ivk.is_none())) + // If ivk is 0 or ⊥, discard this key. + 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()), + ) } /// Returns the raw bytes of the spending key. @@ -435,8 +440,10 @@ impl FullViewingKey { let fvk = FullViewingKey { ak, nk, rivk }; - // If ivk is 0 or ⊥, this FVK is invalid. + // If either ivk is 0 or ⊥, this FVK is invalid. let _: NonZeroPallasBase = Option::from(KeyAgreementPrivateKey::derive_inner(&fvk))?; + let _: NonZeroPallasBase = + Option::from(KeyAgreementPrivateKey::derive_inner(&fvk.derive_internal()))?; Some(fvk) }