Merge pull request #480 from zcash/transparent-ovk

[ZIP 316] Transparent internal and external ovk
This commit is contained in:
Kris Nuttycombe 2022-01-22 23:03:12 -07:00 committed by GitHub
commit cec128b8c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 133 additions and 51 deletions

View File

@ -11,6 +11,9 @@ use zcash_primitives::{
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::{sapling::keys::OutgoingViewingKey, transparent};
use crate::{
address::RecipientAddress,
data_api::{
@ -21,9 +24,6 @@ use crate::{
zip321::{Payment, TransactionRequest},
};
#[cfg(feature = "transparent-inputs")]
use crate::keys::transparent;
/// Scans a [`Transaction`] for any information that can be decrypted by the accounts in
/// the wallet, and saves it to the wallet.
pub fn decrypt_and_store_transaction<N, E, P, D>(
@ -400,13 +400,14 @@ where
.and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?;
// derive the t-address for the extpubkey at child index 0
let taddr = sk.to_external_pubkey().to_address();
let t_ext_pubkey = sk.to_external_pubkey();
let taddr = t_ext_pubkey.to_address();
let ovk = OutgoingViewingKey(t_ext_pubkey.internal_ovk().as_bytes());
// derive own shielded address from the provided extended spending key
// TODO: this should become the internal change address derived from
// the wallet's UFVK
let z_address = extfvk.default_address().1;
let ovk = extfvk.fvk.ovk;
// get UTXOs from DB
let utxos = wallet_db.get_unspent_transparent_outputs(&taddr, latest_anchor)?;

View File

@ -48,10 +48,12 @@ pub mod transparent {
use bs58::{self, decode::Error as Bs58Error};
use hdwallet::{ExtendedPrivKey, ExtendedPubKey, KeyIndex};
use secp256k1::key::SecretKey;
use sha2::{Digest, Sha256};
use crate::wallet::AccountId;
use zcash_primitives::{consensus, legacy::TransparentAddress};
use zcash_primitives::{
consensus,
transparent::{ExternalPrivKey, ExternalPubKey},
};
/// A type representing a BIP-44 private key at the account path level
/// `m/44'/<coin_type>'/<account>'
@ -128,48 +130,6 @@ pub mod transparent {
}
}
/// A type representing a private key at the BIP-44 external child
/// level `m/44'/<coin_type>'/<account>'/0/<child_index>
#[derive(Clone, Debug)]
pub struct ExternalPrivKey(ExtendedPrivKey);
impl ExternalPrivKey {
/// Returns the external public key corresponding to this private key
pub fn to_external_pubkey(&self) -> ExternalPubKey {
ExternalPubKey(ExtendedPubKey::from_private_key(&self.0))
}
/// Extracts the secp256k1 secret key component
pub fn secret_key(&self) -> &secp256k1::key::SecretKey {
&self.0.private_key
}
}
pub(crate) fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddress {
let mut hash160 = ripemd::Ripemd160::new();
hash160.update(Sha256::digest(pubkey.serialize()));
TransparentAddress::PublicKey(*hash160.finalize().as_ref())
}
/// A type representing a public key at the BIP-44 external child
/// level `m/44'/<coin_type>'/<account>'/0/<child_index>
#[derive(Clone, Debug)]
pub struct ExternalPubKey(ExtendedPubKey);
impl ExternalPubKey {
/// Returns the transparent address corresponding to
/// this public key.
pub fn to_address(&self) -> TransparentAddress {
pubkey_to_address(&self.0.public_key)
}
/// Returns the secp256k1::key::PublicKey component of
/// this public key.
pub fn public_key(&self) -> &secp256k1::key::PublicKey {
&self.0.public_key
}
}
/// Wallet Import Format encoded transparent private key.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Wif(pub String);
@ -383,7 +343,7 @@ mod tests {
let sk: SecretKey = (&sk_wif).to_secret_key(&MAIN_NETWORK).expect("invalid wif");
let secp = secp256k1::Secp256k1::new();
let pubkey = secp256k1::key::PublicKey::from_secret_key(&secp, &sk);
let taddr = transparent::pubkey_to_address(&pubkey).encode(&MAIN_NETWORK);
let taddr = zcash_primitives::transparent::pubkey_to_address(&pubkey).encode(&MAIN_NETWORK);
assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string());
}

View File

@ -23,6 +23,12 @@ use zcash_primitives::{
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct AccountId(pub u32);
impl From<u32> for AccountId {
fn from(id: u32) -> Self {
Self(id)
}
}
impl Default for AccountId {
fn default() -> Self {
AccountId(0)

View File

@ -23,6 +23,7 @@ rand_core = "0.6"
rusqlite = { version = "0.24", features = ["bundled", "time"] }
secp256k1 = { version = "0.20" }
time = "0.2"
zcash_address = { version = "0.0", path = "../components/zcash_address"}
zcash_client_backend = { version = "0.5", path = "../zcash_client_backend"}
zcash_primitives = { version = "0.5", path = "../zcash_primitives"}

View File

@ -28,6 +28,7 @@ equihash = { version = "0.1", path = "../components/equihash" }
ff = "0.11"
fpe = "0.5"
group = "0.11"
hdwallet = { version = "0.3.0", optional = true }
hex = "0.4"
incrementalmerkletree = "0.2"
jubjub = "0.8"
@ -42,6 +43,7 @@ ripemd160 = { version = "0.9", optional = true }
secp256k1 = { version = "0.20", optional = true }
sha2 = "0.9"
subtle = "2.2.3"
zcash_address = { version = "0.0", path = "../components/zcash_address" }
zcash_encoding = { version = "0.0", path = "../components/zcash_encoding" }
[dependencies.zcash_note_encryption]
@ -59,7 +61,7 @@ orchard = { version = "=0.1.0-beta.1", features = ["test-dependencies"] }
pprof = { version = "=0.6.1", features = ["criterion", "flamegraph"] }
[features]
transparent-inputs = ["ripemd160", "secp256k1"]
transparent-inputs = ["ripemd160", "hdwallet", "secp256k1"]
test-dependencies = ["proptest", "orchard/test-dependencies"]
zfuture = []

View File

@ -17,6 +17,8 @@ pub mod memo;
pub mod merkle_tree;
pub mod sapling;
pub mod transaction;
#[cfg(feature = "transparent-inputs")]
pub mod transparent;
pub mod zip32;
pub mod zip339;

View File

@ -0,0 +1,105 @@
use crate::{legacy::TransparentAddress, sapling::keys::prf_expand_vec};
use hdwallet::{traits::Deserialize, ExtendedPrivKey, ExtendedPubKey};
use sha2::{Digest, Sha256};
use std::convert::TryInto;
/// A type representing a private key at the BIP-44 external child
/// level `m/44'/<coin_type>'/<account>'/0/<child_index>
#[derive(Clone, Debug)]
pub struct ExternalPrivKey(pub ExtendedPrivKey);
impl ExternalPrivKey {
/// Returns the external public key corresponding to this private key
pub fn to_external_pubkey(&self) -> ExternalPubKey {
ExternalPubKey(ExtendedPubKey::from_private_key(&self.0))
}
/// Extracts the secp256k1 secret key component
pub fn secret_key(&self) -> &secp256k1::key::SecretKey {
&self.0.private_key
}
}
pub fn pubkey_to_address(pubkey: &secp256k1::key::PublicKey) -> TransparentAddress {
let mut hash160 = ripemd160::Ripemd160::new();
hash160.update(Sha256::digest(&pubkey.serialize()));
TransparentAddress::PublicKey(*hash160.finalize().as_ref())
}
/// A type representing a public key at the BIP-44 external child
/// level `m/44'/<coin_type>'/<account>'/0/<child_index>
#[derive(Clone, Debug)]
pub struct ExternalPubKey(pub ExtendedPubKey);
impl std::convert::TryFrom<&[u8]> for ExternalPubKey {
type Error = hdwallet::error::Error;
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
let ext_pub_key = ExtendedPubKey::deserialize(data)?;
Ok(Self(ext_pub_key))
}
}
impl ExternalPubKey {
/// Returns the transparent address corresponding to
/// this public key.
pub fn to_address(&self) -> TransparentAddress {
pubkey_to_address(&self.0.public_key)
}
/// Returns the secp256k1::key::PublicKey component of
/// this public key.
pub fn public_key(&self) -> &secp256k1::key::PublicKey {
&self.0.public_key
}
/// Returns the chain code component of this public key.
pub fn chain_code(&self) -> &[u8] {
&self.0.chain_code
}
/// Derives the internal ovk and external ovk corresponding to this
/// transparent fvk. As specified in [ZIP 316][transparent-ovk].
///
/// [transparent-ovk]: https://zips.z.cash/zip-0316#deriving-internal-keys
fn ovk_for_shielding(&self) -> (InternalOvk, ExternalOvk) {
let i_ovk = prf_expand_vec(
&self.chain_code(),
&[&[0xd0], &self.public_key().serialize()],
);
let i_ovk = i_ovk.as_bytes();
let ovk_internal = InternalOvk(i_ovk[..32].try_into().unwrap());
let ovk_external = ExternalOvk(i_ovk[32..].try_into().unwrap());
(ovk_internal, ovk_external)
}
/// Derives the internal ovk corresponding to this transparent fvk.
pub fn internal_ovk(&self) -> InternalOvk {
self.ovk_for_shielding().0
}
/// Derives the external ovk corresponding to this transparent fvk.
pub fn external_ovk(&self) -> ExternalOvk {
self.ovk_for_shielding().1
}
}
/// Internal ovk used for autoshielding.
pub struct InternalOvk([u8; 32]);
impl InternalOvk {
pub fn as_bytes(&self) -> [u8; 32] {
self.0
}
}
/// External ovk used by zcashd for transparent -> shielded spends to
/// external receivers.
pub struct ExternalOvk([u8; 32]);
impl ExternalOvk {
pub fn as_bytes(&self) -> [u8; 32] {
self.0
}
}

View File

@ -513,6 +513,11 @@ impl ExtendedFullViewingKey {
pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) {
sapling_default_address(&self.fvk, &self.dk)
}
/// Returns the chain code.
pub fn chain_code(&self) -> ChainCode {
self.chain_code
}
}
#[cfg(test)]