Skip to content

Commit

Permalink
Add DiversifiableIncomingViewingKey
Browse files Browse the repository at this point in the history
  • Loading branch information
nuttycom committed Mar 8, 2024
1 parent 953d282 commit d6cf40a
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this library adheres to Rust's notion of

## [Unreleased]

## Added
- `sapling_crypto::zip32::DiversifiableIncomingViewingKey`
- `sapling_crypto::zip32::DiversifiableFullViewingKey::to_external_divk`

## [0.1.1] - 2024-02-15
### Fixed
- `sapling_crypto::builder::BundleType::num_outputs` now matches the previous
Expand Down
143 changes: 139 additions & 4 deletions src/zip32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,14 @@ impl DiversifiableFullViewingKey {
}
}

/// Derives the external diversifiable incoming viewing key corresponding to this full viewing key.
pub fn to_external_divk(&self) -> DiversifiableIncomingViewingKey {
DiversifiableIncomingViewingKey {
dk: self.dk.clone(),
ivk: self.to_ivk(Scope::External),
}
}

/// Derives an outgoing viewing key corresponding to this full viewing key.
pub fn to_ovk(&self, scope: Scope) -> OutgoingViewingKey {
match scope {
Expand All @@ -734,7 +742,7 @@ impl DiversifiableFullViewingKey {
/// Returns `None` if the diversifier index does not produce a valid diversifier for
/// this `DiversifiableFullViewingKey`.
pub fn address(&self, j: DiversifierIndex) -> Option<PaymentAddress> {
sapling_address(&self.fvk, &self.dk, j)
self.to_external_divk().address(j)
}

/// Finds the next valid payment address starting from the given diversifier index.
Expand All @@ -746,21 +754,21 @@ impl DiversifiableFullViewingKey {
/// address constructed using that diversifier, or `None` if the maximum index was
/// reached and no valid diversifier was found.
pub fn find_address(&self, j: DiversifierIndex) -> Option<(DiversifierIndex, PaymentAddress)> {
sapling_find_address(&self.fvk, &self.dk, j)
self.to_external_divk().find_address(j)
}

/// Returns the payment address corresponding to the smallest valid diversifier index,
/// along with that index.
pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) {
sapling_default_address(&self.fvk, &self.dk)
self.to_external_divk().default_address()
}

/// Returns the payment address corresponding to the specified diversifier, if any.
///
/// In general, it is preferable to use `find_address` instead, but this method is
/// useful in some cases for matching keys to existing payment addresses.
pub fn diversified_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
self.fvk.vk.to_payment_address(diversifier)
self.to_external_divk().diversified_address(diversifier)
}

/// Returns the internal address corresponding to the smallest valid diversifier index,
Expand Down Expand Up @@ -811,6 +819,133 @@ impl DiversifiableFullViewingKey {
}
}

/// A Sapling key that provides the capability to decrypt incoming notes and generate diversified
/// Sapling payment addresses.
///
/// This key is useful for detecting received funds and memos, but is not sufficient
/// for determining balance.
///
/// It comprises the subset of the ZIP 32 extended full viewing key that is used for the
/// Sapling item in a [ZIP 316 Unified Incoming Viewing Key][zip-0316-ufvk].
///
/// [zip-0316-ufvk]: https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys
#[derive(Clone, Debug)]
pub struct DiversifiableIncomingViewingKey {
dk: DiversifierKey,
ivk: SaplingIvk,
}

impl From<ExtendedFullViewingKey> for DiversifiableIncomingViewingKey {
fn from(extfvk: ExtendedFullViewingKey) -> Self {
extfvk
.to_diversifiable_full_viewing_key()
.to_external_divk()
}
}

impl From<&ExtendedFullViewingKey> for DiversifiableIncomingViewingKey {
fn from(extfvk: &ExtendedFullViewingKey) -> Self {
extfvk
.to_diversifiable_full_viewing_key()
.to_external_divk()
}
}

impl From<DiversifiableFullViewingKey> for DiversifiableIncomingViewingKey {
fn from(dfvk: DiversifiableFullViewingKey) -> Self {
dfvk.to_external_divk()
}
}

impl From<&DiversifiableFullViewingKey> for DiversifiableIncomingViewingKey {
fn from(dfvk: &DiversifiableFullViewingKey) -> Self {
dfvk.to_external_divk()
}
}

impl DiversifiableIncomingViewingKey {
/// Parses a `DiversifiableIncomingViewingKey` from its raw byte encoding.
///
/// Returns `None` if the bytes do not contain a valid encoding of a diversifiable
/// Sapling incoming viewing key.
pub fn from_bytes(bytes: &[u8; 64]) -> Option<Self> {
Some(DiversifiableIncomingViewingKey {
dk: DiversifierKey::from_bytes(bytes[..32].try_into().unwrap()),
ivk: SaplingIvk(Option::from(jubjub::Fr::from_bytes(
&bytes[32..].try_into().unwrap(),
))?),
})
}

/// Returns the raw encoding of this `DiversifiableIncomingViewingKey`.
pub fn to_bytes(&self) -> [u8; 64] {
let mut bytes = [0; 64];
bytes[..32].copy_from_slice(&self.dk.as_bytes()[..]);
bytes[32..].copy_from_slice(&self.ivk.0.to_bytes()[..]);
bytes
}

/// Returns the incoming viewing key component.
pub fn ivk(&self) -> &SaplingIvk {
&self.ivk
}

/// Attempts to produce a valid payment address for the given diversifier index.
///
/// Returns `None` if the diversifier index does not produce a valid diversifier for
/// this `DiversifiableIncomingViewingKey`.
pub fn address(&self, j: DiversifierIndex) -> Option<PaymentAddress> {
self.dk
.diversifier(j)
.and_then(|d_j| self.ivk.to_payment_address(d_j))
}

/// Finds the next valid payment address starting from the given diversifier index.
///
/// This searches the diversifier space starting at `j` and incrementing, to find an
/// index which will produce a valid diversifier (a 50% probability for each index).
///
/// Returns the index at which the valid diversifier was found along with the payment
/// address constructed using that diversifier, or `None` if the maximum index was
/// reached and no valid diversifier was found.
pub fn find_address(&self, j: DiversifierIndex) -> Option<(DiversifierIndex, PaymentAddress)> {
let (j, d_j) = self.dk.find_diversifier(j)?;
self.ivk().to_payment_address(d_j).map(|addr| (j, addr))
}

/// Returns the payment address corresponding to the smallest valid diversifier index,
/// along with that index.
pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) {
self.find_address(DiversifierIndex::new()).unwrap()
}

/// Returns the payment address corresponding to the specified diversifier, if any.
///
/// In general, it is preferable to use `find_address` instead, but this method is
/// useful in some cases for matching keys to existing payment addresses.
pub fn diversified_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
self.ivk.to_payment_address(diversifier)
}

/// Attempts to decrypt the given address's diversifier with this incoming viewing key.
///
/// This method extracts the diversifier from the given address and decrypts it as a
/// diversifier index, then verifies that this diversifier index produces the same
/// address. Decryption is attempted using both the internal and external parts of the
/// full viewing key.
///
/// Returns the decrypted diversifier index and its scope, or `None` if the address
/// was not generated from this key.
pub fn decrypt_diversifier(&self, addr: &PaymentAddress) -> Option<(DiversifierIndex, Scope)> {
let j_external = self.dk.diversifier_index(addr.diversifier());
if self.address(j_external).as_ref() == Some(addr) {
Some((j_external, Scope::External))
} else {
None
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit d6cf40a

Please sign in to comment.