feat(lite): vendor SDXL backend source; source deps from git.dragonx.is
Vendor the two local Rust crates that build the lite backend artifact into third_party/silentdragonxlite/ (the qtlib C-ABI wrapper + the silentdragonxlitelib core, with proto/res and all the lite-send fixes), and point build-lite-backend-artifact.sh's default --backend-dir there, so the lite wallet builds without the upstream SilentDragonXLite repo. External build inputs are now only the Rust toolchain + git.dragonx.is + crates.io: - the 6 librustzcash git deps point at the git.dragonx.is/DragonX/librustzcash mirror (pinned rev acff1444), not git.hush.is; - the Sapling params are gitignored (not committed, no Git LFS) - the build fetches them from the git.dragonx.is/DragonX/zcash-params 'sapling-v1' release and verifies their SHA-256 before rust-embed bakes them in (ensure_sapling_params). For fully offline builds, cargo vendor into lib/vendor/ and add a vendored-sources redirect (vendor/ is gitignored; the script symlinks it into the prepared dir). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -47,3 +47,7 @@ docs/_archive/
|
|||||||
# ed25519 release-signing keys — the secret key must NEVER be committed
|
# ed25519 release-signing keys — the secret key must NEVER be committed
|
||||||
*.ed25519.key
|
*.ed25519.key
|
||||||
*.ed25519.pub.b64
|
*.ed25519.pub.b64
|
||||||
|
|
||||||
|
|
||||||
|
# Lite-backend deps are fetched (or `cargo vendor`-ed locally for offline); not committed.
|
||||||
|
third_party/silentdragonxlite/lib/vendor/
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ There is no per-test filtering — it is one binary that runs every assertion. T
|
|||||||
|
|
||||||
Variants are selected with CMake options (set by `build.sh` flags), surfaced to C++ as compile definitions:
|
Variants are selected with CMake options (set by `build.sh` flags), surfaced to C++ as compile definitions:
|
||||||
- `DRAGONX_BUILD_LITE` (`--lite`) → `DRAGONX_LITE_BUILD` define; renames the app to `ObsidianDragonLite` and excludes embedded-daemon / full-node assets (Sapling params, asmap, dragonxd).
|
- `DRAGONX_BUILD_LITE` (`--lite`) → `DRAGONX_LITE_BUILD` define; renames the app to `ObsidianDragonLite` and excludes embedded-daemon / full-node assets (Sapling params, asmap, dragonxd).
|
||||||
- `DRAGONX_ENABLE_LITE_BACKEND` → links a real external lite backend. Requires `--lite`, link mode `imported`, ABI `sdxl-c-v1`, and a symbols inventory file (built by `scripts/build-lite-backend-artifact.sh`); CMake hard-fails if any required `litelib_*` symbol is missing.
|
- `DRAGONX_ENABLE_LITE_BACKEND` → links a real external lite backend. Requires `--lite`, link mode `imported`, ABI `sdxl-c-v1`, and a symbols inventory file (built by `scripts/build-lite-backend-artifact.sh`); CMake hard-fails if any required `litelib_*` symbol is missing. The backend **source is vendored in-tree** at `third_party/silentdragonxlite/` — the `qtlib` C-ABI wrapper (`lib/`, produces `libsilentdragonxlite.a`) and the `silentdragonxlitelib` core (`silentdragonxlite-cli/lib/`, with `proto/` + `res/`). `build-lite-backend-artifact.sh` defaults `--backend-dir` there, so the lite wallet builds **without** the upstream SilentDragonXLite repo. External build inputs are limited to the **Rust toolchain (rustc/cargo 1.63)** plus two project-controlled sources on `git.dragonx.is`: the librustzcash crates come from the mirror `git.dragonx.is/DragonX/librustzcash` (the 6 `git =` deps in the core `Cargo.toml`, pinned to rev `acff1444…`), and the **Sapling params are not committed** (gitignored) — the build fetches them from the `git.dragonx.is/DragonX/zcash-params` release `sapling-v1` and verifies their SHA-256 before rust-embed bakes them in (`ensure_sapling_params`; override the URL with `SAPLING_PARAMS_BASE_URL`). Other crate deps come from crates.io. For a fully offline build, `cargo vendor` into `third_party/silentdragonxlite/lib/vendor/` and add a `vendored-sources` redirect to `lib/.cargo/config.toml` (the build script symlinks `vendor/` into its prepared dir if present); `vendor/` is gitignored.
|
||||||
- `DRAGONX_ENABLE_CHAT` → `DRAGONX_ENABLE_CHAT` define gating the chat module.
|
- `DRAGONX_ENABLE_CHAT` → `DRAGONX_ENABLE_CHAT` define gating the chat module.
|
||||||
|
|
||||||
Guard full-node-only code paths with `#if DRAGONX_LITE_BUILD` / chat code with `DRAGONX_ENABLE_CHAT`.
|
Guard full-node-only code paths with `#if DRAGONX_LITE_BUILD` / chat code with `DRAGONX_ENABLE_CHAT`.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|||||||
|
|
||||||
ABI_VERSION="sdxl-c-v1"
|
ABI_VERSION="sdxl-c-v1"
|
||||||
LINK_MODE="imported"
|
LINK_MODE="imported"
|
||||||
BACKEND_DIR="$PROJECT_ROOT/external/SilentDragonXLite/lib"
|
BACKEND_DIR="$PROJECT_ROOT/third_party/silentdragonxlite/lib"
|
||||||
BACKEND_SOURCE_DIR=""
|
BACKEND_SOURCE_DIR=""
|
||||||
BUILD_BACKEND_DIR=""
|
BUILD_BACKEND_DIR=""
|
||||||
BACKEND_DEPENDENCY_DIR=""
|
BACKEND_DEPENDENCY_DIR=""
|
||||||
@@ -314,6 +314,33 @@ validate_backend_dependency_source() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ensure the Sapling proving params are present in the core crate (rust-embed bakes them in at build
|
||||||
|
# time). They are the fixed Zcash trusted-setup output — not buildable — so fetch + verify them from
|
||||||
|
# git.dragonx.is when absent. Override the source with SAPLING_PARAMS_BASE_URL.
|
||||||
|
SAPLING_PARAMS_BASE_URL="${SAPLING_PARAMS_BASE_URL:-https://git.dragonx.is/DragonX/zcash-params/releases/download/sapling-v1}"
|
||||||
|
ensure_sapling_params() {
|
||||||
|
local dir="$1"
|
||||||
|
[[ -n "$dir" ]] || return 0
|
||||||
|
mkdir -p "$dir"
|
||||||
|
local specs=(
|
||||||
|
"sapling-spend.params:8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13"
|
||||||
|
"sapling-output.params:2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4"
|
||||||
|
)
|
||||||
|
local spec name want path got
|
||||||
|
for spec in "${specs[@]}"; do
|
||||||
|
name="${spec%%:*}"; want="${spec##*:}"; path="$dir/$name"
|
||||||
|
if [[ -f "$path" ]] && [[ "$(compute_sha256 "$path")" == "$want" ]]; then
|
||||||
|
info "sapling param present and verified: $name"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
info "fetching $name from $SAPLING_PARAMS_BASE_URL"
|
||||||
|
curl -fsSL "$SAPLING_PARAMS_BASE_URL/$name" -o "$path" || die "failed to download sapling param: $name"
|
||||||
|
got="$(compute_sha256 "$path")"
|
||||||
|
[[ "$got" == "$want" ]] || { rm -f "$path"; die "sapling param $name sha256 mismatch (got $got, want $want)"; }
|
||||||
|
info "downloaded and verified $name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
prepare_backend_source() {
|
prepare_backend_source() {
|
||||||
BUILD_BACKEND_DIR="$BACKEND_SOURCE_DIR"
|
BUILD_BACKEND_DIR="$BACKEND_SOURCE_DIR"
|
||||||
|
|
||||||
@@ -348,6 +375,9 @@ prepare_backend_source() {
|
|||||||
[[ -f "$BACKEND_SOURCE_DIR/Cargo.lock" ]] && ln -s "$BACKEND_SOURCE_DIR/Cargo.lock" "$prepared_root/Cargo.lock"
|
[[ -f "$BACKEND_SOURCE_DIR/Cargo.lock" ]] && ln -s "$BACKEND_SOURCE_DIR/Cargo.lock" "$prepared_root/Cargo.lock"
|
||||||
[[ -d "$BACKEND_SOURCE_DIR/.cargo" ]] && ln -s "$BACKEND_SOURCE_DIR/.cargo" "$prepared_root/.cargo"
|
[[ -d "$BACKEND_SOURCE_DIR/.cargo" ]] && ln -s "$BACKEND_SOURCE_DIR/.cargo" "$prepared_root/.cargo"
|
||||||
[[ -d "$BACKEND_SOURCE_DIR/libsodium-mingw" ]] && ln -s "$BACKEND_SOURCE_DIR/libsodium-mingw" "$prepared_root/libsodium-mingw"
|
[[ -d "$BACKEND_SOURCE_DIR/libsodium-mingw" ]] && ln -s "$BACKEND_SOURCE_DIR/libsodium-mingw" "$prepared_root/libsodium-mingw"
|
||||||
|
# Vendored crate deps (offline builds): the .cargo/config.toml's vendored-sources directory is
|
||||||
|
# "vendor" relative to the build root, so expose it inside the prepared root too.
|
||||||
|
[[ -d "$BACKEND_SOURCE_DIR/vendor" ]] && ln -s "$BACKEND_SOURCE_DIR/vendor" "$prepared_root/vendor"
|
||||||
[[ -f "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" ]] && ln -s "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" "$prepared_root/silentdragonxlitelib.h"
|
[[ -f "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" ]] && ln -s "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" "$prepared_root/silentdragonxlitelib.h"
|
||||||
|
|
||||||
local replacement="silentdragonxlitelib = { path = \"$BACKEND_DEPENDENCY_DIR\" }"
|
local replacement="silentdragonxlitelib = { path = \"$BACKEND_DEPENDENCY_DIR\" }"
|
||||||
@@ -489,6 +519,8 @@ build_with_cargo() {
|
|||||||
export SODIUM_LIB_DIR="$BUILD_BACKEND_DIR/libsodium-mingw"
|
export SODIUM_LIB_DIR="$BUILD_BACKEND_DIR/libsodium-mingw"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
[[ -n "$BACKEND_DEPENDENCY_DIR" ]] && ensure_sapling_params "$BACKEND_DEPENDENCY_DIR/zcash-params"
|
||||||
|
|
||||||
local cargo_cmd=(cargo build --locked --lib --release)
|
local cargo_cmd=(cargo build --locked --lib --release)
|
||||||
if [[ -n "$RUST_TARGET" ]]; then
|
if [[ -n "$RUST_TARGET" ]]; then
|
||||||
cargo_cmd+=(--target "$RUST_TARGET")
|
cargo_cmd+=(--target "$RUST_TARGET")
|
||||||
|
|||||||
2
third_party/silentdragonxlite/lib/.cargo/config.toml
vendored
Normal file
2
third_party/silentdragonxlite/lib/.cargo/config.toml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[net]
|
||||||
|
git-fetch-with-cli = true
|
||||||
2
third_party/silentdragonxlite/lib/.gitignore
vendored
Normal file
2
third_party/silentdragonxlite/lib/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target/
|
||||||
|
|
||||||
2751
third_party/silentdragonxlite/lib/Cargo.lock
generated
vendored
Normal file
2751
third_party/silentdragonxlite/lib/Cargo.lock
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16
third_party/silentdragonxlite/lib/Cargo.toml
vendored
Normal file
16
third_party/silentdragonxlite/lib/Cargo.toml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "qtlib"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["zecwallet", "The Hush Developers"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "silentdragonxlite"
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = "0.2.58"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
blake3 = "0.3.4"
|
||||||
|
silentdragonxlitelib = { path = "../silentdragonxlite-cli/lib" }
|
||||||
|
socket2 = "0.3.11"
|
||||||
28
third_party/silentdragonxlite/lib/Makefile
vendored
Normal file
28
third_party/silentdragonxlite/lib/Makefile
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
ifeq ($(shell uname),Darwin)
|
||||||
|
EXT := dylib
|
||||||
|
CFLAGS := "-mmacosx-version-min=10.11"
|
||||||
|
else
|
||||||
|
EXT := a
|
||||||
|
CFLAGS :=
|
||||||
|
endif
|
||||||
|
|
||||||
|
PWD := $(shell pwd)
|
||||||
|
|
||||||
|
all: release
|
||||||
|
|
||||||
|
winrelease: target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib
|
||||||
|
|
||||||
|
target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib: src/lib.rs Cargo.toml
|
||||||
|
SODIUM_LIB_DIR="$(PWD)/libsodium-mingw/" cargo build --lib --release --target x86_64-pc-windows-gnu
|
||||||
|
|
||||||
|
release: target/release/silentdragonxlite.$(EXT)
|
||||||
|
debug: target/debug/silentdragonxlite.$(EXT)
|
||||||
|
|
||||||
|
target/release/silentdragonxlite.$(EXT): src/lib.rs Cargo.toml
|
||||||
|
LIBS="" CFLAGS=$(CFLAGS) cargo build --lib --release
|
||||||
|
|
||||||
|
target/debug/silentdragonxlite.$(EXT): src/lib.rs Cargo.toml
|
||||||
|
LIBS="" CFLAGS=$(CFLAGS) cargo build --lib
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf target
|
||||||
BIN
third_party/silentdragonxlite/lib/libsodium-mingw/libsodium.a
vendored
Normal file
BIN
third_party/silentdragonxlite/lib/libsodium-mingw/libsodium.a
vendored
Normal file
Binary file not shown.
29
third_party/silentdragonxlite/lib/silentdragonxlitelib.h
vendored
Normal file
29
third_party/silentdragonxlite/lib/silentdragonxlitelib.h
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef _hush_PAPER_RUST_H
|
||||||
|
#define _hush_PAPER_RUST_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern bool litelib_wallet_exists (const char* chain_name);
|
||||||
|
extern char * litelib_initialize_new (bool dangerous, const char* server);
|
||||||
|
extern char * litelib_initialize_new_from_phrase
|
||||||
|
(bool dangerous, const char* server, const char* seed,
|
||||||
|
unsigned long long birthday, unsigned long long number,
|
||||||
|
bool overwrite);
|
||||||
|
extern char * litelib_initialize_existing (bool dangerous,const char* server);
|
||||||
|
extern char * litelib_execute (const char* s, const char* args);
|
||||||
|
extern void litelib_rust_free_string (char* s);
|
||||||
|
extern char * blake3_PW (char* pw);
|
||||||
|
extern bool litelib_check_server_online (const char* server);
|
||||||
|
extern void litelib_shutdown (void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This is a function implemented in connection.cpp that will process a string response from
|
||||||
|
// the litelib and turn into into a QString in a memory-safe way.
|
||||||
|
QString litelib_process_response(char* resp);
|
||||||
|
|
||||||
|
#endif
|
||||||
328
third_party/silentdragonxlite/lib/src/lib.rs
vendored
Normal file
328
third_party/silentdragonxlite/lib/src/lib.rs
vendored
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
use libc::{c_char};
|
||||||
|
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::sync::{Mutex, Arc};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ptr;
|
||||||
|
use std::panic;
|
||||||
|
|
||||||
|
use silentdragonxlitelib::{commands, lightclient::{LightClient, LightClientConfig}};
|
||||||
|
|
||||||
|
/// Helper to create a CString, replacing null bytes to avoid panics
|
||||||
|
fn safe_cstring(s: &str) -> CString {
|
||||||
|
let cleaned: String = s.replace('\0', "");
|
||||||
|
CString::new(cleaned).unwrap_or_else(|_| CString::new("Error: failed to create CString").unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to create an error CString
|
||||||
|
fn error_cstring(msg: &str) -> *mut c_char {
|
||||||
|
safe_cstring(&format!("Error: {}", msg)).into_raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll use a MUTEX to store a global lightclient instance,
|
||||||
|
// so we don't have to keep creating it. We need to store it here, in rust
|
||||||
|
// because we can't return such a complex structure back to C++
|
||||||
|
lazy_static! {
|
||||||
|
static ref LIGHTCLIENT: Mutex<RefCell<Option<Arc<LightClient>>>> = Mutex::new(RefCell::new(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is an existing wallet
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn litelib_wallet_exists(chain_name: *const c_char) -> bool {
|
||||||
|
let chain_name_str = unsafe {
|
||||||
|
assert!(!chain_name.is_null());
|
||||||
|
|
||||||
|
CStr::from_ptr(chain_name).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = LightClientConfig::create_unconnected(chain_name_str, None);
|
||||||
|
|
||||||
|
println!("Wallet exists: {}", config.wallet_exists());
|
||||||
|
config.wallet_exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
//////hash blake3
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn blake3_PW(pw: *const c_char) -> *mut c_char{
|
||||||
|
|
||||||
|
let passwd = unsafe {
|
||||||
|
assert!(!pw.is_null());
|
||||||
|
|
||||||
|
CStr::from_ptr(pw).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = passwd.as_bytes();
|
||||||
|
// Hash an input all at once.
|
||||||
|
let hash1 = blake3::hash(data).to_hex();
|
||||||
|
// This is sensitive metadata, do not log it to stdout
|
||||||
|
//println!("\nBlake3 Hash: {}", hash1);
|
||||||
|
println!("\nBlake3 Hash calculated");
|
||||||
|
|
||||||
|
//let sttring = CString::new(hash1).unwrap();
|
||||||
|
let e_str = CString::new(format!("{}", hash1)).unwrap();
|
||||||
|
return e_str.into_raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new wallet and return the seed for the newly created wallet.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *mut c_char {
|
||||||
|
let server_str = unsafe {
|
||||||
|
assert!(!server.is_null());
|
||||||
|
|
||||||
|
CStr::from_ptr(server).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let server = LightClientConfig::get_server_or_default(Some(server_str));
|
||||||
|
let (config, latest_block_height) = match LightClientConfig::create(server, dangerous) {
|
||||||
|
Ok((c, h)) => (c, h),
|
||||||
|
Err(e) => {
|
||||||
|
let e_str = CString::new(format!("Error: {}", e)).unwrap();
|
||||||
|
return e_str.into_raw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let lightclient = match LightClient::new(&config, latest_block_height) {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) => {
|
||||||
|
let e_str = CString::new(format!("Error: {}", e)).unwrap();
|
||||||
|
return e_str.into_raw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize logging
|
||||||
|
let _ = lightclient.init_logging();
|
||||||
|
|
||||||
|
let seed = match lightclient.do_seed_phrase() {
|
||||||
|
Ok(s) => s.dump(),
|
||||||
|
Err(e) => {
|
||||||
|
let e_str = CString::new(format!("Error: {}", e)).unwrap();
|
||||||
|
return e_str.into_raw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let lc = Arc::new(lightclient);
|
||||||
|
match LightClient::start_mempool_monitor(lc.clone()) {
|
||||||
|
Ok(_) => {println!("Starting Mempool")},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Couldnt start mempool {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => { l.replace(Some(lc)); },
|
||||||
|
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return the wallet's seed
|
||||||
|
let s_str = safe_cstring(&seed);
|
||||||
|
return s_str.into_raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore a wallet from the seed phrase
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *const c_char,
|
||||||
|
seed: *const c_char, birthday: u64, number: u64, overwrite: bool) -> *mut c_char {
|
||||||
|
if server.is_null() || seed.is_null() {
|
||||||
|
println!("Server or seed is null");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
let server_str = unsafe {
|
||||||
|
CStr::from_ptr(server).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
let seed_str = unsafe {
|
||||||
|
CStr::from_ptr(seed).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
//println!("Initializing with server: {}, seed: {}", server_str, seed_str);
|
||||||
|
|
||||||
|
// Shut down the existing client if one is running, to stop background threads
|
||||||
|
if overwrite {
|
||||||
|
let old_lc = match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => l.borrow().clone(),
|
||||||
|
Err(poisoned) => poisoned.into_inner().borrow().clone(),
|
||||||
|
};
|
||||||
|
if let Some(lc) = old_lc {
|
||||||
|
lc.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let server = LightClientConfig::get_server_or_default(Some(server_str));
|
||||||
|
let (config, _latest_block_height) = match LightClientConfig::create(server, dangerous) {
|
||||||
|
Ok((c, h)) => {
|
||||||
|
println!("Config created successfully");
|
||||||
|
(c, h)
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error creating config: {}", e);
|
||||||
|
let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap());
|
||||||
|
return e_str.into_raw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let lightclient = match LightClient::new_from_phrase(seed_str, &config, birthday, number, overwrite) {
|
||||||
|
Ok(l) => {
|
||||||
|
println!("LightClient created successfully");
|
||||||
|
l
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error creating LightClient: {}", e);
|
||||||
|
let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap());
|
||||||
|
return e_str.into_raw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize logging
|
||||||
|
let _ = lightclient.init_logging();
|
||||||
|
|
||||||
|
let lc = Arc::new(lightclient);
|
||||||
|
match LightClient::start_mempool_monitor(lc.clone()) {
|
||||||
|
Ok(_) => println!("Starting Mempool"),
|
||||||
|
Err(e) => println!("Could not start mempool: {}", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => { l.replace(Some(lc)); },
|
||||||
|
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
|
||||||
|
};
|
||||||
|
|
||||||
|
let c_str = safe_cstring("OK");
|
||||||
|
return c_str.into_raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a new lightclient and store its value
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char) -> *mut c_char {
|
||||||
|
let server_str = unsafe {
|
||||||
|
assert!(!server.is_null());
|
||||||
|
|
||||||
|
CStr::from_ptr(server).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let server = LightClientConfig::get_server_or_default(Some(server_str));
|
||||||
|
let (config, _latest_block_height) = match LightClientConfig::create(server,dangerous) {
|
||||||
|
Ok((c, h)) => (c, h),
|
||||||
|
Err(e) => {
|
||||||
|
let e_str = CString::new(format!("Error: {}", e)).unwrap();
|
||||||
|
return e_str.into_raw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let lightclient = match LightClient::read_from_disk(&config) {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) => {
|
||||||
|
let e_str = CString::new(format!("Error: {}", e)).unwrap();
|
||||||
|
return e_str.into_raw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize logging
|
||||||
|
let _ = lightclient.init_logging();
|
||||||
|
|
||||||
|
let lc = Arc::new(lightclient);
|
||||||
|
match LightClient::start_mempool_monitor(lc.clone()) {
|
||||||
|
Ok(_) => {println!("Starting Mempool")},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Couldnt start mempool {}",e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => { l.replace(Some(lc)); },
|
||||||
|
Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
|
||||||
|
};
|
||||||
|
|
||||||
|
let c_str = safe_cstring("OK");
|
||||||
|
return c_str.into_raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn litelib_execute(cmd: *const c_char, args: *const c_char) -> *mut c_char {
|
||||||
|
let result = panic::catch_unwind(|| {
|
||||||
|
let cmd_str = unsafe {
|
||||||
|
assert!(!cmd.is_null());
|
||||||
|
CStr::from_ptr(cmd).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let arg_str = unsafe {
|
||||||
|
assert!(!args.is_null());
|
||||||
|
CStr::from_ptr(args).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let resp: String;
|
||||||
|
{
|
||||||
|
let lightclient: Arc<LightClient>;
|
||||||
|
{
|
||||||
|
let lc = match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(poisoned) => poisoned.into_inner(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if lc.borrow().is_none() {
|
||||||
|
return error_cstring("Light Client is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
lightclient = lc.borrow().as_ref().unwrap().clone();
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = if arg_str.is_empty() { vec![] } else { vec![arg_str.as_ref()] };
|
||||||
|
|
||||||
|
resp = commands::do_user_command(&cmd_str, &args, lightclient.as_ref()).clone();
|
||||||
|
};
|
||||||
|
|
||||||
|
safe_cstring(&resp).into_raw()
|
||||||
|
});
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(ptr) => ptr,
|
||||||
|
Err(_) => error_cstring("Rust panic in litelib_execute"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check is Server Connection is fine
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn litelib_check_server_online(server: *const c_char) -> bool {
|
||||||
|
let server_str = unsafe {
|
||||||
|
assert!(!server.is_null());
|
||||||
|
|
||||||
|
CStr::from_ptr(server).to_string_lossy().into_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let server = LightClientConfig::get_server_or_default(Some(server_str));
|
||||||
|
let result = LightClientConfig::create(server, false);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cleanly shut down the light client, stopping mempool monitor threads.
|
||||||
|
/// Must be called before exit to prevent hangs.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn litelib_shutdown() {
|
||||||
|
let lc_option = match LIGHTCLIENT.lock() {
|
||||||
|
Ok(l) => l.borrow().clone(),
|
||||||
|
Err(poisoned) => poisoned.into_inner().borrow().clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(lc) = lc_option {
|
||||||
|
lc.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callers that receive string return values from other functions should call this to return the string
|
||||||
|
* back to rust, so it can be freed. Failure to call this function will result in a memory leak
|
||||||
|
*/
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn litelib_rust_free_string(s: *mut c_char) {
|
||||||
|
unsafe {
|
||||||
|
if s.is_null() { return }
|
||||||
|
CString::from_raw(s)
|
||||||
|
};
|
||||||
|
}
|
||||||
80
third_party/silentdragonxlite/silentdragonxlite-cli/lib/Cargo.toml
vendored
Normal file
80
third_party/silentdragonxlite/silentdragonxlite-cli/lib/Cargo.toml
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
[package]
|
||||||
|
name = "silentdragonxlitelib"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["embed_params"]
|
||||||
|
embed_params = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base58 = "0.1.0"
|
||||||
|
bs58 = { version = "0.2", features = ["check"] }
|
||||||
|
log = "0.4"
|
||||||
|
log4rs = "0.8.3"
|
||||||
|
dirs = "2.0.2"
|
||||||
|
http = "0.2"
|
||||||
|
hex = "0.3"
|
||||||
|
protobuf = "2"
|
||||||
|
byteorder = "1"
|
||||||
|
json = "0.12.0"
|
||||||
|
tiny-bip39 = "0.6.2"
|
||||||
|
secp256k1 = "=0.15.0"
|
||||||
|
sha2 = "0.8.0"
|
||||||
|
ripemd160 = "0.8.0"
|
||||||
|
lazy_static = "1.2.0"
|
||||||
|
rust-embed = { version = "5.1.0", features = ["debug-embed"] }
|
||||||
|
rand = "0.7.2"
|
||||||
|
sodiumoxide = "0.2.5"
|
||||||
|
ring = "0.16.9"
|
||||||
|
libflate = "0.1"
|
||||||
|
subtle = "2"
|
||||||
|
threadpool = "1.8.0"
|
||||||
|
num_cpus = "1.13.0"
|
||||||
|
|
||||||
|
tonic = { version = "0.1.1", features = ["tls", "tls-roots"] }
|
||||||
|
bytes = "0.4"
|
||||||
|
prost = "0.6"
|
||||||
|
prost-types = "0.6"
|
||||||
|
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds", "full"] }
|
||||||
|
tokio-rustls = { version = "0.12.1", features = ["dangerous_configuration"] }
|
||||||
|
webpki = "0.21.0"
|
||||||
|
webpki-roots = "0.18.0"
|
||||||
|
|
||||||
|
[dependencies.bellman]
|
||||||
|
git = "https://git.dragonx.is/DragonX/librustzcash.git"
|
||||||
|
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||||
|
default-features = false
|
||||||
|
features = ["groth16"]
|
||||||
|
|
||||||
|
[dependencies.pairing]
|
||||||
|
git = "https://git.dragonx.is/DragonX/librustzcash.git"
|
||||||
|
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||||
|
|
||||||
|
[dependencies.zcash_client_backend]
|
||||||
|
git = "https://git.dragonx.is/DragonX/librustzcash.git"
|
||||||
|
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||||
|
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[dependencies.zcash_primitives]
|
||||||
|
git = "https://git.dragonx.is/DragonX/librustzcash.git"
|
||||||
|
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||||
|
default-features = false
|
||||||
|
features = ["transparent-inputs"]
|
||||||
|
|
||||||
|
[dependencies.zcash_proofs]
|
||||||
|
git = "https://git.dragonx.is/DragonX/librustzcash.git"
|
||||||
|
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[dependencies.ff]
|
||||||
|
git = "https://git.dragonx.is/DragonX/librustzcash.git"
|
||||||
|
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||||
|
features = ["ff_derive"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = "0.1.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempdir = "0.3.7"
|
||||||
13
third_party/silentdragonxlite/silentdragonxlite-cli/lib/build.rs
vendored
Normal file
13
third_party/silentdragonxlite/silentdragonxlite-cli/lib/build.rs
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright The Hush Developers 2019-2022
|
||||||
|
// Released under the GPLv3
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
tonic_build::configure()
|
||||||
|
.build_server(false)
|
||||||
|
.compile(
|
||||||
|
&["proto/service.proto", "proto/compact_formats.proto"],
|
||||||
|
&["proto"],
|
||||||
|
)?;
|
||||||
|
println!("cargo:rerun-if-changed=proto/service.proto");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
65
third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/compact_formats.proto
vendored
Normal file
65
third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/compact_formats.proto
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package cash.z.wallet.sdk.rpc;
|
||||||
|
option go_package = "lightwalletd/walletrpc";
|
||||||
|
option swift_prefix = "";
|
||||||
|
|
||||||
|
// Remember that proto3 fields are all optional. A field that is not present will be set to its zero value.
|
||||||
|
// bytes fields of hashes are in canonical little-endian format.
|
||||||
|
|
||||||
|
// CompactBlock is a packaging of ONLY the data from a block that's needed to:
|
||||||
|
// 1. Detect a payment to your shielded Sapling address
|
||||||
|
// 2. Detect a spend of your shielded Sapling notes
|
||||||
|
// 3. Update your witnesses to generate new Sapling spend proofs.
|
||||||
|
message CompactBlock {
|
||||||
|
uint32 protoVersion = 1; // the version of this wire format, for storage
|
||||||
|
uint64 height = 2; // the height of this block
|
||||||
|
bytes hash = 3; // the ID (hash) of this block, same as in block explorers
|
||||||
|
bytes prevHash = 4; // the ID (hash) of this block's predecessor
|
||||||
|
uint32 time = 5; // Unix epoch time when the block was mined
|
||||||
|
bytes header = 6; // (hash, prevHash, and time) OR (full header)
|
||||||
|
repeated CompactTx vtx = 7; // compact transactions from this block
|
||||||
|
}
|
||||||
|
|
||||||
|
message CompactTx {
|
||||||
|
// Index and hash will allow the receiver to call out to chain
|
||||||
|
// explorers or other data structures to retrieve more information
|
||||||
|
// about this transaction.
|
||||||
|
uint64 index = 1; // the index within the full block
|
||||||
|
bytes hash = 2; // the ID (hash) of this transaction, same as in block explorers
|
||||||
|
|
||||||
|
// The transaction fee: present if server can provide. In the case of a
|
||||||
|
// stateless server and a transaction with transparent inputs, this will be
|
||||||
|
// unset because the calculation requires reference to prior transactions.
|
||||||
|
// in a pure-Sapling context, the fee will be calculable as:
|
||||||
|
// valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut))
|
||||||
|
uint32 fee = 3;
|
||||||
|
|
||||||
|
repeated CompactSaplingSpend spends = 4;
|
||||||
|
repeated CompactSaplingOutput outputs = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompactSaplingSpend is a Sapling Spend Description as described in 7.3 of the Zcash
|
||||||
|
// protocol specification.
|
||||||
|
message CompactSaplingSpend {
|
||||||
|
bytes nf = 1; // nullifier (see the Zcash protocol specification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// output is a Sapling Output Description as described in section 7.4 of the
|
||||||
|
// Zcash protocol spec. Total size is 948.
|
||||||
|
message CompactSaplingOutput {
|
||||||
|
bytes cmu = 1; // note commitment u-coordinate
|
||||||
|
bytes epk = 2; // ephemeral public key
|
||||||
|
bytes ciphertext = 3; // first 52 bytes of ciphertext
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
message CompactSpend {
|
||||||
|
bytes nf = 1; // nullifier (see the Zcash protocol specification)
|
||||||
|
}
|
||||||
|
|
||||||
|
message CompactOutput {
|
||||||
|
bytes cmu = 1; // note commitment u-coordinate
|
||||||
|
bytes epk = 2; // ephemeral public key
|
||||||
|
bytes ciphertext = 3; // first 52 bytes of ciphertext
|
||||||
|
}
|
||||||
|
*/
|
||||||
170
third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/service.proto
vendored
Normal file
170
third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/service.proto
vendored
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package cash.z.wallet.sdk.rpc;
|
||||||
|
option go_package = "lightwalletd/walletrpc";
|
||||||
|
option swift_prefix = "";
|
||||||
|
import "compact_formats.proto";
|
||||||
|
|
||||||
|
// A BlockID message contains identifiers to select a block: a height or a
|
||||||
|
// hash. If the hash is present it takes precedence.
|
||||||
|
message BlockID {
|
||||||
|
uint64 height = 1;
|
||||||
|
bytes hash = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockRange technically allows ranging from hash to hash etc but this is not
|
||||||
|
// currently intended for support, though there is no reason you couldn't do
|
||||||
|
// it. Further permutations are left as an exercise.
|
||||||
|
message BlockRange {
|
||||||
|
BlockID start = 1;
|
||||||
|
BlockID end = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A TxFilter contains the information needed to identify a particular
|
||||||
|
// transaction: either a block and an index, or a direct transaction hash.
|
||||||
|
message TxFilter {
|
||||||
|
BlockID block = 1;
|
||||||
|
uint64 index = 2;
|
||||||
|
bytes hash = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawTransaction contains the complete transaction data. It also optionally includes
|
||||||
|
// the block height in which the transaction was included
|
||||||
|
message RawTransaction {
|
||||||
|
bytes data = 1;
|
||||||
|
uint64 height = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SendResponse {
|
||||||
|
int32 errorCode = 1;
|
||||||
|
string errorMessage = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty placeholder. Someday we may want to specify e.g. a particular chain fork.
|
||||||
|
message ChainSpec {}
|
||||||
|
|
||||||
|
message Empty {}
|
||||||
|
|
||||||
|
message LightdInfo {
|
||||||
|
string version = 1;
|
||||||
|
string vendor = 2;
|
||||||
|
bool taddrSupport = 3;
|
||||||
|
string chainName = 4;
|
||||||
|
uint64 saplingActivationHeight = 5;
|
||||||
|
string consensusBranchId = 6; // This should really be u32 or []byte, but string for readability
|
||||||
|
uint64 blockHeight = 7;
|
||||||
|
uint64 difficulty = 8;
|
||||||
|
uint64 longestchain = 9;
|
||||||
|
uint64 notarized = 10;
|
||||||
|
}
|
||||||
|
message Coinsupply {
|
||||||
|
string result = 1;
|
||||||
|
string coin = 2;
|
||||||
|
uint64 height = 3;
|
||||||
|
uint64 supply = 4;
|
||||||
|
uint64 zfunds = 5;
|
||||||
|
uint64 total = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TransparentAddress {
|
||||||
|
string address = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TransparentAddressBlockFilter {
|
||||||
|
string address = 1;
|
||||||
|
BlockRange range = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Address {
|
||||||
|
string address = 1;
|
||||||
|
}
|
||||||
|
message AddressList {
|
||||||
|
repeated string addresses = 1;
|
||||||
|
}
|
||||||
|
message Balance {
|
||||||
|
int64 valueZat = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Exclude {
|
||||||
|
repeated bytes txid = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TreeState is derived from the Hush getblockmerkletree rpc.
|
||||||
|
// https://faq.hush.is/rpc/getblockmerkletree.html
|
||||||
|
message TreeState {
|
||||||
|
string network = 1; // "main" or "test"
|
||||||
|
uint64 height = 2; // block height
|
||||||
|
string hash = 3; // block id
|
||||||
|
uint32 time = 4; // Unix epoch time when the block was mined
|
||||||
|
string saplingTree = 5; // sapling commitment tree state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Results are sorted by height, which makes it easy to issue another
|
||||||
|
// request that picks up from where the previous left off.
|
||||||
|
message GetAddressUtxosArg {
|
||||||
|
repeated string addresses = 1;
|
||||||
|
uint64 startHeight = 2;
|
||||||
|
uint32 maxEntries = 3; // zero means unlimited
|
||||||
|
}
|
||||||
|
message GetAddressUtxosReply {
|
||||||
|
string address = 6;
|
||||||
|
bytes txid = 1;
|
||||||
|
int32 index = 2;
|
||||||
|
bytes script = 3;
|
||||||
|
int64 valueZat = 4;
|
||||||
|
uint64 height = 5;
|
||||||
|
}
|
||||||
|
message GetAddressUtxosReplyList {
|
||||||
|
repeated GetAddressUtxosReply addressUtxos = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service CompactTxStreamer {
|
||||||
|
// Return the height of the tip of the best chain
|
||||||
|
rpc GetLatestBlock(ChainSpec) returns (BlockID) {}
|
||||||
|
// Return the compact block corresponding to the given block identifier
|
||||||
|
rpc GetBlock(BlockID) returns (CompactBlock) {}
|
||||||
|
// Return a list of consecutive compact blocks
|
||||||
|
rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {}
|
||||||
|
|
||||||
|
// Return the requested full (not compact) transaction (as from zcashd)
|
||||||
|
rpc GetTransaction(TxFilter) returns (RawTransaction) {}
|
||||||
|
// Submit the given transaction to the Zcash network
|
||||||
|
rpc SendTransaction(RawTransaction) returns (SendResponse) {}
|
||||||
|
|
||||||
|
// Return the txids corresponding to the given t-address within the given block range
|
||||||
|
rpc GetTaddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {}
|
||||||
|
// wrapper for GetTaddressTxids
|
||||||
|
rpc GetAddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {}
|
||||||
|
rpc GetTaddressBalance(AddressList) returns (Balance) {}
|
||||||
|
rpc GetTaddressBalanceStream(stream Address) returns (Balance) {}
|
||||||
|
|
||||||
|
// Return the compact transactions currently in the mempool; the results
|
||||||
|
// can be a few seconds out of date. If the Exclude list is empty, return
|
||||||
|
// all transactions; otherwise return all *except* those in the Exclude list
|
||||||
|
// (if any); this allows the client to avoid receiving transactions that it
|
||||||
|
// already has (from an earlier call to this rpc). The transaction IDs in the
|
||||||
|
// Exclude list can be shortened to any number of bytes to make the request
|
||||||
|
// more bandwidth-efficient; if two or more transactions in the mempool
|
||||||
|
// match a shortened txid, they are all sent (none is excluded). Transactions
|
||||||
|
// in the exclude list that don't exist in the mempool are ignored.
|
||||||
|
rpc GetMempoolTx(Exclude) returns (stream CompactTx) {}
|
||||||
|
|
||||||
|
// Return a stream of current Mempool transactions. This will keep the output stream open while
|
||||||
|
// there are mempool transactions. It will close the returned stream when a new block is mined.
|
||||||
|
rpc GetMempoolStream(Empty) returns (stream RawTransaction) {}
|
||||||
|
|
||||||
|
// GetTreeState returns the note commitment tree state corresponding to the given block.
|
||||||
|
// See section 3.7 of the Zcash protocol specification. It returns several other useful
|
||||||
|
// values also (even though they can be obtained using GetBlock).
|
||||||
|
// The block can be specified by either height or hash.
|
||||||
|
rpc GetTreeState(BlockID) returns (TreeState) {}
|
||||||
|
rpc GetLatestTreeState(Empty) returns (TreeState) {}
|
||||||
|
|
||||||
|
rpc GetAddressUtxos(GetAddressUtxosArg) returns (GetAddressUtxosReplyList) {}
|
||||||
|
rpc GetAddressUtxosStream(GetAddressUtxosArg) returns (stream GetAddressUtxosReply) {}
|
||||||
|
|
||||||
|
// Return information about this lightwalletd instance and the blockchain
|
||||||
|
rpc GetLightdInfo(Empty) returns (LightdInfo) {}
|
||||||
|
// Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production)
|
||||||
|
// rpc Ping(Duration) returns (PingResponse) {}
|
||||||
|
rpc GetCoinsupply(Empty) returns (Coinsupply) {}
|
||||||
|
}
|
||||||
65
third_party/silentdragonxlite/silentdragonxlite-cli/lib/res/lightwalletd-lite.myhush.pem
vendored
Normal file
65
third_party/silentdragonxlite/silentdragonxlite-cli/lib/res/lightwalletd-lite.myhush.pem
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFlTCCA32gAwIBAgIUCU7sjrIbYfA+bc2qWlyUo0dCU7swDQYJKoZIhvcNAQEL
|
||||||
|
BQAwWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxDTALBgNVBAoM
|
||||||
|
BEh1c2gxDTALBgNVBAsMBEh1c2gxGDAWBgNVBAMMD2xpdGUubXlodXNoLm9yZzAe
|
||||||
|
Fw0xOTEyMDIxNDUyMzBaFw0yMDEyMDExNDUyMzBaMFoxCzAJBgNVBAYTAlVTMRMw
|
||||||
|
EQYDVQQIDApTb21lLVN0YXRlMQ0wCwYDVQQKDARIdXNoMQ0wCwYDVQQLDARIdXNo
|
||||||
|
MRgwFgYDVQQDDA9saXRlLm15aHVzaC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4IC
|
||||||
|
DwAwggIKAoICAQDhd3SLJuGQ3ivUWle6+Est+qNBghf2vMkcgj9NjaxeMjSMLPVq
|
||||||
|
Mxt0j/mJe3+z767yXbiBUKnaQyb7OcWtxUN3PusGmqnMAUuy/tdu9h+2ScYKThh2
|
||||||
|
JHQNdyN1y4c7sFbmntpMIIqm6/v95UXnnStQ+VBlS2/IhLYTgW31DEIiTpyx4jjW
|
||||||
|
xY+QD2+mqf4sSDm4Yq/r3Cxp6YWufEbhkXiHcF15JPk1d6jzkOkcjCJJCqwRMJ+5
|
||||||
|
60q31S1W0Ud/L7AqkOhAKFHmORfCXM0ae4Rive/ZgM688KYIXA9kQzA6ZMdD7VIL
|
||||||
|
4q4IoP1ZjlPhosFoUFB6lHORYp15+Gu43jbC2/SUPWJQbJ1XusjxysqngyJ53/Va
|
||||||
|
MN/iWhOmqBXjx1SqkyIV4W56GDezxT1MhM5zSSKgEHePyFzkGNYasEeHa1/hZoz+
|
||||||
|
zKG1oGLlMQe5TtI3AMZMfLz6t8qtRB+k+XW988mHJZ7BYOjW3KvdN16SOYdFF6K+
|
||||||
|
86MAQ8rNPgcTsnclhmDdjh7+PhQpkF3uqF1EeVTzb03s77Cx6nDc9GCnpXqg8tkE
|
||||||
|
HnJD0WFIXA29PCjWyebuksMBRahekYDR0kn8O0Km/eFAprH3v4qoSc1JLNJR2G20
|
||||||
|
eHVDnNFnR6QdwlM9+39NYUhJV43aj28wx1m0FXI+dSklblk4hkbGPPbFjQIDAQAB
|
||||||
|
o1MwUTAdBgNVHQ4EFgQUo5TCdrNgopYbxQSzSPFSlyCtE6YwHwYDVR0jBBgwFoAU
|
||||||
|
o5TCdrNgopYbxQSzSPFSlyCtE6YwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
|
||||||
|
AQsFAAOCAgEAyPxq4ZyBtKJEQtzmqdkI+28Yw4qDSBE0dj4QQfOErgK5hX29Bk4e
|
||||||
|
Auh6j0eyuRA8gtwngsE8fAAg84kcH/b+hM2zFW4MqpgjigA7oqA51VkIg+8Q9zdF
|
||||||
|
IHweV2kuSunZ2ANcGrr7o/Qy5y8D7URWUpUu8J1ZNyLg7YYMtpyjllTYhbfKpAgM
|
||||||
|
HUX1STCRfSPTgM/JxIsnll8RgacfhUmoOzOsrvvZ39h8cZZo96ksBRL4gvVQ++Hm
|
||||||
|
gzNTbYQXukR26Xfv112AEj5Xo3z3fsLP1KxZxd2p6/24XYktpZf2J71Np2CONdV4
|
||||||
|
gFgxFfPwvPyDO5pKice018qlXz0euhvK5g++s+TrSeZwleDTW4spP3TdVXNB96iZ
|
||||||
|
rrFkTT0SEBtd6iKqeFAX89BpshCUqOlsFdrf10i2dDiKsqxMod6a8i17RrtZL3q7
|
||||||
|
S0nqCsnyc1QvfKIQi08vfMZHHMbSS0Cg/5H8ISexM/R4SQc6C//IhEd5hUJ7E9AC
|
||||||
|
Sepr0xu1JBrglech4N+brHpGZK3Crofzu+hV+qruY5Wg21bD99zigxrgi3YaQPlA
|
||||||
|
6TJVsGk68h00QSy+Ri9dyuvyPnIDyMQfZLLIKCwsxznYXLtmIp+UoA3otGTijhmc
|
||||||
|
ZxFdEhd3cJrSPwihb/IvQJICFp3ya1aI7dLsaZO9h9kPc8GGLi7tiq8=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFozCCA4ugAwIBAgIUaRW0/q8ERGZUZqv+TVkC5lOwgX4wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwYTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlVTMQswCQYDVQQHDAJVUzEPMA0G
|
||||||
|
A1UECgwGTXlIdXNoMQ0wCwYDVQQLDARIVVNIMRgwFgYDVQQDDA9saXRlLm15aHVz
|
||||||
|
aC5vcmcwHhcNMjAwMTAzMjAzMTAwWhcNMjEwMTAyMjAzMTAwWjBhMQswCQYDVQQG
|
||||||
|
EwJVUzELMAkGA1UECAwCVVMxCzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZNeUh1c2gx
|
||||||
|
DTALBgNVBAsMBEhVU0gxGDAWBgNVBAMMD2xpdGUubXlodXNoLm9yZzCCAiIwDQYJ
|
||||||
|
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/oJh+MSxGOedvEthiqdfMEDfjzJ7AW
|
||||||
|
wwvuBtVULvnIvJq0WTMK8INjkMqnj+/ZE9a1aOOSpwN+9CuknhiySTdqHGl6EigM
|
||||||
|
/S19ZdN/7aC881VrMCncguHUOa8HLq5F4R0YCGis1Cor9vXf0GVFJ2mDfPB0C8iz
|
||||||
|
C+gMGnkDwuBy51fUcWbxZ5diRYh7YlOIUxpmb24On+X7sw+7nbmV12x8v644xRKJ
|
||||||
|
MazPIVrxIwXZ3tjz2NR7IMu+SrtCgMAW56M63NJ+iZBYDRoFVMRGEfAgGaFV2Hqi
|
||||||
|
Dx7q89sO2bhVg2lBBOW7403S+T8eK1rGFj7iGJoRLm/cgWwozZteXynHzicYEvX6
|
||||||
|
w1h0lu/OPQQk5AKRn+iI1kKLlT1auKIBXpfnpELnie3XgzzLyKt0pobVrWdMutlJ
|
||||||
|
83Zo7LmnhJYlcG7Qb0UczSyaIn+3dWo2HTbiyzJD23gmUbzFD4AVF1Ee4x5yT4Hb
|
||||||
|
Aw0FpQXDHX2MT04xleM2HdjE86ruZfNCegvQdRtgKRAiVNe4kP5ZzB7OeDX6pgeE
|
||||||
|
/e07tiHiFb2Im7J/IR4PmIh6SuYI/QObFXFXfvwCk/iJCps15/PryGfXZLdz+2z1
|
||||||
|
av1nwSglBRqeRX8HUBIgNcY0Lyq82BKfq4ZU3fKiIDuNV16OxCnFGZRw+ByRNrKR
|
||||||
|
KsgaJEi7qH6DAgMBAAGjUzBRMB0GA1UdDgQWBBRd+rxIkdKrpLsgz5/KiqZOYzUB
|
||||||
|
KDAfBgNVHSMEGDAWgBRd+rxIkdKrpLsgz5/KiqZOYzUBKDAPBgNVHRMBAf8EBTAD
|
||||||
|
AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBR9M0CnCvT1Zd5D/Wwy9ylH6CSFq6AEbdh
|
||||||
|
fMo8+NZl4J0FNji2Iv05xh+V3f+eyuf6oc8q0vsKeC5MZj/44AzqzxCSvMrUnh0V
|
||||||
|
GAiZQqLAVJIR/fi49bX9ku1yfQVJKzGFS93TcHMv9XYHJ4bSnQKlEOF5F6Wh9AO1
|
||||||
|
6GU/+vb0pSOfOv5+pUaj84AYCKQ8kp9nuNpOS12jyjc5hUogflPFAmeIpcIGjiso
|
||||||
|
Ln8+b+Xh0BZGNpdrvJ/wr8InxblMu5chtYrGAGo4mh4q3YWiwYJTkTIhoeTxF4eU
|
||||||
|
BOqMJa9lQgZE4T7V33jrIsuMPEZfACpj+gQNNzl8WQ+jzkZhBdYPTqhO9u1rlRXG
|
||||||
|
9VJfmuQ7+KLXAMFQgsHlX5Y5lux3CV36Knb5+1f/u0cdys1yb9mbQ/Ok3T8cuh6B
|
||||||
|
7Hs53JhAW47+CCnsNTaPzwti3wfzWhS2sjHz4IT1NcacsuDlxk7IykJ2U3auiufE
|
||||||
|
lRFpZoK81jsipEgRPBeF6OesXpldKxK9lnVJA/6ElApuo0amgg++fROQEpZSLyBM
|
||||||
|
lZdYrW6ZKnzCUZpNuwNHg1nfiit3RJ6hRLsk2jHrLDb5BVWzuJCNa7LK9bZ3IbUa
|
||||||
|
5jGb9Rplo+NgglQQYCflptksti9h5DN+GlVGxfJ9yzkg4/4ckmh75colcc1CTQAf
|
||||||
|
eER5vAF7og==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
1004
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/commands.rs
vendored
Normal file
1004
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/commands.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
363
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/grpcconnector.rs
vendored
Normal file
363
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/grpcconnector.rs
vendored
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
// Copyright The Hush Developers 2019-2022
|
||||||
|
// Released under the GPLv3
|
||||||
|
use log::{info,error};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use zcash_primitives::transaction::{TxId};
|
||||||
|
|
||||||
|
use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, CompactBlock,
|
||||||
|
TransparentAddressBlockFilter, TxFilter, Empty, LightdInfo, Coinsupply};
|
||||||
|
use tonic::transport::{Channel, ClientTlsConfig};
|
||||||
|
use tokio_rustls::{rustls::ClientConfig};
|
||||||
|
use tonic::{Request};
|
||||||
|
|
||||||
|
use threadpool::ThreadPool;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
|
||||||
|
use crate::PubCertificate;
|
||||||
|
use crate::grpc_client::compact_tx_streamer_client::CompactTxStreamerClient;
|
||||||
|
|
||||||
|
mod danger {
|
||||||
|
use tokio_rustls::rustls;
|
||||||
|
use webpki;
|
||||||
|
|
||||||
|
pub struct NoCertificateVerification {}
|
||||||
|
|
||||||
|
impl rustls::ServerCertVerifier for NoCertificateVerification {
|
||||||
|
fn verify_server_cert(&self,
|
||||||
|
_roots: &rustls::RootCertStore,
|
||||||
|
_presented_certs: &[rustls::Certificate],
|
||||||
|
_dns_name: webpki::DNSNameRef<'_>,
|
||||||
|
_ocsp: &[u8]) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
|
||||||
|
Ok(rustls::ServerCertVerified::assertion())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_client(uri: &http::Uri, no_cert: bool) -> Result<CompactTxStreamerClient<Channel>, Box<dyn std::error::Error>> {
|
||||||
|
let channel = if uri.scheme_str() == Some("http") {
|
||||||
|
Channel::builder(uri.clone()).connect().await?
|
||||||
|
} else {
|
||||||
|
let mut config = ClientConfig::new();
|
||||||
|
|
||||||
|
config.alpn_protocols.push(b"h2".to_vec());
|
||||||
|
config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||||
|
config.root_store.add_pem_file(
|
||||||
|
&mut PubCertificate::get("lightwalletd-lite.myhush.pem").unwrap().as_ref()).unwrap();
|
||||||
|
|
||||||
|
if no_cert {
|
||||||
|
config.dangerous()
|
||||||
|
.set_certificate_verifier(Arc::new(danger::NoCertificateVerification {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tls = ClientTlsConfig::new()
|
||||||
|
.rustls_client_config(config)
|
||||||
|
.domain_name(uri.host().unwrap());
|
||||||
|
|
||||||
|
Channel::builder(uri.clone())
|
||||||
|
.tls_config(tls)
|
||||||
|
.connect()
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(CompactTxStreamerClient::new(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==============
|
||||||
|
// GRPC code
|
||||||
|
// ==============
|
||||||
|
async fn get_lightd_info(uri: &http::Uri, no_cert: bool) -> Result<LightdInfo, Box<dyn std::error::Error>> {
|
||||||
|
let mut client = get_client(uri, no_cert).await?;
|
||||||
|
|
||||||
|
let request = Request::new(Empty {});
|
||||||
|
|
||||||
|
let response = client.get_lightd_info(request).await?;
|
||||||
|
|
||||||
|
Ok(response.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_info(uri: &http::Uri, no_cert: bool) -> Result<LightdInfo, String> {
|
||||||
|
let mut rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
rt.block_on(get_lightd_info(uri, no_cert)).map_err( |e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn get_coinsupply_info(uri: &http::Uri, no_cert: bool) -> Result<Coinsupply, Box<dyn std::error::Error>> {
|
||||||
|
let mut client = get_client(uri, no_cert).await?;
|
||||||
|
|
||||||
|
let request = Request::new(Empty {});
|
||||||
|
|
||||||
|
let response = client.get_coinsupply(request).await?;
|
||||||
|
|
||||||
|
Ok(response.into_inner())
|
||||||
|
}
|
||||||
|
pub fn get_coinsupply(uri: http::Uri, no_cert: bool) -> Result<Coinsupply, String> {
|
||||||
|
let mut rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
rt.block_on(get_coinsupply_info(&uri, no_cert)).map_err( |e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_block_range<F : 'static + std::marker::Send>(
|
||||||
|
uri: &http::Uri,
|
||||||
|
start_height: u64,
|
||||||
|
end_height: u64,
|
||||||
|
no_cert: bool,
|
||||||
|
pool: ThreadPool,
|
||||||
|
c: F
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>
|
||||||
|
where F : Fn(&[u8], u64) {
|
||||||
|
let mut client = get_client(uri, no_cert).await?;
|
||||||
|
|
||||||
|
let bs = BlockId { height: start_height, hash: vec![] };
|
||||||
|
let be = BlockId { height: end_height, hash: vec![] };
|
||||||
|
|
||||||
|
let request = Request::new(BlockRange { start: Some(bs), end: Some(be) });
|
||||||
|
|
||||||
|
let (tx, rx) = channel::<Option<CompactBlock>>();
|
||||||
|
let (ftx, frx) = channel();
|
||||||
|
|
||||||
|
pool.execute(move || {
|
||||||
|
while let Ok(Some(block)) = rx.recv() {
|
||||||
|
use prost::Message;
|
||||||
|
let mut encoded_buf = vec![];
|
||||||
|
|
||||||
|
if let Err(e) = block.encode(&mut encoded_buf) {
|
||||||
|
error!("Error encoding block: {:?}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
c(&encoded_buf, block.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = ftx.send(Ok(())) {
|
||||||
|
error!("Error sending completion signal: {:?}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut response = client.get_block_range(request).await?.into_inner();
|
||||||
|
|
||||||
|
while let Some(block) = response.message().await? {
|
||||||
|
if let Err(e) = tx.send(Some(block)) {
|
||||||
|
error!("Error sending block to channel: {:?}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = tx.send(None) {
|
||||||
|
error!("Error sending end signal to channel: {:?}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
frx.iter().take(1).collect::<Result<Vec<()>, String>>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn fetch_blocks<F : 'static + std::marker::Send>(uri: &http::Uri, start_height: u64, end_height: u64, no_cert: bool, pool: ThreadPool, c: F) -> Result<(), String>
|
||||||
|
where F : Fn(&[u8], u64) {
|
||||||
|
let mut rt = tokio::runtime::Runtime::new().map_err(|e| format!("Error creating runtime {:?}", e))?;
|
||||||
|
fetch_blocks_with_runtime(&mut rt, uri, start_height, end_height, no_cert, pool, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_blocks_with_runtime<F : 'static + std::marker::Send>(rt: &mut tokio::runtime::Runtime, uri: &http::Uri, start_height: u64, end_height: u64, no_cert: bool, pool: ThreadPool, c: F) -> Result<(), String>
|
||||||
|
where F : Fn(&[u8], u64) {
|
||||||
|
|
||||||
|
match rt.block_on(get_block_range(uri, start_height, end_height, no_cert, pool, c)) {
|
||||||
|
Ok(o) => Ok(o),
|
||||||
|
Err(e) => {
|
||||||
|
let e = format!("Error fetching blocks {:?}", e);
|
||||||
|
error!("{}", e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_address_txids GRPC call
|
||||||
|
async fn get_address_txids<F : 'static + std::marker::Send>(
|
||||||
|
uri: &http::Uri,
|
||||||
|
address: String,
|
||||||
|
start_height: u64,
|
||||||
|
end_height: u64,
|
||||||
|
no_cert: bool,
|
||||||
|
c: F
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>
|
||||||
|
where F : Fn(&[u8], u64) {
|
||||||
|
|
||||||
|
let mut client = match get_client(uri, no_cert).await {
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error creating client: {:?}", e);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let start = Some(BlockId{ height: start_height, hash: vec!()});
|
||||||
|
let end = Some(BlockId{ height: end_height, hash: vec!()});
|
||||||
|
|
||||||
|
let request = Request::new(TransparentAddressBlockFilter{ address, range: Some(BlockRange{ start, end }) });
|
||||||
|
|
||||||
|
let maybe_response = match client.get_address_txids(request).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error getting address txids: {:?}", e);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut response = maybe_response.into_inner();
|
||||||
|
|
||||||
|
while let Some(tx) = response.message().await? {
|
||||||
|
c(&tx.data, tx.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// function to monitor mempool transactions
|
||||||
|
pub async fn monitor_mempool<F: 'static + std::marker::Send>(
|
||||||
|
uri: &http::Uri,
|
||||||
|
no_cert: bool,
|
||||||
|
mut c: F
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>
|
||||||
|
where
|
||||||
|
F: FnMut(RawTransaction) -> Result<(), Box<dyn std::error::Error>>,
|
||||||
|
{
|
||||||
|
|
||||||
|
let mut client = get_client(uri, no_cert)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Error getting client: {:?}", e))?;
|
||||||
|
|
||||||
|
|
||||||
|
let request = Request::new(Empty {});
|
||||||
|
|
||||||
|
let mut response = client
|
||||||
|
.get_mempool_stream(request)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("{}", e))?
|
||||||
|
.into_inner();
|
||||||
|
|
||||||
|
|
||||||
|
while let Ok(Some(rtx)) = response.message().await {
|
||||||
|
|
||||||
|
if let Err(e) = c(rtx) {
|
||||||
|
info!("Error processing RawTransaction: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_transparent_txids<F : 'static + std::marker::Send>(
|
||||||
|
uri: &http::Uri,
|
||||||
|
address: String,
|
||||||
|
start_height: u64,
|
||||||
|
end_height: u64,
|
||||||
|
no_cert: bool,
|
||||||
|
c: F
|
||||||
|
) -> Result<(), String>
|
||||||
|
where F : Fn(&[u8], u64) {
|
||||||
|
|
||||||
|
let mut rt = match tokio::runtime::Runtime::new() {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
let e = format!("Error creating runtime {:?}", e);
|
||||||
|
error!("{}", e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match rt.block_on(get_address_txids(uri, address.clone(), start_height, end_height, no_cert, c)) {
|
||||||
|
Ok(o) => Ok(o),
|
||||||
|
Err(e) => {
|
||||||
|
let e = format!("Error with get_address_txids runtime {:?}", e);
|
||||||
|
error!("{}", e);
|
||||||
|
return Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_transaction GRPC call
|
||||||
|
async fn get_transaction(uri: &http::Uri, txid: TxId, no_cert: bool)
|
||||||
|
-> Result<RawTransaction, Box<dyn std::error::Error>> {
|
||||||
|
let mut client = get_client(uri, no_cert).await?;
|
||||||
|
let request = Request::new(TxFilter { block: None, index: 0, hash: txid.0.to_vec() });
|
||||||
|
|
||||||
|
let response = client.get_transaction(request).await?;
|
||||||
|
|
||||||
|
Ok(response.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_full_tx(uri: &http::Uri, txid: TxId, no_cert: bool) -> Result<Vec<u8>, String> {
|
||||||
|
let mut rt = match tokio::runtime::Runtime::new() {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
let errstr = format!("Error creating runtime {}", e.to_string());
|
||||||
|
error!("{}", errstr);
|
||||||
|
return Err(errstr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match rt.block_on(get_transaction(uri, txid, no_cert)) {
|
||||||
|
Ok(rawtx) => Ok(rawtx.data.to_vec()),
|
||||||
|
Err(e) => {
|
||||||
|
let errstr = format!("Error in get_transaction runtime {}", e.to_string());
|
||||||
|
error!("{}", errstr);
|
||||||
|
Err(errstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send_transaction GRPC call
|
||||||
|
async fn send_transaction(uri: &http::Uri, no_cert: bool, tx_bytes: Box<[u8]>) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let mut client = get_client(uri, no_cert).await?;
|
||||||
|
|
||||||
|
let request = Request::new(RawTransaction {data: tx_bytes.to_vec(), height: 0});
|
||||||
|
|
||||||
|
let response = client.send_transaction(request).await?;
|
||||||
|
|
||||||
|
let sendresponse = response.into_inner();
|
||||||
|
if sendresponse.error_code == 0 {
|
||||||
|
let mut txid = sendresponse.error_message;
|
||||||
|
if txid.starts_with("\"") && txid.ends_with("\"") {
|
||||||
|
txid = txid[1..txid.len()-1].to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(txid)
|
||||||
|
} else {
|
||||||
|
Err(Box::from(format!("Error: {:?}", sendresponse)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn broadcast_raw_tx(uri: &http::Uri, no_cert: bool, tx_bytes: Box<[u8]>) -> Result<String, String> {
|
||||||
|
let mut rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
rt.block_on(send_transaction(uri, no_cert, tx_bytes)).map_err( |e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_latest_block GRPC call
|
||||||
|
async fn get_latest_block(uri: &http::Uri, no_cert: bool) -> Result<BlockId, Box<dyn std::error::Error>> {
|
||||||
|
let mut client = get_client(uri, no_cert).await?;
|
||||||
|
|
||||||
|
let request = Request::new(ChainSpec {});
|
||||||
|
|
||||||
|
let response = client.get_latest_block(request).await?;
|
||||||
|
|
||||||
|
Ok(response.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_latest_block(uri: &http::Uri, no_cert: bool) -> Result<BlockId, String> {
|
||||||
|
let mut rt = match tokio::runtime::Runtime::new() {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
let errstr = format!("Error creating runtime {}", e.to_string());
|
||||||
|
error!("{}", errstr);
|
||||||
|
return Err(errstr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rt.block_on(get_latest_block(uri, no_cert)).map_err(|e| {
|
||||||
|
let errstr = format!("Error getting latest block {}", e.to_string());
|
||||||
|
error!("{}", errstr);
|
||||||
|
errstr
|
||||||
|
})
|
||||||
|
}
|
||||||
29
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lib.rs
vendored
Normal file
29
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lib.rs
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright The Hush Developers 2019-2022
|
||||||
|
// Released under the GPLv3
|
||||||
|
#[macro_use]
|
||||||
|
extern crate rust_embed;
|
||||||
|
|
||||||
|
pub mod lightclient;
|
||||||
|
pub mod grpcconnector;
|
||||||
|
pub mod lightwallet;
|
||||||
|
pub mod commands;
|
||||||
|
|
||||||
|
#[cfg(feature = "embed_params")]
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "zcash-params/"]
|
||||||
|
pub struct SaplingParams;
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "res/"]
|
||||||
|
pub struct PubCertificate;
|
||||||
|
|
||||||
|
|
||||||
|
// Anchor depth back from the tip for shielded spends. MUST be > 0: with 0 the wallet anchors to
|
||||||
|
// the absolute chain tip, which races the node committing that anchor and intermittently triggers
|
||||||
|
// "bad-txns-shielded-requirements-not-met" (missing sapling anchor) on broadcast. 4 matches upstream
|
||||||
|
// zecwallet-lite and uses a confirmed, reorg-stable anchor.
|
||||||
|
pub const ANCHOR_OFFSET: u32 = 4;
|
||||||
|
|
||||||
|
pub mod grpc_client {
|
||||||
|
tonic::include_proto!("cash.z.wallet.sdk.rpc");
|
||||||
|
}
|
||||||
1920
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient.rs
vendored
Normal file
1920
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1021
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient/checkpoints.rs
vendored
Normal file
1021
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient/checkpoints.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2600
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet.rs
vendored
Normal file
2600
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
46
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/address.rs
vendored
Normal file
46
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/address.rs
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//! Structs for handling supported address types.
|
||||||
|
|
||||||
|
use pairing::bls12_381::Bls12;
|
||||||
|
use zcash_primitives::primitives::PaymentAddress;
|
||||||
|
use zcash_client_backend::encoding::{decode_payment_address, decode_transparent_address};
|
||||||
|
use zcash_primitives::legacy::TransparentAddress;
|
||||||
|
|
||||||
|
/// An address that funds can be sent to.
|
||||||
|
pub enum RecipientAddress {
|
||||||
|
Shielded(PaymentAddress<Bls12>),
|
||||||
|
Transparent(TransparentAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PaymentAddress<Bls12>> for RecipientAddress {
|
||||||
|
fn from(addr: PaymentAddress<Bls12>) -> Self {
|
||||||
|
RecipientAddress::Shielded(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TransparentAddress> for RecipientAddress {
|
||||||
|
fn from(addr: TransparentAddress) -> Self {
|
||||||
|
RecipientAddress::Transparent(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecipientAddress {
|
||||||
|
pub fn from_str(s: &str, hrp_sapling_address: &str, b58_pubkey_address: [u8; 1], b58_script_address: [u8; 1]) -> Option<Self> {
|
||||||
|
// Try to match a sapling z address
|
||||||
|
if let Some(pa) = match decode_payment_address(hrp_sapling_address, s) {
|
||||||
|
Ok(ret) => ret,
|
||||||
|
Err(_) => None
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Some(RecipientAddress::Shielded(pa)) // Matched a shielded address
|
||||||
|
} else if let Some(addr) = match decode_transparent_address(
|
||||||
|
&b58_pubkey_address, &b58_script_address, s) {
|
||||||
|
Ok(ret) => ret,
|
||||||
|
Err(_) => None
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Some(RecipientAddress::Transparent(addr)) // Matched a transparent address
|
||||||
|
} else {
|
||||||
|
None // Didn't match anything
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
558
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/data.rs
vendored
Normal file
558
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/data.rs
vendored
Normal file
@@ -0,0 +1,558 @@
|
|||||||
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use pairing::bls12_381::{Bls12};
|
||||||
|
use ff::{PrimeField, PrimeFieldRepr};
|
||||||
|
|
||||||
|
use zcash_primitives::{
|
||||||
|
block::BlockHash,
|
||||||
|
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||||
|
sapling::Node,
|
||||||
|
serialize::{Vector, Optional},
|
||||||
|
transaction::{
|
||||||
|
components::{OutPoint},
|
||||||
|
TxId,
|
||||||
|
},
|
||||||
|
note_encryption::{Memo,},
|
||||||
|
zip32::{ExtendedFullViewingKey,},
|
||||||
|
JUBJUB,
|
||||||
|
primitives::{Diversifier, Note,},
|
||||||
|
jubjub::{
|
||||||
|
JubjubEngine,
|
||||||
|
fs::{Fs, FsRepr},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
use zcash_primitives::zip32::ExtendedSpendingKey;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct BlockData {
|
||||||
|
pub height: i32,
|
||||||
|
pub hash: BlockHash,
|
||||||
|
pub tree: CommitmentTree<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockData {
|
||||||
|
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||||
|
let height = reader.read_i32::<LittleEndian>()?;
|
||||||
|
|
||||||
|
let mut hash_bytes = [0; 32];
|
||||||
|
reader.read_exact(&mut hash_bytes)?;
|
||||||
|
|
||||||
|
let tree = CommitmentTree::<Node>::read(&mut reader)?;
|
||||||
|
|
||||||
|
let endtag = reader.read_u64::<LittleEndian>()?;
|
||||||
|
if endtag != 11 {
|
||||||
|
println!("End tag for blockdata {}", endtag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Ok(BlockData{
|
||||||
|
height,
|
||||||
|
hash: BlockHash{ 0: hash_bytes },
|
||||||
|
tree
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
|
writer.write_i32::<LittleEndian>(self.height)?;
|
||||||
|
writer.write_all(&self.hash.0)?;
|
||||||
|
self.tree.write(&mut writer)?;
|
||||||
|
writer.write_u64::<LittleEndian>(11)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SaplingNoteData {
|
||||||
|
pub(super) account: usize,
|
||||||
|
pub(super) extfvk: ExtendedFullViewingKey, // Technically, this should be recoverable from the account number, but we're going to refactor this in the future, so I'll write it again here.
|
||||||
|
pub diversifier: Diversifier,
|
||||||
|
pub note: Note<Bls12>,
|
||||||
|
pub(super) witnesses: Vec<IncrementalWitness<Node>>,
|
||||||
|
pub(super) nullifier: [u8; 32],
|
||||||
|
pub spent: Option<TxId>, // If this note was confirmed spent
|
||||||
|
pub unconfirmed_spent: Option<TxId>, // If this note was spent in a send, but has not yet been confirmed.
|
||||||
|
pub memo: Option<Memo>,
|
||||||
|
pub is_change: bool,
|
||||||
|
// TODO: We need to remove the unconfirmed_spent (i.e., set it to None) if the Tx has expired
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Reads an FsRepr from [u8] of length 32
|
||||||
|
/// This will panic (abort) if length provided is
|
||||||
|
/// not correct
|
||||||
|
/// TODO: This is duplicate from rustzcash.rs
|
||||||
|
fn read_fs(from: &[u8]) -> FsRepr {
|
||||||
|
assert_eq!(from.len(), 32);
|
||||||
|
|
||||||
|
let mut f = <<Bls12 as JubjubEngine>::Fs as PrimeField>::Repr::default();
|
||||||
|
f.read_le(from).expect("length is 32 bytes");
|
||||||
|
|
||||||
|
f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading a note also needs the corresponding address to read from.
|
||||||
|
pub fn read_note<R: Read>(mut reader: R) -> io::Result<(u64, Fs)> {
|
||||||
|
let value = reader.read_u64::<LittleEndian>()?;
|
||||||
|
|
||||||
|
let mut r_bytes: [u8; 32] = [0; 32];
|
||||||
|
reader.read_exact(&mut r_bytes)?;
|
||||||
|
|
||||||
|
let r = match Fs::from_repr(read_fs(&r_bytes)) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(_) => return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput, "Couldn't parse randomness"))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((value, r))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SaplingNoteData {
|
||||||
|
fn serialized_version() -> u64 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
extfvk: &ExtendedFullViewingKey,
|
||||||
|
output: zcash_client_backend::wallet::WalletShieldedOutput
|
||||||
|
) -> Self {
|
||||||
|
let witness = output.witness;
|
||||||
|
let nf = {
|
||||||
|
let mut nf = [0; 32];
|
||||||
|
nf.copy_from_slice(
|
||||||
|
&output
|
||||||
|
.note
|
||||||
|
.nf(&extfvk.fvk.vk, witness.position() as u64, &JUBJUB),
|
||||||
|
);
|
||||||
|
nf
|
||||||
|
};
|
||||||
|
|
||||||
|
SaplingNoteData {
|
||||||
|
account: output.account,
|
||||||
|
extfvk: extfvk.clone(),
|
||||||
|
diversifier: output.to.diversifier,
|
||||||
|
note: output.note,
|
||||||
|
witnesses: vec![witness],
|
||||||
|
nullifier: nf,
|
||||||
|
spent: None,
|
||||||
|
unconfirmed_spent: None,
|
||||||
|
memo: None,
|
||||||
|
is_change: output.is_change,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading a note also needs the corresponding address to read from.
|
||||||
|
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||||
|
let _version = reader.read_u64::<LittleEndian>()?;
|
||||||
|
|
||||||
|
let account = reader.read_u64::<LittleEndian>()? as usize;
|
||||||
|
|
||||||
|
let extfvk = ExtendedFullViewingKey::read(&mut reader)?;
|
||||||
|
|
||||||
|
let mut diversifier_bytes = [0u8; 11];
|
||||||
|
reader.read_exact(&mut diversifier_bytes)?;
|
||||||
|
let diversifier = Diversifier{0: diversifier_bytes};
|
||||||
|
|
||||||
|
// To recover the note, read the value and r, and then use the payment address
|
||||||
|
// to recreate the note
|
||||||
|
let (value, r) = read_note(&mut reader)?; // TODO: This method is in a different package, because of some fields that are private
|
||||||
|
|
||||||
|
let maybe_note = extfvk.fvk.vk.into_payment_address(diversifier, &JUBJUB).unwrap().create_note(value, r, &JUBJUB);
|
||||||
|
|
||||||
|
let note = match maybe_note {
|
||||||
|
Some(n) => Ok(n),
|
||||||
|
None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the note for the address"))
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let witnesses = Vector::read(&mut reader, |r| IncrementalWitness::<Node>::read(r))?;
|
||||||
|
|
||||||
|
let mut nullifier = [0u8; 32];
|
||||||
|
reader.read_exact(&mut nullifier)?;
|
||||||
|
|
||||||
|
// Note that this is only the spent field, we ignore the unconfirmed_spent field.
|
||||||
|
// The reason is that unconfirmed spents are only in memory, and we need to get the actual value of spent
|
||||||
|
// from the blockchain anyway.
|
||||||
|
let spent = Optional::read(&mut reader, |r| {
|
||||||
|
let mut txid_bytes = [0u8; 32];
|
||||||
|
r.read_exact(&mut txid_bytes)?;
|
||||||
|
Ok(TxId{0: txid_bytes})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let memo = Optional::read(&mut reader, |r| {
|
||||||
|
let mut memo_bytes = [0u8; 512];
|
||||||
|
r.read_exact(&mut memo_bytes)?;
|
||||||
|
match Memo::from_bytes(&memo_bytes) {
|
||||||
|
Some(m) => Ok(m),
|
||||||
|
None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the memo"))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let is_change: bool = reader.read_u8()? > 0;
|
||||||
|
|
||||||
|
Ok(SaplingNoteData {
|
||||||
|
account,
|
||||||
|
extfvk,
|
||||||
|
diversifier,
|
||||||
|
note,
|
||||||
|
witnesses,
|
||||||
|
nullifier,
|
||||||
|
spent,
|
||||||
|
unconfirmed_spent: None,
|
||||||
|
memo,
|
||||||
|
is_change,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
|
// Write a version number first, so we can later upgrade this if needed.
|
||||||
|
writer.write_u64::<LittleEndian>(SaplingNoteData::serialized_version())?;
|
||||||
|
|
||||||
|
writer.write_u64::<LittleEndian>(self.account as u64)?;
|
||||||
|
|
||||||
|
self.extfvk.write(&mut writer)?;
|
||||||
|
|
||||||
|
writer.write_all(&self.diversifier.0)?;
|
||||||
|
|
||||||
|
// Writing the note means writing the note.value and note.r. The Note is recoverable
|
||||||
|
// from these 2 values and the Payment address.
|
||||||
|
writer.write_u64::<LittleEndian>(self.note.value)?;
|
||||||
|
|
||||||
|
let mut rcm = [0; 32];
|
||||||
|
self.note.r.into_repr().write_le(&mut rcm[..])?;
|
||||||
|
writer.write_all(&rcm)?;
|
||||||
|
|
||||||
|
Vector::write(&mut writer, &self.witnesses, |wr, wi| wi.write(wr) )?;
|
||||||
|
|
||||||
|
writer.write_all(&self.nullifier)?;
|
||||||
|
Optional::write(&mut writer, &self.spent, |w, t| w.write_all(&t.0))?;
|
||||||
|
|
||||||
|
Optional::write(&mut writer, &self.memo, |w, m| w.write_all(m.as_bytes()))?;
|
||||||
|
|
||||||
|
writer.write_u8(if self.is_change {1} else {0})?;
|
||||||
|
|
||||||
|
// Note that we don't write the unconfirmed_spent field, because if the wallet is restarted,
|
||||||
|
// we don't want to be beholden to any expired txns
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Utxo {
|
||||||
|
pub address: String,
|
||||||
|
pub txid: TxId,
|
||||||
|
pub output_index: u64,
|
||||||
|
pub script: Vec<u8>,
|
||||||
|
pub value: u64,
|
||||||
|
pub height: i32,
|
||||||
|
|
||||||
|
pub spent: Option<TxId>, // If this utxo was confirmed spent
|
||||||
|
pub unconfirmed_spent: Option<TxId>, // If this utxo was spent in a send, but has not yet been confirmed.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Utxo {
|
||||||
|
pub fn serialized_version() -> u64 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_outpoint(&self) -> OutPoint {
|
||||||
|
OutPoint { hash: self.txid.0, n: self.output_index as u32 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||||
|
let version = reader.read_u64::<LittleEndian>()?;
|
||||||
|
assert_eq!(version, Utxo::serialized_version());
|
||||||
|
|
||||||
|
let address_len = reader.read_i32::<LittleEndian>()?;
|
||||||
|
let mut address_bytes = vec![0; address_len as usize];
|
||||||
|
reader.read_exact(&mut address_bytes)?;
|
||||||
|
let address = String::from_utf8(address_bytes).unwrap();
|
||||||
|
assert_eq!(address.chars().take(1).collect::<Vec<char>>()[0], 'R');
|
||||||
|
|
||||||
|
let mut txid_bytes = [0; 32];
|
||||||
|
reader.read_exact(&mut txid_bytes)?;
|
||||||
|
let txid = TxId { 0: txid_bytes };
|
||||||
|
|
||||||
|
let output_index = reader.read_u64::<LittleEndian>()?;
|
||||||
|
let value = reader.read_u64::<LittleEndian>()?;
|
||||||
|
let height = reader.read_i32::<LittleEndian>()?;
|
||||||
|
|
||||||
|
let script = Vector::read(&mut reader, |r| {
|
||||||
|
let mut byte = [0; 1];
|
||||||
|
r.read_exact(&mut byte)?;
|
||||||
|
Ok(byte[0])
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let spent = Optional::read(&mut reader, |r| {
|
||||||
|
let mut txbytes = [0u8; 32];
|
||||||
|
r.read_exact(&mut txbytes)?;
|
||||||
|
Ok(TxId{0: txbytes})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff.
|
||||||
|
|
||||||
|
Ok(Utxo {
|
||||||
|
address,
|
||||||
|
txid,
|
||||||
|
output_index,
|
||||||
|
script,
|
||||||
|
value,
|
||||||
|
height,
|
||||||
|
spent,
|
||||||
|
unconfirmed_spent: None::<TxId>,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
|
writer.write_u64::<LittleEndian>(Utxo::serialized_version())?;
|
||||||
|
|
||||||
|
writer.write_u32::<LittleEndian>(self.address.as_bytes().len() as u32)?;
|
||||||
|
writer.write_all(self.address.as_bytes())?;
|
||||||
|
|
||||||
|
writer.write_all(&self.txid.0)?;
|
||||||
|
|
||||||
|
writer.write_u64::<LittleEndian>(self.output_index)?;
|
||||||
|
writer.write_u64::<LittleEndian>(self.value)?;
|
||||||
|
writer.write_i32::<LittleEndian>(self.height)?;
|
||||||
|
|
||||||
|
Vector::write(&mut writer, &self.script, |w, b| w.write_all(&[*b]))?;
|
||||||
|
|
||||||
|
Optional::write(&mut writer, &self.spent, |w, txid| w.write_all(&txid.0))?;
|
||||||
|
|
||||||
|
// Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff.
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OutgoingTxMetadata {
|
||||||
|
pub address: String,
|
||||||
|
pub value : u64,
|
||||||
|
pub memo : Memo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutgoingTxMetadata {
|
||||||
|
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||||
|
let address_len = reader.read_u64::<LittleEndian>()?;
|
||||||
|
let mut address_bytes = vec![0; address_len as usize];
|
||||||
|
reader.read_exact(&mut address_bytes)?;
|
||||||
|
let address = String::from_utf8(address_bytes).unwrap();
|
||||||
|
|
||||||
|
let value = reader.read_u64::<LittleEndian>()?;
|
||||||
|
|
||||||
|
let mut memo_bytes = [0u8; 512];
|
||||||
|
reader.read_exact(&mut memo_bytes)?;
|
||||||
|
let memo = Memo::from_bytes(&memo_bytes).unwrap();
|
||||||
|
|
||||||
|
Ok(OutgoingTxMetadata{
|
||||||
|
address,
|
||||||
|
value,
|
||||||
|
memo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
|
// Strings are written as len + utf8
|
||||||
|
writer.write_u64::<LittleEndian>(self.address.as_bytes().len() as u64)?;
|
||||||
|
writer.write_all(self.address.as_bytes())?;
|
||||||
|
|
||||||
|
writer.write_u64::<LittleEndian>(self.value)?;
|
||||||
|
writer.write_all(self.memo.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IncomingTxMetadata {
|
||||||
|
pub address: String,
|
||||||
|
pub value : u64,
|
||||||
|
pub memo : Memo,
|
||||||
|
pub incoming_mempool: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IncomingTxMetadata {
|
||||||
|
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||||
|
let address_len = reader.read_u64::<LittleEndian>()?;
|
||||||
|
let mut address_bytes = vec![0; address_len as usize];
|
||||||
|
reader.read_exact(&mut address_bytes)?;
|
||||||
|
let address = String::from_utf8(address_bytes).unwrap();
|
||||||
|
|
||||||
|
let value = reader.read_u64::<LittleEndian>()?;
|
||||||
|
let incoming_mempool = true;
|
||||||
|
// let position = 0;
|
||||||
|
|
||||||
|
let mut memo_bytes = [0u8; 512];
|
||||||
|
reader.read_exact(&mut memo_bytes)?;
|
||||||
|
let memo = Memo::from_bytes(&memo_bytes).unwrap();
|
||||||
|
|
||||||
|
Ok(IncomingTxMetadata{
|
||||||
|
address,
|
||||||
|
value,
|
||||||
|
memo,
|
||||||
|
incoming_mempool,
|
||||||
|
// position,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
|
// Strings are written as len + utf8
|
||||||
|
writer.write_u64::<LittleEndian>(self.address.as_bytes().len() as u64)?;
|
||||||
|
writer.write_all(self.address.as_bytes())?;
|
||||||
|
|
||||||
|
writer.write_u64::<LittleEndian>(self.value)?;
|
||||||
|
writer.write_all(self.memo.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WalletTx {
|
||||||
|
// Block in which this tx was included
|
||||||
|
pub block: i32,
|
||||||
|
|
||||||
|
// Timestamp of Tx. Added in v4
|
||||||
|
pub datetime: u64,
|
||||||
|
|
||||||
|
// Txid of this transaction. It's duplicated here (It is also the Key in the HashMap that points to this
|
||||||
|
// WalletTx in LightWallet::txs)
|
||||||
|
pub txid: TxId,
|
||||||
|
|
||||||
|
// List of all notes received in this tx. Some of these might be change notes.
|
||||||
|
pub notes: Vec<SaplingNoteData>,
|
||||||
|
|
||||||
|
// List of all Utxos received in this Tx. Some of these might be change notes
|
||||||
|
pub utxos: Vec<Utxo>,
|
||||||
|
|
||||||
|
// Total shielded value spent in this Tx. Note that this is the value of the wallet's notes spent.
|
||||||
|
// Some change may be returned in one of the notes above. Subtract the two to get the actual value spent.
|
||||||
|
// Also note that even after subtraction, you might need to account for transparent inputs and outputs
|
||||||
|
// to make sure the value is accurate.
|
||||||
|
pub total_shielded_value_spent: u64,
|
||||||
|
|
||||||
|
// Total amount of transparent funds that belong to us that were spent in this Tx.
|
||||||
|
pub total_transparent_value_spent : u64,
|
||||||
|
|
||||||
|
// All outgoing sapling sends to addresses outside this wallet
|
||||||
|
pub outgoing_metadata: Vec<OutgoingTxMetadata>,
|
||||||
|
|
||||||
|
pub incoming_metadata: Vec<IncomingTxMetadata>,
|
||||||
|
|
||||||
|
// Whether this TxID was downloaded from the server and scanned for Memos
|
||||||
|
pub full_tx_scanned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletTx {
|
||||||
|
pub fn serialized_version() -> u64 {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(height: i32, datetime: u64, txid: &TxId) -> Self {
|
||||||
|
WalletTx {
|
||||||
|
block: height,
|
||||||
|
datetime,
|
||||||
|
txid: txid.clone(),
|
||||||
|
notes: vec![],
|
||||||
|
utxos: vec![],
|
||||||
|
total_shielded_value_spent: 0,
|
||||||
|
total_transparent_value_spent: 0,
|
||||||
|
outgoing_metadata: vec![],
|
||||||
|
incoming_metadata: vec![],
|
||||||
|
full_tx_scanned: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||||
|
let version = reader.read_u64::<LittleEndian>()?;
|
||||||
|
assert!(version <= WalletTx::serialized_version(), "Version mismatch. Please restore with your Seed");
|
||||||
|
|
||||||
|
let block = reader.read_i32::<LittleEndian>()?;
|
||||||
|
let datetime = if version >= 4 {
|
||||||
|
reader.read_u64::<LittleEndian>()?
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut txid_bytes = [0u8; 32];
|
||||||
|
reader.read_exact(&mut txid_bytes)?;
|
||||||
|
let txid = TxId{0: txid_bytes};
|
||||||
|
|
||||||
|
let notes = Vector::read(&mut reader, |r| SaplingNoteData::read(r))?;
|
||||||
|
let utxos = Vector::read(&mut reader, |r| Utxo::read(r))?;
|
||||||
|
let total_shielded_value_spent = reader.read_u64::<LittleEndian>()?;
|
||||||
|
let total_transparent_value_spent = reader.read_u64::<LittleEndian>()?;
|
||||||
|
let outgoing_metadata = Vector::read(&mut reader, |r| OutgoingTxMetadata::read(r))?;
|
||||||
|
|
||||||
|
// Read incoming_metadata only if version is 5 or higher
|
||||||
|
let incoming_metadata = if version >= 5 {
|
||||||
|
Vector::read(&mut reader, |r| IncomingTxMetadata::read(r))?
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
let full_tx_scanned = reader.read_u8()? > 0;
|
||||||
|
|
||||||
|
Ok(WalletTx {
|
||||||
|
block,
|
||||||
|
datetime,
|
||||||
|
txid,
|
||||||
|
notes,
|
||||||
|
utxos,
|
||||||
|
total_shielded_value_spent,
|
||||||
|
total_transparent_value_spent,
|
||||||
|
outgoing_metadata,
|
||||||
|
incoming_metadata,
|
||||||
|
full_tx_scanned
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
|
writer.write_u64::<LittleEndian>(WalletTx::serialized_version())?;
|
||||||
|
|
||||||
|
writer.write_i32::<LittleEndian>(self.block)?;
|
||||||
|
|
||||||
|
writer.write_u64::<LittleEndian>(self.datetime)?;
|
||||||
|
|
||||||
|
writer.write_all(&self.txid.0)?;
|
||||||
|
|
||||||
|
Vector::write(&mut writer, &self.notes, |w, nd| nd.write(w))?;
|
||||||
|
Vector::write(&mut writer, &self.utxos, |w, u| u.write(w))?;
|
||||||
|
|
||||||
|
writer.write_u64::<LittleEndian>(self.total_shielded_value_spent)?;
|
||||||
|
writer.write_u64::<LittleEndian>(self.total_transparent_value_spent)?;
|
||||||
|
|
||||||
|
// Write the outgoing metadata
|
||||||
|
Vector::write(&mut writer, &self.outgoing_metadata, |w, om| om.write(w))?;
|
||||||
|
Vector::write(&mut writer, &self.incoming_metadata, |w, om| om.write(w))?;
|
||||||
|
|
||||||
|
writer.write_u8(if self.full_tx_scanned {1} else {0})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SpendableNote {
|
||||||
|
pub txid: TxId,
|
||||||
|
pub nullifier: [u8; 32],
|
||||||
|
pub diversifier: Diversifier,
|
||||||
|
pub note: Note<Bls12>,
|
||||||
|
pub witness: IncrementalWitness<Node>,
|
||||||
|
pub extsk: ExtendedSpendingKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpendableNote {
|
||||||
|
pub fn from(txid: TxId, nd: &SaplingNoteData, anchor_offset: usize, extsk: &Option<ExtendedSpendingKey>) -> Option<Self> {
|
||||||
|
// Include only notes that haven't been spent, or haven't been included in an unconfirmed spend yet.
|
||||||
|
if nd.spent.is_none() && nd.unconfirmed_spent.is_none() && extsk.is_some() &&
|
||||||
|
nd.witnesses.len() >= (anchor_offset + 1) {
|
||||||
|
let witness = nd.witnesses.get(nd.witnesses.len() - anchor_offset - 1);
|
||||||
|
|
||||||
|
witness.map(|w| SpendableNote {
|
||||||
|
txid,
|
||||||
|
nullifier: nd.nullifier,
|
||||||
|
diversifier: nd.diversifier,
|
||||||
|
note: nd.note.clone(),
|
||||||
|
witness: w.clone(),
|
||||||
|
extsk: extsk.clone().unwrap(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
126
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/extended_key.rs
vendored
Normal file
126
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/extended_key.rs
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
use ring::{
|
||||||
|
hmac::{self, Context, Key},
|
||||||
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use secp256k1::{PublicKey, Secp256k1, SecretKey, SignOnly, VerifyOnly, Error};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref SECP256K1_SIGN_ONLY: Secp256k1<SignOnly> = Secp256k1::signing_only();
|
||||||
|
static ref SECP256K1_VERIFY_ONLY: Secp256k1<VerifyOnly> = Secp256k1::verification_only();
|
||||||
|
}
|
||||||
|
/// Random entropy, part of extended key.
|
||||||
|
type ChainCode = Vec<u8>;
|
||||||
|
|
||||||
|
|
||||||
|
const HARDENED_KEY_START_INDEX: u32 = 2_147_483_648; // 2 ** 31
|
||||||
|
|
||||||
|
/// KeyIndex indicates the key type and index of a child key.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum KeyIndex {
|
||||||
|
/// Normal key, index range is from 0 to 2 ** 31 - 1
|
||||||
|
Normal(u32),
|
||||||
|
/// Hardened key, index range is from 2 ** 31 to 2 ** 32 - 1
|
||||||
|
Hardened(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyIndex {
|
||||||
|
|
||||||
|
/// Check index range.
|
||||||
|
pub fn is_valid(self) -> bool {
|
||||||
|
match self {
|
||||||
|
KeyIndex::Normal(i) => i < HARDENED_KEY_START_INDEX,
|
||||||
|
KeyIndex::Hardened(i) => i >= HARDENED_KEY_START_INDEX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate Hardened KeyIndex from normalize index value.
|
||||||
|
pub fn hardened_from_normalize_index(i: u32) -> Result<KeyIndex, Error> {
|
||||||
|
if i < HARDENED_KEY_START_INDEX {
|
||||||
|
Ok(KeyIndex::Hardened(HARDENED_KEY_START_INDEX + i))
|
||||||
|
} else {
|
||||||
|
Ok(KeyIndex::Hardened(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate KeyIndex from raw index value.
|
||||||
|
pub fn from_index(i: u32) -> Result<Self, Error> {
|
||||||
|
if i < HARDENED_KEY_START_INDEX {
|
||||||
|
Ok(KeyIndex::Normal(i))
|
||||||
|
} else {
|
||||||
|
Ok(KeyIndex::Hardened(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for KeyIndex {
|
||||||
|
fn from(index: u32) -> Self {
|
||||||
|
KeyIndex::from_index(index).expect("KeyIndex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// ExtendedPrivKey is used for child key derivation.
|
||||||
|
/// See [secp256k1 crate documentation](https://docs.rs/secp256k1) for SecretKey signatures usage.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ExtendedPrivKey {
|
||||||
|
pub private_key: SecretKey,
|
||||||
|
pub chain_code: ChainCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ExtendedPrivKey {
|
||||||
|
|
||||||
|
/// Generate an ExtendedPrivKey from seed
|
||||||
|
pub fn with_seed(seed: &[u8]) -> Result<ExtendedPrivKey, Error> {
|
||||||
|
let signature = {
|
||||||
|
let signing_key = Key::new(hmac::HMAC_SHA512, b"Bitcoin seed");
|
||||||
|
let mut h = Context::with_key(&signing_key);
|
||||||
|
h.update(&seed);
|
||||||
|
h.sign()
|
||||||
|
};
|
||||||
|
let sig_bytes = signature.as_ref();
|
||||||
|
let (key, chain_code) = sig_bytes.split_at(sig_bytes.len() / 2);
|
||||||
|
let private_key = SecretKey::from_slice(key)?;
|
||||||
|
Ok(ExtendedPrivKey {
|
||||||
|
private_key,
|
||||||
|
chain_code: chain_code.to_vec(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_hardended_key(&self, index: u32) -> ring::hmac::Tag {
|
||||||
|
let signing_key = Key::new(hmac::HMAC_SHA512, &self.chain_code);
|
||||||
|
let mut h = Context::with_key(&signing_key);
|
||||||
|
h.update(&[0x00]);
|
||||||
|
h.update(&self.private_key[..]);
|
||||||
|
h.update(&index.to_be_bytes());
|
||||||
|
h.sign()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_normal_key(&self, index: u32) -> ring::hmac::Tag {
|
||||||
|
let signing_key = Key::new(hmac::HMAC_SHA512, &self.chain_code);
|
||||||
|
let mut h = Context::with_key(&signing_key);
|
||||||
|
let public_key = PublicKey::from_secret_key(&SECP256K1_SIGN_ONLY, &self.private_key);
|
||||||
|
h.update(&public_key.serialize());
|
||||||
|
h.update(&index.to_be_bytes());
|
||||||
|
h.sign()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive a child key from ExtendedPrivKey.
|
||||||
|
pub fn derive_private_key(&self, key_index: KeyIndex) -> Result<ExtendedPrivKey, Error> {
|
||||||
|
if !key_index.is_valid() {
|
||||||
|
return Err(Error::InvalidTweak);
|
||||||
|
}
|
||||||
|
let signature = match key_index {
|
||||||
|
KeyIndex::Hardened(index) => self.sign_hardended_key(index),
|
||||||
|
KeyIndex::Normal(index) => self.sign_normal_key(index),
|
||||||
|
};
|
||||||
|
let sig_bytes = signature.as_ref();
|
||||||
|
let (key, chain_code) = sig_bytes.split_at(sig_bytes.len() / 2);
|
||||||
|
let mut private_key = SecretKey::from_slice(key)?;
|
||||||
|
private_key.add_assign(&self.private_key[..])?;
|
||||||
|
Ok(ExtendedPrivKey {
|
||||||
|
private_key,
|
||||||
|
chain_code: chain_code.to_vec(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
123
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/prover.rs
vendored
Normal file
123
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/prover.rs
vendored
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
//! Abstractions over the proving system and parameters for ease of use.
|
||||||
|
|
||||||
|
use bellman::groth16::{prepare_verifying_key, Parameters, PreparedVerifyingKey};
|
||||||
|
use pairing::bls12_381::{Bls12, Fr};
|
||||||
|
use zcash_primitives::{
|
||||||
|
jubjub::{edwards, fs::Fs, Unknown},
|
||||||
|
primitives::{Diversifier, PaymentAddress, ProofGenerationKey},
|
||||||
|
redjubjub::{PublicKey, Signature},
|
||||||
|
transaction::components::Amount
|
||||||
|
};
|
||||||
|
use zcash_primitives::{
|
||||||
|
merkle_tree::CommitmentTreeWitness, prover::TxProver, sapling::Node,
|
||||||
|
transaction::components::GROTH_PROOF_SIZE, JUBJUB,
|
||||||
|
};
|
||||||
|
use zcash_proofs::sapling::SaplingProvingContext;
|
||||||
|
|
||||||
|
/// An implementation of [`TxProver`] using Sapling Spend and Output parameters provided
|
||||||
|
/// in-memory.
|
||||||
|
pub struct InMemTxProver {
|
||||||
|
spend_params: Parameters<Bls12>,
|
||||||
|
spend_vk: PreparedVerifyingKey<Bls12>,
|
||||||
|
output_params: Parameters<Bls12>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InMemTxProver {
|
||||||
|
pub fn new(spend_params: &[u8], output_params: &[u8]) -> Self {
|
||||||
|
// Deserialize params
|
||||||
|
let spend_params = Parameters::<Bls12>::read(spend_params, false)
|
||||||
|
.expect("couldn't deserialize Sapling spend parameters file");
|
||||||
|
let output_params = Parameters::<Bls12>::read(output_params, false)
|
||||||
|
.expect("couldn't deserialize Sapling spend parameters file");
|
||||||
|
|
||||||
|
// Prepare verifying keys
|
||||||
|
let spend_vk = prepare_verifying_key(&spend_params.vk);
|
||||||
|
|
||||||
|
InMemTxProver {
|
||||||
|
spend_params,
|
||||||
|
spend_vk,
|
||||||
|
output_params,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxProver for InMemTxProver {
|
||||||
|
type SaplingProvingContext = SaplingProvingContext;
|
||||||
|
|
||||||
|
fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext {
|
||||||
|
SaplingProvingContext::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spend_proof(
|
||||||
|
&self,
|
||||||
|
ctx: &mut Self::SaplingProvingContext,
|
||||||
|
proof_generation_key: ProofGenerationKey<Bls12>,
|
||||||
|
diversifier: Diversifier,
|
||||||
|
rcm: Fs,
|
||||||
|
ar: Fs,
|
||||||
|
value: u64,
|
||||||
|
anchor: Fr,
|
||||||
|
witness: CommitmentTreeWitness<Node>,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
[u8; GROTH_PROOF_SIZE],
|
||||||
|
edwards::Point<Bls12, Unknown>,
|
||||||
|
PublicKey<Bls12>,
|
||||||
|
),
|
||||||
|
(),
|
||||||
|
> {
|
||||||
|
let (proof, cv, rk) = ctx.spend_proof(
|
||||||
|
proof_generation_key,
|
||||||
|
diversifier,
|
||||||
|
rcm,
|
||||||
|
ar,
|
||||||
|
value,
|
||||||
|
anchor,
|
||||||
|
witness,
|
||||||
|
&self.spend_params,
|
||||||
|
&self.spend_vk,
|
||||||
|
&JUBJUB,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
|
||||||
|
proof
|
||||||
|
.write(&mut zkproof[..])
|
||||||
|
.expect("should be able to serialize a proof");
|
||||||
|
|
||||||
|
Ok((zkproof, cv, rk))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_proof(
|
||||||
|
&self,
|
||||||
|
ctx: &mut Self::SaplingProvingContext,
|
||||||
|
esk: Fs,
|
||||||
|
payment_address: PaymentAddress<Bls12>,
|
||||||
|
rcm: Fs,
|
||||||
|
value: u64,
|
||||||
|
) -> ([u8; GROTH_PROOF_SIZE], edwards::Point<Bls12, Unknown>) {
|
||||||
|
let (proof, cv) = ctx.output_proof(
|
||||||
|
esk,
|
||||||
|
payment_address,
|
||||||
|
rcm,
|
||||||
|
value,
|
||||||
|
&self.output_params,
|
||||||
|
&JUBJUB,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
|
||||||
|
proof
|
||||||
|
.write(&mut zkproof[..])
|
||||||
|
.expect("should be able to serialize a proof");
|
||||||
|
|
||||||
|
(zkproof, cv)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn binding_sig(
|
||||||
|
&self,
|
||||||
|
ctx: &mut Self::SaplingProvingContext,
|
||||||
|
value_balance: Amount,
|
||||||
|
sighash: &[u8; 32],
|
||||||
|
) -> Result<Signature, ()> {
|
||||||
|
ctx.binding_sig(value_balance, sighash, &JUBJUB)
|
||||||
|
}
|
||||||
|
}
|
||||||
2339
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/tests.rs
vendored
Normal file
2339
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/tests.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
21
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/utils.rs
vendored
Normal file
21
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/utils.rs
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
pub fn read_string<R: Read>(mut reader: R) -> io::Result<String> {
|
||||||
|
// Strings are written as <littleendian> len + bytes
|
||||||
|
let str_len = reader.read_u64::<LittleEndian>()?;
|
||||||
|
let mut str_bytes = vec![0; str_len as usize];
|
||||||
|
reader.read_exact(&mut str_bytes)?;
|
||||||
|
|
||||||
|
let str = String::from_utf8(str_bytes).map_err(|e| {
|
||||||
|
io::Error::new(io::ErrorKind::InvalidData, e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_string<W: Write>(mut writer: W, s: &String) -> io::Result<()> {
|
||||||
|
// Strings are written as len + utf8
|
||||||
|
writer.write_u64::<LittleEndian>(s.as_bytes().len() as u64)?;
|
||||||
|
writer.write_all(s.as_bytes())
|
||||||
|
}
|
||||||
585
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/walletzkey.rs
vendored
Normal file
585
third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/walletzkey.rs
vendored
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::io::{Error, ErrorKind};
|
||||||
|
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use pairing::bls12_381::{Bls12};
|
||||||
|
|
||||||
|
use sodiumoxide::crypto::secretbox;
|
||||||
|
|
||||||
|
use zcash_primitives::{
|
||||||
|
serialize::{Vector, Optional},
|
||||||
|
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||||
|
primitives::{PaymentAddress},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::lightclient::{LightClientConfig};
|
||||||
|
use crate::lightwallet::{LightWallet, utils};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
|
pub enum WalletTKeyType {
|
||||||
|
HdKey = 0,
|
||||||
|
ImportedKey = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A struct that holds z-address private keys or view keys
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct WalletTKey {
|
||||||
|
pub(super) keytype: WalletTKeyType,
|
||||||
|
locked: bool,
|
||||||
|
pub(super) address: String,
|
||||||
|
pub(super) tkey: Option<secp256k1::SecretKey>,
|
||||||
|
|
||||||
|
// If locked, the encrypted key is here
|
||||||
|
enc_key: Option<Vec<u8>>,
|
||||||
|
nonce: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletTKey {
|
||||||
|
pub fn new_hdkey(key: secp256k1::SecretKey, address: String) -> Self {
|
||||||
|
WalletTKey {
|
||||||
|
keytype: WalletTKeyType::HdKey,
|
||||||
|
locked: false,
|
||||||
|
address,
|
||||||
|
tkey: Some(key),
|
||||||
|
|
||||||
|
enc_key: None,
|
||||||
|
nonce: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_hdkey(key: secp256k1::SecretKey, address: String) -> Self {
|
||||||
|
WalletTKey {
|
||||||
|
keytype: WalletTKeyType::ImportedKey,
|
||||||
|
locked: false,
|
||||||
|
address,
|
||||||
|
tkey: Some(key),
|
||||||
|
|
||||||
|
enc_key: None,
|
||||||
|
nonce: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialized_version() -> u8 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<R: Read>(mut inp: R) -> io::Result<Self> {
|
||||||
|
let version = inp.read_u8()?;
|
||||||
|
assert!(version <= Self::serialized_version());
|
||||||
|
|
||||||
|
let keytype: WalletTKeyType = match inp.read_u32::<LittleEndian>()? {
|
||||||
|
0 => Ok(WalletTKeyType::HdKey),
|
||||||
|
1 => Ok(WalletTKeyType::ImportedKey),
|
||||||
|
n => Err(io::Error::new(ErrorKind::InvalidInput, format!("Unknown tkey type {}", n)))
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let locked = inp.read_u8()? > 0;
|
||||||
|
|
||||||
|
let address = utils::read_string(&mut inp)?;
|
||||||
|
let tkey = Optional::read(&mut inp, |r| {
|
||||||
|
let mut tpk_bytes = [0u8; 32];
|
||||||
|
r.read_exact(&mut tpk_bytes)?;
|
||||||
|
secp256k1::SecretKey::from_slice(&tpk_bytes).map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let enc_key = Optional::read(&mut inp, |r|
|
||||||
|
Vector::read(r, |r| r.read_u8()))?;
|
||||||
|
let nonce = Optional::read(&mut inp, |r|
|
||||||
|
Vector::read(r, |r| r.read_u8()))?;
|
||||||
|
|
||||||
|
Ok(WalletTKey {
|
||||||
|
keytype,
|
||||||
|
locked,
|
||||||
|
address,
|
||||||
|
tkey,
|
||||||
|
enc_key,
|
||||||
|
nonce,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut out: W) -> io::Result<()> {
|
||||||
|
out.write_u8(Self::serialized_version())?;
|
||||||
|
|
||||||
|
out.write_u32::<LittleEndian>(self.keytype.clone() as u32)?;
|
||||||
|
|
||||||
|
out.write_u8(self.locked as u8)?;
|
||||||
|
|
||||||
|
utils::write_string(&mut out, &self.address)?;
|
||||||
|
Optional::write(&mut out, &self.tkey, |w, pk|
|
||||||
|
w.write_all(&pk[..])
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Write enc_key
|
||||||
|
Optional::write(&mut out, &self.enc_key, |o, v|
|
||||||
|
Vector::write(o, v, |o,n| o.write_u8(*n)))?;
|
||||||
|
|
||||||
|
// Write nonce
|
||||||
|
Optional::write(&mut out, &self.nonce, |o, v|
|
||||||
|
Vector::write(o, v, |o,n| o.write_u8(*n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn lock(&mut self) -> io::Result<()> {
|
||||||
|
// For keys, encrypt the key into enckey
|
||||||
|
// assert that we have the encrypted key.
|
||||||
|
if self.enc_key.is_none() {
|
||||||
|
return Err(Error::new(ErrorKind::InvalidInput, "Can't lock when t-addr private key is not encrypted"));
|
||||||
|
}
|
||||||
|
self.tkey = None;
|
||||||
|
self.locked = true;
|
||||||
|
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unlock(&mut self, key: &secretbox::Key) -> io::Result<()> {
|
||||||
|
// For imported keys, we need to decrypt from the encrypted key
|
||||||
|
let nonce = secretbox::Nonce::from_slice(&self.nonce.as_ref().unwrap()).unwrap();
|
||||||
|
let sk_bytes = match secretbox::open(&self.enc_key.as_ref().unwrap(), &nonce, &key) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {return Err(io::Error::new(ErrorKind::InvalidData, "Decryption failed. Is your password correct?"));}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tkey = Some(secp256k1::SecretKey::from_slice(&sk_bytes[..]).map_err(|e|
|
||||||
|
io::Error::new(ErrorKind::InvalidData, format!("{}", e))
|
||||||
|
)?);
|
||||||
|
|
||||||
|
self.locked = false;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(&mut self, key: &secretbox::Key) -> io::Result<()> {
|
||||||
|
// For keys, encrypt the key into enckey
|
||||||
|
let nonce = secretbox::gen_nonce();
|
||||||
|
|
||||||
|
let sk_bytes = &self.tkey.unwrap()[..];
|
||||||
|
|
||||||
|
self.enc_key = Some(secretbox::seal(&sk_bytes, &nonce, &key));
|
||||||
|
self.nonce = Some(nonce.as_ref().to_vec());
|
||||||
|
|
||||||
|
self.tkey = None;
|
||||||
|
|
||||||
|
// Also lock after encrypt
|
||||||
|
self.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_encryption(&mut self) -> io::Result<()> {
|
||||||
|
if self.locked {
|
||||||
|
return Err(Error::new(ErrorKind::InvalidInput, "Can't remove encryption while locked"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.enc_key = None;
|
||||||
|
self.nonce = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
|
pub enum WalletZKeyType {
|
||||||
|
HdKey = 0,
|
||||||
|
ImportedSpendingKey = 1,
|
||||||
|
ImportedViewKey = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// A struct that holds z-address private keys or view keys
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct WalletZKey {
|
||||||
|
pub(super) keytype: WalletZKeyType,
|
||||||
|
locked: bool,
|
||||||
|
pub(super) extsk: Option<ExtendedSpendingKey>,
|
||||||
|
pub(super) extfvk: ExtendedFullViewingKey,
|
||||||
|
pub(super) zaddress: PaymentAddress<Bls12>,
|
||||||
|
|
||||||
|
// If this is a HD key, what is the key number
|
||||||
|
pub(super) hdkey_num: Option<u32>,
|
||||||
|
|
||||||
|
// If locked, the encrypted private key is stored here
|
||||||
|
enc_key: Option<Vec<u8>>,
|
||||||
|
nonce: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletZKey {
|
||||||
|
pub fn new_hdkey(hdkey_num: u32, extsk: ExtendedSpendingKey) -> Self {
|
||||||
|
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||||
|
let zaddress = extfvk.default_address().unwrap().1;
|
||||||
|
|
||||||
|
WalletZKey {
|
||||||
|
keytype: WalletZKeyType::HdKey,
|
||||||
|
locked: false,
|
||||||
|
extsk: Some(extsk),
|
||||||
|
extfvk,
|
||||||
|
zaddress,
|
||||||
|
hdkey_num: Some(hdkey_num),
|
||||||
|
enc_key: None,
|
||||||
|
nonce: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_locked_hdkey(hdkey_num: u32, extfvk: ExtendedFullViewingKey) -> Self {
|
||||||
|
let zaddress = extfvk.default_address().unwrap().1;
|
||||||
|
|
||||||
|
WalletZKey {
|
||||||
|
keytype: WalletZKeyType::HdKey,
|
||||||
|
locked: true,
|
||||||
|
extsk: None,
|
||||||
|
extfvk,
|
||||||
|
zaddress,
|
||||||
|
hdkey_num: Some(hdkey_num),
|
||||||
|
enc_key: None,
|
||||||
|
nonce: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_imported_sk(extsk: ExtendedSpendingKey) -> Self {
|
||||||
|
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||||
|
let zaddress = extfvk.default_address().unwrap().1;
|
||||||
|
|
||||||
|
WalletZKey {
|
||||||
|
keytype: WalletZKeyType::ImportedSpendingKey,
|
||||||
|
locked: false,
|
||||||
|
extsk: Some(extsk),
|
||||||
|
extfvk,
|
||||||
|
zaddress,
|
||||||
|
hdkey_num: None,
|
||||||
|
enc_key: None,
|
||||||
|
nonce: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_imported_viewkey(extfvk: ExtendedFullViewingKey) -> Self {
|
||||||
|
let zaddress = extfvk.default_address().unwrap().1;
|
||||||
|
|
||||||
|
WalletZKey {
|
||||||
|
keytype: WalletZKeyType::ImportedViewKey,
|
||||||
|
locked: false,
|
||||||
|
extsk: None,
|
||||||
|
extfvk,
|
||||||
|
zaddress,
|
||||||
|
hdkey_num: None,
|
||||||
|
enc_key: None,
|
||||||
|
nonce: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialized_version() -> u8 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn have_spending_key(&self) -> bool {
|
||||||
|
self.extsk.is_some() || self.enc_key.is_some() || self.hdkey_num.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<R: Read>(mut inp: R) -> io::Result<Self> {
|
||||||
|
let version = inp.read_u8()?;
|
||||||
|
assert!(version <= Self::serialized_version());
|
||||||
|
|
||||||
|
let keytype: WalletZKeyType = match inp.read_u32::<LittleEndian>()? {
|
||||||
|
0 => Ok(WalletZKeyType::HdKey),
|
||||||
|
1 => Ok(WalletZKeyType::ImportedSpendingKey),
|
||||||
|
2 => Ok(WalletZKeyType::ImportedViewKey),
|
||||||
|
n => Err(io::Error::new(ErrorKind::InvalidInput, format!("Unknown zkey type {}", n)))
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let locked = inp.read_u8()? > 0;
|
||||||
|
|
||||||
|
let extsk = Optional::read(&mut inp, |r| ExtendedSpendingKey::read(r))?;
|
||||||
|
let extfvk = ExtendedFullViewingKey::read(&mut inp)?;
|
||||||
|
let zaddress = extfvk.default_address().unwrap().1;
|
||||||
|
|
||||||
|
let hdkey_num = Optional::read(&mut inp, |r| r.read_u32::<LittleEndian>())?;
|
||||||
|
|
||||||
|
let enc_key = Optional::read(&mut inp, |r|
|
||||||
|
Vector::read(r, |r| r.read_u8()))?;
|
||||||
|
let nonce = Optional::read(&mut inp, |r|
|
||||||
|
Vector::read(r, |r| r.read_u8()))?;
|
||||||
|
|
||||||
|
Ok(WalletZKey {
|
||||||
|
keytype,
|
||||||
|
locked,
|
||||||
|
extsk,
|
||||||
|
extfvk,
|
||||||
|
zaddress,
|
||||||
|
hdkey_num,
|
||||||
|
enc_key,
|
||||||
|
nonce,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut out: W) -> io::Result<()> {
|
||||||
|
out.write_u8(Self::serialized_version())?;
|
||||||
|
|
||||||
|
out.write_u32::<LittleEndian>(self.keytype.clone() as u32)?;
|
||||||
|
|
||||||
|
out.write_u8(self.locked as u8)?;
|
||||||
|
|
||||||
|
Optional::write(&mut out, &self.extsk, |w, sk| ExtendedSpendingKey::write(sk, w))?;
|
||||||
|
|
||||||
|
ExtendedFullViewingKey::write(&self.extfvk, &mut out)?;
|
||||||
|
|
||||||
|
Optional::write(&mut out, &self.hdkey_num, |o, n| o.write_u32::<LittleEndian>(*n))?;
|
||||||
|
|
||||||
|
// Write enc_key
|
||||||
|
Optional::write(&mut out, &self.enc_key, |o, v|
|
||||||
|
Vector::write(o, v, |o,n| o.write_u8(*n)))?;
|
||||||
|
|
||||||
|
// Write nonce
|
||||||
|
Optional::write(&mut out, &self.nonce, |o, v|
|
||||||
|
Vector::write(o, v, |o,n| o.write_u8(*n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock(&mut self) -> io::Result<()> {
|
||||||
|
match self.keytype {
|
||||||
|
WalletZKeyType::HdKey => {
|
||||||
|
// For HD keys, just empty out the keys, since they will be reconstructed from the hdkey_num
|
||||||
|
self.extsk = None;
|
||||||
|
self.locked = true;
|
||||||
|
},
|
||||||
|
WalletZKeyType::ImportedSpendingKey => {
|
||||||
|
// For imported keys, encrypt the key into enckey
|
||||||
|
// assert that we have the encrypted key.
|
||||||
|
if self.enc_key.is_none() {
|
||||||
|
return Err(Error::new(ErrorKind::InvalidInput, "Can't lock when imported key is not encrypted"));
|
||||||
|
}
|
||||||
|
self.extsk = None;
|
||||||
|
self.locked = true;
|
||||||
|
},
|
||||||
|
WalletZKeyType::ImportedViewKey => {
|
||||||
|
// For viewing keys, there is nothing to lock, so just return true
|
||||||
|
self.locked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unlock(&mut self, config: &LightClientConfig, bip39_seed: &[u8], key: &secretbox::Key) -> io::Result<()> {
|
||||||
|
match self.keytype {
|
||||||
|
WalletZKeyType::HdKey => {
|
||||||
|
let (extsk, extfvk, address) =
|
||||||
|
LightWallet::get_zaddr_from_bip39seed(&config, &bip39_seed, self.hdkey_num.unwrap());
|
||||||
|
|
||||||
|
if address != self.zaddress {
|
||||||
|
return Err(io::Error::new(ErrorKind::InvalidData,
|
||||||
|
format!("zaddress mismatch at {}. {:?} vs {:?}", self.hdkey_num.unwrap(), address, self.zaddress)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if extfvk != self.extfvk {
|
||||||
|
return Err(io::Error::new(ErrorKind::InvalidData,
|
||||||
|
format!("fvk mismatch at {}. {:?} vs {:?}", self.hdkey_num.unwrap(), extfvk, self.extfvk)));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.extsk = Some(extsk);
|
||||||
|
},
|
||||||
|
WalletZKeyType::ImportedSpendingKey => {
|
||||||
|
// For imported keys, we need to decrypt from the encrypted key
|
||||||
|
let nonce = secretbox::Nonce::from_slice(&self.nonce.as_ref().unwrap()).unwrap();
|
||||||
|
let extsk_bytes = match secretbox::open(&self.enc_key.as_ref().unwrap(), &nonce, &key) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {return Err(io::Error::new(ErrorKind::InvalidData, "Decryption failed. Is your password correct?"));}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.extsk = Some(ExtendedSpendingKey::read(&extsk_bytes[..])?);
|
||||||
|
},
|
||||||
|
WalletZKeyType::ImportedViewKey => {
|
||||||
|
// Viewing key unlocking is basically a no op
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.locked = false;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(&mut self, key: &secretbox::Key) -> io::Result<()> {
|
||||||
|
match self.keytype {
|
||||||
|
WalletZKeyType::HdKey => {
|
||||||
|
// For HD keys, we don't need to do anything, since the hdnum has all the info to recreate this key
|
||||||
|
},
|
||||||
|
WalletZKeyType::ImportedSpendingKey => {
|
||||||
|
// For imported keys, encrypt the key into enckey
|
||||||
|
let nonce = secretbox::gen_nonce();
|
||||||
|
|
||||||
|
let mut sk_bytes = vec![];
|
||||||
|
self.extsk.as_ref().unwrap().write(&mut sk_bytes)?;
|
||||||
|
|
||||||
|
self.enc_key = Some(secretbox::seal(&sk_bytes, &nonce, &key));
|
||||||
|
self.nonce = Some(nonce.as_ref().to_vec());
|
||||||
|
},
|
||||||
|
WalletZKeyType::ImportedViewKey => {
|
||||||
|
// Encrypting a viewing key is a no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also lock after encrypt
|
||||||
|
self.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_encryption(&mut self) -> io::Result<()> {
|
||||||
|
if self.locked {
|
||||||
|
return Err(Error::new(ErrorKind::InvalidInput, "Can't remove encryption while locked"));
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.keytype {
|
||||||
|
WalletZKeyType::HdKey => {
|
||||||
|
// For HD keys, we don't need to do anything, since the hdnum has all the info to recreate this key
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
WalletZKeyType::ImportedSpendingKey => {
|
||||||
|
self.enc_key = None;
|
||||||
|
self.nonce = None;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
WalletZKeyType::ImportedViewKey => {
|
||||||
|
// Removing encryption is a no-op for viewing keys
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use zcash_client_backend::{
|
||||||
|
encoding::{encode_payment_address, decode_extended_spending_key, decode_extended_full_viewing_key}
|
||||||
|
};
|
||||||
|
use sodiumoxide::crypto::secretbox;
|
||||||
|
|
||||||
|
use crate::lightclient::LightClientConfig;
|
||||||
|
use super::WalletZKey;
|
||||||
|
|
||||||
|
fn get_config() -> LightClientConfig {
|
||||||
|
LightClientConfig {
|
||||||
|
server: "0.0.0.0:0".parse().unwrap(),
|
||||||
|
chain_name: "main".to_string(),
|
||||||
|
sapling_activation_height: 0,
|
||||||
|
consensus_branch_id: "000000".to_string(),
|
||||||
|
anchor_offset: 0,
|
||||||
|
data_dir: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialize() {
|
||||||
|
let config = get_config();
|
||||||
|
|
||||||
|
// Priv Key's address is "zs1fxgluwznkzm52ux7jkf4st5znwzqay8zyz4cydnyegt2rh9uhr9458z0nk62fdsssx0cqhy6lyv"
|
||||||
|
let privkey = "secret-extended-key-main1q0p44m9zqqqqpqyxfvy5w2vq6ahvxyrwsk2w4h2zleun4cft4llmnsjlv77lhuuknv6x9jgu5g2clf3xq0wz9axxxq8klvv462r5pa32gjuj5uhxnvps6wsrdg6xll05unwks8qpgp4psmvy5e428uxaggn4l29duk82k3sv3njktaaj453fdmfmj2fup8rls4egqxqtj2p5a3yt4070khn99vzxj5ag5qjngc4v2kq0ctl9q2rpc2phu4p3e26egu9w88mchjf83sqgh3cev";
|
||||||
|
|
||||||
|
let esk = decode_extended_spending_key(config.hrp_sapling_private_key(), privkey).unwrap().unwrap();
|
||||||
|
let wzk = WalletZKey::new_imported_sk(esk);
|
||||||
|
assert_eq!(encode_payment_address(config.hrp_sapling_address(), &wzk.zaddress), "zs1fxgluwznkzm52ux7jkf4st5znwzqay8zyz4cydnyegt2rh9uhr9458z0nk62fdsssx0cqhy6lyv".to_string());
|
||||||
|
|
||||||
|
let mut v: Vec<u8> = vec![];
|
||||||
|
// Serialize
|
||||||
|
wzk.write(&mut v).unwrap();
|
||||||
|
// Read it right back
|
||||||
|
let wzk2 = WalletZKey::read(&v[..]).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
assert_eq!(wzk, wzk2);
|
||||||
|
assert_eq!(wzk.extsk, wzk2.extsk);
|
||||||
|
assert_eq!(wzk.extfvk, wzk2.extfvk);
|
||||||
|
assert_eq!(wzk.zaddress, wzk2.zaddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encrypt_decrypt_sk() {
|
||||||
|
let config = get_config();
|
||||||
|
|
||||||
|
// Priv Key's address is "zs1fxgluwznkzm52ux7jkf4st5znwzqay8zyz4cydnyegt2rh9uhr9458z0nk62fdsssx0cqhy6lyv"
|
||||||
|
let privkey = "secret-extended-key-main1q0p44m9zqqqqpqyxfvy5w2vq6ahvxyrwsk2w4h2zleun4cft4llmnsjlv77lhuuknv6x9jgu5g2clf3xq0wz9axxxq8klvv462r5pa32gjuj5uhxnvps6wsrdg6xll05unwks8qpgp4psmvy5e428uxaggn4l29duk82k3sv3njktaaj453fdmfmj2fup8rls4egqxqtj2p5a3yt4070khn99vzxj5ag5qjngc4v2kq0ctl9q2rpc2phu4p3e26egu9w88mchjf83sqgh3cev";
|
||||||
|
|
||||||
|
let esk = decode_extended_spending_key(config.hrp_sapling_private_key(), privkey).unwrap().unwrap();
|
||||||
|
let mut wzk = WalletZKey::new_imported_sk(esk);
|
||||||
|
assert_eq!(encode_payment_address(config.hrp_sapling_address(), &wzk.zaddress), "zs1fxgluwznkzm52ux7jkf4st5znwzqay8zyz4cydnyegt2rh9uhr9458z0nk62fdsssx0cqhy6lyv".to_string());
|
||||||
|
|
||||||
|
// Can't lock without encryption
|
||||||
|
assert!(wzk.lock().is_err());
|
||||||
|
|
||||||
|
// Encryption key
|
||||||
|
let key = secretbox::Key::from_slice(&[0; 32]).unwrap();
|
||||||
|
|
||||||
|
// Encrypt, but save the extsk first
|
||||||
|
let orig_extsk = wzk.extsk.clone().unwrap();
|
||||||
|
wzk.encrypt(&key).unwrap();
|
||||||
|
{
|
||||||
|
assert!(wzk.enc_key.is_some());
|
||||||
|
assert!(wzk.nonce.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now lock
|
||||||
|
assert!(wzk.lock().is_ok());
|
||||||
|
{
|
||||||
|
assert!(wzk.extsk.is_none());
|
||||||
|
assert_eq!(wzk.locked, true);
|
||||||
|
assert_eq!(wzk.zaddress, wzk.extfvk.default_address().unwrap().1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't remove encryption without unlocking
|
||||||
|
assert!(wzk.remove_encryption().is_err());
|
||||||
|
|
||||||
|
// Unlock
|
||||||
|
assert!(wzk.unlock(&config, &[], &key).is_ok());
|
||||||
|
{
|
||||||
|
assert_eq!(wzk.extsk, Some(orig_extsk));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove encryption
|
||||||
|
assert!(wzk.remove_encryption().is_ok());
|
||||||
|
{
|
||||||
|
assert_eq!(wzk.enc_key, None);
|
||||||
|
assert_eq!(wzk.nonce, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encrypt_decrypt_vk() {
|
||||||
|
let config = get_config();
|
||||||
|
|
||||||
|
// Priv Key's address is "zs1va5902apnzlhdu0pw9r9q7ca8s4vnsrp2alr6xndt69jnepn2v2qrj9vg3wfcnjyks5pg65g9dc"
|
||||||
|
let viewkey = "zxviews1qvvx7cqdqyqqpqqte7292el2875kw2fgvnkmlmrufyszlcy8xgstwarnumqye3tr3d9rr3ydjm9zl9464majh4pa3ejkfy779dm38sfnkar67et7ykxkk0z9rfsmf9jclfj2k85xt2exkg4pu5xqyzyxzlqa6x3p9wrd7pwdq2uvyg0sal6zenqgfepsdp8shestvkzxuhm846r2h3m4jvsrpmxl8pfczxq87886k0wdasppffjnd2eh47nlmkdvrk6rgyyl0ekh3ycqtvvje";
|
||||||
|
|
||||||
|
let extfvk = decode_extended_full_viewing_key(config.hrp_sapling_viewing_key(), viewkey).unwrap().unwrap();
|
||||||
|
let mut wzk = WalletZKey::new_imported_viewkey(extfvk);
|
||||||
|
|
||||||
|
assert_eq!(encode_payment_address(config.hrp_sapling_address(), &wzk.zaddress), "zs1va5902apnzlhdu0pw9r9q7ca8s4vnsrp2alr6xndt69jnepn2v2qrj9vg3wfcnjyks5pg65g9dc".to_string());
|
||||||
|
|
||||||
|
// Encryption key
|
||||||
|
let key = secretbox::Key::from_slice(&[0; 32]).unwrap();
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
wzk.encrypt(&key).unwrap();
|
||||||
|
{
|
||||||
|
assert!(wzk.enc_key.is_none());
|
||||||
|
assert!(wzk.nonce.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now lock
|
||||||
|
assert!(wzk.lock().is_ok());
|
||||||
|
{
|
||||||
|
assert!(wzk.extsk.is_none());
|
||||||
|
assert_eq!(wzk.locked, true);
|
||||||
|
assert_eq!(wzk.zaddress, wzk.extfvk.default_address().unwrap().1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't remove encryption without unlocking
|
||||||
|
assert!(wzk.remove_encryption().is_err());
|
||||||
|
|
||||||
|
// Unlock
|
||||||
|
assert!(wzk.unlock(&config, &[], &key).is_ok());
|
||||||
|
{
|
||||||
|
assert_eq!(wzk.extsk, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove encryption
|
||||||
|
assert!(wzk.remove_encryption().is_ok());
|
||||||
|
{
|
||||||
|
assert_eq!(wzk.enc_key, None);
|
||||||
|
assert_eq!(wzk.nonce, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user