Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add(chain): Adds ViewingKey type in zebra-chain #8198

Merged
merged 12 commits into from
Jan 30, 2024
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5709,6 +5709,7 @@ dependencies = [
"uint",
"x25519-dalek",
"zcash_address",
"zcash_client_backend",
"zcash_encoding",
"zcash_history",
"zcash_note_encryption",
Expand Down
10 changes: 9 additions & 1 deletion zebra-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -127,9 +132,12 @@ serde_json = { version = "1.0.111", 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 }
Expand Down
3 changes: 3 additions & 0 deletions zebra-chain/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
51 changes: 51 additions & 0 deletions zebra-chain/src/primitives/viewing_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! 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 {
/// Returns an encoded byte representation of the viewing key
pub fn to_bytes(&self) -> Vec<u8> {
match self {
Self::Sapling(sapling_key) => sapling_key.to_bytes(),
Self::Orchard(_) => vec![], // TODO: add Orchard keys
}
}

/// 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))
}
}
17 changes: 17 additions & 0 deletions zebra-chain/src/primitives/viewing_key/orchard.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
122 changes: 122 additions & 0 deletions zebra-chain/src/primitives/viewing_key/sapling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//! Defines types and implements methods for parsing Sapling viewing keys and converting them to `zebra-chain` types

use group::GroupEncoding;
use jubjub::ExtendedPoint;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_primitives::{
constants::*,
sapling::keys::{FullViewingKey as SaplingFvk, SaplingIvk, ViewingKey as SaplingVk},
zip32::DiversifiableFullViewingKey as SaplingDfvk,
};

use crate::parameters::Network;

/// A Zcash Sapling viewing key
#[derive(Debug, Clone)]
pub enum SaplingViewingKey {
/// A viewing key for Sapling
Vk(Box<SaplingVk>),
arya2 marked this conversation as resolved.
Show resolved Hide resolved

/// 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>),
}

/// Accepts a Sapling viewing key, [`SaplingVk`]
///
/// Returns its byte representation.
fn viewing_key_to_bytes(SaplingVk { ak, nk }: &SaplingVk) -> Vec<u8> {
ExtendedPoint::from(*ak)
.to_bytes()
.into_iter()
.chain(ExtendedPoint::from(nk.0).to_bytes())
.collect()
}

impl SaplingViewingKey {
/// Returns an encoded byte representation of the Sapling viewing key
pub(super) fn to_bytes(&self) -> Vec<u8> {
match self {
Self::Vk(vk) => viewing_key_to_bytes(vk),
Self::Ivk(ivk) => ivk.to_repr().to_vec(),
Self::Fvk(fvk) => fvk.to_bytes().to_vec(),
Self::Dfvk(dfvk) => dfvk.to_bytes().to_vec(),
}
}

/// 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()
arya2 marked this conversation as resolved.
Show resolved Hide resolved
.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::Vk`] if successful, or None otherwise
fn parse_viewing_key(_sapling_key: &str, _network: Network) -> Option<Self> {
// TODO: Parse Sapling 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_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
}
}
}
15 changes: 15 additions & 0 deletions zebra-chain/src/primitives/viewing_key/tests.rs
Original file line number Diff line number Diff line change
@@ -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");
}
5 changes: 3 additions & 2 deletions zebra-scan/src/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading