Merge pull request #114 from str4d/compact-blocks
CompactBlock parsing and scanning
This commit is contained in:
commit
2cd8a7f4d2
|
@ -14,6 +14,14 @@ jobs:
|
||||||
toolchain: 1.37.0
|
toolchain: 1.37.0
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
|
# cargo fmt does not build the code, and running it in a fresh clone of
|
||||||
|
# the codebase will fail because the protobuf code has not been generated.
|
||||||
|
- name: cargo build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --all
|
||||||
|
|
||||||
# Ensure all code has been formatted with rustfmt
|
# Ensure all code has been formatted with rustfmt
|
||||||
- run: rustup component add rustfmt
|
- run: rustup component add rustfmt
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
|
|
|
@ -14,8 +14,8 @@ before_script:
|
||||||
- rustup component add rustfmt
|
- rustup component add rustfmt
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cargo fmt --all -- --check
|
|
||||||
- cargo build --verbose --release --all
|
- cargo build --verbose --release --all
|
||||||
|
- cargo fmt --all -- --check
|
||||||
- cargo test --verbose --release --all
|
- cargo test --verbose --release --all
|
||||||
- cargo test --verbose --release --all -- --ignored
|
- cargo test --verbose --release --all -- --ignored
|
||||||
|
|
||||||
|
|
|
@ -467,6 +467,28 @@ dependencies = [
|
||||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protobuf"
|
||||||
|
version = "2.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protobuf-codegen"
|
||||||
|
version = "2.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protobuf-codegen-pure"
|
||||||
|
version = "2.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"protobuf-codegen 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -512,6 +534,15 @@ dependencies = [
|
||||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_os"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_xorshift"
|
name = "rand_xorshift"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -557,6 +588,11 @@ dependencies = [
|
||||||
"opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
@ -606,9 +642,15 @@ name = "zcash_client_backend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ff 0.5.0",
|
||||||
|
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"pairing 0.15.0",
|
"pairing 0.15.0",
|
||||||
|
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"protobuf-codegen-pure 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"zcash_primitives 0.1.0",
|
"zcash_primitives 0.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -700,17 +742,22 @@ dependencies = [
|
||||||
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
|
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
|
||||||
"checksum proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e688f31d92ffd7c1ddc57a1b4e6d773c0f2a14ee437a4b0a4f5a69c80eb221c8"
|
"checksum proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e688f31d92ffd7c1ddc57a1b4e6d773c0f2a14ee437a4b0a4f5a69c80eb221c8"
|
||||||
"checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8"
|
"checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8"
|
||||||
|
"checksum protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20"
|
||||||
|
"checksum protobuf-codegen 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a"
|
||||||
|
"checksum protobuf-codegen-pure 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c1646acda5319f5b28b0bff4a484324df43ddae2c0f5a3f3e63c0b26095cd600"
|
||||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||||
"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c"
|
"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c"
|
||||||
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
|
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
|
||||||
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||||
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||||
|
"checksum rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a788ae3edb696cfcba1c19bfd388cc4b8c21f8a408432b199c072825084da58a"
|
||||||
"checksum rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8"
|
"checksum rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8"
|
||||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||||
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
|
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
|
||||||
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
|
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
|
||||||
|
"checksum subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3af2eb31c42e8f0ccf43548232556c42737e01a96db6e1777b0be108e79799"
|
||||||
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
|
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
|
||||||
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
||||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Protobufs
|
||||||
|
src/proto/
|
|
@ -13,11 +13,19 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bech32 = "0.7"
|
bech32 = "0.7"
|
||||||
|
ff = { version = "0.5.0", path = "../ff" }
|
||||||
|
hex = "0.3"
|
||||||
pairing = { version = "0.15.0", path = "../pairing" }
|
pairing = { version = "0.15.0", path = "../pairing" }
|
||||||
|
protobuf = "2"
|
||||||
|
subtle = "2"
|
||||||
zcash_primitives = { version = "0.1.0", path = "../zcash_primitives" }
|
zcash_primitives = { version = "0.1.0", path = "../zcash_primitives" }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
protobuf-codegen-pure = "2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand_core = "0.5"
|
rand_core = "0.5"
|
||||||
|
rand_os = "0.2"
|
||||||
rand_xorshift = "0.2"
|
rand_xorshift = "0.2"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
use protobuf_codegen_pure;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
protobuf_codegen_pure::run(protobuf_codegen_pure::Args {
|
||||||
|
out_dir: "src/proto",
|
||||||
|
input: &["proto/compact_formats.proto"],
|
||||||
|
includes: &["proto"],
|
||||||
|
customize: Default::default(),
|
||||||
|
})
|
||||||
|
.expect("protoc");
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
package cash.z.wallet.sdk.rpc;
|
||||||
|
option go_package = "walletrpc";
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
bytes prevHash = 4;
|
||||||
|
uint32 time = 5;
|
||||||
|
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;
|
||||||
|
bytes hash = 2;
|
||||||
|
|
||||||
|
// 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 CompactSpend spends = 4;
|
||||||
|
repeated CompactOutput outputs = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CompactSpend {
|
||||||
|
bytes nf = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CompactOutput {
|
||||||
|
bytes cmu = 1;
|
||||||
|
bytes epk = 2;
|
||||||
|
bytes ciphertext = 3;
|
||||||
|
}
|
|
@ -9,3 +9,6 @@
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod encoding;
|
pub mod encoding;
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
|
pub mod proto;
|
||||||
|
pub mod wallet;
|
||||||
|
pub mod welding_rig;
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
//! Generated code for handling light client protobuf structs.
|
||||||
|
|
||||||
|
use ff::{PrimeField, PrimeFieldRepr};
|
||||||
|
use pairing::bls12_381::{Bls12, Fr, FrRepr};
|
||||||
|
use zcash_primitives::{
|
||||||
|
block::{BlockHash, BlockHeader},
|
||||||
|
jubjub::{edwards, PrimeOrder},
|
||||||
|
JUBJUB,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod compact_formats;
|
||||||
|
|
||||||
|
impl compact_formats::CompactBlock {
|
||||||
|
/// Returns the [`BlockHash`] for this block.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic if [`CompactBlock.header`] is not set and
|
||||||
|
/// [`CompactBlock.hash`] is not exactly 32 bytes.
|
||||||
|
///
|
||||||
|
/// [`CompactBlock.header`]: #structfield.header
|
||||||
|
/// [`CompactBlock.hash`]: #structfield.hash
|
||||||
|
pub fn hash(&self) -> BlockHash {
|
||||||
|
if let Some(header) = self.header() {
|
||||||
|
header.hash()
|
||||||
|
} else {
|
||||||
|
BlockHash::from_slice(&self.hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [`BlockHash`] for this block's parent.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic if [`CompactBlock.header`] is not set and
|
||||||
|
/// [`CompactBlock.prevHash`] is not exactly 32 bytes.
|
||||||
|
///
|
||||||
|
/// [`CompactBlock.header`]: #structfield.header
|
||||||
|
/// [`CompactBlock.prevHash`]: #structfield.prevHash
|
||||||
|
pub fn prev_hash(&self) -> BlockHash {
|
||||||
|
if let Some(header) = self.header() {
|
||||||
|
header.prev_block
|
||||||
|
} else {
|
||||||
|
BlockHash::from_slice(&self.prevHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [`BlockHeader`] for this block if present.
|
||||||
|
///
|
||||||
|
/// A convenience method that parses [`CompactBlock.header`] if present.
|
||||||
|
///
|
||||||
|
/// [`CompactBlock.header`]: #structfield.header
|
||||||
|
pub fn header(&self) -> Option<BlockHeader> {
|
||||||
|
if self.header.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
BlockHeader::read(&self.header[..]).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl compact_formats::CompactOutput {
|
||||||
|
/// Returns the note commitment for this output.
|
||||||
|
///
|
||||||
|
/// A convenience method that parses [`CompactOutput.cmu`].
|
||||||
|
///
|
||||||
|
/// [`CompactOutput.cmu`]: #structfield.cmu
|
||||||
|
pub fn cmu(&self) -> Result<Fr, ()> {
|
||||||
|
let mut repr = FrRepr::default();
|
||||||
|
repr.read_le(&self.cmu[..]).map_err(|_| ())?;
|
||||||
|
Fr::from_repr(repr).map_err(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ephemeral public key for this output.
|
||||||
|
///
|
||||||
|
/// A convenience method that parses [`CompactOutput.epk`].
|
||||||
|
///
|
||||||
|
/// [`CompactOutput.epk`]: #structfield.epk
|
||||||
|
pub fn epk(&self) -> Result<edwards::Point<Bls12, PrimeOrder>, ()> {
|
||||||
|
let p = edwards::Point::<Bls12, _>::read(&self.epk[..], &JUBJUB).map_err(|_| ())?;
|
||||||
|
p.as_prime_order(&JUBJUB).ok_or(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
//! Structs representing transaction data scanned from the block chain by a wallet or
|
||||||
|
//! light client.
|
||||||
|
|
||||||
|
use pairing::bls12_381::{Bls12, Fr};
|
||||||
|
use zcash_primitives::{
|
||||||
|
jubjub::{edwards, PrimeOrder},
|
||||||
|
merkle_tree::IncrementalWitness,
|
||||||
|
primitives::{Note, PaymentAddress},
|
||||||
|
sapling::Node,
|
||||||
|
transaction::TxId,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A subset of a [`Transaction`] relevant to wallets and light clients.
|
||||||
|
///
|
||||||
|
/// [`Transaction`]: zcash_primitives::transaction::Transaction
|
||||||
|
pub struct WalletTx {
|
||||||
|
pub txid: TxId,
|
||||||
|
pub index: usize,
|
||||||
|
pub num_spends: usize,
|
||||||
|
pub num_outputs: usize,
|
||||||
|
pub shielded_spends: Vec<WalletShieldedSpend>,
|
||||||
|
pub shielded_outputs: Vec<WalletShieldedOutput>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A subset of a [`SpendDescription`] relevant to wallets and light clients.
|
||||||
|
///
|
||||||
|
/// [`SpendDescription`]: zcash_primitives::transaction::components::SpendDescription
|
||||||
|
pub struct WalletShieldedSpend {
|
||||||
|
pub index: usize,
|
||||||
|
pub nf: Vec<u8>,
|
||||||
|
pub account: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A subset of an [`OutputDescription`] relevant to wallets and light clients.
|
||||||
|
///
|
||||||
|
/// [`OutputDescription`]: zcash_primitives::transaction::components::OutputDescription
|
||||||
|
pub struct WalletShieldedOutput {
|
||||||
|
pub index: usize,
|
||||||
|
pub cmu: Fr,
|
||||||
|
pub epk: edwards::Point<Bls12, PrimeOrder>,
|
||||||
|
pub account: usize,
|
||||||
|
pub note: Note<Bls12>,
|
||||||
|
pub to: PaymentAddress<Bls12>,
|
||||||
|
pub is_change: bool,
|
||||||
|
pub witness: IncrementalWitness<Node>,
|
||||||
|
}
|
|
@ -0,0 +1,399 @@
|
||||||
|
//! Tools for scanning a compact representation of the Zcash block chain.
|
||||||
|
|
||||||
|
use ff::PrimeField;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption};
|
||||||
|
use zcash_primitives::{
|
||||||
|
jubjub::fs::Fs,
|
||||||
|
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||||
|
note_encryption::try_sapling_compact_note_decryption,
|
||||||
|
sapling::Node,
|
||||||
|
transaction::TxId,
|
||||||
|
zip32::ExtendedFullViewingKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::proto::compact_formats::{CompactBlock, CompactOutput};
|
||||||
|
use crate::wallet::{WalletShieldedOutput, WalletShieldedSpend, WalletTx};
|
||||||
|
|
||||||
|
/// Scans a [`CompactOutput`] with a set of [`ExtendedFullViewingKey`]s.
|
||||||
|
///
|
||||||
|
/// Returns a [`WalletShieldedOutput`] and corresponding [`IncrementalWitness`] if this
|
||||||
|
/// output belongs to any of the given [`ExtendedFullViewingKey`]s.
|
||||||
|
///
|
||||||
|
/// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are incremented
|
||||||
|
/// with this output's commitment.
|
||||||
|
fn scan_output(
|
||||||
|
(index, output): (usize, CompactOutput),
|
||||||
|
ivks: &[Fs],
|
||||||
|
spent_from_accounts: &HashSet<usize>,
|
||||||
|
tree: &mut CommitmentTree<Node>,
|
||||||
|
existing_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||||
|
block_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||||
|
new_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||||
|
) -> Option<WalletShieldedOutput> {
|
||||||
|
let cmu = output.cmu().ok()?;
|
||||||
|
let epk = output.epk().ok()?;
|
||||||
|
let ct = output.ciphertext;
|
||||||
|
|
||||||
|
// Increment tree and witnesses
|
||||||
|
let node = Node::new(cmu.into_repr());
|
||||||
|
for witness in existing_witnesses {
|
||||||
|
witness.append(node).unwrap();
|
||||||
|
}
|
||||||
|
for witness in block_witnesses {
|
||||||
|
witness.append(node).unwrap();
|
||||||
|
}
|
||||||
|
for witness in new_witnesses {
|
||||||
|
witness.append(node).unwrap();
|
||||||
|
}
|
||||||
|
tree.append(node).unwrap();
|
||||||
|
|
||||||
|
for (account, ivk) in ivks.iter().enumerate() {
|
||||||
|
let (note, to) = match try_sapling_compact_note_decryption(ivk, &epk, &cmu, &ct) {
|
||||||
|
Some(ret) => ret,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// A note is marked as "change" if the account that received it
|
||||||
|
// also spent notes in the same transaction. This will catch,
|
||||||
|
// for instance:
|
||||||
|
// - Change created by spending fractions of notes.
|
||||||
|
// - Notes created by consolidation transactions.
|
||||||
|
// - Notes sent from one account to itself.
|
||||||
|
let is_change = spent_from_accounts.contains(&account);
|
||||||
|
|
||||||
|
return Some(WalletShieldedOutput {
|
||||||
|
index,
|
||||||
|
cmu,
|
||||||
|
epk,
|
||||||
|
account,
|
||||||
|
note,
|
||||||
|
to,
|
||||||
|
is_change,
|
||||||
|
witness: IncrementalWitness::from_tree(tree),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scans a [`CompactBlock`] with a set of [`ExtendedFullViewingKey`]s.
|
||||||
|
///
|
||||||
|
/// Returns a vector of [`WalletTx`]s belonging to any of the given
|
||||||
|
/// [`ExtendedFullViewingKey`]s, and the corresponding new [`IncrementalWitness`]es.
|
||||||
|
///
|
||||||
|
/// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are
|
||||||
|
/// incremented appropriately.
|
||||||
|
pub fn scan_block(
|
||||||
|
block: CompactBlock,
|
||||||
|
extfvks: &[ExtendedFullViewingKey],
|
||||||
|
nullifiers: &[(&[u8], usize)],
|
||||||
|
tree: &mut CommitmentTree<Node>,
|
||||||
|
existing_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||||
|
) -> Vec<WalletTx> {
|
||||||
|
let mut wtxs: Vec<WalletTx> = vec![];
|
||||||
|
let ivks: Vec<_> = extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect();
|
||||||
|
|
||||||
|
for tx in block.vtx.into_iter() {
|
||||||
|
let num_spends = tx.spends.len();
|
||||||
|
let num_outputs = tx.outputs.len();
|
||||||
|
|
||||||
|
// Check for spent notes
|
||||||
|
// The only step that is not constant-time is the filter() at the end.
|
||||||
|
let shielded_spends: Vec<_> = tx
|
||||||
|
.spends
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, spend)| {
|
||||||
|
// Find the first tracked nullifier that matches this spend, and produce
|
||||||
|
// a WalletShieldedSpend if there is a match, in constant time.
|
||||||
|
nullifiers
|
||||||
|
.iter()
|
||||||
|
.map(|&(nf, account)| CtOption::new(account as u64, nf.ct_eq(&spend.nf[..])))
|
||||||
|
.fold(CtOption::new(0, 0.into()), |first, next| {
|
||||||
|
CtOption::conditional_select(&next, &first, first.is_some())
|
||||||
|
})
|
||||||
|
.map(|account| WalletShieldedSpend {
|
||||||
|
index,
|
||||||
|
nf: spend.nf,
|
||||||
|
account: account as usize,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.filter(|spend| spend.is_some().into())
|
||||||
|
.map(|spend| spend.unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Collect the set of accounts that were spent from in this transaction
|
||||||
|
let spent_from_accounts: HashSet<_> =
|
||||||
|
shielded_spends.iter().map(|spend| spend.account).collect();
|
||||||
|
|
||||||
|
// Check for incoming notes while incrementing tree and witnesses
|
||||||
|
let mut shielded_outputs: Vec<WalletShieldedOutput> = vec![];
|
||||||
|
{
|
||||||
|
// Grab mutable references to new witnesses from previous transactions
|
||||||
|
// in this block so that we can update them. Scoped so we don't hold
|
||||||
|
// mutable references to wtxs for too long.
|
||||||
|
let mut block_witnesses: Vec<_> = wtxs
|
||||||
|
.iter_mut()
|
||||||
|
.map(|tx| {
|
||||||
|
tx.shielded_outputs
|
||||||
|
.iter_mut()
|
||||||
|
.map(|output| &mut output.witness)
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for to_scan in tx.outputs.into_iter().enumerate() {
|
||||||
|
// Grab mutable references to new witnesses from previous outputs
|
||||||
|
// in this transaction so that we can update them. Scoped so we
|
||||||
|
// don't hold mutable references to shielded_outputs for too long.
|
||||||
|
let mut new_witnesses: Vec<_> = shielded_outputs
|
||||||
|
.iter_mut()
|
||||||
|
.map(|output| &mut output.witness)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(output) = scan_output(
|
||||||
|
to_scan,
|
||||||
|
&ivks,
|
||||||
|
&spent_from_accounts,
|
||||||
|
tree,
|
||||||
|
existing_witnesses,
|
||||||
|
&mut block_witnesses,
|
||||||
|
&mut new_witnesses,
|
||||||
|
) {
|
||||||
|
shielded_outputs.push(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(shielded_spends.is_empty() && shielded_outputs.is_empty()) {
|
||||||
|
let mut txid = TxId([0u8; 32]);
|
||||||
|
txid.0.copy_from_slice(&tx.hash);
|
||||||
|
wtxs.push(WalletTx {
|
||||||
|
txid,
|
||||||
|
index: tx.index as usize,
|
||||||
|
num_spends,
|
||||||
|
num_outputs,
|
||||||
|
shielded_spends,
|
||||||
|
shielded_outputs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wtxs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use ff::{Field, PrimeField, PrimeFieldRepr};
|
||||||
|
use pairing::bls12_381::{Bls12, Fr};
|
||||||
|
use rand_core::RngCore;
|
||||||
|
use rand_os::OsRng;
|
||||||
|
use zcash_primitives::{
|
||||||
|
jubjub::{fs::Fs, FixedGenerators, JubjubParams, ToUniform},
|
||||||
|
merkle_tree::CommitmentTree,
|
||||||
|
note_encryption::{Memo, SaplingNoteEncryption},
|
||||||
|
primitives::Note,
|
||||||
|
transaction::components::Amount,
|
||||||
|
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||||
|
JUBJUB,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::scan_block;
|
||||||
|
use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactSpend, CompactTx};
|
||||||
|
|
||||||
|
fn random_compact_tx<R: RngCore>(rng: &mut R) -> CompactTx {
|
||||||
|
let fake_nf = {
|
||||||
|
let mut nf = vec![0; 32];
|
||||||
|
rng.fill_bytes(&mut nf);
|
||||||
|
nf
|
||||||
|
};
|
||||||
|
let fake_cmu = {
|
||||||
|
let fake_cmu = Fr::random(rng);
|
||||||
|
let mut bytes = vec![];
|
||||||
|
fake_cmu.into_repr().write_le(&mut bytes).unwrap();
|
||||||
|
bytes
|
||||||
|
};
|
||||||
|
let fake_epk = {
|
||||||
|
let mut buffer = vec![0; 64];
|
||||||
|
rng.fill_bytes(&mut buffer);
|
||||||
|
let fake_esk = Fs::to_uniform(&buffer[..]);
|
||||||
|
let fake_epk = JUBJUB
|
||||||
|
.generator(FixedGenerators::SpendingKeyGenerator)
|
||||||
|
.mul(fake_esk, &JUBJUB);
|
||||||
|
let mut bytes = vec![];
|
||||||
|
fake_epk.write(&mut bytes).unwrap();
|
||||||
|
bytes
|
||||||
|
};
|
||||||
|
let mut cspend = CompactSpend::new();
|
||||||
|
cspend.set_nf(fake_nf);
|
||||||
|
let mut cout = CompactOutput::new();
|
||||||
|
cout.set_cmu(fake_cmu);
|
||||||
|
cout.set_epk(fake_epk);
|
||||||
|
cout.set_ciphertext(vec![0; 52]);
|
||||||
|
let mut ctx = CompactTx::new();
|
||||||
|
let mut txid = vec![0; 32];
|
||||||
|
rng.fill_bytes(&mut txid);
|
||||||
|
ctx.set_hash(txid);
|
||||||
|
ctx.spends.push(cspend);
|
||||||
|
ctx.outputs.push(cout);
|
||||||
|
ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a fake CompactBlock at the given height, with a transaction containing a
|
||||||
|
/// single spend of the given nullifier and a single output paying the given address.
|
||||||
|
/// Returns the CompactBlock.
|
||||||
|
fn fake_compact_block(
|
||||||
|
height: i32,
|
||||||
|
nf: [u8; 32],
|
||||||
|
extfvk: ExtendedFullViewingKey,
|
||||||
|
value: Amount,
|
||||||
|
tx_after: bool,
|
||||||
|
) -> CompactBlock {
|
||||||
|
let to = extfvk.default_address().unwrap().1;
|
||||||
|
|
||||||
|
// Create a fake Note for the account
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let note = Note {
|
||||||
|
g_d: to.diversifier().g_d::<Bls12>(&JUBJUB).unwrap(),
|
||||||
|
pk_d: to.pk_d().clone(),
|
||||||
|
value: value.into(),
|
||||||
|
r: Fs::random(&mut rng),
|
||||||
|
};
|
||||||
|
let encryptor = SaplingNoteEncryption::new(
|
||||||
|
extfvk.fvk.ovk,
|
||||||
|
note.clone(),
|
||||||
|
to.clone(),
|
||||||
|
Memo::default(),
|
||||||
|
&mut rng,
|
||||||
|
);
|
||||||
|
let mut cmu = vec![];
|
||||||
|
note.cm(&JUBJUB).into_repr().write_le(&mut cmu).unwrap();
|
||||||
|
let mut epk = vec![];
|
||||||
|
encryptor.epk().write(&mut epk).unwrap();
|
||||||
|
let enc_ciphertext = encryptor.encrypt_note_plaintext();
|
||||||
|
|
||||||
|
// Create a fake CompactBlock containing the note
|
||||||
|
let mut cb = CompactBlock::new();
|
||||||
|
cb.set_height(height as u64);
|
||||||
|
|
||||||
|
// Add a random Sapling tx before ours
|
||||||
|
{
|
||||||
|
let mut tx = random_compact_tx(&mut rng);
|
||||||
|
tx.index = cb.vtx.len() as u64;
|
||||||
|
cb.vtx.push(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cspend = CompactSpend::new();
|
||||||
|
cspend.set_nf(nf.to_vec());
|
||||||
|
let mut cout = CompactOutput::new();
|
||||||
|
cout.set_cmu(cmu);
|
||||||
|
cout.set_epk(epk);
|
||||||
|
cout.set_ciphertext(enc_ciphertext[..52].to_vec());
|
||||||
|
let mut ctx = CompactTx::new();
|
||||||
|
let mut txid = vec![0; 32];
|
||||||
|
rng.fill_bytes(&mut txid);
|
||||||
|
ctx.set_hash(txid);
|
||||||
|
ctx.spends.push(cspend);
|
||||||
|
ctx.outputs.push(cout);
|
||||||
|
ctx.index = cb.vtx.len() as u64;
|
||||||
|
cb.vtx.push(ctx);
|
||||||
|
|
||||||
|
// Optionally add another random Sapling tx after ours
|
||||||
|
if tx_after {
|
||||||
|
let mut tx = random_compact_tx(&mut rng);
|
||||||
|
tx.index = cb.vtx.len() as u64;
|
||||||
|
cb.vtx.push(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scan_block_with_my_tx() {
|
||||||
|
let extsk = ExtendedSpendingKey::master(&[]);
|
||||||
|
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||||
|
|
||||||
|
let cb = fake_compact_block(
|
||||||
|
1,
|
||||||
|
[0; 32],
|
||||||
|
extfvk.clone(),
|
||||||
|
Amount::from_u64(5).unwrap(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert_eq!(cb.vtx.len(), 2);
|
||||||
|
|
||||||
|
let mut tree = CommitmentTree::new();
|
||||||
|
let txs = scan_block(cb, &[extfvk], &[], &mut tree, &mut []);
|
||||||
|
assert_eq!(txs.len(), 1);
|
||||||
|
|
||||||
|
let tx = &txs[0];
|
||||||
|
assert_eq!(tx.index, 1);
|
||||||
|
assert_eq!(tx.num_spends, 1);
|
||||||
|
assert_eq!(tx.num_outputs, 1);
|
||||||
|
assert_eq!(tx.shielded_spends.len(), 0);
|
||||||
|
assert_eq!(tx.shielded_outputs.len(), 1);
|
||||||
|
assert_eq!(tx.shielded_outputs[0].index, 0);
|
||||||
|
assert_eq!(tx.shielded_outputs[0].account, 0);
|
||||||
|
assert_eq!(tx.shielded_outputs[0].note.value, 5);
|
||||||
|
|
||||||
|
// Check that the witness root matches
|
||||||
|
assert_eq!(tx.shielded_outputs[0].witness.root(), tree.root());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scan_block_with_txs_after_my_tx() {
|
||||||
|
let extsk = ExtendedSpendingKey::master(&[]);
|
||||||
|
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||||
|
|
||||||
|
let cb = fake_compact_block(
|
||||||
|
1,
|
||||||
|
[0; 32],
|
||||||
|
extfvk.clone(),
|
||||||
|
Amount::from_u64(5).unwrap(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert_eq!(cb.vtx.len(), 3);
|
||||||
|
|
||||||
|
let mut tree = CommitmentTree::new();
|
||||||
|
let txs = scan_block(cb, &[extfvk], &[], &mut tree, &mut []);
|
||||||
|
assert_eq!(txs.len(), 1);
|
||||||
|
|
||||||
|
let tx = &txs[0];
|
||||||
|
assert_eq!(tx.index, 1);
|
||||||
|
assert_eq!(tx.num_spends, 1);
|
||||||
|
assert_eq!(tx.num_outputs, 1);
|
||||||
|
assert_eq!(tx.shielded_spends.len(), 0);
|
||||||
|
assert_eq!(tx.shielded_outputs.len(), 1);
|
||||||
|
assert_eq!(tx.shielded_outputs[0].index, 0);
|
||||||
|
assert_eq!(tx.shielded_outputs[0].account, 0);
|
||||||
|
assert_eq!(tx.shielded_outputs[0].note.value, 5);
|
||||||
|
|
||||||
|
// Check that the witness root matches
|
||||||
|
assert_eq!(tx.shielded_outputs[0].witness.root(), tree.root());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scan_block_with_my_spend() {
|
||||||
|
let extsk = ExtendedSpendingKey::master(&[]);
|
||||||
|
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||||
|
let nf = [7; 32];
|
||||||
|
let account = 12;
|
||||||
|
|
||||||
|
let cb = fake_compact_block(1, nf, extfvk, Amount::from_u64(5).unwrap(), false);
|
||||||
|
assert_eq!(cb.vtx.len(), 2);
|
||||||
|
|
||||||
|
let mut tree = CommitmentTree::new();
|
||||||
|
let txs = scan_block(cb, &[], &[(&nf, account)], &mut tree, &mut []);
|
||||||
|
assert_eq!(txs.len(), 1);
|
||||||
|
|
||||||
|
let tx = &txs[0];
|
||||||
|
assert_eq!(tx.index, 1);
|
||||||
|
assert_eq!(tx.num_spends, 1);
|
||||||
|
assert_eq!(tx.num_outputs, 1);
|
||||||
|
assert_eq!(tx.shielded_spends.len(), 1);
|
||||||
|
assert_eq!(tx.shielded_outputs.len(), 0);
|
||||||
|
assert_eq!(tx.shielded_spends[0].index, 0);
|
||||||
|
assert_eq!(tx.shielded_spends[0].nf, nf);
|
||||||
|
assert_eq!(tx.shielded_spends[0].account, account);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use hex;
|
use hex;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
@ -21,14 +22,31 @@ impl fmt::Display for BlockHash {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockHash {
|
||||||
|
/// Constructs a [`BlockHash`] from the given slice.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic if the slice is not exactly 32 bytes.
|
||||||
|
pub fn from_slice(bytes: &[u8]) -> Self {
|
||||||
|
assert_eq!(bytes.len(), 32);
|
||||||
|
let mut hash = [0; 32];
|
||||||
|
hash.copy_from_slice(&bytes);
|
||||||
|
BlockHash(hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A Zcash block header.
|
/// A Zcash block header.
|
||||||
pub struct BlockHeader(BlockHeaderData);
|
pub struct BlockHeader {
|
||||||
|
hash: BlockHash,
|
||||||
|
data: BlockHeaderData,
|
||||||
|
}
|
||||||
|
|
||||||
impl Deref for BlockHeader {
|
impl Deref for BlockHeader {
|
||||||
type Target = BlockHeaderData;
|
type Target = BlockHeaderData;
|
||||||
|
|
||||||
fn deref(&self) -> &BlockHeaderData {
|
fn deref(&self) -> &BlockHeaderData {
|
||||||
&self.0
|
&self.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,12 +62,31 @@ pub struct BlockHeaderData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockHeaderData {
|
impl BlockHeaderData {
|
||||||
pub fn freeze(self) -> BlockHeader {
|
pub fn freeze(self) -> io::Result<BlockHeader> {
|
||||||
BlockHeader(self)
|
BlockHeader::from_data(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockHeader {
|
impl BlockHeader {
|
||||||
|
fn from_data(data: BlockHeaderData) -> io::Result<Self> {
|
||||||
|
let mut header = BlockHeader {
|
||||||
|
hash: BlockHash([0; 32]),
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
let mut raw = vec![];
|
||||||
|
header.write(&mut raw)?;
|
||||||
|
header
|
||||||
|
.hash
|
||||||
|
.0
|
||||||
|
.copy_from_slice(&Sha256::digest(&Sha256::digest(&raw)));
|
||||||
|
Ok(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the hash of this header.
|
||||||
|
pub fn hash(&self) -> BlockHash {
|
||||||
|
self.hash
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||||
let version = reader.read_i32::<LittleEndian>()?;
|
let version = reader.read_i32::<LittleEndian>()?;
|
||||||
|
|
||||||
|
@ -70,7 +107,7 @@ impl BlockHeader {
|
||||||
|
|
||||||
let solution = Vector::read(&mut reader, |r| r.read_u8())?;
|
let solution = Vector::read(&mut reader, |r| r.read_u8())?;
|
||||||
|
|
||||||
Ok(BlockHeader(BlockHeaderData {
|
BlockHeader::from_data(BlockHeaderData {
|
||||||
version,
|
version,
|
||||||
prev_block,
|
prev_block,
|
||||||
merkle_root,
|
merkle_root,
|
||||||
|
@ -79,7 +116,7 @@ impl BlockHeader {
|
||||||
bits,
|
bits,
|
||||||
nonce,
|
nonce,
|
||||||
solution,
|
solution,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
|
@ -206,6 +243,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn header_read_write() {
|
fn header_read_write() {
|
||||||
let header = BlockHeader::read(&HEADER_MAINNET_415000[..]).unwrap();
|
let header = BlockHeader::read(&HEADER_MAINNET_415000[..]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", header.hash()),
|
||||||
|
"0000000001ab37793ce771262b2ffa082519aa3fe891250a1adb43baaf856168"
|
||||||
|
);
|
||||||
let mut encoded = Vec::with_capacity(HEADER_MAINNET_415000.len());
|
let mut encoded = Vec::with_capacity(HEADER_MAINNET_415000.len());
|
||||||
header.write(&mut encoded).unwrap();
|
header.write(&mut encoded).unwrap();
|
||||||
assert_eq!(&HEADER_MAINNET_415000[..], &encoded[..]);
|
assert_eq!(&HEADER_MAINNET_415000[..], &encoded[..]);
|
||||||
|
|
Loading…
Reference in New Issue