From 77cf4c983111c0753ce9634ddc24c0d4d32abd5a Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 20 Jul 2021 13:37:07 -0600 Subject: [PATCH 01/12] Implement IncomingViewingKey::to_bytes --- src/keys.rs | 18 ++++++++++++++++++ src/spec.rs | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/src/keys.rs b/src/keys.rs index 5f71bedc..571b0dc6 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -305,6 +305,16 @@ impl DiversifierKey { .unwrap(); Diversifier(enc.to_bytes_le().try_into().unwrap()) } + + /// Return the raw bytes of the diversifier key + pub fn to_bytes(&self) -> &[u8; 32] { + &self.0 + } + + /// Construct a diversifier key from bytes + pub fn from_bytes(bytes: [u8; 32]) -> Self { + DiversifierKey(bytes) + } } /// A diversifier that can be used to derive a specific [`Address`] from a @@ -398,6 +408,14 @@ impl From<&FullViewingKey> for IncomingViewingKey { } impl IncomingViewingKey { + /// Serializes an Orchard incoming viewing key to its raw encoding + pub fn to_bytes(&self) -> [u8; 64] { + let mut result = [0u8; 64]; + result.copy_from_slice(self.dk.to_bytes()); + result[32..].copy_from_slice(&self.ivk.0.to_bytes()); + result + } + /// Parses an Orchard incoming viewing key from its raw encoding. pub fn from_bytes(bytes: &[u8; 64]) -> CtOption { NonZeroPallasBase::from_bytes(bytes[32..].try_into().unwrap()).map(|ivk| { diff --git a/src/spec.rs b/src/spec.rs index 1fae12b5..31cf1c95 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -73,6 +73,10 @@ impl NonZeroPallasBase { pallas::Base::from_bytes(bytes).and_then(NonZeroPallasBase::from_base) } + pub(crate) fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + pub(crate) fn from_base(b: pallas::Base) -> CtOption { CtOption::new(NonZeroPallasBase(b), !b.ct_is_zero()) } From e33cd4ade4bad343125347a053f94e4b38bd1f39 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 21 Jul 2021 14:31:37 -0600 Subject: [PATCH 02/12] Add trial decryption of actions to Bundle --- src/bundle.rs | 25 ++++++++++++++++++++++++- src/keys.rs | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index 890c3303..a9d4b4e3 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -7,11 +7,15 @@ use std::mem; use blake2b_simd::Hash as Blake2bHash; use nonempty::NonEmpty; +use zcash_note_encryption::try_note_decryption; use crate::{ + address::Address, bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data}, circuit::{Instance, Proof, VerifyingKey}, - note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext}, + keys::IncomingViewingKey, + note::{ExtractedNoteCommitment, Note, Nullifier, TransmittedNoteCiphertext}, + note_encryption::OrchardDomain, primitives::redpallas::{self, Binding, SpendAuth}, tree::Anchor, value::{ValueCommitTrapdoor, ValueCommitment, ValueSum}, @@ -354,6 +358,25 @@ impl Bundle { .map(|a| a.to_instance(self.flags, self.anchor)) .collect() } + + /// Perform trial decryption of each action in the bundle with each of the + /// specified incoming viewing keys, and return the decrypted note contents + /// along with the index of the action from which it was derived. + pub fn decrypt_outputs_for_keys( + &self, + keys: &[IncomingViewingKey], + ) -> Vec<(usize, Note, Address, [u8; 512])> { + self.actions + .iter() + .enumerate() + .filter_map(|(idx, action)| { + let domain = OrchardDomain::for_action(action); + keys.iter().find_map(move |ivk| { + try_note_decryption(&domain, ivk, action).map(|(n, a, m)| (idx, n, a, m)) + }) + }) + .collect() + } } impl> Bundle { diff --git a/src/keys.rs b/src/keys.rs index 571b0dc6..4e474168 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -311,7 +311,7 @@ impl DiversifierKey { &self.0 } - /// Construct a diversifier key from bytes + /// Construct a diversifier key from bytes pub fn from_bytes(bytes: [u8; 32]) -> Self { DiversifierKey(bytes) } From 1fd00e6236f182eafb5c7b40fc3a817062f74799 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 22 Jul 2021 16:59:02 -0600 Subject: [PATCH 03/12] Add raw address serialization and parsing. --- src/address.rs | 22 ++++++++++++++++++++++ src/keys.rs | 17 +++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/address.rs b/src/address.rs index 0cc052a2..4c99021e 100644 --- a/src/address.rs +++ b/src/address.rs @@ -1,3 +1,7 @@ +use std::convert::TryInto; + +use subtle::CtOption; + use crate::{ keys::{DiversifiedTransmissionKey, Diversifier}, spec::{diversify_hash, NonIdentityPallasPoint}, @@ -39,6 +43,24 @@ impl Address { pub(crate) fn pk_d(&self) -> &DiversifiedTransmissionKey { &self.pk_d } + + /// Serialize this address to its "raw" encoding as defined in + /// https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding + pub fn to_raw_address_bytes(&self) -> [u8; 43] { + let mut result = [0u8; 43]; + result[..11].copy_from_slice(self.d.as_array()); + result[11..].copy_from_slice(&self.pk_d.to_bytes()); + result + } + + /// Parse an address from its "raw" encoding as defined in + /// https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding + pub fn from_raw_address_bytes(bytes: &[u8; 43]) -> CtOption { + DiversifiedTransmissionKey::from_bytes(bytes[11..].try_into().unwrap()).map(|pk_d| { + let d = Diversifier::from_bytes(bytes[..11].try_into().unwrap()); + Self::from_parts(d, pk_d) + }) + } } /// Generators for property testing. diff --git a/src/keys.rs b/src/keys.rs index 4e474168..58b25a48 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -10,8 +10,7 @@ use group::{prime::PrimeCurveAffine, Curve, GroupEncoding}; use halo2::arithmetic::FieldExt; use pasta_curves::pallas; use rand::RngCore; -use subtle::ConstantTimeEq; -use subtle::CtOption; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use zcash_note_encryption::EphemeralKeyBytes; use crate::{ @@ -510,6 +509,20 @@ impl DiversifiedTransmissionKey { } } +impl Default for DiversifiedTransmissionKey { + fn default() -> Self { + DiversifiedTransmissionKey(NonIdentityPallasPoint::default()) + } +} + +impl ConditionallySelectable for DiversifiedTransmissionKey { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + DiversifiedTransmissionKey(NonIdentityPallasPoint::conditional_select( + &a.0, &b.0, choice, + )) + } +} + /// An ephemeral secret key used to encrypt an output note on-chain. /// /// `esk` is "ephemeral" in the sense that each secret key is only used once. In From 52f0f158efe776493b447fec0c73bd8dcb01ebe9 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 23 Jul 2021 14:09:30 -0600 Subject: [PATCH 04/12] Add serialization and parsing of full viewing keys. --- src/address.rs | 10 +++++--- src/keys.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/address.rs b/src/address.rs index 4c99021e..0f61ed26 100644 --- a/src/address.rs +++ b/src/address.rs @@ -44,8 +44,9 @@ impl Address { &self.pk_d } - /// Serialize this address to its "raw" encoding as defined in - /// https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding + /// Serializes this address to its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.2: Orchard Raw Payment Addresses][orchardpaymentaddrencoding] + /// + /// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding pub fn to_raw_address_bytes(&self) -> [u8; 43] { let mut result = [0u8; 43]; result[..11].copy_from_slice(self.d.as_array()); @@ -53,8 +54,9 @@ impl Address { result } - /// Parse an address from its "raw" encoding as defined in - /// https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding + /// Parse an address from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.2: Orchard Raw Payment Addresses][orchardpaymentaddrencoding] + /// + /// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding pub fn from_raw_address_bytes(bytes: &[u8; 43]) -> CtOption { DiversifiedTransmissionKey::from_bytes(bytes[11..].try_into().unwrap()).map(|pk_d| { let d = Diversifier::from_bytes(bytes[..11].try_into().unwrap()); diff --git a/src/keys.rs b/src/keys.rs index 58b25a48..152e3e62 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,6 +1,6 @@ //! Key structures for Orchard. -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; use std::mem; use aes::Aes256; @@ -15,7 +15,7 @@ use zcash_note_encryption::EphemeralKeyBytes; use crate::{ address::Address, - primitives::redpallas::{self, SpendAuth}, + primitives::redpallas::{self, SpendAuth, VerificationKey}, spec::{ commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar, NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand, @@ -137,6 +137,18 @@ impl SpendValidatingKey { pub fn randomize(&self, randomizer: &pallas::Scalar) -> redpallas::VerificationKey { self.0.randomize(randomizer) } + + /// Converts this spend validating key to its serialized form. + pub(crate) fn to_bytes(&self) -> [u8; 32] { + <[u8; 32]>::from(&self.0) + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> Option { + <[u8; 32]>::try_from(&bytes[..32]) + .ok() + .and_then(|b| >::try_from(b).ok()) + .map(SpendValidatingKey) + } } /// A key used to derive [`Nullifier`]s from [`Note`]s. @@ -165,6 +177,21 @@ impl NullifierDerivingKey { pub(crate) fn prf_nf(&self, rho: pallas::Base) -> pallas::Base { prf_nf(self.0, rho) } + + /// Converts this nullifier deriving key to its serialized form. + pub(crate) fn to_bytes(&self) -> [u8; 32] { + <[u8; 32]>::from(self.0) + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> Option { + let nk_bytes = <[u8; 32]>::try_from(&bytes[..32]).ok()?; + let nk = pallas::Base::from_bytes(&nk_bytes).map(NullifierDerivingKey); + if nk.is_some().into() { + Some(nk.unwrap()) + } else { + None + } + } } /// The randomness for $\mathsf{Commit}^\mathsf{ivk}$. @@ -185,6 +212,21 @@ impl CommitIvkRandomness { pub(crate) fn inner(&self) -> pallas::Scalar { self.0 } + + /// Converts this nullifier deriving key to its serialized form. + pub(crate) fn to_bytes(&self) -> [u8; 32] { + <[u8; 32]>::from(self.0) + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> Option { + let rivk_bytes = <[u8; 32]>::try_from(&bytes[..32]).ok()?; + let rivk = pallas::Scalar::from_bytes(&rivk_bytes).map(CommitIvkRandomness); + if rivk.is_some().into() { + Some(rivk.unwrap()) + } else { + None + } + } } /// A key that provides the capability to view incoming and outgoing transactions. @@ -255,6 +297,28 @@ impl FullViewingKey { // Shortcut: we don't need to derive DiversifierKey. KeyAgreementPrivateKey::from(self).address(d) } + + /// Serializes the full viewing key as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys] + /// + /// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding + pub fn to_raw_fvk_bytes(&self) -> [u8; 96] { + let mut result = [0u8; 96]; + result[..32].copy_from_slice(&self.ak.to_bytes()); + result[32..64].copy_from_slice(&self.nk.to_bytes()); + result[64..].copy_from_slice(&<[u8; 32]>::from(&self.rivk.0)); + result + } + + /// Parses a full viewing key from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys] + /// + /// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding + pub fn from_raw_fvk_bytes(bytes: &[u8; 96]) -> Option { + let ak = SpendValidatingKey::from_bytes(&bytes[..32])?; + let nk = NullifierDerivingKey::from_bytes(&bytes[32..64])?; + let rivk = CommitIvkRandomness::from_bytes(&bytes[64..])?; + + Some(FullViewingKey { ak, nk, rivk }) + } } /// A key that provides the capability to derive a sequence of diversifiers. From 5d78ab3508dcd1f9a4ae730024126ba9b9f53ea2 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 26 Jul 2021 12:05:28 -0600 Subject: [PATCH 05/12] Add Eq and Ord implementations for Orchard keys. --- src/keys.rs | 20 +++++++++++--------- src/primitives/redpallas.rs | 15 +++++++++++++++ src/spec.rs | 2 +- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index 152e3e62..708a546f 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -29,7 +29,7 @@ const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF"; /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct SpendingKey([u8; 32]); impl SpendingKey { @@ -111,7 +111,7 @@ impl From<&SpendingKey> for SpendAuthorizingKey { /// $\mathsf{ak}$ but stored here as a RedPallas verification key. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialOrd, Ord)] pub struct SpendValidatingKey(redpallas::VerificationKey); impl From<&SpendAuthorizingKey> for SpendValidatingKey { @@ -132,6 +132,8 @@ impl PartialEq for SpendValidatingKey { } } +impl Eq for SpendValidatingKey {} + impl SpendValidatingKey { /// Randomizes this spend validating key with the given `randomizer`. pub fn randomize(&self, randomizer: &pallas::Scalar) -> redpallas::VerificationKey { @@ -158,7 +160,7 @@ impl SpendValidatingKey { /// [`Nullifier`]: crate::note::Nullifier /// [`Note`]: crate::note::Note /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Copy, Debug, Clone)] +#[derive(Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct NullifierDerivingKey(pallas::Base); impl NullifierDerivingKey { @@ -199,7 +201,7 @@ impl NullifierDerivingKey { /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Copy, Debug, Clone)] +#[derive(Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct CommitIvkRandomness(pallas::Scalar); impl From<&SpendingKey> for CommitIvkRandomness { @@ -237,7 +239,7 @@ impl CommitIvkRandomness { /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct FullViewingKey { ak: SpendValidatingKey, nk: NullifierDerivingKey, @@ -326,7 +328,7 @@ impl FullViewingKey { /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct DiversifierKey([u8; 32]); impl From<&FullViewingKey> for DiversifierKey { @@ -336,7 +338,7 @@ impl From<&FullViewingKey> for DiversifierKey { } /// The index for a particular diversifier. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct DiversifierIndex([u8; 11]); macro_rules! di_from { @@ -418,7 +420,7 @@ impl Diversifier { /// decryption of notes). When we actually want to serialize ivk, we're guaranteed to get /// a valid base field element encoding, because we always construct ivk from an integer /// in the correct range. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] struct KeyAgreementPrivateKey(NonZeroPallasScalar); impl From<&FullViewingKey> for KeyAgreementPrivateKey { @@ -455,7 +457,7 @@ impl KeyAgreementPrivateKey { /// Defined in [Zcash Protocol Spec § 5.6.4.3: Orchard Raw Incoming Viewing Keys][orchardinviewingkeyencoding]. /// /// [orchardinviewingkeyencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardinviewingkeyencoding -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct IncomingViewingKey { dk: DiversifierKey, ivk: KeyAgreementPrivateKey, diff --git a/src/primitives/redpallas.rs b/src/primitives/redpallas.rs index f367c0b8..38de97f9 100644 --- a/src/primitives/redpallas.rs +++ b/src/primitives/redpallas.rs @@ -1,5 +1,6 @@ //! A minimal RedPallas implementation for use in Zcash. +use std::cmp::{Ord, Ordering, PartialOrd}; use std::convert::{TryFrom, TryInto}; use pasta_curves::pallas; @@ -97,6 +98,20 @@ impl PartialEq for VerificationKey { } } +impl Eq for VerificationKey {} + +impl PartialOrd for VerificationKey { + fn partial_cmp(&self, other: &Self) -> Option { + <[u8; 32]>::from(self).partial_cmp(&<[u8; 32]>::from(other)) + } +} + +impl Ord for VerificationKey { + fn cmp(&self, other: &Self) -> Ordering { + <[u8; 32]>::from(self).cmp(&<[u8; 32]>::from(other)) + } +} + impl VerificationKey { /// Used in the note encryption tests. #[cfg(test)] diff --git a/src/spec.rs b/src/spec.rs index 31cf1c95..0ffd4cb2 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -93,7 +93,7 @@ impl NonZeroPallasBase { } /// An integer in [1..r_P]. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct NonZeroPallasScalar(pallas::Scalar); impl Default for NonZeroPallasScalar { From d8bf892c722a4d0de22d9d6bb1365c74501b414b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 27 Jul 2021 12:07:55 -0600 Subject: [PATCH 06/12] Return key used to decrypt an output along with decrypted note contents. --- src/bundle.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index a9d4b4e3..4fec9d6f 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -362,17 +362,17 @@ impl Bundle { /// Perform trial decryption of each action in the bundle with each of the /// specified incoming viewing keys, and return the decrypted note contents /// along with the index of the action from which it was derived. - pub fn decrypt_outputs_for_keys( + pub fn decrypt_outputs_for_keys<'a>( &self, - keys: &[IncomingViewingKey], - ) -> Vec<(usize, Note, Address, [u8; 512])> { + keys: &[&'a IncomingViewingKey], + ) -> Vec<(usize, &'a IncomingViewingKey, Note, Address, [u8; 512])> { self.actions .iter() .enumerate() .filter_map(|(idx, action)| { let domain = OrchardDomain::for_action(action); keys.iter().find_map(move |ivk| { - try_note_decryption(&domain, ivk, action).map(|(n, a, m)| (idx, n, a, m)) + try_note_decryption(&domain, ivk, action).map(|(n, a, m)| (idx, *ivk, n, a, m)) }) }) .collect() From c803114bf614d18538e99ced308dbaf7e12a6de2 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 30 Jul 2021 11:45:01 -0600 Subject: [PATCH 07/12] Go ahead and clone IVKs to limit borrowing hassles. --- src/bundle.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index 4fec9d6f..b92fdf5e 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -364,15 +364,15 @@ impl Bundle { /// along with the index of the action from which it was derived. pub fn decrypt_outputs_for_keys<'a>( &self, - keys: &[&'a IncomingViewingKey], - ) -> Vec<(usize, &'a IncomingViewingKey, Note, Address, [u8; 512])> { + keys: &[IncomingViewingKey], + ) -> Vec<(usize, IncomingViewingKey, Note, Address, [u8; 512])> { self.actions .iter() .enumerate() .filter_map(|(idx, action)| { let domain = OrchardDomain::for_action(action); keys.iter().find_map(move |ivk| { - try_note_decryption(&domain, ivk, action).map(|(n, a, m)| (idx, *ivk, n, a, m)) + try_note_decryption(&domain, ivk, action).map(|(n, a, m)| (idx, ivk.clone(), n, a, m)) }) }) .collect() From 872f337811fa2f2b34eff4e1f9c24e2abaa31483 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 2 Aug 2021 14:54:48 -0600 Subject: [PATCH 08/12] Expose SpendingKey byte representation. --- src/keys.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/keys.rs b/src/keys.rs index 708a546f..ca1c9774 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -64,6 +64,11 @@ impl SpendingKey { let ivk = KeyAgreementPrivateKey::derive_inner(&(&sk).into()); CtOption::new(sk, !(ask.ct_is_zero() | ivk.is_none())) } + + /// Returns the raw bytes of the spending key. + pub fn to_bytes(&self) -> &[u8; 32] { + &self.0 + } } /// A spend authorizing key, used to create spend authorization signatures. From c406461f6443fe1aeb0fc2aa2aac9719f9bdaee3 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 4 Aug 2021 13:11:20 -0600 Subject: [PATCH 09/12] Expose inner representation of NoteValue --- src/value.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/value.rs b/src/value.rs index d7ec5289..7152643d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -71,7 +71,8 @@ impl NoteValue { Default::default() } - pub(crate) fn inner(&self) -> u64 { + /// Returns the raw underlying value. + pub fn inner(&self) -> u64 { self.0 } From 43abadfb55b92dc818fadc7c1c0b93ac89d808ba Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 18 Aug 2021 19:00:21 -0600 Subject: [PATCH 10/12] Adds decryption for a specific index within a bundle. --- src/bundle.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index b92fdf5e..302c0fc6 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -362,7 +362,7 @@ impl Bundle { /// Perform trial decryption of each action in the bundle with each of the /// specified incoming viewing keys, and return the decrypted note contents /// along with the index of the action from which it was derived. - pub fn decrypt_outputs_for_keys<'a>( + pub fn decrypt_outputs_for_keys( &self, keys: &[IncomingViewingKey], ) -> Vec<(usize, IncomingViewingKey, Note, Address, [u8; 512])> { @@ -372,11 +372,25 @@ impl Bundle { .filter_map(|(idx, action)| { let domain = OrchardDomain::for_action(action); keys.iter().find_map(move |ivk| { - try_note_decryption(&domain, ivk, action).map(|(n, a, m)| (idx, ivk.clone(), n, a, m)) + try_note_decryption(&domain, ivk, action) + .map(|(n, a, m)| (idx, ivk.clone(), n, a, m)) }) }) .collect() } + + /// Perform trial decryption of each action at `action_idx` in the bundle with the + /// specified incoming viewing key, and return the decrypted note contents. + pub fn decrypt_output_with_key( + &self, + action_idx: usize, + key: &IncomingViewingKey, + ) -> Option<(Note, Address, [u8; 512])> { + self.actions.get(action_idx).and_then(move |action| { + let domain = OrchardDomain::for_action(action); + try_note_decryption(&domain, key, action) + }) + } } impl> Bundle { From 0449edd5b84588f78ed0d4011c6d3cef6e711e65 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 23 Aug 2021 11:21:52 -0600 Subject: [PATCH 11/12] Validate the sign of the y-coordinate for ak when deserializing. --- src/keys.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index ca1c9774..a7459d51 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -15,7 +15,7 @@ use zcash_note_encryption::EphemeralKeyBytes; use crate::{ address::Address, - primitives::redpallas::{self, SpendAuth, VerificationKey}, + primitives::redpallas::{self, SpendAuth}, spec::{ commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar, NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand, @@ -151,9 +151,16 @@ impl SpendValidatingKey { } pub(crate) fn from_bytes(bytes: &[u8]) -> Option { - <[u8; 32]>::try_from(&bytes[..32]) + <[u8; 32]>::try_from(bytes) .ok() - .and_then(|b| >::try_from(b).ok()) + .and_then(|b| + // check that the sign of the y-coordinate is positive + if b[31] & 0x80 == 0 { + >::try_from(b).ok() + } else { + None + } + ) .map(SpendValidatingKey) } } @@ -191,7 +198,7 @@ impl NullifierDerivingKey { } pub(crate) fn from_bytes(bytes: &[u8]) -> Option { - let nk_bytes = <[u8; 32]>::try_from(&bytes[..32]).ok()?; + let nk_bytes = <[u8; 32]>::try_from(bytes).ok()?; let nk = pallas::Base::from_bytes(&nk_bytes).map(NullifierDerivingKey); if nk.is_some().into() { Some(nk.unwrap()) @@ -226,7 +233,7 @@ impl CommitIvkRandomness { } pub(crate) fn from_bytes(bytes: &[u8]) -> Option { - let rivk_bytes = <[u8; 32]>::try_from(&bytes[..32]).ok()?; + let rivk_bytes = <[u8; 32]>::try_from(bytes).ok()?; let rivk = pallas::Scalar::from_bytes(&rivk_bytes).map(CommitIvkRandomness); if rivk.is_some().into() { Some(rivk.unwrap()) From 77be35591255de9e86bdfb8e2dcc5af739e4fd52 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 23 Aug 2021 11:25:29 -0600 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Daira Hopwood Co-authored-by: ying tong --- src/keys.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index a7459d51..477b175a 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -72,6 +72,7 @@ impl SpendingKey { } /// A spend authorizing key, used to create spend authorization signatures. +/// This type enforces that the corresponding public point (ak^ℙ) has ỹ = 0. /// /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// @@ -145,8 +146,11 @@ impl SpendValidatingKey { self.0.randomize(randomizer) } - /// Converts this spend validating key to its serialized form. + /// Converts this spend validating key to its serialized form, + /// I2LEOSP_256(ak). pub(crate) fn to_bytes(&self) -> [u8; 32] { + // This is correct because the wrapped point must have ỹ = 0, and + // so the point repr is the same as I2LEOSP of its x-coordinate. <[u8; 32]>::from(&self.0) } @@ -485,7 +489,9 @@ impl From<&FullViewingKey> for IncomingViewingKey { } impl IncomingViewingKey { - /// Serializes an Orchard incoming viewing key to its raw encoding + /// Serializes an Orchard incoming viewing key to its raw encoding as specified in [Zcash Protocol Spec § 5.6.4.3: Orchard Raw Incoming Viewing Keys][orchardrawinviewingkeys] + /// + /// [orchardrawinviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardinviewingkeyencoding pub fn to_bytes(&self) -> [u8; 64] { let mut result = [0u8; 64]; result.copy_from_slice(self.dk.to_bytes());