diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index 92df0fad8..0907e4202 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -13,6 +13,7 @@ edition = "2018" [dependencies] bech32 = "0.7" +bs58 = { version = "0.3", features = ["check"] } ff = { version = "0.6", path = "../ff" } pairing = { version = "0.16", path = "../pairing" } protobuf = "2" diff --git a/zcash_client_sqlite/src/address.rs b/zcash_client_sqlite/src/address.rs new file mode 100644 index 000000000..f72512fa8 --- /dev/null +++ b/zcash_client_sqlite/src/address.rs @@ -0,0 +1,63 @@ +//! Structs for handling supported address types. + +use pairing::bls12_381::Bls12; +use zcash_client_backend::encoding::{ + decode_payment_address, decode_transparent_address, encode_payment_address, + encode_transparent_address, +}; +use zcash_primitives::{legacy::TransparentAddress, primitives::PaymentAddress}; + +#[cfg(feature = "mainnet")] +use zcash_client_backend::constants::mainnet::{ + B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX, HRP_SAPLING_PAYMENT_ADDRESS, +}; + +#[cfg(not(feature = "mainnet"))] +use zcash_client_backend::constants::testnet::{ + B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX, HRP_SAPLING_PAYMENT_ADDRESS, +}; + +/// An address that funds can be sent to. +pub enum RecipientAddress { + Shielded(PaymentAddress), + Transparent(TransparentAddress), +} + +impl From> for RecipientAddress { + fn from(addr: PaymentAddress) -> Self { + RecipientAddress::Shielded(addr) + } +} + +impl From for RecipientAddress { + fn from(addr: TransparentAddress) -> Self { + RecipientAddress::Transparent(addr) + } +} + +impl RecipientAddress { + pub fn from_str(s: &str) -> Option { + if let Ok(Some(pa)) = decode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, s) { + Some(pa.into()) + } else if let Ok(Some(addr)) = + decode_transparent_address(&B58_PUBKEY_ADDRESS_PREFIX, &B58_SCRIPT_ADDRESS_PREFIX, s) + { + Some(addr.into()) + } else { + None + } + } + + pub fn to_string(&self) -> String { + match self { + RecipientAddress::Shielded(pa) => { + encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, pa) + } + RecipientAddress::Transparent(addr) => encode_transparent_address( + &B58_PUBKEY_ADDRESS_PREFIX, + &B58_SCRIPT_ADDRESS_PREFIX, + addr, + ), + } + } +} diff --git a/zcash_client_sqlite/src/error.rs b/zcash_client_sqlite/src/error.rs index b0dfb475a..ed33922b7 100644 --- a/zcash_client_sqlite/src/error.rs +++ b/zcash_client_sqlite/src/error.rs @@ -20,6 +20,7 @@ pub enum ErrorKind { ScanRequired, TableNotEmpty, Bech32(bech32::Error), + Base58(bs58::decode::Error), Builder(builder::Error), Database(rusqlite::Error), Io(std::io::Error), @@ -65,6 +66,7 @@ impl fmt::Display for Error { ErrorKind::ScanRequired => write!(f, "Must scan blocks first"), ErrorKind::TableNotEmpty => write!(f, "Table is not empty"), ErrorKind::Bech32(e) => write!(f, "{}", e), + ErrorKind::Base58(e) => write!(f, "{}", e), ErrorKind::Builder(e) => write!(f, "{:?}", e), ErrorKind::Database(e) => write!(f, "{}", e), ErrorKind::Io(e) => write!(f, "{}", e), @@ -93,6 +95,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: bs58::decode::Error) -> Self { + Error(ErrorKind::Base58(e)) + } +} + impl From for Error { fn from(e: builder::Error) -> Self { Error(ErrorKind::Builder(e)) diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 58fc2b1be..e8f16de81 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -39,6 +39,7 @@ use zcash_client_backend::constants::testnet::{ HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_PAYMENT_ADDRESS, }; +pub mod address; pub mod chain; pub mod error; pub mod init; diff --git a/zcash_client_sqlite/src/transact.rs b/zcash_client_sqlite/src/transact.rs index 931e2672a..155059bfa 100644 --- a/zcash_client_sqlite/src/transact.rs +++ b/zcash_client_sqlite/src/transact.rs @@ -5,13 +5,13 @@ use pairing::bls12_381::Bls12; use rusqlite::{types::ToSql, Connection, NO_PARAMS}; use std::convert::TryInto; use std::path::Path; -use zcash_client_backend::encoding::{encode_extended_full_viewing_key, encode_payment_address}; +use zcash_client_backend::encoding::encode_extended_full_viewing_key; use zcash_primitives::{ consensus, jubjub::fs::{Fs, FsRepr}, merkle_tree::{IncrementalWitness, MerklePath}, note_encryption::Memo, - primitives::{Diversifier, Note, PaymentAddress}, + primitives::{Diversifier, Note}, prover::TxProver, sapling::Node, transaction::{ @@ -23,9 +23,9 @@ use zcash_primitives::{ }; use crate::{ + address::RecipientAddress, error::{Error, ErrorKind}, get_target_and_anchor_heights, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, - HRP_SAPLING_PAYMENT_ADDRESS, }; struct SelectedNoteRow { @@ -63,7 +63,7 @@ struct SelectedNoteRow { /// /// let account = 0; /// let extsk = spending_key(&[0; 32][..], COIN_TYPE, account); -/// let to = extsk.default_address().unwrap().1; +/// let to = extsk.default_address().unwrap().1.into(); /// match create_to_address( /// "/path/to/data.db", /// consensus::BranchId::Sapling, @@ -82,7 +82,7 @@ pub fn create_to_address>( consensus_branch_id: consensus::BranchId, prover: impl TxProver, (account, extsk): (u32, &ExtendedSpendingKey), - to: &PaymentAddress, + to: &RecipientAddress, value: Amount, memo: Option, ) -> Result { @@ -228,7 +228,12 @@ pub fn create_to_address>( selected.merkle_path, )?; } - builder.add_sapling_output(ovk, to.clone(), value, memo.clone())?; + match to { + RecipientAddress::Shielded(to) => { + builder.add_sapling_output(ovk, to.clone(), value, memo.clone()) + } + RecipientAddress::Transparent(to) => builder.add_transparent_output(&to, value), + }?; let (tx, tx_metadata) = builder.build(consensus_branch_id, &prover)?; // We only called add_sapling_output() once. let output_index = match tx_metadata.output_index(0) { @@ -270,7 +275,8 @@ pub fn create_to_address>( } // Save the sent note in the database. - let to_str = encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, to); + // TODO: Decide how to save transparent output information. + let to_str = to.to_string(); if let Some(memo) = memo { let mut stmt_insert_sent_note = data.prepare( "INSERT INTO sent_notes (tx, output_index, from_account, address, value, memo) @@ -348,7 +354,7 @@ mod tests { ExtendedFullViewingKey::from(&extsk1), ]; init_accounts_table(&db_data, &extfvks).unwrap(); - let to = extsk0.default_address().unwrap().1; + let to = extsk0.default_address().unwrap().1.into(); // Invalid extsk for the given account should cause an error match create_to_address( @@ -387,7 +393,7 @@ mod tests { let extsk = ExtendedSpendingKey::master(&[]); let extfvks = [ExtendedFullViewingKey::from(&extsk)]; init_accounts_table(&db_data, &extfvks).unwrap(); - let to = extsk.default_address().unwrap().1; + let to = extsk.default_address().unwrap().1.into(); // We cannot do anything if we aren't synchronised match create_to_address( @@ -415,7 +421,7 @@ mod tests { let extsk = ExtendedSpendingKey::master(&[]); let extfvks = [ExtendedFullViewingKey::from(&extsk)]; init_accounts_table(&db_data, &extfvks).unwrap(); - let to = extsk.default_address().unwrap().1; + let to = extsk.default_address().unwrap().1.into(); // Account balance should be zero assert_eq!(get_balance(db_data, 0).unwrap(), Amount::zero()); @@ -484,7 +490,7 @@ mod tests { // Spend fails because there are insufficient verified notes let extsk2 = ExtendedSpendingKey::master(&[]); - let to = extsk2.default_address().unwrap().1; + let to = extsk2.default_address().unwrap().1.into(); match create_to_address( db_data, consensus::BranchId::Blossom, @@ -583,7 +589,7 @@ mod tests { // Send some of the funds to another address let extsk2 = ExtendedSpendingKey::master(&[]); - let to = extsk2.default_address().unwrap().1; + let to = extsk2.default_address().unwrap().1.into(); create_to_address( db_data, consensus::BranchId::Blossom,