Merge pull request #480 from zcash/transparent-ovk
[ZIP 316] Transparent internal and external ovk
This commit is contained in:
commit
cec128b8c7
|
@ -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)?;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"}
|
||||
|
||||
|
|
|
@ -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 = []
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)]
|
||||
|
|
Loading…
Reference in New Issue