ZcashLightClientKit/rust/src/lib.rs

1419 lines
48 KiB
Rust

use failure::format_err;
use ffi_helpers::panic::catch_panic;
use std::ffi::{CStr, CString, OsStr};
use std::os::raw::c_char;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::slice;
use std::str::FromStr;
use zcash_client_backend::{
address::RecipientAddress,
data_api::{
chain::{scan_cached_blocks, validate_chain},
error::Error,
wallet::{create_spend_to_address, decrypt_and_store_transaction, shield_funds},
WalletRead,
},
encoding::{
AddressCodec,
decode_extended_full_viewing_key,
decode_extended_spending_key,
encode_extended_full_viewing_key,
encode_extended_spending_key,
encode_payment_address,
},
keys::{
derive_secret_key_from_seed,
derive_public_key_from_seed,
derive_transparent_address_from_public_key,
derive_transparent_address_from_secret_key,
spending_key, Wif,
},
wallet::{AccountId, OvkPolicy, WalletTransparentOutput},
};
use zcash_client_sqlite::{
error::SqliteClientError,
wallet::{
rewind_to_height,
get_rewind_height,
put_received_transparent_utxo, delete_utxos_above,
init::{init_accounts_table, init_blocks_table, init_wallet_db,}
},
BlockDb, NoteId, WalletDb,
};
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, BranchId, Network, Parameters},
memo::{Memo, MemoBytes},
transaction::{components::Amount, components::OutPoint, Transaction},
zip32::ExtendedFullViewingKey,
legacy::TransparentAddress,
};
use zcash_primitives::consensus::Network::{MainNetwork, TestNetwork};
use zcash_proofs::prover::LocalTxProver;
use std::convert::{TryFrom, TryInto};
use secp256k1::key::{SecretKey, PublicKey};
const ANCHOR_OFFSET: u32 = 10;
fn unwrap_exc_or<T>(exc: Result<T, ()>, def: T) -> T {
match exc {
Ok(value) => value,
Err(_) => def,
}
}
fn unwrap_exc_or_null<T>(exc: Result<T, ()>) -> T
where
T: ffi_helpers::Nullable,
{
match exc {
Ok(value) => value,
Err(_) => ffi_helpers::Nullable::NULL,
}
}
fn wallet_db(
db_data: *const u8,
db_data_len: usize,
network: Network,
) -> Result<WalletDb<Network>, failure::Error> {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
WalletDb::for_path(db_data, network)
.map_err(|e| format_err!("Error opening wallet database connection: {}", e))
}
fn block_db(cache_db: *const u8,
cache_db_len: usize) -> Result<BlockDb, failure::Error> {
let cache_db = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(cache_db, cache_db_len)
}));
BlockDb::for_path(cache_db)
.map_err(|e| format_err!("Error opening block source database connection: {}", e))
}
/// Returns the length of the last error message to be logged.
#[no_mangle]
pub extern "C" fn zcashlc_last_error_length() -> i32 {
ffi_helpers::error_handling::last_error_length()
}
/// Copies the last error message into the provided allocated buffer.
#[no_mangle]
pub unsafe extern "C" fn zcashlc_error_message_utf8(buf: *mut c_char, length: i32) -> i32 {
ffi_helpers::error_handling::error_message_utf8(buf, length)
}
/// Clears the record of the last error message.
#[no_mangle]
pub extern "C" fn zcashlc_clear_last_error() {
ffi_helpers::error_handling::clear_last_error()
}
/// Sets up the internal structure of the data database.
#[no_mangle]
pub extern "C" fn zcashlc_init_data_database(
db_data: *const u8,
db_data_len: usize,
network_id: u32,
) -> i32 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
WalletDb::for_path(db_data, network)
.map(|db| init_wallet_db(&db))
.map(|_| 1)
.map_err(|e| format_err!("Error while initializing data DB: {}", e))
});
unwrap_exc_or_null(res)
}
/// Initialises the data database with the given number of accounts using the given seed.
///
/// Returns the ExtendedSpendingKeys for the accounts. The caller should store these
/// securely for use while spending.
///
/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub extern "C" fn zcashlc_init_accounts_table(
db_data: *const u8,
db_data_len: usize,
seed: *const u8,
seed_len: usize,
accounts: i32,
capacity_ret: *mut usize,
network_id: u32,
) -> *mut *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let seed = unsafe { slice::from_raw_parts(seed, seed_len) };
let accounts = if accounts >= 0 {
accounts as u32
} else {
return Err(format_err!("accounts argument must be positive"));
};
let extsks: Vec<_> = (0..accounts)
.map(|account| spending_key(&seed, network.coin_type(), AccountId(account)))
.collect();
let extfvks: Vec<_> = extsks.iter().map(ExtendedFullViewingKey::from).collect();
let t_addreses: Vec<_> = (0..accounts)
.map(|account| {
let tsk = derive_secret_key_from_seed(&network, &seed, AccountId(account), 0).unwrap();
derive_transparent_address_from_secret_key(&tsk)
}).collect();
init_accounts_table(&db_data, &extfvks, &t_addreses)
.map(|_| {
// Return the ExtendedSpendingKeys for the created accounts.
let mut v: Vec<_> = extsks
.iter()
.map(|extsk| {
let encoded = encode_extended_spending_key(
network.hrp_sapling_extended_spending_key(),
extsk,
);
CString::new(encoded).unwrap().into_raw()
})
.collect();
assert!(v.len() == accounts as usize);
unsafe { *capacity_ret.as_mut().unwrap() = v.capacity() };
let p = v.as_mut_ptr();
std::mem::forget(v);
return p;
})
.map_err(|e| format_err!("Error while initializing accounts: {}", e))
});
unwrap_exc_or_null(res)
}
/// Initialises the data database with the given extended full viewing keys
/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub extern "C" fn zcashlc_init_accounts_table_with_keys(
db_data: *const u8,
db_data_len: usize,
uvks: *mut FFIUVKBoxedSlice,
network_id: u32,
) -> bool {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let s: Box<FFIUVKBoxedSlice> = unsafe { Box::from_raw(uvks) };
let slice: &mut [FFIUnifiedViewingKey] = unsafe { slice::from_raw_parts_mut(s.ptr, s.len) };
let mut extfvks: Vec<ExtendedFullViewingKey> = Vec::new();
let mut t_addreses: Vec<TransparentAddress> = Vec::new();
for u in slice.into_iter() {
let vkstr = unsafe { CStr::from_ptr(u.extfvk).to_str().unwrap() };
let extfvk = decode_extended_full_viewing_key(
network.hrp_sapling_extended_full_viewing_key(),
&vkstr,
)
.unwrap()
.unwrap();
extfvks.push(extfvk);
let extpub_str = unsafe { CStr::from_ptr(u.extpub).to_str().unwrap() };
let pubkey = PublicKey::from_str(&extpub_str).unwrap();
let t_addr = derive_transparent_address_from_public_key(&pubkey);
t_addreses.push(t_addr);
}
match init_accounts_table(&db_data, &extfvks, &t_addreses) {
Ok(()) => Ok(true),
Err(e) => Err(format_err!("Error while initializing accounts: {}", e)),
}
});
unwrap_exc_or(res, false)
}
/// Derives Extended Spending Keys from the given seed into 'accounts' number of accounts.
/// Returns the ExtendedSpendingKeys for the accounts. The caller should store these
/// securely for use while spending.
///
/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_spending_keys(
seed: *const u8,
seed_len: usize,
accounts: i32,
capacity_ret: *mut usize,
network_id: u32,
) -> *mut *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let seed = slice::from_raw_parts(seed, seed_len);
let accounts = if accounts > 0 {
accounts as u32
} else {
return Err(format_err!("accounts argument must be greater than zero"));
};
let extsks: Vec<_> = (0..accounts)
.map(|account| spending_key(&seed, network.coin_type(), AccountId(account)))
.collect();
// Return the ExtendedSpendingKeys for the created accounts.
let mut v: Vec<_> = extsks
.iter()
.map(|extsk| {
let encoded = encode_extended_spending_key(
network.hrp_sapling_extended_spending_key(),
extsk,
);
CString::new(encoded).unwrap().into_raw()
})
.collect();
assert!(v.len() == accounts as usize);
*capacity_ret.as_mut().unwrap() = v.capacity();
let p = v.as_mut_ptr();
std::mem::forget(v);
Ok(p)
});
unwrap_exc_or_null(res)
}
#[repr(C)]
pub struct FFIUnifiedViewingKey {
extfvk: *const c_char,
extpub: *const c_char,
}
#[repr(C)]
pub struct FFIUVKBoxedSlice {
ptr: *mut FFIUnifiedViewingKey,
len: usize, // number of elems
}
fn unified_viewing_key_new(
extfvk: &ExtendedFullViewingKey,
extpub: &PublicKey,
network: Network) -> FFIUnifiedViewingKey {
let encoded_extfvk = encode_extended_full_viewing_key(
network.hrp_sapling_extended_full_viewing_key(),
extfvk,
);
let encoded_pubkey = hex::encode(&extpub.serialize());
FFIUnifiedViewingKey {
extfvk: CString::new(encoded_extfvk).unwrap().into_raw(),
extpub: CString::new(encoded_pubkey).unwrap().into_raw()
}
}
fn uvk_vec_to_ffi (v: Vec<FFIUnifiedViewingKey>)
-> *mut FFIUVKBoxedSlice
{
// Going from Vec<_> to Box<[_]> just drops the (extra) `capacity`
let boxed_slice: Box<[FFIUnifiedViewingKey]> = v.into_boxed_slice();
let len = boxed_slice.len();
let fat_ptr: *mut [FFIUnifiedViewingKey] =
Box::into_raw(boxed_slice)
;
let slim_ptr: *mut FFIUnifiedViewingKey = fat_ptr as _;
Box::into_raw(
Box::new(FFIUVKBoxedSlice { ptr: slim_ptr, len })
)
}
#[no_mangle]
pub unsafe extern "C" fn zcashlc_free_uvk_array(uvks: *mut FFIUVKBoxedSlice)
{
if uvks.is_null() {
return;
}
let s: Box<FFIUVKBoxedSlice> = Box::from_raw(uvks);
let slice: &mut [FFIUnifiedViewingKey] = slice::from_raw_parts_mut(s.ptr, s.len);
drop(Box::from_raw(slice));
drop(s);
}
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_unified_viewing_keys_from_seed(
seed: *const u8,
seed_len: usize,
accounts: i32,
network_id: u32,
) -> *mut FFIUVKBoxedSlice {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let seed = slice::from_raw_parts(seed, seed_len);
let accounts = if accounts > 0 {
accounts as u32
} else {
return Err(format_err!("accounts argument must be greater than zero"));
};
let uvks: Vec<_> = (0..accounts)
.map(|account| {
let extfvk = ExtendedFullViewingKey::from(&spending_key(&seed, network.coin_type(), AccountId(account)));
let extpub = derive_public_key_from_seed(&network, &seed, AccountId(account), 0).unwrap();
unified_viewing_key_new(&extfvk, &extpub, network)
})
.collect();
Ok(uvk_vec_to_ffi(uvks))
});
unwrap_exc_or_null(res)
}
/// Derives Extended Full Viewing Keys from the given seed into 'accounts' number of accounts.
/// Returns the Extended Full Viewing Keys for the accounts. The caller should store these
/// securely
///
/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_keys(
seed: *const u8,
seed_len: usize,
accounts: i32,
capacity_ret: *mut usize,
network_id: u32,
) -> *mut *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let seed = slice::from_raw_parts(seed, seed_len);
let accounts = if accounts > 0 {
accounts as u32
} else {
return Err(format_err!("accounts argument must be greater than zero"));
};
let extsks: Vec<_> = (0..accounts)
.map(|account| {
ExtendedFullViewingKey::from(&spending_key(&seed, network.coin_type(), AccountId(account)))
})
.collect();
// Return the ExtendedSpendingKeys for the created accounts.
let mut v: Vec<_> = extsks
.iter()
.map(|extsk| {
let encoded = encode_extended_full_viewing_key(
network.hrp_sapling_extended_full_viewing_key(),
extsk,
);
CString::new(encoded).unwrap().into_raw()
})
.collect();
assert!(v.len() == accounts as usize);
*capacity_ret.as_mut().unwrap() = v.capacity();
let p = v.as_mut_ptr();
std::mem::forget(v);
Ok(p)
});
unwrap_exc_or_null(res)
}
/// derives a shielded address from the given seed.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_shielded_address_from_seed(
seed: *const u8,
seed_len: usize,
account_index: i32,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let seed = slice::from_raw_parts(seed, seed_len);
let account_index = if account_index >= 0 {
account_index as u32
} else {
return Err(format_err!("accounts argument must be greater than zero"));
};
let address = spending_key(&seed, network.coin_type(), AccountId(account_index))
.default_address()
.unwrap()
.1;
let address_str = encode_payment_address(network.hrp_sapling_payment_address(), &address);
Ok(CString::new(address_str).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// derives a shielded address from the given viewing key.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_transparent_address_from_public_key(
pubkey: *const c_char,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let public_key_str = CStr::from_ptr(pubkey).to_str()?;
let pk = PublicKey::from_str(&public_key_str)?;
let taddr =
derive_transparent_address_from_public_key(&pk)
.encode(&network);
Ok(CString::new(taddr).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// derives a shielded address from the given viewing key.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_shielded_address_from_viewing_key(
extfvk: *const c_char,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let extfvk_string = CStr::from_ptr(extfvk).to_str()?;
let extfvk = match decode_extended_full_viewing_key(
network.hrp_sapling_extended_full_viewing_key(),
&extfvk_string,
) {
Ok(Some(extfvk)) => extfvk,
Ok(None) => {
return Err(format_err!("Failed to parse viewing key string in order to derive the address. Deriving a viewing key from the string returned no results. Encoding was valid but type was incorrect."));
}
Err(e) => {
return Err(format_err!(
"Error while deriving viewing key from string input: {}",
e
));
}
};
let address = extfvk.default_address().unwrap().1;
let address_str = encode_payment_address(network.hrp_sapling_payment_address(), &address);
Ok(CString::new(address_str).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// derives a shielded address from the given extended full viewing key.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key(
extsk: *const c_char,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let extsk = CStr::from_ptr(extsk).to_str()?;
let extfvk = match decode_extended_spending_key(
network.hrp_sapling_extended_spending_key(),
&extsk,
) {
Ok(Some(extsk)) => ExtendedFullViewingKey::from(&extsk),
Ok(None) => {
return Err(format_err!("Deriving viewing key from spending key returned no results. Encoding was valid but type was incorrect."));
}
Err(e) => {
return Err(format_err!(
"Error while deriving viewing key from spending key: {}",
e
));
}
};
let encoded = encode_extended_full_viewing_key(
network.hrp_sapling_extended_full_viewing_key(),
&extfvk,
);
Ok(CString::new(encoded).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// Initialises the data database with the given block.
///
/// This enables a newly-created database to be immediately-usable, without needing to
/// synchronise historic blocks.
#[no_mangle]
pub extern "C" fn zcashlc_init_blocks_table(
db_data: *const u8,
db_data_len: usize,
height: i32,
hash_hex: *const c_char,
time: u32,
sapling_tree_hex: *const c_char,
network_id: u32,
) -> i32 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len,network)?;
let hash = {
let mut hash = hex::decode(unsafe { CStr::from_ptr(hash_hex) }.to_str()?).unwrap();
hash.reverse();
BlockHash::from_slice(&hash)
};
let sapling_tree =
hex::decode(unsafe { CStr::from_ptr(sapling_tree_hex) }.to_str()?).unwrap();
match init_blocks_table(
&db_data,
BlockHeight::from_u32(height as u32),
hash,
time,
&sapling_tree,
) {
Ok(()) => Ok(1),
Err(e) => Err(format_err!("Error while initializing blocks table: {}", e)),
}
});
unwrap_exc_or_null(res)
}
/// Returns the address for the account.
///
/// Call `zcashlc_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub extern "C" fn zcashlc_get_address(
db_data: *const u8,
db_data_len: usize,
account: i32,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("accounts argument must be positive"));
};
let account = AccountId(account);
match (&db_data).get_address(account) {
Ok(Some(addr)) => {
let addr_str = encode_payment_address(network.hrp_sapling_payment_address(), &addr);
let c_str_addr = CString::new(addr_str).unwrap();
Ok(c_str_addr.into_raw())
}
Ok(None) => Err(format_err!(
"No payment address was available for account {:?}",
account
)),
Err(e) => Err(format_err!("Error while fetching address: {}", e)),
}
});
unwrap_exc_or_null(res)
}
/// Returns true when the address is valid and shielded.
/// Returns false in any other case
/// Errors when the provided address belongs to another network
#[no_mangle]
pub unsafe extern "C" fn zcashlc_is_valid_shielded_address(address: *const c_char,
network_id: u32) -> bool {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let addr = CStr::from_ptr(address).to_str()?;
Ok(is_valid_shielded_address(&addr, &network))
});
unwrap_exc_or(res, false)
}
fn is_valid_shielded_address(address: &str,
network: &Network) -> bool {
match RecipientAddress::decode(network, &address) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => true,
RecipientAddress::Transparent(_) => false,
},
None => false,
}
}
/// Returns true when the address is valid and transparent.
/// Returns false in any other case
#[no_mangle]
pub unsafe extern "C" fn zcashlc_is_valid_transparent_address(address: *const c_char,
network_id: u32) -> bool {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let addr = CStr::from_ptr(address).to_str()?;
Ok(is_valid_transparent_address(&addr, &network))
});
unwrap_exc_or(res, false)
}
/// returns whether the given viewing key is valid or not
#[no_mangle]
pub unsafe extern "C" fn zcashlc_is_valid_viewing_key(key: *const c_char,
network_id: u32) -> bool {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let vkstr = CStr::from_ptr(key).to_str()?;
match decode_extended_full_viewing_key(&network.hrp_sapling_extended_full_viewing_key(), &vkstr) {
Ok(s) => match s {
None => Ok(false),
_ => Ok(true),
},
Err(_) => Ok(false),
}
});
unwrap_exc_or(res, false)
}
fn is_valid_transparent_address(address: &str,
network: &Network) -> bool {
match RecipientAddress::decode(network, &address) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => false,
RecipientAddress::Transparent(_) => true,
},
None => false,
}
}
/// Returns the balance for the account, including all unspent notes that we know about.
#[no_mangle]
pub extern "C" fn zcashlc_get_balance(
db_data: *const u8,
db_data_len: usize,
account: i32,
network_id: u32,
) -> i64 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
if account >= 0 {
let (_, max_height) = (&db_data)
.block_height_extrema()
.map_err(|e| format_err!("Error while fetching max block height: {}", e))
.and_then(|opt| {
opt.ok_or(format_err!(
"No blockchain information available; scan required."
))
})?;
(&db_data)
.get_balance_at(AccountId(account as u32), max_height)
.map(|b| b.into())
.map_err(|e| format_err!("Error while fetching balance: {}", e))
} else {
Err(format_err!("account argument must be positive"))
}
});
unwrap_exc_or(res, -1)
}
/// Returns the verified balance for the account, which ignores notes that have been
/// received too recently and are not yet deemed spendable.
#[no_mangle]
pub extern "C" fn zcashlc_get_verified_balance(
db_data: *const u8,
db_data_len: usize,
account: i32,
network_id: u32,
) -> i64 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
if account >= 0 {
(&db_data)
.get_target_and_anchor_heights()
.map_err(|e| format_err!("Error while fetching anchor height: {}", e))
.and_then(|opt_anchor| {
opt_anchor
.map(|(_, a)| a)
.ok_or(format_err!("Anchor height not available; scan required."))
})
.and_then(|anchor| {
(&db_data)
.get_balance_at(AccountId(account as u32), anchor)
.map_err(|e| format_err!("Error while fetching verified balance: {}", e))
})
.map(|amount| amount.into())
} else {
Err(format_err!("account argument must be positive"))
}
});
unwrap_exc_or(res, -1)
}
/// Returns the verified transparent balance for the address, which ignores utxos that have been
/// received too recently and are not yet deemed spendable.
#[no_mangle]
pub extern "C" fn zcashlc_get_verified_transparent_balance(
db_data: *const u8,
db_data_len: usize,
address: *const c_char,
network_id: u32,
) -> i64 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let addr = unsafe { CStr::from_ptr(address).to_str()? };
let taddr = TransparentAddress::decode(&network, &addr).unwrap();
let amount = (&db_data)
.get_target_and_anchor_heights()
.map_err(|e| format_err!("Error while fetching anchor height: {}", e))
.and_then(|opt_anchor| {
opt_anchor
.map(|(h, _)| h)
.ok_or(format_err!("height not available; scan required."))
})
.and_then(|anchor| {
(&db_data)
.get_unspent_transparent_utxos(&taddr, anchor)
.map_err(|e| format_err!("Error while fetching verified transparent balance: {}", e))
})?
.iter()
.map(|utxo| utxo.value)
.sum::<Amount>();
Ok(amount.into())
});
unwrap_exc_or(res, -1)
}
/// Returns the verified transparent balance for the address, which ignores utxos that have been
/// received too recently and are not yet deemed spendable.
#[no_mangle]
pub extern "C" fn zcashlc_get_total_transparent_balance(
db_data: *const u8,
db_data_len: usize,
address: *const c_char,
network_id: u32,
) -> i64 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let addr = unsafe { CStr::from_ptr(address).to_str()? };
let taddr = TransparentAddress::decode(&network, &addr).unwrap();
let amount = (&db_data)
.get_target_and_anchor_heights()
.map_err(|e| format_err!("Error while fetching anchor height: {}", e))
.and_then(|opt_anchor| {
opt_anchor
.map(|(h, _)| h)
.ok_or(format_err!("height not available; scan required."))
})
.and_then(|anchor| {
(&db_data)
.get_unspent_transparent_utxos(&taddr, anchor)
.map_err(|e| format_err!("Error while fetching total transparent balance: {}", e))
})?
.iter()
.map(|utxo| utxo.value)
.sum::<Amount>();
Ok(amount.into())
});
unwrap_exc_or(res, -1)
}
/// Returns the memo for a received note, if it is known and a valid UTF-8 string.
///
/// The note is identified by its row index in the `received_notes` table within the data
/// database.
///
/// Call `zcashlc_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub extern "C" fn zcashlc_get_received_memo_as_utf8(
db_data: *const u8,
db_data_len: usize,
id_note: i64,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let memo = (&db_data).get_memo(NoteId::ReceivedNoteId(id_note))
.map_err(|e| format_err!("An error occurred retrieving the memo, {}", e))
.and_then(|memo| {
match memo {
Memo::Empty => Ok("".to_string()),
Memo::Text(memo) => Ok(memo.into()),
_ => Err(format_err!("This memo does not contain UTF-8 text")),
}
})?;
Ok(CString::new(memo).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// Returns the memo for a sent note, if it is known and a valid UTF-8 string.
///
/// The note is identified by its row index in the `sent_notes` table within the data
/// database.
///
/// Call `zcashlc_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub extern "C" fn zcashlc_get_sent_memo_as_utf8(
db_data: *const u8,
db_data_len: usize,
id_note: i64,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let memo = (&db_data).get_memo(NoteId::SentNoteId(id_note))
.map_err(|e| format_err!("An error occurred retrieving the memo, {}", e))
.and_then(|memo| {
match memo {
Memo::Empty => Ok("".to_string()),
Memo::Text(memo) => Ok(memo.into()),
_ => Err(format_err!("This memo does not contain UTF-8 text")),
}
})?;
Ok(CString::new(memo).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// Checks that the scanned blocks in the data database, when combined with the recent
/// `CompactBlock`s in the cache database, form a valid chain.
///
/// This function is built on the core assumption that the information provided in the
/// cache database is more likely to be accurate than the previously-scanned information.
/// This follows from the design (and trust) assumption that the `lightwalletd` server
/// provides accurate block information as of the time it was requested.
///
/// Returns:
/// - `-1` if the combined chain is valid.
/// - `upper_bound` if the combined chain is invalid.
/// `upper_bound` is the height of the highest invalid block (on the assumption that the
/// highest block in the cache database is correct).
/// - `0` if there was an error during validation unrelated to chain validity.
///
/// This function does not mutate either of the databases.
#[no_mangle]
pub extern "C" fn zcashlc_validate_combined_chain(
db_cache: *const u8,
db_cache_len: usize,
db_data: *const u8,
db_data_len: usize,
network_id: u32,
) -> i32 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let block_db = block_db(db_cache, db_cache_len)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let validate_from = (&db_data)
.get_max_height_hash()
.map_err(|e| format_err!("Error while validating chain: {}", e))?;
let val_res = validate_chain(&network, &block_db, validate_from);
if let Err(e) = val_res {
match e {
SqliteClientError::BackendError(Error::InvalidChain(upper_bound, _)) => {
let upper_bound_u32 = u32::from(upper_bound);
Ok(upper_bound_u32 as i32)
}
_ => Err(format_err!("Error while validating chain: {}", e)),
}
} else {
// All blocks are valid, so "highest invalid block height" is below genesis.
Ok(-1)
}
});
unwrap_exc_or_null(res)
}
#[no_mangle]
pub extern "C" fn zcashlc_get_nearest_rewind_height(
db_data: *const u8,
db_data_len: usize,
height: i32,
network_id: u32,
) -> i32 {
let res = catch_panic(|| {
if height < 100 {
Ok(height)
} else {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let height = BlockHeight::try_from(height)?;
match get_rewind_height(&db_data) {
Ok(Some(best_height)) => {
let first_unspent_note_height = u32::from(best_height);
let rewind_height = u32::from(height);
Ok(std::cmp::min(first_unspent_note_height as i32, rewind_height as i32))
},
Ok(None) => {
let rewind_height = u32::from(height);
Ok(rewind_height as i32)
},
Err(e) => Err(format_err!("Error while getting nearest rewind height for {}: {}", height, e)),
}
}
});
unwrap_exc_or(res, -1)
}
/// Rewinds the data database to the given height.
///
/// If the requested height is greater than or equal to the height of the last scanned
/// block, this function does nothing.
#[no_mangle]
pub extern "C" fn zcashlc_rewind_to_height(
db_data: *const u8,
db_data_len: usize,
height: i32,
network_id: u32,
) -> bool {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let height = BlockHeight::try_from(height)?;
rewind_to_height(&db_data, height)
.map(|_| true)
.map_err(|e| format_err!("Error while rewinding data DB to height {}: {}", height, e))
});
unwrap_exc_or(res, false)
}
/// Scans new blocks added to the cache for any transactions received by the tracked
/// accounts.
///
/// This function pays attention only to cached blocks with heights greater than the
/// highest scanned block in `db_data`. Cached blocks with lower heights are not verified
/// against previously-scanned blocks. In particular, this function **assumes** that the
/// caller is handling rollbacks.
///
/// For brand-new light client databases, this function starts scanning from the Sapling
/// activation height. This height can be fast-forwarded to a more recent block by calling
/// [`zcashlc_init_blocks_table`] before this function.
///
/// Scanned blocks are required to be height-sequential. If a block is missing from the
/// cache, an error will be signalled.
#[no_mangle]
pub extern "C" fn zcashlc_scan_blocks(
db_cache: *const u8,
db_cache_len: usize,
db_data: *const u8,
db_data_len: usize,
scan_limit: u32,
network_id: u32,
) -> i32 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let block_db = block_db(db_cache, db_cache_len)?;
let db_read = wallet_db(db_data, db_data_len, network)?;
let mut db_data = db_read.get_update_ops()?;
let limit = if scan_limit <= 0 {
None
} else {
Some(scan_limit)
};
match scan_cached_blocks(&network, &block_db, &mut db_data, limit) {
Ok(()) => Ok(1),
Err(e) => Err(format_err!("Error while scanning blocks: {}", e)),
}
});
unwrap_exc_or_null(res)
}
#[no_mangle]
pub extern "C" fn zcashlc_put_utxo(
db_data: *const u8,
db_data_len: usize,
address_str: *const c_char,
txid_bytes: *const u8,
txid_bytes_len: usize,
index: i32,
script_bytes: *const u8,
script_bytes_len: usize,
value: i64,
height: i32,
network_id: u32,
) -> bool {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let mut db_data = db_data.get_update_ops()?;
let addr = unsafe { CStr::from_ptr(address_str).to_str()? };
let txid_bytes = unsafe { slice::from_raw_parts(txid_bytes, txid_bytes_len) };
let mut txid = [0u8; 32];
txid.copy_from_slice(&txid_bytes);
let script_bytes = unsafe { slice::from_raw_parts(script_bytes, script_bytes_len) };
let script = script_bytes.to_vec();
let address = TransparentAddress::decode(&network, &addr).unwrap();
let output = WalletTransparentOutput {
address: address,
outpoint: OutPoint::new(txid, index as u32),
script: script,
value: Amount::from_i64(value).unwrap(),
height: BlockHeight::from(height as u32),
};
match put_received_transparent_utxo(&mut db_data, &output) {
Ok(_) => Ok(true),
Err(e) => Err(format_err!("Error while inserting UTXO: {}", e)),
}
});
unwrap_exc_or(res, false)
}
#[no_mangle]
pub unsafe extern "C" fn zcashlc_clear_utxos(
db_data: *const u8,
db_data_len: usize,
taddress: *const c_char,
above_height: i32,
network_id: u32,
) -> i32 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let mut db_data = db_data.get_update_ops()?;
let addr = CStr::from_ptr(taddress).to_str()?;
let taddress = TransparentAddress::decode(&network, &addr).unwrap();
let height = BlockHeight::from(above_height as u32);
match delete_utxos_above(&mut db_data, &taddress, height) {
Ok(rows) => Ok(rows as i32),
Err(e) => Err(format_err!("Error while clearing UTXOs: {}", e)),
}
});
unwrap_exc_or(res, -1)
}
#[no_mangle]
pub extern "C" fn zcashlc_decrypt_and_store_transaction(
db_data: *const u8,
db_data_len: usize,
tx: *const u8,
tx_len: usize,
_mined_height: u32,
network_id: u32,
) -> i32 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_read = wallet_db(db_data, db_data_len, network)?;
let mut db_data = db_read.get_update_ops()?;
let tx_bytes = unsafe { slice::from_raw_parts(tx, tx_len) };
let tx = Transaction::read(&tx_bytes[..])?;
match decrypt_and_store_transaction(&network, &mut db_data, &tx) {
Ok(()) => Ok(1),
Err(e) => Err(format_err!("Error while decrypting transaction: {}", e)),
}
});
unwrap_exc_or(res, -1)
}
/// Creates a transaction paying the specified address from the given account.
///
/// Returns the row index of the newly-created transaction in the `transactions` table
/// within the data database. The caller can read the raw transaction bytes from the `raw`
/// column in order to broadcast the transaction to the network.
///
/// Do not call this multiple times in parallel, or you will generate transactions that
/// double-spend the same notes.
#[no_mangle]
pub extern "C" fn zcashlc_create_to_address(
db_data: *const u8,
db_data_len: usize,
account: i32,
extsk: *const c_char,
to: *const c_char,
value: i64,
memo: *const c_char,
spend_params: *const u8,
spend_params_len: usize,
output_params: *const u8,
output_params_len: usize,
network_id: u32,
) -> i64 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_read = wallet_db(db_data, db_data_len, network)?;
let mut db_data = db_read.get_update_ops()?;
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("account argument must be positive"));
};
let extsk = unsafe { CStr::from_ptr(extsk) }.to_str()?;
let to = unsafe { CStr::from_ptr(to) }.to_str()?;
let value =
Amount::from_i64(value).map_err(|()| format_err!("Invalid amount, out of range"))?;
if value.is_negative() {
return Err(format_err!("Amount is negative"));
}
let memo = unsafe { CStr::from_ptr(memo) }.to_str()?;
let spend_params = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(spend_params, spend_params_len)
}));
let output_params = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(output_params, output_params_len)
}));
let extsk =
match decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk)
{
Ok(Some(extsk)) => extsk,
Ok(None) => {
return Err(format_err!("ExtendedSpendingKey is for the wrong network"));
}
Err(e) => {
return Err(format_err!("Invalid ExtendedSpendingKey: {}", e));
}
};
let to = match RecipientAddress::decode(&network, &to) {
Some(to) => to,
None => {
return Err(format_err!("PaymentAddress is for the wrong network"));
}
};
// TODO: consider warning in this case somehow, rather than swallowing this error
let memo = match to {
RecipientAddress::Shielded(_) => {
let memo_value = Memo::from_str(&memo).map_err(|_| format_err!("Invalid memo"))?;
Some(MemoBytes::from(&memo_value))
},
RecipientAddress::Transparent(_) => None
};
let prover = LocalTxProver::new(spend_params, output_params);
create_spend_to_address(
&mut db_data,
&network,
prover,
AccountId(account),
&extsk,
&to,
value,
memo,
OvkPolicy::Sender
)
.map_err(|e| format_err!("Error while sending funds: {}", e))
});
unwrap_exc_or(res, -1)
}
#[no_mangle]
pub extern "C" fn zcashlc_branch_id_for_height(
height: i32,
network_id: u32,
) -> i32 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let branch: BranchId = BranchId::for_height(&network, BlockHeight::from(height as u32));
let branch_id: u32 = u32::from(branch);
Ok(branch_id as i32)
});
unwrap_exc_or(res, -1)
}
/// Frees strings returned by other zcashlc functions.
#[no_mangle]
pub extern "C" fn zcashlc_string_free(s: *mut c_char) {
unsafe {
if s.is_null() {
return;
}
CString::from_raw(s)
};
}
/// Frees vectors of strings returned by other zcashlc functions.
#[no_mangle]
pub extern "C" fn zcashlc_vec_string_free(v: *mut *mut c_char, len: usize, capacity: usize) {
unsafe {
if v.is_null() {
return;
}
assert!(len <= capacity);
let v = Vec::from_raw_parts(v, len, capacity);
v.into_iter().map(|s| CString::from_raw(s)).for_each(drop);
};
}
/// TEST TEST 123 TEST
///
///
/// Derives a transparent private key from seed
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_transparent_private_key_from_seed(
seed: *const u8,
seed_len: usize,
account: i32,
index: i32,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let seed = slice::from_raw_parts(seed, seed_len);
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("account argument must be positive"));
};
let index = if index >= 0 {
index as u32
} else {
return Err(format_err!("index argument must be positive"));
};
let sk = derive_secret_key_from_seed(&network, &seed, AccountId(account), index).unwrap();
let sk_wif = Wif::from_secret_key(&sk, true);
Ok(CString::new(sk_wif.0.to_string()).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// Derives a transparent address from the given seed
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_transparent_address_from_seed(
seed: *const u8,
seed_len: usize,
account: i32,
index: i32,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let seed = slice::from_raw_parts(seed, seed_len);
let network = parse_network(network_id)?;
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("account argument must be positive"));
};
let index = if index >= 0 {
index as u32
} else {
return Err(format_err!("index argument must be positive"));
};
let sk = derive_secret_key_from_seed(&network, &seed, AccountId(account), index);
let taddr = derive_transparent_address_from_secret_key(&sk.unwrap())
.encode(&network);
Ok(CString::new(taddr).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// Derives a transparent address from the given secret key enconded as a WIF string
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_transparent_address_from_secret_key(
tsk: *const c_char,
network_id: u32,
) -> *mut c_char {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let tsk_wif = CStr::from_ptr(tsk).to_str()?;
let sk: SecretKey = (&Wif(tsk_wif.to_string())).try_into().expect("invalid private key WIF");
// derive the corresponding t-address
let taddr =
derive_transparent_address_from_secret_key(&sk)
.encode(&network);
Ok(CString::new(taddr).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
#[no_mangle]
pub extern "C" fn zcashlc_shield_funds(
db_data: *const u8,
db_data_len: usize,
account: i32,
tsk: *const c_char,
extsk: *const c_char,
memo: *const c_char,
spend_params: *const u8,
spend_params_len: usize,
output_params: *const u8,
output_params_len: usize,
network_id: u32,
) -> i64 {
let res = catch_panic(|| {
let network = parse_network(network_id)?;
let db_data = wallet_db(db_data, db_data_len, network)?;
let mut update_ops = (&db_data)
.get_update_ops()
.map_err(|e| format_err!("Could not obtain a writable database connection: {}", e))?;
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("account argument must be positive"));
};
let tsk_wif = unsafe { CStr::from_ptr(tsk) }.to_str()?;
let extsk = unsafe { CStr::from_ptr(extsk) }.to_str()?;
let memo = unsafe { CStr::from_ptr(memo) }.to_str()?;
let spend_params = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(spend_params, spend_params_len)
}));
let output_params = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(output_params, output_params_len)
}));
//grab secret private key for t-funds
let sk:SecretKey = (&Wif(tsk_wif.to_string())).try_into()?;
let extsk =
match decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk)
{
Ok(Some(extsk)) => extsk,
Ok(None) => {
return Err(format_err!("ExtendedSpendingKey is for the wrong network"));
},
Err(e) => {
return Err(format_err!("Invalid ExtendedSpendingKey: {}", e));
},
};
let memo = Memo::from_str(&memo).map_err(|_| format_err!("Invalid memo"))?;
let memo_bytes = MemoBytes::from(memo);
// shield_funds(&db_cache, &db_data, account, &tsk, &extsk, &memo, &spend_params, &output_params)
shield_funds(&mut update_ops,
&network,
LocalTxProver::new(spend_params, output_params),
AccountId(account),
&sk,
&extsk,
&memo_bytes,
ANCHOR_OFFSET)
.map_err(|e| format_err!("Error while shielding transaction: {}", e))
});
unwrap_exc_or(res, -1)
}
//
// Utility functions
//
fn parse_network(value: u32) -> Result<Network, failure::Error> {
match value {
0 => Ok(TestNetwork),
1 => Ok(MainNetwork),
_ => Err(format_err!("Invalid network type: {}. Expected either 0 or 1 for Testnet or Mainnet, respectively.", value))
}
}