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:
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