Merge pull request #128 from nuttycom/sapling_divk

Add `DiversifiableIncomingViewingKey`
This commit is contained in:
str4d 2024-03-08 19:49:29 +00:00 committed by GitHub
commit f2219462ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 113 additions and 4 deletions

View File

@ -7,6 +7,10 @@ and this library adheres to Rust's notion of
## [Unreleased]
## Added
- `sapling_crypto::zip32::IncomingViewingKey`
- `sapling_crypto::zip32::DiversifiableFullViewingKey::to_external_ivk`
## [0.1.1] - 2024-02-15
### Fixed
- `sapling_crypto::builder::BundleType::num_outputs` now matches the previous

View File

@ -8,6 +8,7 @@ use aes::Aes256;
use blake2b_simd::Params as Blake2bParams;
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
use fpe::ff1::{BinaryNumeralString, FF1};
use subtle::CtOption;
use zcash_spec::PrfExpand;
use zip32::{ChainCode, ChildIndex, DiversifierIndex, Scope};
@ -15,6 +16,7 @@ use std::io::{self, Read, Write};
use std::ops::AddAssign;
use super::{Diversifier, NullifierDerivingKey, PaymentAddress, ViewingKey};
use crate::note_encryption::PreparedIncomingViewingKey;
use crate::{
constants::PROOF_GENERATION_KEY_GENERATOR,
keys::{
@ -714,6 +716,7 @@ impl DiversifiableFullViewingKey {
}
/// Derives an incoming viewing key corresponding to this full viewing key.
// TODO: This should be renamed "prepare" and return a PreparedIncomingViewingKey
pub fn to_ivk(&self, scope: Scope) -> SaplingIvk {
match scope {
Scope::External => self.fvk.vk.ivk(),
@ -721,6 +724,14 @@ impl DiversifiableFullViewingKey {
}
}
/// Derives the external diversifiable incoming viewing key corresponding to this full viewing key.
pub fn to_external_ivk(&self) -> IncomingViewingKey {
IncomingViewingKey {
dk: self.dk,
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 {
@ -734,7 +745,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_ivk().address_at(j)
}
/// Finds the next valid payment address starting from the given diversifier index.
@ -746,13 +757,13 @@ 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_ivk().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_ivk().find_address(0u32).unwrap()
}
/// Returns the payment address corresponding to the specified diversifier, if any.
@ -760,7 +771,7 @@ impl DiversifiableFullViewingKey {
/// 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_ivk().address(diversifier)
}
/// Returns the internal address corresponding to the smallest valid diversifier index,
@ -811,6 +822,100 @@ 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 IncomingViewingKey {
dk: DiversifierKey,
ivk: SaplingIvk,
}
impl IncomingViewingKey {
/// Parses a `IncomingViewingKey` 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]) -> CtOption<Self> {
jubjub::Fr::from_bytes(&bytes[32..].try_into().unwrap()).map(|fr| IncomingViewingKey {
dk: DiversifierKey::from_bytes(bytes[..32].try_into().unwrap()),
ivk: SaplingIvk(fr),
})
}
/// Returns the raw encoding of this `IncomingViewingKey`.
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 prepared incoming viewing key for this IVK.
pub fn prepare(&self) -> PreparedIncomingViewingKey {
PreparedIncomingViewingKey::new(&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 `IncomingViewingKey`.
pub fn address_at(&self, j: impl Into<DiversifierIndex>) -> Option<PaymentAddress> {
self.dk
.diversifier(j.into())
.and_then(|d_j| self.ivk.to_payment_address(d_j))
}
/// 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 address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
self.ivk.to_payment_address(diversifier)
}
/// 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: impl Into<DiversifierIndex>,
) -> Option<(DiversifierIndex, PaymentAddress)> {
let (j, d_j) = self.dk.find_diversifier(j.into())?;
self.ivk.to_payment_address(d_j).map(|addr| (j, addr))
}
/// 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> {
let j = self.dk.diversifier_index(addr.diversifier());
if self.address_at(j).as_ref() == Some(addr) {
Some(j)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;