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

Initialize wallet with unified spending key #459

Merged
merged 1 commit into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion zingolib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ orchard = { workspace = true }
subtle = "2.4.1"
incrementalmerkletree = { workspace = true, features = ["test-dependencies"] }
zcash_address = { workspace = true }
zcash_client_backend = { workspace = true }
zcash_client_backend = { workspace = true, features = ["unstable", "transparent-inputs"] }
zcash_encoding = { workspace = true }
zcash_note_encryption = { workspace = true }
zcash_primitives = { workspace = true }
Expand Down
14 changes: 14 additions & 0 deletions zingolib/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,10 @@ pub enum WalletBase {
SeedBytes([u8; 32]),
MnemonicPhrase(String),
Mnemonic(Mnemonic),
/// Unified full viewing key
Ufvk(String),
/// Unified spending key
Usk(Vec<u8>),
}
impl WalletBase {
pub fn from_string(base: String) -> WalletBase {
Expand Down Expand Up @@ -616,6 +619,17 @@ impl LightWallet {
})?;
(wc, None)
}
WalletBase::Usk(unified_spending_key) => {
let wc = WalletCapability::new_from_usk(unified_spending_key.as_slice()).map_err(
|e| {
Error::new(
ErrorKind::InvalidData,
format!("Error parsing unified spending key: {}", e),
)
},
)?;
(wc, None)
}
};

if let Err(e) = wc.new_address(wc.can_view()) {
Expand Down
37 changes: 36 additions & 1 deletion zingolib/src/wallet/keys/unified.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ use bip0039::Mnemonic;
use byteorder::{ReadBytesExt, WriteBytesExt};
use orchard::keys::Scope;

use secp256k1::SecretKey;
use zcash_address::unified::{Container, Encoding, Fvk, Ufvk};
use zcash_client_backend::address::UnifiedAddress;
use zcash_client_backend::keys::{Era, UnifiedSpendingKey};
use zcash_encoding::Vector;
use zcash_primitives::{
legacy::TransparentAddress, sapling::note_encryption::PreparedIncomingViewingKey,
Expand All @@ -22,7 +24,7 @@ use zingoconfig::ZingoConfig;
use crate::wallet::traits::ReadableWriteable;

use super::{
extended_transparent::{ExtendedPubKey, KeyIndex},
extended_transparent::{ExtendedPrivKey, ExtendedPubKey, KeyIndex},
get_zaddr_from_bip39seed, ToBase58Check,
};

Expand Down Expand Up @@ -352,6 +354,25 @@ impl WalletCapability {
Ok(Self::new_from_seed(config, &bip39_seed, position))
}

/// Creates a new `WalletCapability` from a unified spending key.
pub fn new_from_usk(usk: &[u8]) -> Result<Self, String> {
// Decode unified spending key
let usk = UnifiedSpendingKey::from_bytes(Era::Orchard, usk)
.map_err(|_| "Error decoding unified spending key.")?;

// Workaround https://github.com/zcash/librustzcash/issues/929 by serializing and deserializing the transparent key.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a bit of digging, and it turns out the TransparentPrivKey type being used here is actually a zingolib type, albeit a redefinition/copy-paste of the hdwallet type with a bunch of additional methods....I think it would make more sense to use the hdwallet type (either sacrifice a little bit of method call syntax and define local helpers instead of being method calls, or just define a local trait with the additional functionality in order to keep the method syntax) to avoid the surprising amount of duplicated code I just discovered, but that's not a blocking concern and definitely beyond the scope of this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where's the hdwallet type defined? Why do we have to copy paste?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC it was because the struct we might have reused carried private fields, and exposed them only in a limited way.
But as I think @AloeareV was alluding to, I may have even been looking at a similarly named, but incompatible type anyway, so maybe this was all necessary. I'd have to go back and check.

let transparent_bytes = usk.transparent().to_bytes();
let transparent_ext_key = transparent_key_from_bytes(transparent_bytes.as_slice())
.map_err(|e| format!("Error processing transparent key: {}", e))?;

Ok(Self {
orchard: Capability::Spend(usk.orchard().to_owned()),
sapling: Capability::Spend(usk.sapling().to_owned()),
transparent: Capability::Spend(transparent_ext_key),
..Default::default()
})
}

pub fn new_from_ufvk(config: &ZingoConfig, ufvk_encoded: String) -> Result<Self, String> {
// Decode UFVK
if ufvk_encoded.starts_with(config.hrp_sapling_viewing_key()) {
Expand Down Expand Up @@ -459,6 +480,20 @@ impl WalletCapability {
}
}

/// Reads a transparent ExtendedPrivKey from a buffer that has a 32 byte private key and 32 byte chain code.
fn transparent_key_from_bytes(bytes: &[u8]) -> Result<ExtendedPrivKey, std::io::Error> {
let mut reader = std::io::Cursor::new(bytes);

let private_key = SecretKey::read(&mut reader, ())?;
let mut chain_code = [0; 32];
reader.read_exact(&mut chain_code)?;

Ok(ExtendedPrivKey {
chain_code: chain_code.to_vec(),
private_key,
})
}

impl<V, S> ReadableWriteable<()> for Capability<V, S>
where
V: ReadableWriteable<()>,
Expand Down
Loading