From 768eb90722ed7518df43e62e6790742a6c110e53 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 30 Jan 2024 15:32:36 -0500 Subject: [PATCH] add(chain): Adds `ViewingKey` type in zebra-chain (#8198) * adds/impls types in zebra-chain to be used by the zebra-scan RPC server * Adds methods for parsing and a test * adds test for key hash encoding * Apply suggestions from code review Co-authored-by: Marek * refactors viewing key type * refactors/renames for viewing keys * fixes doc links * Apply suggestions from code review * Apply suggestions from code review * removes ViewingKeyHash and ViewingKeyWithHash types * removes `to_bytes` methods * remove outdated method and call --------- Co-authored-by: Marek --- Cargo.lock | 1 + zebra-chain/Cargo.toml | 10 ++- zebra-chain/src/primitives.rs | 3 + zebra-chain/src/primitives/viewing_key.rs | 43 +++++++++ .../src/primitives/viewing_key/orchard.rs | 17 ++++ .../src/primitives/viewing_key/sapling.rs | 87 +++++++++++++++++++ .../src/primitives/viewing_key/tests.rs | 15 ++++ zebra-scan/src/scan.rs | 5 +- 8 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 zebra-chain/src/primitives/viewing_key.rs create mode 100644 zebra-chain/src/primitives/viewing_key/orchard.rs create mode 100644 zebra-chain/src/primitives/viewing_key/sapling.rs create mode 100644 zebra-chain/src/primitives/viewing_key/tests.rs diff --git a/Cargo.lock b/Cargo.lock index a69d46cda05..f6f429089fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5709,6 +5709,7 @@ dependencies = [ "uint", "x25519-dalek", "zcash_address", + "zcash_client_backend", "zcash_encoding", "zcash_history", "zcash_note_encryption", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index 78077df6d22..815d83443dc 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -34,6 +34,11 @@ getblocktemplate-rpcs = [ "zcash_address", ] +# Experimental shielded scanning support +shielded-scan = [ + "zcash_client_backend" +] + # Experimental internal miner support # TODO: Internal miner feature functionality was removed at https://github.com/ZcashFoundation/zebra/issues/8180 # See what was removed at https://github.com/ZcashFoundation/zebra/blob/v1.5.1/zebra-chain/Cargo.toml#L38-L43 @@ -127,9 +132,12 @@ serde_json = { version = "1.0.113", optional = true } # Production feature async-error and testing feature proptest-impl tokio = { version = "1.35.1", optional = true } -# Experimental feature getblocktemplate-rpcs +# Production feature getblocktemplate-rpcs zcash_address = { version = "0.3.1", optional = true } +# Experimental feature shielded-scan +zcash_client_backend = { version = "0.10.0-rc.1", optional = true } + # Optional testing dependencies proptest = { version = "1.4.0", optional = true } proptest-derive = { version = "0.4.0", optional = true } diff --git a/zebra-chain/src/primitives.rs b/zebra-chain/src/primitives.rs index d074463286e..f5b14f8cece 100644 --- a/zebra-chain/src/primitives.rs +++ b/zebra-chain/src/primitives.rs @@ -12,6 +12,9 @@ mod address; #[cfg(feature = "getblocktemplate-rpcs")] pub use address::Address; +#[cfg(feature = "shielded-scan")] +pub mod viewing_key; + pub mod byte_array; pub use ed25519_zebra as ed25519; diff --git a/zebra-chain/src/primitives/viewing_key.rs b/zebra-chain/src/primitives/viewing_key.rs new file mode 100644 index 00000000000..3a34d534dc6 --- /dev/null +++ b/zebra-chain/src/primitives/viewing_key.rs @@ -0,0 +1,43 @@ +//! Type definitions for viewing keys and their hashes. + +use crate::parameters::Network; + +mod orchard; +mod sapling; + +use orchard::OrchardViewingKey; +use sapling::SaplingViewingKey; + +#[cfg(test)] +mod tests; + +/// A Zcash Sapling or Orchard viewing key +#[derive(Debug, Clone)] +pub enum ViewingKey { + /// A viewing key for Sapling + Sapling(SaplingViewingKey), + + /// A viewing key for Orchard + Orchard(OrchardViewingKey), +} + +impl ViewingKey { + /// Accepts an encoded Sapling viewing key to decode + /// + /// Returns a [`ViewingKey`] if successful, or None otherwise + fn parse_sapling(sapling_key: &str, network: Network) -> Option { + SaplingViewingKey::parse(sapling_key, network).map(Self::Sapling) + } + + /// Accepts an encoded Orchard viewing key to decode + /// + /// Returns a [`ViewingKey`] if successful, or None otherwise + fn parse_orchard(sapling_key: &str, network: Network) -> Option { + OrchardViewingKey::parse(sapling_key, network).map(Self::Orchard) + } + + /// Parses an encoded viewing key and returns it as a [`ViewingKey`] type. + pub fn parse(key: &str, network: Network) -> Option { + Self::parse_sapling(key, network).or_else(|| Self::parse_orchard(key, network)) + } +} diff --git a/zebra-chain/src/primitives/viewing_key/orchard.rs b/zebra-chain/src/primitives/viewing_key/orchard.rs new file mode 100644 index 00000000000..ddb0664be2a --- /dev/null +++ b/zebra-chain/src/primitives/viewing_key/orchard.rs @@ -0,0 +1,17 @@ +//! Defines types and implements methods for parsing Orchard viewing keys and converting them to `zebra-chain` types + +use crate::parameters::Network; + +/// A Zcash Orchard viewing key +#[derive(Debug, Clone)] +pub enum OrchardViewingKey {} + +impl OrchardViewingKey { + /// Accepts an encoded Orchard viewing key to decode + /// + /// Returns a [`OrchardViewingKey`] if successful, or None otherwise + pub fn parse(_key: &str, _network: Network) -> Option { + // TODO: parse Orchard viewing keys + None + } +} diff --git a/zebra-chain/src/primitives/viewing_key/sapling.rs b/zebra-chain/src/primitives/viewing_key/sapling.rs new file mode 100644 index 00000000000..f56731d44c3 --- /dev/null +++ b/zebra-chain/src/primitives/viewing_key/sapling.rs @@ -0,0 +1,87 @@ +//! Defines types and implements methods for parsing Sapling viewing keys and converting them to `zebra-chain` types + +use zcash_client_backend::encoding::decode_extended_full_viewing_key; +use zcash_primitives::{ + constants::*, + sapling::keys::{FullViewingKey as SaplingFvk, SaplingIvk}, + zip32::DiversifiableFullViewingKey as SaplingDfvk, +}; + +use crate::parameters::Network; + +/// A Zcash Sapling viewing key +#[derive(Debug, Clone)] +pub enum SaplingViewingKey { + /// An incoming viewing key for Sapling + Ivk(Box), + + /// A full viewing key for Sapling + Fvk(Box), + + /// A diversifiable full viewing key for Sapling + Dfvk(Box), +} + +impl SaplingViewingKey { + /// Accepts an encoded Sapling extended full viewing key to decode + /// + /// Returns a [`SaplingViewingKey::Dfvk`] if successful, or None otherwise + fn parse_extended_full_viewing_key(sapling_key: &str, network: Network) -> Option { + decode_extended_full_viewing_key(network.sapling_efvk_hrp(), sapling_key) + // this should fail often, so a debug-level log is okay + .map_err(|err| debug!(?err, "could not decode Sapling extended full viewing key")) + .ok() + .map(|efvk| Box::new(efvk.to_diversifiable_full_viewing_key())) + .map(Self::Dfvk) + } + + /// Accepts an encoded Sapling diversifiable full viewing key to decode + /// + /// Returns a [`SaplingViewingKey::Dfvk`] if successful, or None otherwise + fn parse_diversifiable_full_viewing_key(_sapling_key: &str, _network: Network) -> Option { + // TODO: Parse Sapling diversifiable full viewing key + None + } + + /// Accepts an encoded Sapling full viewing key to decode + /// + /// Returns a [`SaplingViewingKey::Fvk`] if successful, or None otherwise + fn parse_full_viewing_key(_sapling_key: &str, _network: Network) -> Option { + // TODO: Parse Sapling full viewing key + None + } + + /// Accepts an encoded Sapling incoming viewing key to decode + /// + /// Returns a [`SaplingViewingKey::Ivk`] if successful, or None otherwise + fn parse_incoming_viewing_key(_sapling_key: &str, _network: Network) -> Option { + // TODO: Parse Sapling incoming viewing key + None + } + + /// Accepts an encoded Sapling viewing key to decode + /// + /// Returns a [`SaplingViewingKey`] if successful, or None otherwise + pub(super) fn parse(key: &str, network: Network) -> Option { + // TODO: Try types with prefixes first if some don't have prefixes? + Self::parse_extended_full_viewing_key(key, network) + .or_else(|| Self::parse_diversifiable_full_viewing_key(key, network)) + .or_else(|| Self::parse_full_viewing_key(key, network)) + .or_else(|| Self::parse_incoming_viewing_key(key, network)) + } +} + +impl Network { + /// Returns the human-readable prefix for an Zcash Sapling extended full viewing key + /// for this network. + fn sapling_efvk_hrp(&self) -> &'static str { + if self.is_a_test_network() { + // Assume custom testnets have the same HRP + // + // TODO: add the regtest HRP here + testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY + } else { + mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY + } + } +} diff --git a/zebra-chain/src/primitives/viewing_key/tests.rs b/zebra-chain/src/primitives/viewing_key/tests.rs new file mode 100644 index 00000000000..0b86c143131 --- /dev/null +++ b/zebra-chain/src/primitives/viewing_key/tests.rs @@ -0,0 +1,15 @@ +//! Tests for zebra-chain viewing key hashes + +use super::*; + +/// The extended Sapling viewing key of [ZECpages](https://zecpages.com/boardinfo) +pub const ZECPAGES_SAPLING_VIEWING_KEY: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz"; + +/// Tests that `ViewingKey::parse` successfully decodes the zecpages sapling extended full viewing key +#[test] +fn parses_sapling_efvk_correctly() { + let _init_guard = zebra_test::init(); + + ViewingKey::parse(ZECPAGES_SAPLING_VIEWING_KEY, Network::Mainnet) + .expect("should parse hard-coded viewing key successfully"); +} diff --git a/zebra-scan/src/scan.rs b/zebra-scan/src/scan.rs index 0c6c6352ff4..e326cc66a28 100644 --- a/zebra-scan/src/scan.rs +++ b/zebra-scan/src/scan.rs @@ -288,8 +288,9 @@ pub fn scan_block( /// Currently only accepts extended full viewing keys, and returns both their diversifiable full /// viewing key and their individual viewing key, for testing purposes. /// -/// TODO: work out what string format is used for SaplingIvk, if any, and support it here -/// performance: stop returning both the dfvk and ivk for the same key +// TODO: work out what string format is used for SaplingIvk, if any, and support it here +// performance: stop returning both the dfvk and ivk for the same key +// TODO: use `ViewingKey::parse` from zebra-chain instead pub fn sapling_key_to_scan_block_keys( sapling_key: &SaplingScanningKey, network: Network,