Crash fixes, reorg handling, and sync performance improvements
Some checks failed
Rust / Build on macOS-latest (push) Has been cancelled
Rust / Build on ubuntu-16.04 (push) Has been cancelled
Rust / Build on windows-latest (push) Has been cancelled
Rust / Linux ARMv7 (push) Has been cancelled
Rust / Linux ARM64 (push) Has been cancelled
Rust / Build on ubuntu-latest (push) Has been cancelled
Some checks failed
Rust / Build on macOS-latest (push) Has been cancelled
Rust / Build on ubuntu-16.04 (push) Has been cancelled
Rust / Build on windows-latest (push) Has been cancelled
Rust / Linux ARMv7 (push) Has been cancelled
Rust / Linux ARM64 (push) Has been cancelled
Rust / Build on ubuntu-latest (push) Has been cancelled
- Fix FFI panics with catch_unwind and safe CString construction - Handle poisoned mutex/RwLock after prior panics instead of crashing - Fix empty block list panics in clear_blocks and invalidate_block - Reuse Tokio runtime across block fetch batches to reduce overhead - Add fetch_blocks_with_runtime for caller-managed runtime lifecycle - Update branding, dependencies, and checkpoints for DragonX
This commit is contained in:
160
lib/Cargo.toml
160
lib/Cargo.toml
@@ -1,80 +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.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
default-features = false
|
||||
features = ["groth16"]
|
||||
|
||||
[dependencies.pairing]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
|
||||
[dependencies.zcash_client_backend]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
|
||||
default-features = false
|
||||
|
||||
[dependencies.zcash_primitives]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
default-features = false
|
||||
features = ["transparent-inputs"]
|
||||
|
||||
[dependencies.zcash_proofs]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
default-features = false
|
||||
|
||||
[dependencies.ff]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
features = ["ff_derive"]
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3.7"
|
||||
[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.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
default-features = false
|
||||
features = ["groth16"]
|
||||
|
||||
[dependencies.pairing]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
|
||||
[dependencies.zcash_client_backend]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
|
||||
default-features = false
|
||||
|
||||
[dependencies.zcash_primitives]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
default-features = false
|
||||
features = ["transparent-inputs"]
|
||||
|
||||
[dependencies.zcash_proofs]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
default-features = false
|
||||
|
||||
[dependencies.ff]
|
||||
git = "https://git.hush.is/hush/librustzcash.git"
|
||||
rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b"
|
||||
features = ["ff_derive"]
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3.7"
|
||||
|
||||
26
lib/build.rs
26
lib/build.rs
@@ -1,13 +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(())
|
||||
}
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,65 +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
|
||||
}
|
||||
*/
|
||||
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
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,170 +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) {}
|
||||
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) {}
|
||||
}
|
||||
1950
lib/src/commands.rs
1950
lib/src/commands.rs
File diff suppressed because it is too large
Load Diff
@@ -1,366 +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 = match tokio::runtime::Runtime::new() {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
let es = format!("Error creating runtime {:?}", e);
|
||||
error!("{}", es);
|
||||
return Err(es);
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// 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;
|
||||
|
||||
|
||||
pub const ANCHOR_OFFSET: u32 = 0;
|
||||
|
||||
pub mod grpc_client {
|
||||
tonic::include_proto!("cash.z.wallet.sdk.rpc");
|
||||
}
|
||||
// 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;
|
||||
|
||||
|
||||
pub const ANCHOR_OFFSET: u32 = 0;
|
||||
|
||||
pub mod grpc_client {
|
||||
tonic::include_proto!("cash.z.wallet.sdk.rpc");
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,46 +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
|
||||
}
|
||||
}
|
||||
}
|
||||
//! 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,126 +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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +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)
|
||||
}
|
||||
}
|
||||
//! 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)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +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())
|
||||
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())
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user