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 <mail@marek.onl>

* 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 <mail@marek.onl>
This commit is contained in:
Arya 2024-01-30 15:32:36 -05:00 committed by GitHub
parent 18477ecbbe
commit 768eb90722
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 178 additions and 3 deletions

View File

@ -5709,6 +5709,7 @@ dependencies = [
"uint",
"x25519-dalek",
"zcash_address",
"zcash_client_backend",
"zcash_encoding",
"zcash_history",
"zcash_note_encryption",

View File

@ -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 }

View File

@ -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;

View File

@ -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<Self> {
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<Self> {
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> {
Self::parse_sapling(key, network).or_else(|| Self::parse_orchard(key, network))
}
}

View File

@ -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<Self> {
// TODO: parse Orchard viewing keys
None
}
}

View File

@ -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<SaplingIvk>),
/// A full viewing key for Sapling
Fvk(Box<SaplingFvk>),
/// A diversifiable full viewing key for Sapling
Dfvk(Box<SaplingDfvk>),
}
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<Self> {
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<Self> {
// 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<Self> {
// 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<Self> {
// 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<Self> {
// 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
}
}
}

View File

@ -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");
}

View File

@ -288,8 +288,9 @@ pub fn scan_block<K: ScanningKey>(
/// 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,