Initial implementation of account initialization and address fetching

Errors are indicated by returning a NULL-equivalent value, and functions
are provided to query the last error message.
This commit is contained in:
Jack Grigg 2019-05-07 18:09:51 -04:00
parent 3ca39955ea
commit bac9957f2b
5 changed files with 1294 additions and 5 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/target
**/*.rs.bk
zcashlc.h

1084
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,31 @@ name = "libzcashlc"
version = "0.0.1"
authors = ["Jack Grigg <jack@z.cash>"]
edition = "2018"
build = "rust/build.rs"
[dependencies]
failure = "0.1"
ffi_helpers = "0.1"
[dependencies.zcash_client_backend]
git = "https://github.com/str4d/librustzcash.git"
branch = "note-spending-v5"
[dependencies.zcash_client_sqlite]
git = "https://github.com/str4d/librustzcash.git"
branch = "note-spending-v5"
[dependencies.zcash_primitives]
git = "https://github.com/str4d/librustzcash.git"
branch = "note-spending-v5"
[build-dependencies]
cbindgen = "0.8"
[lib]
name = "zcashlc"
path = "rust/src/lib.rs"
crate-type = ["staticlib"]
[profile.release]
lto = true

14
rust/build.rs Normal file
View File

@ -0,0 +1,14 @@
extern crate cbindgen;
use std::env;
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
cbindgen::Builder::new()
.with_crate(crate_dir)
.with_language(cbindgen::Language::C)
.generate()
.expect("Unable to generate bindings")
.write_to_file("zcashlc.h");
}

View File

@ -1,7 +1,174 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
use failure::format_err;
use ffi_helpers::panic::catch_panic;
use std::ffi::{CString, OsStr};
use std::os::raw::c_char;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::slice;
use zcash_client_backend::{
constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, encoding::encode_extended_spending_key,
keys::spending_key,
};
use zcash_client_sqlite::{get_address, init_accounts_table, init_data_database, ErrorKind};
use zcash_primitives::zip32::ExtendedFullViewingKey;
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,
}
}
/// 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) -> i32 {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
init_data_database(&db_data)
.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,
) -> *mut *mut c_char {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
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, 1, account))
.collect();
let extfvks: Vec<_> = extsks.iter().map(ExtendedFullViewingKey::from).collect();
match init_accounts_table(&db_data, &extfvks) {
Ok(()) => (),
Err(e) => match e.kind() {
ErrorKind::TableNotEmpty => {
// Ignore this error.
}
_ => return Err(format_err!("Error while initializing accounts: {}", e)),
},
}
// Return the ExtendedSpendingKeys for the created accounts.
let mut v: Vec<_> = extsks
.iter()
.map(|extsk| {
let encoded =
encode_extended_spending_key(HRP_SAPLING_EXTENDED_SPENDING_KEY, extsk);
CString::new(encoded).unwrap().into_raw()
})
.collect();
assert!(v.len() == v.capacity());
let p = v.as_mut_ptr();
std::mem::forget(v);
Ok(p)
});
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,
) -> *mut c_char {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("accounts argument must be positive"));
};
match get_address(&db_data, account) {
Ok(addr) => {
let c_str_addr = CString::new(addr).unwrap();
Ok(c_str_addr.into_raw())
}
Err(e) => Err(format_err!("Error while fetching address: {}", e)),
}
});
unwrap_exc_or_null(res)
}
/// 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) {
unsafe {
if v.is_null() {
return;
}
// All Vecs created by other functions MUST have length == capacity.
let v = Vec::from_raw_parts(v, len, len);
v.into_iter().map(|s| CString::from_raw(s)).for_each(drop);
};
}