diff --git a/.gitignore b/.gitignore index 2d8bdb1..bd6b0aa 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,7 @@ docs/_archive/ # ed25519 release-signing keys — the secret key must NEVER be committed *.ed25519.key *.ed25519.pub.b64 + + +# Lite-backend deps are fetched (or `cargo vendor`-ed locally for offline); not committed. +third_party/silentdragonxlite/lib/vendor/ diff --git a/CLAUDE.md b/CLAUDE.md index 5c967fc..a104136 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,7 +61,7 @@ There is no per-test filtering — it is one binary that runs every assertion. T Variants are selected with CMake options (set by `build.sh` flags), surfaced to C++ as compile definitions: - `DRAGONX_BUILD_LITE` (`--lite`) → `DRAGONX_LITE_BUILD` define; renames the app to `ObsidianDragonLite` and excludes embedded-daemon / full-node assets (Sapling params, asmap, dragonxd). -- `DRAGONX_ENABLE_LITE_BACKEND` → links a real external lite backend. Requires `--lite`, link mode `imported`, ABI `sdxl-c-v1`, and a symbols inventory file (built by `scripts/build-lite-backend-artifact.sh`); CMake hard-fails if any required `litelib_*` symbol is missing. +- `DRAGONX_ENABLE_LITE_BACKEND` → links a real external lite backend. Requires `--lite`, link mode `imported`, ABI `sdxl-c-v1`, and a symbols inventory file (built by `scripts/build-lite-backend-artifact.sh`); CMake hard-fails if any required `litelib_*` symbol is missing. The backend **source is vendored in-tree** at `third_party/silentdragonxlite/` — the `qtlib` C-ABI wrapper (`lib/`, produces `libsilentdragonxlite.a`) and the `silentdragonxlitelib` core (`silentdragonxlite-cli/lib/`, with `proto/` + `res/`). `build-lite-backend-artifact.sh` defaults `--backend-dir` there, so the lite wallet builds **without** the upstream SilentDragonXLite repo. External build inputs are limited to the **Rust toolchain (rustc/cargo 1.63)** plus two project-controlled sources on `git.dragonx.is`: the librustzcash crates come from the mirror `git.dragonx.is/DragonX/librustzcash` (the 6 `git =` deps in the core `Cargo.toml`, pinned to rev `acff1444…`), and the **Sapling params are not committed** (gitignored) — the build fetches them from the `git.dragonx.is/DragonX/zcash-params` release `sapling-v1` and verifies their SHA-256 before rust-embed bakes them in (`ensure_sapling_params`; override the URL with `SAPLING_PARAMS_BASE_URL`). Other crate deps come from crates.io. For a fully offline build, `cargo vendor` into `third_party/silentdragonxlite/lib/vendor/` and add a `vendored-sources` redirect to `lib/.cargo/config.toml` (the build script symlinks `vendor/` into its prepared dir if present); `vendor/` is gitignored. - `DRAGONX_ENABLE_CHAT` → `DRAGONX_ENABLE_CHAT` define gating the chat module. Guard full-node-only code paths with `#if DRAGONX_LITE_BUILD` / chat code with `DRAGONX_ENABLE_CHAT`. diff --git a/scripts/build-lite-backend-artifact.sh b/scripts/build-lite-backend-artifact.sh index 324f20b..e83b021 100755 --- a/scripts/build-lite-backend-artifact.sh +++ b/scripts/build-lite-backend-artifact.sh @@ -7,7 +7,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" ABI_VERSION="sdxl-c-v1" LINK_MODE="imported" -BACKEND_DIR="$PROJECT_ROOT/external/SilentDragonXLite/lib" +BACKEND_DIR="$PROJECT_ROOT/third_party/silentdragonxlite/lib" BACKEND_SOURCE_DIR="" BUILD_BACKEND_DIR="" BACKEND_DEPENDENCY_DIR="" @@ -314,6 +314,33 @@ validate_backend_dependency_source() { fi } +# Ensure the Sapling proving params are present in the core crate (rust-embed bakes them in at build +# time). They are the fixed Zcash trusted-setup output — not buildable — so fetch + verify them from +# git.dragonx.is when absent. Override the source with SAPLING_PARAMS_BASE_URL. +SAPLING_PARAMS_BASE_URL="${SAPLING_PARAMS_BASE_URL:-https://git.dragonx.is/DragonX/zcash-params/releases/download/sapling-v1}" +ensure_sapling_params() { + local dir="$1" + [[ -n "$dir" ]] || return 0 + mkdir -p "$dir" + local specs=( + "sapling-spend.params:8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13" + "sapling-output.params:2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4" + ) + local spec name want path got + for spec in "${specs[@]}"; do + name="${spec%%:*}"; want="${spec##*:}"; path="$dir/$name" + if [[ -f "$path" ]] && [[ "$(compute_sha256 "$path")" == "$want" ]]; then + info "sapling param present and verified: $name" + continue + fi + info "fetching $name from $SAPLING_PARAMS_BASE_URL" + curl -fsSL "$SAPLING_PARAMS_BASE_URL/$name" -o "$path" || die "failed to download sapling param: $name" + got="$(compute_sha256 "$path")" + [[ "$got" == "$want" ]] || { rm -f "$path"; die "sapling param $name sha256 mismatch (got $got, want $want)"; } + info "downloaded and verified $name" + done +} + prepare_backend_source() { BUILD_BACKEND_DIR="$BACKEND_SOURCE_DIR" @@ -348,6 +375,9 @@ prepare_backend_source() { [[ -f "$BACKEND_SOURCE_DIR/Cargo.lock" ]] && ln -s "$BACKEND_SOURCE_DIR/Cargo.lock" "$prepared_root/Cargo.lock" [[ -d "$BACKEND_SOURCE_DIR/.cargo" ]] && ln -s "$BACKEND_SOURCE_DIR/.cargo" "$prepared_root/.cargo" [[ -d "$BACKEND_SOURCE_DIR/libsodium-mingw" ]] && ln -s "$BACKEND_SOURCE_DIR/libsodium-mingw" "$prepared_root/libsodium-mingw" + # Vendored crate deps (offline builds): the .cargo/config.toml's vendored-sources directory is + # "vendor" relative to the build root, so expose it inside the prepared root too. + [[ -d "$BACKEND_SOURCE_DIR/vendor" ]] && ln -s "$BACKEND_SOURCE_DIR/vendor" "$prepared_root/vendor" [[ -f "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" ]] && ln -s "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" "$prepared_root/silentdragonxlitelib.h" local replacement="silentdragonxlitelib = { path = \"$BACKEND_DEPENDENCY_DIR\" }" @@ -489,6 +519,8 @@ build_with_cargo() { export SODIUM_LIB_DIR="$BUILD_BACKEND_DIR/libsodium-mingw" fi + [[ -n "$BACKEND_DEPENDENCY_DIR" ]] && ensure_sapling_params "$BACKEND_DEPENDENCY_DIR/zcash-params" + local cargo_cmd=(cargo build --locked --lib --release) if [[ -n "$RUST_TARGET" ]]; then cargo_cmd+=(--target "$RUST_TARGET") diff --git a/third_party/silentdragonxlite/lib/.cargo/config.toml b/third_party/silentdragonxlite/lib/.cargo/config.toml new file mode 100644 index 0000000..c91c3f3 --- /dev/null +++ b/third_party/silentdragonxlite/lib/.cargo/config.toml @@ -0,0 +1,2 @@ +[net] +git-fetch-with-cli = true diff --git a/third_party/silentdragonxlite/lib/.gitignore b/third_party/silentdragonxlite/lib/.gitignore new file mode 100644 index 0000000..8c0ae06 --- /dev/null +++ b/third_party/silentdragonxlite/lib/.gitignore @@ -0,0 +1,2 @@ +/target/ + diff --git a/third_party/silentdragonxlite/lib/Cargo.lock b/third_party/silentdragonxlite/lib/Cargo.lock new file mode 100644 index 0000000..87f1dfb --- /dev/null +++ b/third_party/silentdragonxlite/lib/Cargo.lock @@ -0,0 +1,2751 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler32" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" + +[[package]] +name = "aes" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" +dependencies = [ + "aes-soft", + "aesni", + "block-cipher-trait", +] + +[[package]] +name = "aes-soft" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" +dependencies = [ + "block-cipher-trait", + "byteorder", + "opaque-debug", +] + +[[package]] +name = "aesni" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" +dependencies = [ + "block-cipher-trait", + "opaque-debug", +] + +[[package]] +name = "antidote" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" + +[[package]] +name = "anyhow" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" + +[[package]] +name = "arc-swap" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" + +[[package]] +name = "arc-swap" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "async-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "async-trait" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "750b1c38a1dfadd108da0f01c08f4cdc7ff1bb39b325f9c82cc972361780a6e1" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "backtrace" +version = "0.3.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f80256bc78f67e7df7e36d77366f636ed976895d91fe2ab9efa3973e8fe8c4f" +dependencies = [ + "backtrace-sys", + "cfg-if 0.1.10", + "libc", + "rustc-demangle", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "base58" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "bech32" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c" + +[[package]] +name = "bellman" +version = "0.1.0" +source = "git+https://git.dragonx.is/DragonX/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "bit-vec", + "blake2s_simd", + "byteorder", + "crossbeam", + "ff", + "futures", + "futures-cpupool", + "group", + "num_cpus", + "pairing", + "rand_core 0.5.1", +] + +[[package]] +name = "bit-vec" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "blake2b_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9e07352b829279624ceb7c64adb4f585dacdb81d35cafae81139ccd617cf44" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "423897d97e11b810c9da22458400b28ec866991c711409073662eb34dc44bfff" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 0.1.10", + "constant_time_eq", + "crypto-mac", + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-cipher-trait" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bs58" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95ee6bba9d950218b6cc910cf62bc9e0a171d0f4537e3627b0f54d08549b188" +dependencies = [ + "sha2", +] + +[[package]] +name = "bumpalo" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb8038c1ddc0a5f73787b130f4cc75151e96ed33e417fde765eb5a81e3532f4" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "bytes" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" + +[[package]] +name = "c2-chacha" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" +dependencies = [ + "ppv-lite86", +] + +[[package]] +name = "cc" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "crossbeam" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19" + +[[package]] +name = "crossbeam-utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +dependencies = [ + "autocfg 0.1.7", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array", + "subtle 1.0.0", +] + +[[package]] +name = "crypto_api" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f855e87e75a4799e18b8529178adcde6fd4f97c1449ff4821e747ff728bb102" + +[[package]] +name = "crypto_api_chachapoly" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b2ad7cab08fd71addba81df5077c49df208effdfb3118a1519f9cdeac5aaf2" +dependencies = [ + "crypto_api", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "redox_users", + "winapi 0.3.8", +] + +[[package]] +name = "dtoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + +[[package]] +name = "failure" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", + "synstructure", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "ff" +version = "0.4.0" +source = "git+https://git.dragonx.is/DragonX/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "byteorder", + "ff_derive", + "rand_core 0.5.1", +] + +[[package]] +name = "ff_derive" +version = "0.3.0" +source = "git+https://git.dragonx.is/DragonX/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.14.9", +] + +[[package]] +name = "filetime" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff6d4dab0aa0c8e6346d46052e93b13a16cf847b54ed357087c35011048cc7d" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "redox_syscall", + "winapi 0.3.8", +] + +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "flate2" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" +dependencies = [ + "cfg-if 0.1.10", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" + +[[package]] +name = "fpe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21988a326139165b75e3196bc6962ca638e5fb0c95102fbf152a3743174b01e4" +dependencies = [ + "aes", + "byteorder", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" + +[[package]] +name = "futures-channel" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92c2137e8e1ebf1ac99453550ab46eb4f35c5c53476d57d75eb782fb4d71e84" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfb301b0b09e940a67376cf40d1b0ac4db9366ee737f65c02edea225057e91e" + +[[package]] +name = "futures-cpupool" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" +dependencies = [ + "futures", + "num_cpus", +] + +[[package]] +name = "futures-sink" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0485279d763e8a3669358f500e805339138b7bbe90f5718c80eedfdcb2ea36a4" + +[[package]] +name = "futures-task" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefffab2aacc73845afd3f202e09fc775a55e2e96f46c8b1a46c117ae1c126ca" + +[[package]] +name = "futures-util" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3f8c59707f898b8b6f0b54c2aef5408ae90a560b7bf0fbf1b95b3c652b0171" +dependencies = [ + "futures-core", + "futures-task", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.1.0" +source = "git+https://git.dragonx.is/DragonX/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "ff", + "rand 0.7.3", + "rand_xorshift 0.2.0", +] + +[[package]] +name = "h2" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" +dependencies = [ + "bytes 0.5.4", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "log", + "slab", + "tokio", + "tokio-util 0.3.1", +] + +[[package]] +name = "hashbrown" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" +dependencies = [ + "byteorder", + "scopeguard", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" + +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac", + "digest", +] + +[[package]] +name = "http" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" +dependencies = [ + "bytes 0.5.4", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes 0.5.4", + "http", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "hyper" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" +dependencies = [ + "bytes 0.5.4", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "log", + "pin-project", + "socket2", + "time", + "tokio", + "tower-service", + "want", +] + +[[package]] +name = "indexmap" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b54058f0a6ff80b6803da8faf8997cde53872b38f4023728f6830b06cd3c0dc" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + +[[package]] +name = "js-sys" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7889c7c36282151f6bf465be4700359318aef36baa951462382eae49e9577cf9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a38661a28126f8621fb246611288ae28935ddf180f5e21f2d0fbfe5e4131dbe" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libflate" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" +dependencies = [ + "adler32", + "crc32fast", + "rle-decode-fast", + "take_mut", +] + +[[package]] +name = "libsodium-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c344ff12b90ef8fa1f0fffacd348c1fd041db331841fec9eab23fdb991f5e73" +dependencies = [ + "cc", + "libc", + "libflate", + "pkg-config", + "tar", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" + +[[package]] +name = "lock_api" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if 0.1.10", + "serde", +] + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "100052474df98158c0738a7d3f4249c99978490178b5f9f68cd835ac57adbd1b" +dependencies = [ + "antidote", + "arc-swap 0.3.11", + "chrono", + "flate2", + "fnv", + "humantime", + "libc", + "log", + "log-mdc", + "serde", + "serde-value", + "serde_derive", + "serde_json", + "serde_yaml", + "thread-id", + "typemap", + "winapi 0.3.8", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" + +[[package]] +name = "miniz_oxide" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" +dependencies = [ + "adler32", +] + +[[package]] +name = "mio" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.1", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-named-pipes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" +dependencies = [ + "log", + "mio", + "miow 0.3.3", + "winapi 0.3.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" +dependencies = [ + "socket2", + "winapi 0.3.8", +] + +[[package]] +name = "multimap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97fbd5d00e0e37bfb10f433af8f5aaf631e739368dc9fc28286ca81ca4948dc" + +[[package]] +name = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532c29a261168a45ce28948f9537ddd7a5dd272cc513b3017b1e82a88f962c37" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "ordered-float" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" +dependencies = [ + "num-traits", +] + +[[package]] +name = "pairing" +version = "0.14.2" +source = "git+https://git.dragonx.is/DragonX/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "byteorder", + "ff", + "group", + "rand_core 0.5.1", +] + +[[package]] +name = "parking_lot" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" +dependencies = [ + "libc", + "rand 0.6.5", + "rustc_version", + "smallvec", + "winapi 0.3.8", +] + +[[package]] +name = "pbkdf2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" +dependencies = [ + "byteorder", + "crypto-mac", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "petgraph" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c127eea4a29ec6c85d153c59dc1213f33ec74cead30fe4730aecc88cc1fd92" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" + +[[package]] +name = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" + +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" +dependencies = [ + "unicode-xid 0.2.0", +] + +[[package]] +name = "prost" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce49aefe0a6144a45de32927c77bd2859a5f7677b55f220ae5b744e87389c212" +dependencies = [ + "bytes 0.5.4", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b10678c913ecbd69350e8535c3aef91a8676c0773fc1d7b95cdd196d7f2f26" +dependencies = [ + "bytes 0.5.4", + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537aa19b95acde10a12fec4301466386f757403de4cd4e5b4fa78fb5ecb18f72" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "prost-types" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1834f67c0697c001304b75be76f67add9c89742eda3a085ad8ee0bb38c3417aa" +dependencies = [ + "bytes 0.5.4", + "prost", +] + +[[package]] +name = "protobuf" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6686ddd96a8dbe2687b5f2a687b2cfb520854010ec480f2d74c32e7c9873d3c5" + +[[package]] +name = "protobuf-codegen" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456421eecf7fc72905868cd760c3e35848ded3552e480cfe67726ed4dbd8d23" +dependencies = [ + "protobuf", +] + +[[package]] +name = "protobuf-codegen-pure" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a7cb42d5ab6073333be90208ab5ea6ab41c8f6803b35fd773a7572624cc15c9" +dependencies = [ + "protobuf", + "protobuf-codegen", +] + +[[package]] +name = "qtlib" +version = "0.1.0" +dependencies = [ + "blake3", + "lazy_static", + "libc", + "silentdragonxlitelib", + "socket2", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +dependencies = [ + "proc-macro2 1.0.8", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os 0.1.3", + "rand_pcg 0.1.2", + "rand_xorshift 0.1.1", + "winapi 0.3.8", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha 0.2.1", + "rand_core 0.5.1", + "rand_hc 0.2.0", + "rand_pcg 0.2.1", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +dependencies = [ + "c2-chacha", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi 0.3.8", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi 0.3.8", +] + +[[package]] +name = "rand_os" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a788ae3edb696cfcba1c19bfd388cc4b8c21f8a408432b199c072825084da58a" +dependencies = [ + "getrandom", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "redox_users" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +dependencies = [ + "winapi 0.3.8", +] + +[[package]] +name = "ring" +version = "0.16.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862" +dependencies = [ + "cc", + "lazy_static", + "libc", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.8", +] + +[[package]] +name = "ripemd160" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a" +dependencies = [ + "block-buffer", + "digest", + "opaque-debug", +] + +[[package]] +name = "rle-decode-fast" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" + +[[package]] +name = "rust-argon2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +dependencies = [ + "base64 0.11.0", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + +[[package]] +name = "rust-embed" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18893bdbdb0fa5bce588f5d7ab4afbd0678fc879d31535912bf39b7fbc062d6" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50633968284cfc373661345fc6382e62b738079f045738023ebc5e445cf44357" +dependencies = [ + "quote 1.0.2", + "rust-embed-utils", + "syn 1.0.14", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97655158074ccb2d2cfb1ccb4c956ef0f4054e43a2c1e71146d4991e6961e105" +dependencies = [ + "walkdir", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" +dependencies = [ + "base64 0.10.1", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ffebdbb48c14f84eba0b715197d673aff1dd22cc1007ca647e28483bbcc307" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework", +] + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" +dependencies = [ + "lazy_static", + "winapi 0.3.8", +] + +[[package]] +name = "scopeguard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" + +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secp256k1" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0344a794ff109f85547039536028e12f313178ac1545e49fdf16a530d900a7b" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" +dependencies = [ + "core-foundation-sys", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" + +[[package]] +name = "serde-value" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a663f873dedc4eac1a559d4c6bc0d0b2c34dc5ac4702e105014b8281489e44f" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "serde_json" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b01d7f0288608a01dca632cf1df859df6fd6ffa885300fc275ce2ba6221953" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" +dependencies = [ + "arc-swap 0.4.4", + "libc", +] + +[[package]] +name = "silentdragonxlitelib" +version = "0.1.0" +dependencies = [ + "base58", + "bellman", + "bs58", + "byteorder", + "bytes 0.4.12", + "dirs", + "ff", + "hex", + "http", + "json", + "lazy_static", + "libflate", + "log", + "log4rs", + "num_cpus", + "pairing", + "prost", + "prost-types", + "protobuf", + "rand 0.7.3", + "ring", + "ripemd160", + "rust-embed", + "secp256k1", + "sha2", + "sodiumoxide", + "subtle 2.2.2", + "threadpool", + "tiny-bip39", + "tokio", + "tokio-rustls", + "tonic", + "tonic-build", + "webpki", + "webpki-roots", + "zcash_client_backend", + "zcash_primitives", + "zcash_proofs", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "sodiumoxide" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585232e78a4fc18133eef9946d3080befdf68b906c51b621531c37e91787fa2b" +dependencies = [ + "libc", + "libsodium-sys", + "serde", +] + +[[package]] +name = "sourcefile" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" + +[[package]] +name = "syn" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syn" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "unicode-xid 0.2.0", +] + +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", + "unicode-xid 0.2.0", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tar" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3196bfbffbba3e57481b6ea32249fbaf590396a52505a2615adbb79d9d826d3" +dependencies = [ + "filetime", + "libc", + "redox_syscall", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "rand 0.7.3", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.8", +] + +[[package]] +name = "thread-id" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" +dependencies = [ + "libc", + "redox_syscall", + "winapi 0.3.8", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +dependencies = [ + "libc", + "redox_syscall", + "winapi 0.3.8", +] + +[[package]] +name = "tiny-bip39" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c5676413eaeb1ea35300a0224416f57abc3bd251657e0fafc12c47ff98c060" +dependencies = [ + "failure", + "hashbrown", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.6.5", + "sha2", +] + +[[package]] +name = "tokio" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" +dependencies = [ + "bytes 0.5.4", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-named-pipes", + "mio-uds", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi 0.3.8", +] + +[[package]] +name = "tokio-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "tokio-rustls" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3068d891551949b37681724d6b73666787cc63fa8e255c812a41d2513aff9775" +dependencies = [ + "futures-core", + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" +dependencies = [ + "bytes 0.5.4", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes 0.5.4", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08283643b1d483eb7f3fc77069e63b5cba3e4db93514b3d45470e67f123e4e48" +dependencies = [ + "async-stream", + "async-trait", + "base64 0.10.1", + "bytes 0.5.4", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tokio-util 0.2.0", + "tower", + "tower-balance", + "tower-load", + "tower-make", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0436413ba71545bcc6c2b9a0f9d78d72deb0123c6a75ccdfe7c056f9930f5e52" +dependencies = [ + "proc-macro2 1.0.8", + "prost-build", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "tower" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3169017c090b7a28fce80abaad0ab4f5566423677c9331bb320af7e49cfe62" +dependencies = [ + "futures-core", + "tower-buffer", + "tower-discover", + "tower-layer", + "tower-limit", + "tower-load-shed", + "tower-retry", + "tower-service", + "tower-timeout", + "tower-util", +] + +[[package]] +name = "tower-balance" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a792277613b7052448851efcf98a2c433e6f1d01460832dc60bef676bc275d4c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "rand 0.7.3", + "slab", + "tokio", + "tower-discover", + "tower-layer", + "tower-load", + "tower-make", + "tower-ready-cache", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-buffer" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4887dc2a65d464c8b9b66e0e4d51c2fd6cf5b3373afc72805b0a60bce00446a" +dependencies = [ + "futures-core", + "pin-project", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-discover" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6b5000c3c54d269cc695dff28136bb33d08cbf1df2c48129e143ab65bf3c2a" +dependencies = [ + "futures-core", + "pin-project", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35d656f2638b288b33495d1053ea74c40dc05ec0b92084dd71ca5566c4ed1dc" + +[[package]] +name = "tower-limit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4030a1dc1ab99ec6fc9475fc18c62f6cc4da035d370fcbd22fe342f9dd16cd" +dependencies = [ + "futures-core", + "pin-project", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-load" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cc79fc3afd07492b7966d7efa7c6c50f8ed58d768a6075dd7ae6591c5d2017b" +dependencies = [ + "futures-core", + "log", + "pin-project", + "tokio", + "tower-discover", + "tower-service", +] + +[[package]] +name = "tower-load-shed" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f021e23900173dc315feb4b6922510dae3e79c689b74c089112066c11f0ae4e" +dependencies = [ + "futures-core", + "pin-project", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-make" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce50370d644a0364bf4877ffd4f76404156a248d104e2cc234cd391ea5cdc965" +dependencies = [ + "tokio", + "tower-service", +] + +[[package]] +name = "tower-ready-cache" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2183d0a00b68a41c0af9e281cf51f40c7de2e1d4af4a43f92a5c35bbe7728d7" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "log", + "tokio", + "tower-service", +] + +[[package]] +name = "tower-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6727956aaa2f8957d4d9232b308fe8e4e65d99db30f42b225646e86c9b6a952" +dependencies = [ + "futures-core", + "pin-project", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-service" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" + +[[package]] +name = "tower-timeout" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "127b8924b357be938823eaaec0608c482d40add25609481027b96198b2e4b31e" +dependencies = [ + "pin-project", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-util" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5702d7890e35b2aae6ee420e8a762547505dbed30c075fbc84ec069a0aa18314" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "tower-service", +] + +[[package]] +name = "tracing" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e213bd24252abeb86a0b7060e02df677d367ce6cb772cef17e9214b8390a8d3" +dependencies = [ + "cfg-if 0.1.10", + "log", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cfd395def5a60236e187e1ff905cb55668a59f29928dec05e6e1b1fd2ac1f3" +dependencies = [ + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "tracing-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a46f11e372b8bd4b4398ea54353412fdd7fd42a8370c7e543e218cf7661978" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33848db47a7c848ab48b66aab3293cb9c61ea879a3586ecfcd17302fcea0baf1" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" + +[[package]] +name = "try-lock" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" + +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" +dependencies = [ + "unsafe-any", +] + +[[package]] +name = "typenum" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "unsafe-any" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" +dependencies = [ + "traitobject", +] + +[[package]] +name = "untrusted" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" + +[[package]] +name = "vcpkg" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi 0.3.8", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5205e9afdf42282b192e2310a5b463a6d1c1d774e30dc3c791ac37ab42d2616c" +dependencies = [ + "cfg-if 0.1.10", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11cdb95816290b525b32587d76419facd99662a07e59d3cdb560488a819d9a45" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "574094772ce6921576fb6f2e3f7497b8a76273b6db092be18fc48a082de09dc3" +dependencies = [ + "quote 1.0.2", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e85031354f25eaebe78bb7db1c3d86140312a911a106b2e29f9cc440ce3e7668" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e7e61fc929f4c0dddb748b102ebf9f632e2b8d739f2016542b4de2965a9601" + +[[package]] +name = "wasm-bindgen-webidl" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef012a0d93fc0432df126a8eaf547b2dce25a8ce9212e1d3cbeef5c11157975d" +dependencies = [ + "anyhow", + "heck", + "log", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", + "wasm-bindgen-backend", + "weedle", +] + +[[package]] +name = "web-sys" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf97caf6aa8c2b1dac90faf0db529d9d63c93846cca4911856f78a83cebf53b" +dependencies = [ + "anyhow", + "js-sys", + "sourcefile", + "wasm-bindgen", + "wasm-bindgen-webidl", +] + +[[package]] +name = "webpki" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4" +dependencies = [ + "webpki", +] + +[[package]] +name = "weedle" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" +dependencies = [ + "nom", +] + +[[package]] +name = "which" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5475d47078209a02e60614f7ba5e645ef3ed60f771920ac1906d7c1cc65024c8" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" +dependencies = [ + "winapi 0.3.8", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +dependencies = [ + "libc", +] + +[[package]] +name = "yaml-rust" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zcash_client_backend" +version = "0.0.0" +source = "git+https://git.dragonx.is/DragonX/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "bech32", + "bs58", + "ff", + "hex", + "pairing", + "protobuf", + "protobuf-codegen-pure", + "subtle 2.2.2", + "zcash_primitives", +] + +[[package]] +name = "zcash_primitives" +version = "0.0.0" +source = "git+https://git.dragonx.is/DragonX/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "aes", + "blake2b_simd", + "blake2s_simd", + "byteorder", + "crypto_api_chachapoly", + "ff", + "fpe", + "hex", + "lazy_static", + "pairing", + "rand 0.7.3", + "rand_core 0.5.1", + "rand_os 0.2.2", + "ripemd160", + "secp256k1", + "sha2", +] + +[[package]] +name = "zcash_proofs" +version = "0.0.0" +source = "git+https://git.dragonx.is/DragonX/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "bellman", + "blake2b_simd", + "byteorder", + "ff", + "pairing", + "rand_os 0.2.2", + "zcash_primitives", +] diff --git a/third_party/silentdragonxlite/lib/Cargo.toml b/third_party/silentdragonxlite/lib/Cargo.toml new file mode 100644 index 0000000..9b03a78 --- /dev/null +++ b/third_party/silentdragonxlite/lib/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "qtlib" +version = "0.1.0" +authors = ["zecwallet", "The Hush Developers"] +edition = "2018" + +[lib] +name = "silentdragonxlite" +crate-type = ["staticlib"] + +[dependencies] +libc = "0.2.58" +lazy_static = "1.4.0" +blake3 = "0.3.4" +silentdragonxlitelib = { path = "../silentdragonxlite-cli/lib" } +socket2 = "0.3.11" diff --git a/third_party/silentdragonxlite/lib/Makefile b/third_party/silentdragonxlite/lib/Makefile new file mode 100644 index 0000000..ec8546a --- /dev/null +++ b/third_party/silentdragonxlite/lib/Makefile @@ -0,0 +1,28 @@ +ifeq ($(shell uname),Darwin) + EXT := dylib + CFLAGS := "-mmacosx-version-min=10.11" +else + EXT := a + CFLAGS := +endif + +PWD := $(shell pwd) + +all: release + +winrelease: target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib + +target/x86_64-pc-windows-gnu/release/silentdragonxlite.lib: src/lib.rs Cargo.toml + SODIUM_LIB_DIR="$(PWD)/libsodium-mingw/" cargo build --lib --release --target x86_64-pc-windows-gnu + +release: target/release/silentdragonxlite.$(EXT) +debug: target/debug/silentdragonxlite.$(EXT) + +target/release/silentdragonxlite.$(EXT): src/lib.rs Cargo.toml + LIBS="" CFLAGS=$(CFLAGS) cargo build --lib --release + +target/debug/silentdragonxlite.$(EXT): src/lib.rs Cargo.toml + LIBS="" CFLAGS=$(CFLAGS) cargo build --lib + +clean: + rm -rf target \ No newline at end of file diff --git a/third_party/silentdragonxlite/lib/libsodium-mingw/libsodium.a b/third_party/silentdragonxlite/lib/libsodium-mingw/libsodium.a new file mode 100644 index 0000000..363c743 Binary files /dev/null and b/third_party/silentdragonxlite/lib/libsodium-mingw/libsodium.a differ diff --git a/third_party/silentdragonxlite/lib/silentdragonxlitelib.h b/third_party/silentdragonxlite/lib/silentdragonxlitelib.h new file mode 100644 index 0000000..e6f76e7 --- /dev/null +++ b/third_party/silentdragonxlite/lib/silentdragonxlitelib.h @@ -0,0 +1,29 @@ +#ifndef _hush_PAPER_RUST_H +#define _hush_PAPER_RUST_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern bool litelib_wallet_exists (const char* chain_name); +extern char * litelib_initialize_new (bool dangerous, const char* server); +extern char * litelib_initialize_new_from_phrase + (bool dangerous, const char* server, const char* seed, + unsigned long long birthday, unsigned long long number, + bool overwrite); +extern char * litelib_initialize_existing (bool dangerous,const char* server); +extern char * litelib_execute (const char* s, const char* args); +extern void litelib_rust_free_string (char* s); +extern char * blake3_PW (char* pw); +extern bool litelib_check_server_online (const char* server); +extern void litelib_shutdown (void); + +#ifdef __cplusplus +} +#endif + +// This is a function implemented in connection.cpp that will process a string response from +// the litelib and turn into into a QString in a memory-safe way. +QString litelib_process_response(char* resp); + +#endif diff --git a/third_party/silentdragonxlite/lib/src/lib.rs b/third_party/silentdragonxlite/lib/src/lib.rs new file mode 100644 index 0000000..1b1fcbd --- /dev/null +++ b/third_party/silentdragonxlite/lib/src/lib.rs @@ -0,0 +1,328 @@ +#[macro_use] +extern crate lazy_static; + +use libc::{c_char}; + +use std::ffi::{CStr, CString}; +use std::sync::{Mutex, Arc}; +use std::cell::RefCell; +use std::ptr; +use std::panic; + +use silentdragonxlitelib::{commands, lightclient::{LightClient, LightClientConfig}}; + +/// Helper to create a CString, replacing null bytes to avoid panics +fn safe_cstring(s: &str) -> CString { + let cleaned: String = s.replace('\0', ""); + CString::new(cleaned).unwrap_or_else(|_| CString::new("Error: failed to create CString").unwrap()) +} + +/// Helper to create an error CString +fn error_cstring(msg: &str) -> *mut c_char { + safe_cstring(&format!("Error: {}", msg)).into_raw() +} + +// We'll use a MUTEX to store a global lightclient instance, +// so we don't have to keep creating it. We need to store it here, in rust +// because we can't return such a complex structure back to C++ +lazy_static! { + static ref LIGHTCLIENT: Mutex>>> = Mutex::new(RefCell::new(None)); +} + +// Check if there is an existing wallet +#[no_mangle] +pub extern fn litelib_wallet_exists(chain_name: *const c_char) -> bool { + let chain_name_str = unsafe { + assert!(!chain_name.is_null()); + + CStr::from_ptr(chain_name).to_string_lossy().into_owned() + }; + + let config = LightClientConfig::create_unconnected(chain_name_str, None); + + println!("Wallet exists: {}", config.wallet_exists()); + config.wallet_exists() +} + +//////hash blake3 + +#[no_mangle] +pub extern fn blake3_PW(pw: *const c_char) -> *mut c_char{ + + let passwd = unsafe { + assert!(!pw.is_null()); + + CStr::from_ptr(pw).to_string_lossy().into_owned() + }; + + let data = passwd.as_bytes(); + // Hash an input all at once. + let hash1 = blake3::hash(data).to_hex(); + // This is sensitive metadata, do not log it to stdout + //println!("\nBlake3 Hash: {}", hash1); + println!("\nBlake3 Hash calculated"); + + //let sttring = CString::new(hash1).unwrap(); + let e_str = CString::new(format!("{}", hash1)).unwrap(); + return e_str.into_raw(); +} + +/// Create a new wallet and return the seed for the newly created wallet. +#[no_mangle] +pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *mut c_char { + let server_str = unsafe { + assert!(!server.is_null()); + + CStr::from_ptr(server).to_string_lossy().into_owned() + }; + + let server = LightClientConfig::get_server_or_default(Some(server_str)); + let (config, latest_block_height) = match LightClientConfig::create(server, dangerous) { + Ok((c, h)) => (c, h), + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + let lightclient = match LightClient::new(&config, latest_block_height) { + Ok(l) => l, + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + // Initialize logging + let _ = lightclient.init_logging(); + + let seed = match lightclient.do_seed_phrase() { + Ok(s) => s.dump(), + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + let lc = Arc::new(lightclient); + match LightClient::start_mempool_monitor(lc.clone()) { + Ok(_) => {println!("Starting Mempool")}, + Err(e) => { + println!("Couldnt start mempool {}", e) + } + } + + match LIGHTCLIENT.lock() { + Ok(l) => { l.replace(Some(lc)); }, + Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); }, + }; + + // Return the wallet's seed + let s_str = safe_cstring(&seed); + return s_str.into_raw(); +} + +/// Restore a wallet from the seed phrase +#[no_mangle] +pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *const c_char, + seed: *const c_char, birthday: u64, number: u64, overwrite: bool) -> *mut c_char { + if server.is_null() || seed.is_null() { + println!("Server or seed is null"); + return ptr::null_mut(); + } + + let server_str = unsafe { + CStr::from_ptr(server).to_string_lossy().into_owned() + }; + let seed_str = unsafe { + CStr::from_ptr(seed).to_string_lossy().into_owned() + }; + + //println!("Initializing with server: {}, seed: {}", server_str, seed_str); + + // Shut down the existing client if one is running, to stop background threads + if overwrite { + let old_lc = match LIGHTCLIENT.lock() { + Ok(l) => l.borrow().clone(), + Err(poisoned) => poisoned.into_inner().borrow().clone(), + }; + if let Some(lc) = old_lc { + lc.shutdown(); + } + } + + let server = LightClientConfig::get_server_or_default(Some(server_str)); + let (config, _latest_block_height) = match LightClientConfig::create(server, dangerous) { + Ok((c, h)) => { + println!("Config created successfully"); + (c, h) + }, + Err(e) => { + println!("Error creating config: {}", e); + let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap()); + return e_str.into_raw(); + } + }; + + let lightclient = match LightClient::new_from_phrase(seed_str, &config, birthday, number, overwrite) { + Ok(l) => { + println!("LightClient created successfully"); + l + }, + Err(e) => { + println!("Error creating LightClient: {}", e); + let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap()); + return e_str.into_raw(); + } + }; + + // Initialize logging + let _ = lightclient.init_logging(); + + let lc = Arc::new(lightclient); + match LightClient::start_mempool_monitor(lc.clone()) { + Ok(_) => println!("Starting Mempool"), + Err(e) => println!("Could not start mempool: {}", e) + } + + match LIGHTCLIENT.lock() { + Ok(l) => { l.replace(Some(lc)); }, + Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); }, + }; + + let c_str = safe_cstring("OK"); + return c_str.into_raw(); +} + +// Initialize a new lightclient and store its value +#[no_mangle] +pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char) -> *mut c_char { + let server_str = unsafe { + assert!(!server.is_null()); + + CStr::from_ptr(server).to_string_lossy().into_owned() + }; + + let server = LightClientConfig::get_server_or_default(Some(server_str)); + let (config, _latest_block_height) = match LightClientConfig::create(server,dangerous) { + Ok((c, h)) => (c, h), + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + let lightclient = match LightClient::read_from_disk(&config) { + Ok(l) => l, + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + // Initialize logging + let _ = lightclient.init_logging(); + + let lc = Arc::new(lightclient); + match LightClient::start_mempool_monitor(lc.clone()) { + Ok(_) => {println!("Starting Mempool")}, + Err(e) => { + println!("Couldnt start mempool {}",e) + } + } + + match LIGHTCLIENT.lock() { + Ok(l) => { l.replace(Some(lc)); }, + Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); }, + }; + + let c_str = safe_cstring("OK"); + return c_str.into_raw(); +} + +#[no_mangle] +pub extern fn litelib_execute(cmd: *const c_char, args: *const c_char) -> *mut c_char { + let result = panic::catch_unwind(|| { + let cmd_str = unsafe { + assert!(!cmd.is_null()); + CStr::from_ptr(cmd).to_string_lossy().into_owned() + }; + + let arg_str = unsafe { + assert!(!args.is_null()); + CStr::from_ptr(args).to_string_lossy().into_owned() + }; + + let resp: String; + { + let lightclient: Arc; + { + let lc = match LIGHTCLIENT.lock() { + Ok(l) => l, + Err(poisoned) => poisoned.into_inner(), + }; + + if lc.borrow().is_none() { + return error_cstring("Light Client is not initialized"); + } + + lightclient = lc.borrow().as_ref().unwrap().clone(); + }; + + let args = if arg_str.is_empty() { vec![] } else { vec![arg_str.as_ref()] }; + + resp = commands::do_user_command(&cmd_str, &args, lightclient.as_ref()).clone(); + }; + + safe_cstring(&resp).into_raw() + }); + + match result { + Ok(ptr) => ptr, + Err(_) => error_cstring("Rust panic in litelib_execute"), + } +} + +// Check is Server Connection is fine +#[no_mangle] +pub extern "C" fn litelib_check_server_online(server: *const c_char) -> bool { + let server_str = unsafe { + assert!(!server.is_null()); + + CStr::from_ptr(server).to_string_lossy().into_owned() + }; + + let server = LightClientConfig::get_server_or_default(Some(server_str)); + let result = LightClientConfig::create(server, false); + + match result { + Ok(_) => true, + Err(_) => false, + } +} + +/// Cleanly shut down the light client, stopping mempool monitor threads. +/// Must be called before exit to prevent hangs. +#[no_mangle] +pub extern "C" fn litelib_shutdown() { + let lc_option = match LIGHTCLIENT.lock() { + Ok(l) => l.borrow().clone(), + Err(poisoned) => poisoned.into_inner().borrow().clone(), + }; + + if let Some(lc) = lc_option { + lc.shutdown(); + } +} + +/** + * Callers that receive string return values from other functions should call this to return the string + * back to rust, so it can be freed. Failure to call this function will result in a memory leak + */ +#[no_mangle] +pub extern fn litelib_rust_free_string(s: *mut c_char) { + unsafe { + if s.is_null() { return } + CString::from_raw(s) + }; +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/Cargo.toml b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/Cargo.toml new file mode 100644 index 0000000..55f3776 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/Cargo.toml @@ -0,0 +1,80 @@ +[package] +name = "silentdragonxlitelib" +version = "0.1.0" +edition = "2018" + +[features] +default = ["embed_params"] +embed_params = [] + +[dependencies] +base58 = "0.1.0" +bs58 = { version = "0.2", features = ["check"] } +log = "0.4" +log4rs = "0.8.3" +dirs = "2.0.2" +http = "0.2" +hex = "0.3" +protobuf = "2" +byteorder = "1" +json = "0.12.0" +tiny-bip39 = "0.6.2" +secp256k1 = "=0.15.0" +sha2 = "0.8.0" +ripemd160 = "0.8.0" +lazy_static = "1.2.0" +rust-embed = { version = "5.1.0", features = ["debug-embed"] } +rand = "0.7.2" +sodiumoxide = "0.2.5" +ring = "0.16.9" +libflate = "0.1" +subtle = "2" +threadpool = "1.8.0" +num_cpus = "1.13.0" + +tonic = { version = "0.1.1", features = ["tls", "tls-roots"] } +bytes = "0.4" +prost = "0.6" +prost-types = "0.6" +tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds", "full"] } +tokio-rustls = { version = "0.12.1", features = ["dangerous_configuration"] } +webpki = "0.21.0" +webpki-roots = "0.18.0" + +[dependencies.bellman] +git = "https://git.dragonx.is/DragonX/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" +default-features = false +features = ["groth16"] + +[dependencies.pairing] +git = "https://git.dragonx.is/DragonX/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" + +[dependencies.zcash_client_backend] +git = "https://git.dragonx.is/DragonX/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" + +default-features = false + +[dependencies.zcash_primitives] +git = "https://git.dragonx.is/DragonX/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" +default-features = false +features = ["transparent-inputs"] + +[dependencies.zcash_proofs] +git = "https://git.dragonx.is/DragonX/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" +default-features = false + +[dependencies.ff] +git = "https://git.dragonx.is/DragonX/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" +features = ["ff_derive"] + +[build-dependencies] +tonic-build = "0.1.1" + +[dev-dependencies] +tempdir = "0.3.7" diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/build.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/build.rs new file mode 100644 index 0000000..241f937 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/build.rs @@ -0,0 +1,13 @@ +// Copyright The Hush Developers 2019-2022 +// Released under the GPLv3 +fn main() -> Result<(), Box> { + tonic_build::configure() + .build_server(false) + .compile( + &["proto/service.proto", "proto/compact_formats.proto"], + &["proto"], + )?; + println!("cargo:rerun-if-changed=proto/service.proto"); + Ok(()) + } + diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/compact_formats.proto b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/compact_formats.proto new file mode 100644 index 0000000..4471ae3 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/compact_formats.proto @@ -0,0 +1,65 @@ +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "lightwalletd/walletrpc"; +option swift_prefix = ""; + +// Remember that proto3 fields are all optional. A field that is not present will be set to its zero value. +// bytes fields of hashes are in canonical little-endian format. + +// CompactBlock is a packaging of ONLY the data from a block that's needed to: +// 1. Detect a payment to your shielded Sapling address +// 2. Detect a spend of your shielded Sapling notes +// 3. Update your witnesses to generate new Sapling spend proofs. +message CompactBlock { + uint32 protoVersion = 1; // the version of this wire format, for storage + uint64 height = 2; // the height of this block + bytes hash = 3; // the ID (hash) of this block, same as in block explorers + bytes prevHash = 4; // the ID (hash) of this block's predecessor + uint32 time = 5; // Unix epoch time when the block was mined + bytes header = 6; // (hash, prevHash, and time) OR (full header) + repeated CompactTx vtx = 7; // compact transactions from this block +} + +message CompactTx { + // Index and hash will allow the receiver to call out to chain + // explorers or other data structures to retrieve more information + // about this transaction. + uint64 index = 1; // the index within the full block + bytes hash = 2; // the ID (hash) of this transaction, same as in block explorers + + // The transaction fee: present if server can provide. In the case of a + // stateless server and a transaction with transparent inputs, this will be + // unset because the calculation requires reference to prior transactions. + // in a pure-Sapling context, the fee will be calculable as: + // valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut)) + uint32 fee = 3; + + repeated CompactSaplingSpend spends = 4; + repeated CompactSaplingOutput outputs = 5; +} + +// CompactSaplingSpend is a Sapling Spend Description as described in 7.3 of the Zcash +// protocol specification. +message CompactSaplingSpend { + bytes nf = 1; // nullifier (see the Zcash protocol specification) +} + +// output is a Sapling Output Description as described in section 7.4 of the +// Zcash protocol spec. Total size is 948. +message CompactSaplingOutput { + bytes cmu = 1; // note commitment u-coordinate + bytes epk = 2; // ephemeral public key + bytes ciphertext = 3; // first 52 bytes of ciphertext +} + +/* +message CompactSpend { + bytes nf = 1; // nullifier (see the Zcash protocol specification) +} + +message CompactOutput { + bytes cmu = 1; // note commitment u-coordinate + bytes epk = 2; // ephemeral public key + bytes ciphertext = 3; // first 52 bytes of ciphertext +} +*/ diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/service.proto b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/service.proto new file mode 100644 index 0000000..0080a37 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/proto/service.proto @@ -0,0 +1,170 @@ +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "lightwalletd/walletrpc"; +option swift_prefix = ""; +import "compact_formats.proto"; + +// A BlockID message contains identifiers to select a block: a height or a +// hash. If the hash is present it takes precedence. +message BlockID { + uint64 height = 1; + bytes hash = 2; +} + +// BlockRange technically allows ranging from hash to hash etc but this is not +// currently intended for support, though there is no reason you couldn't do +// it. Further permutations are left as an exercise. +message BlockRange { + BlockID start = 1; + BlockID end = 2; +} + +// A TxFilter contains the information needed to identify a particular +// transaction: either a block and an index, or a direct transaction hash. +message TxFilter { + BlockID block = 1; + uint64 index = 2; + bytes hash = 3; +} + +// RawTransaction contains the complete transaction data. It also optionally includes +// the block height in which the transaction was included +message RawTransaction { + bytes data = 1; + uint64 height = 2; +} + +message SendResponse { + int32 errorCode = 1; + string errorMessage = 2; +} + +// Empty placeholder. Someday we may want to specify e.g. a particular chain fork. +message ChainSpec {} + +message Empty {} + +message LightdInfo { + string version = 1; + string vendor = 2; + bool taddrSupport = 3; + string chainName = 4; + uint64 saplingActivationHeight = 5; + string consensusBranchId = 6; // This should really be u32 or []byte, but string for readability + uint64 blockHeight = 7; + uint64 difficulty = 8; + uint64 longestchain = 9; + uint64 notarized = 10; +} +message Coinsupply { + string result = 1; + string coin = 2; + uint64 height = 3; + uint64 supply = 4; + uint64 zfunds = 5; + uint64 total = 6; +} + +message TransparentAddress { + string address = 1; +} + +message TransparentAddressBlockFilter { + string address = 1; + BlockRange range = 2; +} + +message Address { + string address = 1; +} +message AddressList { + repeated string addresses = 1; +} +message Balance { + int64 valueZat = 1; +} + +message Exclude { + repeated bytes txid = 1; +} + +// The TreeState is derived from the Hush getblockmerkletree rpc. +// https://faq.hush.is/rpc/getblockmerkletree.html +message TreeState { + string network = 1; // "main" or "test" + uint64 height = 2; // block height + string hash = 3; // block id + uint32 time = 4; // Unix epoch time when the block was mined + string saplingTree = 5; // sapling commitment tree state +} + +// Results are sorted by height, which makes it easy to issue another +// request that picks up from where the previous left off. +message GetAddressUtxosArg { + repeated string addresses = 1; + uint64 startHeight = 2; + uint32 maxEntries = 3; // zero means unlimited +} +message GetAddressUtxosReply { + string address = 6; + bytes txid = 1; + int32 index = 2; + bytes script = 3; + int64 valueZat = 4; + uint64 height = 5; +} +message GetAddressUtxosReplyList { + repeated GetAddressUtxosReply addressUtxos = 1; +} + +service CompactTxStreamer { + // Return the height of the tip of the best chain + rpc GetLatestBlock(ChainSpec) returns (BlockID) {} + // Return the compact block corresponding to the given block identifier + rpc GetBlock(BlockID) returns (CompactBlock) {} + // Return a list of consecutive compact blocks + rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {} + + // Return the requested full (not compact) transaction (as from zcashd) + rpc GetTransaction(TxFilter) returns (RawTransaction) {} + // Submit the given transaction to the Zcash network + rpc SendTransaction(RawTransaction) returns (SendResponse) {} + + // Return the txids corresponding to the given t-address within the given block range + rpc GetTaddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} + // wrapper for GetTaddressTxids + rpc GetAddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} + rpc GetTaddressBalance(AddressList) returns (Balance) {} + rpc GetTaddressBalanceStream(stream Address) returns (Balance) {} + + // Return the compact transactions currently in the mempool; the results + // can be a few seconds out of date. If the Exclude list is empty, return + // all transactions; otherwise return all *except* those in the Exclude list + // (if any); this allows the client to avoid receiving transactions that it + // already has (from an earlier call to this rpc). The transaction IDs in the + // Exclude list can be shortened to any number of bytes to make the request + // more bandwidth-efficient; if two or more transactions in the mempool + // match a shortened txid, they are all sent (none is excluded). Transactions + // in the exclude list that don't exist in the mempool are ignored. + rpc GetMempoolTx(Exclude) returns (stream CompactTx) {} + + // Return a stream of current Mempool transactions. This will keep the output stream open while + // there are mempool transactions. It will close the returned stream when a new block is mined. + rpc GetMempoolStream(Empty) returns (stream RawTransaction) {} + + // GetTreeState returns the note commitment tree state corresponding to the given block. + // See section 3.7 of the Zcash protocol specification. It returns several other useful + // values also (even though they can be obtained using GetBlock). + // The block can be specified by either height or hash. + rpc GetTreeState(BlockID) returns (TreeState) {} + rpc GetLatestTreeState(Empty) returns (TreeState) {} + + rpc GetAddressUtxos(GetAddressUtxosArg) returns (GetAddressUtxosReplyList) {} + rpc GetAddressUtxosStream(GetAddressUtxosArg) returns (stream GetAddressUtxosReply) {} + + // Return information about this lightwalletd instance and the blockchain + rpc GetLightdInfo(Empty) returns (LightdInfo) {} + // Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production) + // rpc Ping(Duration) returns (PingResponse) {} + rpc GetCoinsupply(Empty) returns (Coinsupply) {} +} \ No newline at end of file diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/res/lightwalletd-lite.myhush.pem b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/res/lightwalletd-lite.myhush.pem new file mode 100644 index 0000000..571de5b --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/res/lightwalletd-lite.myhush.pem @@ -0,0 +1,65 @@ +-----BEGIN CERTIFICATE----- +MIIFlTCCA32gAwIBAgIUCU7sjrIbYfA+bc2qWlyUo0dCU7swDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxDTALBgNVBAoM +BEh1c2gxDTALBgNVBAsMBEh1c2gxGDAWBgNVBAMMD2xpdGUubXlodXNoLm9yZzAe +Fw0xOTEyMDIxNDUyMzBaFw0yMDEyMDExNDUyMzBaMFoxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIDApTb21lLVN0YXRlMQ0wCwYDVQQKDARIdXNoMQ0wCwYDVQQLDARIdXNo +MRgwFgYDVQQDDA9saXRlLm15aHVzaC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDhd3SLJuGQ3ivUWle6+Est+qNBghf2vMkcgj9NjaxeMjSMLPVq +Mxt0j/mJe3+z767yXbiBUKnaQyb7OcWtxUN3PusGmqnMAUuy/tdu9h+2ScYKThh2 +JHQNdyN1y4c7sFbmntpMIIqm6/v95UXnnStQ+VBlS2/IhLYTgW31DEIiTpyx4jjW +xY+QD2+mqf4sSDm4Yq/r3Cxp6YWufEbhkXiHcF15JPk1d6jzkOkcjCJJCqwRMJ+5 +60q31S1W0Ud/L7AqkOhAKFHmORfCXM0ae4Rive/ZgM688KYIXA9kQzA6ZMdD7VIL +4q4IoP1ZjlPhosFoUFB6lHORYp15+Gu43jbC2/SUPWJQbJ1XusjxysqngyJ53/Va +MN/iWhOmqBXjx1SqkyIV4W56GDezxT1MhM5zSSKgEHePyFzkGNYasEeHa1/hZoz+ +zKG1oGLlMQe5TtI3AMZMfLz6t8qtRB+k+XW988mHJZ7BYOjW3KvdN16SOYdFF6K+ +86MAQ8rNPgcTsnclhmDdjh7+PhQpkF3uqF1EeVTzb03s77Cx6nDc9GCnpXqg8tkE +HnJD0WFIXA29PCjWyebuksMBRahekYDR0kn8O0Km/eFAprH3v4qoSc1JLNJR2G20 +eHVDnNFnR6QdwlM9+39NYUhJV43aj28wx1m0FXI+dSklblk4hkbGPPbFjQIDAQAB +o1MwUTAdBgNVHQ4EFgQUo5TCdrNgopYbxQSzSPFSlyCtE6YwHwYDVR0jBBgwFoAU +o5TCdrNgopYbxQSzSPFSlyCtE6YwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAgEAyPxq4ZyBtKJEQtzmqdkI+28Yw4qDSBE0dj4QQfOErgK5hX29Bk4e +Auh6j0eyuRA8gtwngsE8fAAg84kcH/b+hM2zFW4MqpgjigA7oqA51VkIg+8Q9zdF +IHweV2kuSunZ2ANcGrr7o/Qy5y8D7URWUpUu8J1ZNyLg7YYMtpyjllTYhbfKpAgM +HUX1STCRfSPTgM/JxIsnll8RgacfhUmoOzOsrvvZ39h8cZZo96ksBRL4gvVQ++Hm +gzNTbYQXukR26Xfv112AEj5Xo3z3fsLP1KxZxd2p6/24XYktpZf2J71Np2CONdV4 +gFgxFfPwvPyDO5pKice018qlXz0euhvK5g++s+TrSeZwleDTW4spP3TdVXNB96iZ +rrFkTT0SEBtd6iKqeFAX89BpshCUqOlsFdrf10i2dDiKsqxMod6a8i17RrtZL3q7 +S0nqCsnyc1QvfKIQi08vfMZHHMbSS0Cg/5H8ISexM/R4SQc6C//IhEd5hUJ7E9AC +Sepr0xu1JBrglech4N+brHpGZK3Crofzu+hV+qruY5Wg21bD99zigxrgi3YaQPlA +6TJVsGk68h00QSy+Ri9dyuvyPnIDyMQfZLLIKCwsxznYXLtmIp+UoA3otGTijhmc +ZxFdEhd3cJrSPwihb/IvQJICFp3ya1aI7dLsaZO9h9kPc8GGLi7tiq8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFozCCA4ugAwIBAgIUaRW0/q8ERGZUZqv+TVkC5lOwgX4wDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlVTMQswCQYDVQQHDAJVUzEPMA0G +A1UECgwGTXlIdXNoMQ0wCwYDVQQLDARIVVNIMRgwFgYDVQQDDA9saXRlLm15aHVz +aC5vcmcwHhcNMjAwMTAzMjAzMTAwWhcNMjEwMTAyMjAzMTAwWjBhMQswCQYDVQQG +EwJVUzELMAkGA1UECAwCVVMxCzAJBgNVBAcMAlVTMQ8wDQYDVQQKDAZNeUh1c2gx +DTALBgNVBAsMBEhVU0gxGDAWBgNVBAMMD2xpdGUubXlodXNoLm9yZzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/oJh+MSxGOedvEthiqdfMEDfjzJ7AW +wwvuBtVULvnIvJq0WTMK8INjkMqnj+/ZE9a1aOOSpwN+9CuknhiySTdqHGl6EigM +/S19ZdN/7aC881VrMCncguHUOa8HLq5F4R0YCGis1Cor9vXf0GVFJ2mDfPB0C8iz +C+gMGnkDwuBy51fUcWbxZ5diRYh7YlOIUxpmb24On+X7sw+7nbmV12x8v644xRKJ +MazPIVrxIwXZ3tjz2NR7IMu+SrtCgMAW56M63NJ+iZBYDRoFVMRGEfAgGaFV2Hqi +Dx7q89sO2bhVg2lBBOW7403S+T8eK1rGFj7iGJoRLm/cgWwozZteXynHzicYEvX6 +w1h0lu/OPQQk5AKRn+iI1kKLlT1auKIBXpfnpELnie3XgzzLyKt0pobVrWdMutlJ +83Zo7LmnhJYlcG7Qb0UczSyaIn+3dWo2HTbiyzJD23gmUbzFD4AVF1Ee4x5yT4Hb +Aw0FpQXDHX2MT04xleM2HdjE86ruZfNCegvQdRtgKRAiVNe4kP5ZzB7OeDX6pgeE +/e07tiHiFb2Im7J/IR4PmIh6SuYI/QObFXFXfvwCk/iJCps15/PryGfXZLdz+2z1 +av1nwSglBRqeRX8HUBIgNcY0Lyq82BKfq4ZU3fKiIDuNV16OxCnFGZRw+ByRNrKR +KsgaJEi7qH6DAgMBAAGjUzBRMB0GA1UdDgQWBBRd+rxIkdKrpLsgz5/KiqZOYzUB +KDAfBgNVHSMEGDAWgBRd+rxIkdKrpLsgz5/KiqZOYzUBKDAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBR9M0CnCvT1Zd5D/Wwy9ylH6CSFq6AEbdh +fMo8+NZl4J0FNji2Iv05xh+V3f+eyuf6oc8q0vsKeC5MZj/44AzqzxCSvMrUnh0V +GAiZQqLAVJIR/fi49bX9ku1yfQVJKzGFS93TcHMv9XYHJ4bSnQKlEOF5F6Wh9AO1 +6GU/+vb0pSOfOv5+pUaj84AYCKQ8kp9nuNpOS12jyjc5hUogflPFAmeIpcIGjiso +Ln8+b+Xh0BZGNpdrvJ/wr8InxblMu5chtYrGAGo4mh4q3YWiwYJTkTIhoeTxF4eU +BOqMJa9lQgZE4T7V33jrIsuMPEZfACpj+gQNNzl8WQ+jzkZhBdYPTqhO9u1rlRXG +9VJfmuQ7+KLXAMFQgsHlX5Y5lux3CV36Knb5+1f/u0cdys1yb9mbQ/Ok3T8cuh6B +7Hs53JhAW47+CCnsNTaPzwti3wfzWhS2sjHz4IT1NcacsuDlxk7IykJ2U3auiufE +lRFpZoK81jsipEgRPBeF6OesXpldKxK9lnVJA/6ElApuo0amgg++fROQEpZSLyBM +lZdYrW6ZKnzCUZpNuwNHg1nfiit3RJ6hRLsk2jHrLDb5BVWzuJCNa7LK9bZ3IbUa +5jGb9Rplo+NgglQQYCflptksti9h5DN+GlVGxfJ9yzkg4/4ckmh75colcc1CTQAf +eER5vAF7og== +-----END CERTIFICATE----- diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/commands.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/commands.rs new file mode 100644 index 0000000..f90b67a --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/commands.rs @@ -0,0 +1,1004 @@ +// Copyright The Hush Developers 2019-2022 +// Released under the GPLv3 +use std::collections::HashMap; +use json::{object}; + +use crate::lightclient::LightClient; +use crate::lightwallet::LightWallet; +use zcash_primitives::transaction::components::amount::DEFAULT_FEE; +use std::convert::TryInto; + +pub trait Command { + fn help(&self) -> String; + + fn short_help(&self) -> String; + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String; +} + +struct SyncCommand {} +impl Command for SyncCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Sync the light client with the server"); + h.push("Usage:"); + h.push("sync"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Download CompactBlocks and sync to the server".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + match lightclient.do_sync(true) { + Ok(j) => j.pretty(2), + Err(e) => e.to_string() + } + } + +} + +struct EncryptionStatusCommand {} +impl Command for EncryptionStatusCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Check if the wallet is encrypted and if it is locked"); + h.push("Usage:"); + h.push("encryptionstatus"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Check if the wallet is encrypted and if it is locked".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + lightclient.do_encryption_status().pretty(2) + } +} + +struct SyncStatusCommand {} +impl Command for SyncStatusCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Get the sync status of the wallet"); + h.push("Usage:"); + h.push("syncstatus"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Get the sync status of the wallet".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + let status = lightclient.do_scan_status(); + match status.is_syncing { + false => object!{ "syncing" => "false" }, + true => object!{ "syncing" => "true", + "synced_blocks" => status.synced_blocks, + "total_blocks" => status.total_blocks } + }.pretty(2) + } +} + +struct RescanCommand {} +impl Command for RescanCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Rescan the wallet, rescanning all blocks for new transactions"); + h.push("Usage:"); + h.push("rescan"); + h.push(""); + h.push("This command will download all blocks since the intial block again from the light client server"); + h.push("and attempt to scan each block for transactions belonging to the wallet."); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Rescan the wallet, downloading and scanning all blocks and transactions".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + match lightclient.do_rescan() { + Ok(j) => j.pretty(2), + Err(e) => e + } + } +} + +struct ClearCommand {} +impl Command for ClearCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Clear the wallet state, rolling back the wallet to an empty state."); + h.push("Usage:"); + h.push("clear"); + h.push(""); + h.push("This command will clear all notes, utxos and transactions from the wallet, setting up the wallet to be synced from scratch."); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Clear the wallet state, rolling back the wallet to an empty state.".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if !args.is_empty() { + if let Ok(height) = args[0].parse::() { + lightclient.clear_state_from(height); + } else { + return format!("Error: invalid height '{}'", args[0]); + } + } else { + lightclient.clear_state(); + } + + let result = object!{ "result" => "success" }; + + result.pretty(2) + } +} + +struct HelpCommand {} +impl Command for HelpCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("List all available commands"); + h.push("Usage:"); + h.push("help [command_name]"); + h.push(""); + h.push("If no \"command_name\" is specified, a list of all available commands is returned"); + h.push("Example:"); + h.push("help send"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Lists all available commands".to_string() + } + + fn exec(&self, args: &[&str], _: &LightClient) -> String { + let mut responses = vec![]; + + // Print a list of all commands + match args.len() { + 0 => { + responses.push(format!("Available commands:")); + get_commands().iter().for_each(| (cmd, obj) | { + responses.push(format!("{} - {}", cmd, obj.short_help())); + }); + + responses.join("\n") + }, + 1 => { + match get_commands().get(args[0]) { + Some(cmd) => cmd.help(), + None => format!("Command {} not found", args[0]) + } + }, + _ => self.help() + } + } +} + +struct InfoCommand {} +impl Command for InfoCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Get info about the lightwalletd we're connected to"); + h.push("Usage:"); + h.push("info"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Get the lightwalletd server's info".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + lightclient.do_info() + } +} + +struct SaplingTreeCommand {} +impl Command for SaplingTreeCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("DEBUG: dump the wallet's latest Sapling commitment tree (height, hash, tree hex)"); + h.push("Usage:"); + h.push("saplingtree"); + h.push(""); + h.join("\n") + } + + fn short_help(&self) -> String { + "Dump the latest Sapling commitment tree (debug)".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + lightclient.do_sapling_tree().pretty(2) + } +} + +struct CoinsupplyCommand {} +impl Command for CoinsupplyCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Get info about the actual Coinsupply of Hush"); + h.push("Usage:"); + h.push("coinsupply"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Get the Coinsupply info".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + lightclient.do_coinsupply() + } +} + +struct BalanceCommand {} +impl Command for BalanceCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Show the current HUSH balance in the wallet"); + h.push("Usage:"); + h.push("balance"); + h.push(""); + h.push("Transparent and Shielded balances, along with the addresses they belong to are displayed"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Show the current HUSH balance in the wallet".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + match lightclient.do_sync(true) { + Ok(_) => format!("{}", lightclient.do_balance().pretty(2)), + Err(e) => e + } + } +} + +struct AddressCommand {} +impl Command for AddressCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("List current addresses in the wallet"); + h.push("Usage:"); + h.push("address"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "List all addresses in the wallet".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + format!("{}", lightclient.do_address().pretty(2)) + } +} + +struct ExportCommand {} +impl Command for ExportCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Export private key for an individual wallet addresses."); + h.push("Note: To backup the whole wallet, use the 'seed' command insted"); + h.push("Usage:"); + h.push("export [t-address or z-address]"); + h.push(""); + h.push("If no address is passed, private key for all addresses in the wallet are exported."); + h.push(""); + h.push("Example:"); + h.push("export ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Export private key for wallet addresses".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() > 1 { + return self.help(); + } + + let address = if args.is_empty() { None } else { Some(args[0].to_string()) }; + match lightclient.do_export(address) { + Ok(j) => j, + Err(e) => object!{ "error" => e } + }.pretty(2) + } +} + +struct EncryptCommand {} +impl Command for EncryptCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Encrypt the wallet with a password"); + h.push("Note 1: This will encrypt the seed and the sapling and transparent private keys."); + h.push(" Use 'unlock' to temporarily unlock the wallet for spending or 'decrypt' "); + h.push(" to permanatly remove the encryption"); + h.push("Note 2: If you forget the password, the only way to recover the wallet is to restore"); + h.push(" from the seed phrase."); + h.push("Usage:"); + h.push("encrypt password"); + h.push(""); + h.push("Example:"); + h.push("encrypt my_strong_password"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Encrypt the wallet with a password".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() != 1 { + return self.help(); + } + + let passwd = args[0].to_string(); + + match lightclient.wallet.write().unwrap().encrypt(passwd) { + Ok(_) => object!{ "result" => "success" }, + Err(e) => object!{ + "result" => "error", + "error" => e.to_string() + } + }.pretty(2) + } +} + +struct DecryptCommand {} +impl Command for DecryptCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Completely remove wallet encryption, storing the wallet in plaintext on disk"); + h.push("Note 1: This will decrypt the seed and the sapling and transparent private keys and store them on disk."); + h.push(" Use 'unlock' to temporarily unlock the wallet for spending"); + h.push("Note 2: If you've forgotten the password, the only way to recover the wallet is to restore"); + h.push(" from the seed phrase."); + h.push("Usage:"); + h.push("decrypt password"); + h.push(""); + h.push("Example:"); + h.push("decrypt my_strong_password"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Completely remove wallet encryption".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() != 1 { + return self.help(); + } + + let passwd = args[0].to_string(); + + match lightclient.wallet.write().unwrap().remove_encryption(passwd) { + Ok(_) => object!{ "result" => "success" }, + Err(e) => object!{ + "result" => "error", + "error" => e.to_string() + } + }.pretty(2) + } +} + +struct UnlockCommand {} +impl Command for UnlockCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Unlock the wallet's encryption in memory, allowing spending from this wallet."); + h.push("Note 1: This will decrypt spending keys in memory only. The wallet remains encrypted on disk"); + h.push(" Use 'decrypt' to remove the encryption permanatly."); + h.push("Note 2: If you've forgotten the password, the only way to recover the wallet is to restore"); + h.push(" from the seed phrase."); + h.push("Usage:"); + h.push("unlock password"); + h.push(""); + h.push("Example:"); + h.push("unlock my_strong_password"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Unlock wallet encryption for spending".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() != 1 { + return self.help(); + } + + let passwd = args[0].to_string(); + + match lightclient.wallet.write().unwrap().unlock(passwd) { + Ok(_) => object!{ "result" => "success" }, + Err(e) => object!{ + "result" => "error", + "error" => e.to_string() + } + }.pretty(2) + } +} + +struct LockCommand {} +impl Command for LockCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Lock a wallet that's been temporarily unlocked. You should already have encryption enabled."); + h.push("Note 1: This will remove all spending keys from memory. The wallet remains encrypted on disk"); + h.push("Note 2: If you've forgotten the password, the only way to recover the wallet is to restore"); + h.push(" from the seed phrase."); + h.push("Usage:"); + h.push("lock"); + h.push(""); + h.push("Example:"); + h.push("lock"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Lock a wallet that's been temporarily unlocked".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() != 0 { + let mut h = vec![]; + h.push("Extra arguments to lock. Did you mean 'encrypt'?"); + h.push(""); + + return format!("{}\n{}", h.join("\n"), self.help()); + } + + match lightclient.wallet.write().unwrap().lock() { + Ok(_) => object!{ "result" => "success" }, + Err(e) => object!{ + "result" => "error", + "error" => e.to_string() + } + }.pretty(2) + } +} + +struct SendCommand {} +impl Command for SendCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Send HUSH to a given address(es)"); + h.push("Usage:"); + h.push("send
\"optional_memo\""); + h.push("OR"); + h.push("send '[{'address':
, 'amount': , 'memo': }, ...]'"); + h.push(""); + h.push("NOTE: The fee required to send this transaction (currently HUSH 0.0001) is additionally deducted from your balance."); + h.push("Example:"); + h.push("send ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d 200000 \"Hello from the command line\""); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Send HUSH to the given address".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + // Parse the args. There are two argument types. + // 1 - A set of 2(+1 optional) arguments for a single address send representing address, value, memo? + // 2 - A single argument in the form of a JSON string that is "[{address: address, value: value, memo: memo},...]" + // 1 - Destination address. T or Z address + if args.len() < 1 || args.len() > 3 { + return self.help(); + } + // Check for a single argument that can be parsed as JSON + let send_args = if args.len() == 1 { + let arg_list = args[0]; + match json::parse(&arg_list) { + Ok(json_args) => { + // Check if the parsed JSON is an array. + if !json_args.is_array() { + return format!("Couldn't parse argument as array\n{}", self.help()); + } + + // Map each JSON object to a tuple (address, amount, memo, fee). + let maybe_send_args = json_args.members().map(|j| { + if !j.has_key("address") || !j.has_key("amount") { + Err(format!("Need 'address' and 'amount'\n")) + } else { + let fee = j["fee"].as_u64().unwrap_or(DEFAULT_FEE.try_into().unwrap()); + Ok(( + j["address"].as_str().unwrap().to_string(), + j["amount"].as_u64().unwrap(), + j["memo"].as_str().map(|s| s.to_string()), + fee + )) + } + }).collect::, u64)>, String>>(); + + // Handle any errors that occurred during mapping. + match maybe_send_args { + Ok(a) => a, + Err(s) => return format!("Error: {}\n{}", s, self.help()), + } + }, + Err(e) => return format!("Couldn't understand JSON: {}\n{}", e, self.help()), + } + } else { + // Handle the case where individual arguments are provided. + let address = args[0].to_string(); + let value = match args[1].parse::() { + Ok(amt) => amt, + Err(e) => return format!("Couldn't parse amount: {}", e), + }; + let memo = args.get(2).map(|m| m.to_string()); + + // Memo should be None if the address is not shielded. + if memo.is_some() && !LightWallet::is_shielded_address(&address, &lightclient.config) { + return format!("Can't send a memo to the non-shielded address {}", address); + } + + // Create a vector with a single transaction (address, amount, memo). + vec![(address, value, memo, DEFAULT_FEE.try_into().unwrap())] + }; + + // Transform transaction data into the required format (String -> &str). + let tos = send_args.iter().map(|(a, v, m, _)| (a.as_str(), *v, m.clone())).collect::>(); + + // Calculate the total fee for all transactions. + // This assumes that all transactions have the same fee. + // If they can have different fees, you need to modify this logic. + + let default_fee: u64 = DEFAULT_FEE.try_into().unwrap(); + let mut selected_fee = default_fee; + + for (_, _, _, fee) in send_args.iter() { + if *fee != default_fee{ + selected_fee = *fee; + break; + } +} + // Execute the transaction and handle the result. + match lightclient.do_send(tos, &selected_fee) { + Ok(txid) => object!{ "txid" => txid }.pretty(2), + Err(e) => object!{ "error" => e }.pretty(2), + } + } +} + +struct SaveCommand {} +impl Command for SaveCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Save the wallet to disk"); + h.push("Usage:"); + h.push("save"); + h.push(""); + h.push("The wallet is saved to disk. The wallet is periodically saved to disk (and also saved upon exit)"); + h.push("but you can use this command to explicitly save it to disk"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Save wallet file to disk".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + match lightclient.do_save() { + Ok(_) => { + let r = object!{ "result" => "success" }; + r.pretty(2) + }, + Err(e) => { + let r = object!{ + "result" => "error", + "error" => e + }; + r.pretty(2) + } + } + } +} + +struct SeedCommand {} +impl Command for SeedCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Show the wallet's seed phrase"); + h.push("Usage:"); + h.push("seed"); + h.push(""); + h.push("Your wallet is entirely recoverable from the seed phrase. Please save it carefully and don't share it with anyone"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Display the seed phrase".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + match lightclient.do_seed_phrase() { + Ok(j) => j, + Err(e) => object!{ "error" => e } + }.pretty(2) + } +} + +struct TransactionsCommand {} +impl Command for TransactionsCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("List all incoming and outgoing transactions from this wallet"); + h.push("Usage:"); + h.push("list"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "List all transactions in the wallet".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + match lightclient.do_sync(true) { + Ok(_) => { + format!("{}", lightclient.do_list_transactions().pretty(2)) + }, + Err(e) => e + } + } +} + +struct ImportCommand {} +impl Command for ImportCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Import an external spending or viewing key into the wallet"); + h.push("Usage:"); + h.push("import [norescan]"); + h.push(""); + h.push("Birthday is the earliest block number that has transactions belonging to the imported key. Rescanning will start from this block. If not sure, you can specify '0', which will start rescanning from the first sapling block."); + h.push("Note that you can import only the full spending (private) key or the full viewing key."); + + h.join("\n") + } + + + fn short_help(&self) -> String { + "Import spending or viewing keys into the wallet".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() < 1 || args.len() > 2 { + return format!("Insufficient arguments\n\n{}", self.help()); + } + + let key = args[0]; + + let r = match lightclient.do_import_key(key.to_string(), 0) { + Ok(r) => r.pretty(2), + Err(e) => return format!("Error: {}", e), + }; + + return r; + } +} + +struct TImportCommand {} +impl Command for TImportCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Import an external WIF"); + h.push("Usage:"); + h.push("timport wif (Begins with U, 5, K or L"); + h.push(""); + + h.join("\n") + } + + + fn short_help(&self) -> String { + "Import wif to the wallet".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() < 1 || args.len() > 2 { + return format!("Insufficient arguments\n\n{}", self.help()); + } + + let key = args[0]; + + let r = match lightclient.do_import_tk(key.to_string(), 0) { + Ok(r) => r.pretty(2), + Err(e) => return format!("Error: {}", e), + }; + + return r; + } +} + +struct ShieldCommand {} +impl Command for ShieldCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Shield all your transparent funds"); + h.push("Usage:"); + h.push("shield [optional address]"); + h.push(""); + h.push("NOTE: The fee required to send this transaction (currently HUSH 0.0001) is additionally deducted from your balance."); + h.push("Example:"); + h.push("shield"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Shield your transparent HUSH into a sapling address".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + // Parse the address or amount + let address = if args.len() > 0 { + Some(args[0].to_string()) + } else { + None + }; + + match lightclient.do_shield(address) { + Ok(txid) => { object!{ "txid" => txid } }, + Err(e) => { object!{ "error" => e } } + }.pretty(2) + } +} + +struct HeightCommand {} +impl Command for HeightCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Get the latest block height that the wallet is at."); + h.push("Usage:"); + h.push("height"); + h.push(""); + h.push("Pass 'true' (default) to sync to the server to get the latest block height. Pass 'false' to get the latest height in the wallet without checking with the server."); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Get the latest block height that the wallet is at".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + format!("{}", object! { "height" => lightclient.last_scanned_height()}.pretty(2)) + } + } + +struct NewAddressCommand {} +impl Command for NewAddressCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Create a new address in this wallet"); + h.push("Usage:"); + h.push("new [z | r]"); + h.push(""); + h.push("Example:"); + h.push("To create a new zs address:"); + h.push("new zs"); + h.join("\n") + } + + fn short_help(&self) -> String { + "Create a new address in this wallet".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() != 1 { + return format!("No address type specified\n{}", self.help()); + } + + match lightclient.do_new_address(args[0]) { + Ok(j) => j, + Err(e) => object!{ "error" => e } + }.pretty(2) + } +} + +struct NewSietchAddressCommand {} +impl Command for NewSietchAddressCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("New Sietch Address"); + h.push("Usage:"); + h.push("sietch [zs]"); + h.push(""); + h.push("Example:"); + h.push("To create a new zdust:"); + h.push("sietch zs"); + h.join("\n") + } + + fn short_help(&self) -> String { + "Create a sietch Address".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() != 1 { + return format!("No address type specified\n{}", self.help()); + } + + match lightclient.do_new_sietchaddress(args[0]) { + Ok(j) => j, + Err(e) => object!{ "error" => e } + }.pretty(2) + } +} + +struct NotesCommand {} +impl Command for NotesCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Show all sapling notes and utxos in this wallet"); + h.push("Usage:"); + h.push("notes [all]"); + h.push(""); + h.push("If you supply the \"all\" parameter, all previously spent sapling notes and spent utxos are also included"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "List all sapling notes and utxos in the wallet".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + // Parse the args. + if args.len() > 1 { + return self.short_help(); + } + + // Make sure we can parse the amount + let all_notes = if args.len() == 1 { + match args[0] { + "all" => true, + a => return format!("Invalid argument \"{}\". Specify 'all' to include unspent notes", a) + } + } else { + false + }; + + match lightclient.do_sync(true) { + Ok(_) => { + format!("{}", lightclient.do_list_notes(all_notes).pretty(2)) + }, + Err(e) => e + } + } +} + +struct QuitCommand {} +impl Command for QuitCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Save the wallet to disk and quit"); + h.push("Usage:"); + h.push("quit"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Quit the lightwallet, saving state to disk".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + match lightclient.do_save() { + Ok(_) => {"".to_string()}, + Err(e) => e + } + } +} + +pub fn get_commands() -> Box>> { + let mut map: HashMap> = HashMap::new(); + + map.insert("sync".to_string(), Box::new(SyncCommand{})); + map.insert("syncstatus".to_string(), Box::new(SyncStatusCommand{})); + map.insert("encryptionstatus".to_string(), Box::new(EncryptionStatusCommand{})); + map.insert("rescan".to_string(), Box::new(RescanCommand{})); + map.insert("clear".to_string(), Box::new(ClearCommand{})); + map.insert("help".to_string(), Box::new(HelpCommand{})); + map.insert("balance".to_string(), Box::new(BalanceCommand{})); + map.insert("addresses".to_string(), Box::new(AddressCommand{})); + map.insert("height".to_string(), Box::new(HeightCommand{})); + map.insert("import".to_string(), Box::new(ImportCommand{})); + map.insert("timport".to_string(), Box::new(TImportCommand{})); + map.insert("export".to_string(), Box::new(ExportCommand{})); + map.insert("info".to_string(), Box::new(InfoCommand{})); + map.insert("saplingtree".to_string(), Box::new(SaplingTreeCommand{})); + map.insert("coinsupply".to_string(), Box::new(CoinsupplyCommand{})); + map.insert("send".to_string(), Box::new(SendCommand{})); + map.insert("shield".to_string(), Box::new(ShieldCommand{})); + map.insert("save".to_string(), Box::new(SaveCommand{})); + map.insert("quit".to_string(), Box::new(QuitCommand{})); + map.insert("list".to_string(), Box::new(TransactionsCommand{})); + map.insert("notes".to_string(), Box::new(NotesCommand{})); + map.insert("new".to_string(), Box::new(NewAddressCommand{})); + map.insert("sietch".to_string(), Box::new(NewSietchAddressCommand{})); + map.insert("seed".to_string(), Box::new(SeedCommand{})); + map.insert("encrypt".to_string(), Box::new(EncryptCommand{})); + map.insert("decrypt".to_string(), Box::new(DecryptCommand{})); + map.insert("unlock".to_string(), Box::new(UnlockCommand{})); + map.insert("lock".to_string(), Box::new(LockCommand{})); + + Box::new(map) +} + +pub fn do_user_command(cmd: &str, args: &Vec<&str>, lightclient: &LightClient) -> String { + match get_commands().get(&cmd.to_ascii_lowercase()) { + Some(cmd) => cmd.exec(args, lightclient), + None => format!("Unknown command : {}. Type 'help' for a list of commands", cmd) + } +} + +#[cfg(test)] +pub mod tests { + use lazy_static::lazy_static; + use super::do_user_command; + use crate::lightclient::{LightClient}; + + lazy_static!{ + static ref TEST_SEED: String = "youth strong sweet gorilla hammer unhappy congress stamp left stereo riot salute road tag clean toilet artefact fork certain leopard entire civil degree wonder".to_string(); + } + + #[test] + pub fn test_command_caseinsensitive() { + let lc = LightClient::unconnected(TEST_SEED.to_string(), None).unwrap(); + + assert_eq!(do_user_command("addresses", &vec![], &lc), + do_user_command("AddReSSeS", &vec![], &lc)); + assert_eq!(do_user_command("addresses", &vec![], &lc), + do_user_command("Addresses", &vec![], &lc)); + } + + #[test] + pub fn test_nosync_commands() { + // The following commands should run + } +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/grpcconnector.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/grpcconnector.rs new file mode 100644 index 0000000..ba08292 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/grpcconnector.rs @@ -0,0 +1,363 @@ +// Copyright The Hush Developers 2019-2022 +// Released under the GPLv3 +use log::{info,error}; +use std::sync::Arc; +use zcash_primitives::transaction::{TxId}; + +use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, CompactBlock, + TransparentAddressBlockFilter, TxFilter, Empty, LightdInfo, Coinsupply}; +use tonic::transport::{Channel, ClientTlsConfig}; +use tokio_rustls::{rustls::ClientConfig}; +use tonic::{Request}; + +use threadpool::ThreadPool; +use std::sync::mpsc::channel; + +use crate::PubCertificate; +use crate::grpc_client::compact_tx_streamer_client::CompactTxStreamerClient; + +mod danger { + use tokio_rustls::rustls; + use webpki; + + pub struct NoCertificateVerification {} + + impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert(&self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, + _ocsp: &[u8]) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } + } +} + +async fn get_client(uri: &http::Uri, no_cert: bool) -> Result, Box> { + 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> { + 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 { + 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> { + 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 { + 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( + uri: &http::Uri, + start_height: u64, + end_height: u64, + no_cert: bool, + pool: ThreadPool, + c: F +) -> Result<(), Box> +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::>(); + 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::, String>>()?; + + Ok(()) +} + + +pub fn fetch_blocks(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(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( + uri: &http::Uri, + address: String, + start_height: u64, + end_height: u64, + no_cert: bool, + c: F +) -> Result<(), Box> +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( + uri: &http::Uri, + no_cert: bool, + mut c: F +) -> Result<(), Box> +where + F: FnMut(RawTransaction) -> Result<(), Box>, +{ + + 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( + 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> { + 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, 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> { + 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 { + 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> { + 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 { + 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 + }) +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lib.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lib.rs new file mode 100644 index 0000000..4f6ad39 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lib.rs @@ -0,0 +1,29 @@ +// Copyright The Hush Developers 2019-2022 +// Released under the GPLv3 +#[macro_use] +extern crate rust_embed; + +pub mod lightclient; +pub mod grpcconnector; +pub mod lightwallet; +pub mod commands; + +#[cfg(feature = "embed_params")] +#[derive(RustEmbed)] +#[folder = "zcash-params/"] +pub struct SaplingParams; + +#[derive(RustEmbed)] +#[folder = "res/"] +pub struct PubCertificate; + + +// Anchor depth back from the tip for shielded spends. MUST be > 0: with 0 the wallet anchors to +// the absolute chain tip, which races the node committing that anchor and intermittently triggers +// "bad-txns-shielded-requirements-not-met" (missing sapling anchor) on broadcast. 4 matches upstream +// zecwallet-lite and uses a confirmed, reorg-stable anchor. +pub const ANCHOR_OFFSET: u32 = 4; + +pub mod grpc_client { + tonic::include_proto!("cash.z.wallet.sdk.rpc"); +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient.rs new file mode 100644 index 0000000..8872eb1 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient.rs @@ -0,0 +1,1920 @@ +// Copyright The Hush Developers 2019-2022 +// Released under the GPLv3 +use crate::lightwallet::LightWallet; + +use std::sync::{Arc, RwLock, Mutex, mpsc::channel}; +use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering}; +use std::path::{Path, PathBuf}; +use std::fs::File; +use std::collections::{HashSet, HashMap}; +use std::cmp::{max, min}; +use std::io; +use std::io::prelude::*; +use std::io::{BufReader, BufWriter, Error, ErrorKind}; +use tokio::runtime::Runtime; +use tokio::time::{Duration}; + +use crate::grpc_client::RawTransaction; + +use protobuf::parse_from_bytes; + +use threadpool::ThreadPool; + +use json::{object, array, JsonValue}; +use zcash_primitives::transaction::{TxId, Transaction}; +use zcash_client_backend::{constants::testnet, constants::mainnet, constants::regtest,}; + +use log::{info, warn, error, LevelFilter}; +use log4rs::append::rolling_file::RollingFileAppender; +use log4rs::encode::pattern::PatternEncoder; +use log4rs::config::{Appender, Config, Root}; +use log4rs::filter::threshold::ThresholdFilter; +use log4rs::append::rolling_file::policy::compound::{ + CompoundPolicy, + trigger::size::SizeTrigger, + roll::fixed_window::FixedWindowRoller, +}; + +use crate::grpcconnector::{self, *}; + + +use crate::ANCHOR_OFFSET; + +mod checkpoints; + +pub const DEFAULT_SERVER: &str = "https://lite.dragonx.is"; +pub const WALLET_NAME: &str = "silentdragonxlite-wallet.dat"; +pub const LOGFILE_NAME: &str = "silentdragonxlite-wallet.debug.log"; + +#[derive(Clone, Debug)] +pub struct WalletStatus { + pub is_syncing: bool, + pub total_blocks: u64, + pub synced_blocks: u64, +} + +impl WalletStatus { + pub fn new() -> Self { + WalletStatus { + is_syncing: false, + total_blocks: 0, + synced_blocks: 0 + } + } +} + +#[derive(Clone, Debug)] +pub struct LightClientConfig { + pub server : http::Uri, + pub chain_name : String, + pub sapling_activation_height : u64, + pub consensus_branch_id : String, + pub anchor_offset : u32, + pub no_cert_verification : bool, + pub data_dir : Option +} + +impl LightClientConfig { + + // Create an unconnected (to any server) config to test for local wallet etc... + pub fn create_unconnected(chain_name: String, dir: Option) -> LightClientConfig { + LightClientConfig { + server : http::Uri::default(), + chain_name : chain_name, + sapling_activation_height : 0, + consensus_branch_id : "".to_string(), + anchor_offset : ANCHOR_OFFSET, + no_cert_verification : false, + data_dir : dir, + } + } + + pub fn create(server: http::Uri, dangerous: bool) -> io::Result<(LightClientConfig, u64)> { + use std::net::ToSocketAddrs; + // Test for a connection first + format!("{}:{}", server.host().unwrap(), server.port().unwrap()) + .to_socket_addrs()? + .next() + .ok_or(std::io::Error::new(ErrorKind::ConnectionRefused, "Couldn't resolve server!"))?; + + // Do a getinfo first, before opening the wallet + let info = grpcconnector::get_info(&server, dangerous) + .map_err(|e| std::io::Error::new(ErrorKind::ConnectionRefused, e))?; + + // Create a Light Client Config + let config = LightClientConfig { + server, + chain_name : info.chain_name, + sapling_activation_height : info.sapling_activation_height, + consensus_branch_id : info.consensus_branch_id, + anchor_offset : ANCHOR_OFFSET, + no_cert_verification : dangerous, + data_dir : None, + }; + + Ok((config, info.block_height)) + } + + + /// Build the Logging config + pub fn get_log_config(&self) -> io::Result { + let window_size = 3; // log0, log1, log2 + let fixed_window_roller = + FixedWindowRoller::builder().build("zecwallet-light-wallet-log{}",window_size).unwrap(); + let size_limit = 5 * 1024 * 1024; // 5MB as max log file size to roll + let size_trigger = SizeTrigger::new(size_limit); + let compound_policy = CompoundPolicy::new(Box::new(size_trigger),Box::new(fixed_window_roller)); + + Config::builder() + .appender( + Appender::builder() + .filter(Box::new(ThresholdFilter::new(LevelFilter::Info))) + .build( + "logfile", + Box::new( + RollingFileAppender::builder() + .encoder(Box::new(PatternEncoder::new("{d} {l}::{m}{n}"))) + .build(self.get_log_path(), Box::new(compound_policy))?, + ), + ), + ) + .build( + Root::builder() + .appender("logfile") + .build(LevelFilter::Debug), + ) + .map_err(|e|Error::new(ErrorKind::Other, format!("{}", e))) + } + + pub fn get_zcash_data_path(&self) -> Box { + let mut zcash_data_location; + if self.data_dir.is_some() { + zcash_data_location = PathBuf::from(&self.data_dir.as_ref().unwrap()); + } else { + if cfg!(target_os="macos") || cfg!(target_os="windows") { + zcash_data_location = dirs::data_dir().expect("Couldn't determine app data directory!"); + zcash_data_location.push("silentdragonxlite"); + } else { + zcash_data_location = dirs::home_dir().expect("Couldn't determine home directory!"); + zcash_data_location.push(".silentdragonxlite"); + }; + + match &self.chain_name[..] { + "main" => {}, + "test" => zcash_data_location.push("testnet3"), + "regtest" => zcash_data_location.push("regtest"), + c => panic!("Unknown chain {}", c), + }; + } + + // Create directory if it doesn't exist + match std::fs::create_dir_all(zcash_data_location.clone()) { + Ok(_) => zcash_data_location.into_boxed_path(), + Err(e) => { + eprintln!("Couldn't create zcash directory!\n{}", e); + panic!("Couldn't create zcash directory!"); + } + } + } + + pub fn get_wallet_path(&self) -> Box { + let mut wallet_location = self.get_zcash_data_path().into_path_buf(); + wallet_location.push(WALLET_NAME); + + wallet_location.into_boxed_path() + } + + pub fn wallet_exists(&self) -> bool { + return self.get_wallet_path().exists() + } + + pub fn get_zcash_params_path(&self) -> io::Result> { + let mut zcash_params = self.get_zcash_data_path().into_path_buf(); + zcash_params.push(".."); + if cfg!(target_os="macos") || cfg!(target_os="windows") { + zcash_params.push("ZcashParams"); + } else { + zcash_params.push(".zcash-params"); + } + + match std::fs::create_dir_all(zcash_params.clone()) { + Ok(_) => Ok(zcash_params.into_boxed_path()), + Err(e) => { + eprintln!("Couldn't create zcash params directory\n{}", e); + Err(e) + } + } + } + + pub fn get_log_path(&self) -> Box { + let mut log_path = self.get_zcash_data_path().into_path_buf(); + log_path.push(LOGFILE_NAME); + + log_path.into_boxed_path() + } + + pub fn get_initial_state(&self, height: u64) -> Option<(u64, &str, &str)> { + checkpoints::get_closest_checkpoint(&self.chain_name, height) + } + + pub fn get_server_or_default(server: Option) -> http::Uri { + match server { + Some(s) => { + let mut s = if s.starts_with("http") {s} else { "http://".to_string() + &s}; + let uri: http::Uri = s.parse().unwrap(); + if uri.port().is_none() { + s = s + ":443"; + } + s + } + None => DEFAULT_SERVER.to_string() + }.parse().unwrap() + } + + pub fn get_coin_type(&self) -> u32 { + match &self.chain_name[..] { + "main" => mainnet::COIN_TYPE, + "test" => testnet::COIN_TYPE, + "regtest" => regtest::COIN_TYPE, + c => panic!("Unknown chain {}", c) + } + } + + pub fn hrp_sapling_address(&self) -> &str { + match &self.chain_name[..] { + "main" => mainnet::HRP_SAPLING_PAYMENT_ADDRESS, + "test" => testnet::HRP_SAPLING_PAYMENT_ADDRESS, + "regtest" => regtest::HRP_SAPLING_PAYMENT_ADDRESS, + c => panic!("Unknown chain {}", c) + } + } + + pub fn hrp_sapling_private_key(&self) -> &str { + match &self.chain_name[..] { + "main" => mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, + "test" => testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, + "regtest" => regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY, + c => panic!("Unknown chain {}", c) + } + } + + pub fn hrp_sapling_viewing_key(&self) -> &str { + match &self.chain_name[..] { + "main" => mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + "test" => testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + "regtest" => regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + c => panic!("Unknown chain {}", c) + } + } + + pub fn base58_pubkey_address(&self) -> [u8; 1] { + match &self.chain_name[..] { + "main" => mainnet::B58_PUBKEY_ADDRESS_PREFIX, + + + c => panic!("Unknown chain {}", c) + } + } + + + pub fn base58_script_address(&self) -> [u8; 1] { + match &self.chain_name[..] { + "main" => mainnet::B58_SCRIPT_ADDRESS_PREFIX, + + + c => panic!("Unknown chain {}", c) + } + } + + pub fn base58_secretkey_prefix(&self) -> [u8; 1] { + match &self.chain_name[..] { + "main" => [0xBC], + "test" => [0xEF], + "regtest" => [0xEF], + c => panic!("Unknown chain {}", c) + } + } +} + +pub struct LightClient { + pub wallet : Arc>, + + pub config : LightClientConfig, + + // zcash-params + pub sapling_output : Vec, + pub sapling_spend : Vec, + + sync_lock : Mutex<()>, + sync_status : Arc>, // The current syncing status of the Wallet. + pub shutdown_flag : Arc, // Signal mempool threads to stop +} + +impl LightClient { + + pub fn set_wallet_initial_state(&self, height: u64) { + use std::convert::TryInto; + + let state = self.config.get_initial_state(height); + + match state { + Some((height, hash, tree)) => self.wallet.read().unwrap().set_initial_block(height.try_into().unwrap(), hash, tree), + _ => true, + }; + } + + fn write_file_if_not_exists(dir: &Box, name: &str, bytes: &[u8]) -> io::Result<()> { + let mut file_path = dir.to_path_buf(); + file_path.push(name); + if !file_path.exists() { + let mut file = File::create(&file_path)?; + file.write_all(bytes)?; + } + + Ok(()) + } + + #[cfg(feature = "embed_params")] + fn read_sapling_params(&mut self) { + // Read Sapling Params + use crate::SaplingParams; + self.sapling_output.extend_from_slice(SaplingParams::get("sapling-output.params").unwrap().as_ref()); + self.sapling_spend.extend_from_slice(SaplingParams::get("sapling-spend.params").unwrap().as_ref()); + } + pub fn set_sapling_params(&mut self, sapling_output: &[u8], sapling_spend: &[u8]) -> Result<(), String> { + use sha2::{Sha256, Digest}; + // The hashes of the params need to match + const SAPLING_OUTPUT_HASH: &str = "2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4"; + const SAPLING_SPEND_HASH: &str = "8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13"; + if SAPLING_OUTPUT_HASH.to_string() != hex::encode(Sha256::digest(&sapling_output)) { + return Err(format!("sapling-output hash didn't match. expected {}, found {}", SAPLING_OUTPUT_HASH, hex::encode(Sha256::digest(&sapling_output)) )) + } + if SAPLING_SPEND_HASH.to_string() != hex::encode(Sha256::digest(&sapling_spend)) { + return Err(format!("sapling-spend hash didn't match. expected {}, found {}", SAPLING_SPEND_HASH, hex::encode(Sha256::digest(&sapling_spend)) )) + } + // Will not overwrite previous params + if self.sapling_output.is_empty() { + self.sapling_output.extend_from_slice(sapling_output); + } + if self.sapling_spend.is_empty() { + self.sapling_spend.extend_from_slice(sapling_spend); + } + + // Ensure that the sapling params are stored on disk properly as well. + match self.config.get_zcash_params_path() { + Ok(zcash_params_dir) => { + // Create the sapling output and spend params files + match LightClient::write_file_if_not_exists(&zcash_params_dir, "sapling-output.params", &self.sapling_output) { + Ok(_) => {}, + Err(e) => eprintln!("Warning: Couldn't write the output params!\n{}", e) + }; + + match LightClient::write_file_if_not_exists(&zcash_params_dir, "sapling-spend.params", &self.sapling_spend) { + Ok(_) => {}, + Err(e) => eprintln!("Warning: Couldn't write the output params!\n{}", e) + } + }, + Err(e) => { + eprintln!("{}", e); + } + }; + + Ok(()) + } + + /// Method to create a test-only version of the LightClient + #[allow(dead_code)] + pub fn unconnected(seed_phrase: String, dir: Option) -> io::Result { + let config = LightClientConfig::create_unconnected("test".to_string(), dir); + let mut l = LightClient { + wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), &config, 0,0 )?)), + config : config.clone(), + sapling_output : vec![], + sapling_spend : vec![], + sync_lock : Mutex::new(()), + sync_status : Arc::new(RwLock::new(WalletStatus::new())), + shutdown_flag : Arc::new(AtomicBool::new(false)), + }; + + l.set_wallet_initial_state(0); + + #[cfg(feature = "embed_params")] + l.read_sapling_params(); + + info!("Created new wallet!"); + info!("Created LightClient to {}", &config.server); + + Ok(l) + } + + /// Create a brand new wallet with a new seed phrase. Will fail if a wallet file + /// already exists on disk + pub fn new(config: &LightClientConfig, latest_block: u64) -> io::Result { + if config.wallet_exists() { + return Err(Error::new(ErrorKind::AlreadyExists, + "Cannot create a new wallet from seed, because a wallet already exists")); + } + + let mut l = LightClient { + wallet : Arc::new(RwLock::new(LightWallet::new(None, config, latest_block, 0)?)), + config : config.clone(), + sapling_output : vec![], + sapling_spend : vec![], + sync_lock : Mutex::new(()), + sync_status : Arc::new(RwLock::new(WalletStatus::new())), + shutdown_flag : Arc::new(AtomicBool::new(false)), + }; + + l.set_wallet_initial_state(latest_block); + + #[cfg(feature = "embed_params")] + l.read_sapling_params(); + + info!("Created new wallet with a new seed!"); + info!("Created LightClient to {}", &config.server); + l.do_save().map_err(|s| io::Error::new(ErrorKind::PermissionDenied, s))?; + + Ok(l) + } + + pub fn new_from_phrase(seed_phrase: String, config: &LightClientConfig, birthday: u64,number: u64, overwrite: bool) -> io::Result { + if !overwrite && config.wallet_exists() { + return Err(Error::new(ErrorKind::AlreadyExists, + "Cannot create a new wallet from seed, because a wallet already exists")); + } + + let mut l = LightClient { + wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), config, birthday, number)?)), + config : config.clone(), + sapling_output : vec![], + sapling_spend : vec![], + sync_lock : Mutex::new(()), + sync_status : Arc::new(RwLock::new(WalletStatus::new())), + shutdown_flag : Arc::new(AtomicBool::new(false)), + }; + + // println!("Setting birthday to {}", birthday); + l.set_wallet_initial_state(birthday); + #[cfg(feature = "embed_params")] + l.read_sapling_params(); + + + info!("Created new wallet!"); + info!("Created LightClient to {}", &config.server); + l.do_save().map_err(|s| io::Error::new(ErrorKind::PermissionDenied, s))?; + + Ok(l) + } + + pub fn read_from_disk(config: &LightClientConfig) -> io::Result { + if !config.wallet_exists() { + // Try to recover from backup + let bak_path = config.get_wallet_path().with_extension("dat.bak"); + if bak_path.exists() { + warn!("Wallet file missing but backup found, attempting recovery from {:?}", bak_path); + std::fs::copy(&bak_path, config.get_wallet_path())?; + info!("Wallet recovered from backup"); + } else { + return Err(Error::new(ErrorKind::AlreadyExists, + format!("Cannot read wallet. No file at {}", config.get_wallet_path().display()))); + } + } + + // Try to open the wallet file; if it fails (corrupted/truncated), try the backup + let wallet = match File::open(config.get_wallet_path()) + .and_then(|f| { + let mut file_buffer = BufReader::new(f); + LightWallet::read(&mut file_buffer, config) + }) { + Ok(w) => w, + Err(e) => { + warn!("Failed to read wallet file: {}, trying backup", e); + let bak_path = config.get_wallet_path().with_extension("dat.bak"); + if bak_path.exists() { + std::fs::copy(&bak_path, config.get_wallet_path())?; + let mut file_buffer = BufReader::new(File::open(config.get_wallet_path())?); + LightWallet::read(&mut file_buffer, config)? + } else { + return Err(e); + } + } + }; + + let mut lc = LightClient { + wallet : Arc::new(RwLock::new(wallet)), + config : config.clone(), + sapling_output : vec![], + sapling_spend : vec![], + sync_lock : Mutex::new(()), + sync_status : Arc::new(RwLock::new(WalletStatus::new())), + shutdown_flag : Arc::new(AtomicBool::new(false)), + }; + + #[cfg(feature = "embed_params")] + lc.read_sapling_params(); + + info!("Read wallet with birthday {}", lc.wallet.read().unwrap().get_first_tx_block()); + info!("Created LightClient to {}", &config.server); + + Ok(lc) + } + + pub fn init_logging(&self) -> io::Result<()> { + // Configure logging first. + let log_config = self.config.get_log_config()?; + log4rs::init_config(log_config).map_err(|e| { + std::io::Error::new(ErrorKind::Other, e) + })?; + + Ok(()) + } + + pub fn attempt_recover_seed(config: &LightClientConfig, password: Option) -> Result { + use std::io::prelude::*; + use byteorder::{LittleEndian, ReadBytesExt}; + use libflate::gzip::Decoder; + use bip39::{Mnemonic, Language}; + use zcash_primitives::serialize::Vector; + + let mut inp = BufReader::new(File::open(config.get_wallet_path()).unwrap()); + let version = inp.read_u64::().unwrap(); + println!("Reading wallet version {}", version); + + // At version 5, we're writing the rest of the file as a compressed stream (gzip) + let mut reader: Box = if version != 5 { + Box::new(inp) + } else { + Box::new(Decoder::new(inp).unwrap()) + }; + + let encrypted = if version >= 4 { + reader.read_u8().unwrap() > 0 + } else { + false + }; + + if encrypted && password.is_none() { + return Err("The wallet is encrypted and a password was not specified. Please specify the password with '--password'!".to_string()); + } + + let mut enc_seed = [0u8; 48]; + if version >= 4 { + reader.read_exact(&mut enc_seed).unwrap(); + } + + let nonce = if version >= 4 { + Vector::read(&mut reader, |r| r.read_u8()).unwrap() + } else { + vec![] + }; + + let phrase = if encrypted { + use sodiumoxide::crypto::secretbox; + use crate::lightwallet::double_sha256; + + // Get the doublesha256 of the password, which is the right length + let key = secretbox::Key::from_slice(&double_sha256(password.unwrap().as_bytes())).unwrap(); + let nonce = secretbox::Nonce::from_slice(&nonce).unwrap(); + + let seed = match secretbox::open(&enc_seed, &nonce, &key) { + Ok(s) => s, + Err(_) => return Err("Decryption failed. Is your password correct?".to_string()) + }; + + Mnemonic::from_entropy(&seed, Language::English) + } else { + // Seed + let mut seed_bytes = [0u8; 32]; + reader.read_exact(&mut seed_bytes).unwrap(); + + Mnemonic::from_entropy(&seed_bytes, Language::English) + }.map_err(|e| format!("Failed to read seed. {:?}", e)); + + phrase.map(|m| m.phrase().to_string()) + } + + + pub fn last_scanned_height(&self) -> u64 { + self.wallet.read().unwrap().last_scanned_height() as u64 + } + + // Export private keys + pub fn do_export(&self, addr: Option) -> Result { + if !self.wallet.read().unwrap().is_unlocked_for_spending() { + error!("Wallet is locked"); + return Err("Wallet is locked"); + } + + // Clone address so it can be moved into the closure + let address = addr.clone(); + let wallet = self.wallet.read().unwrap(); + // Go over all z addresses + let z_keys = wallet.get_z_private_keys().iter() + .filter( move |(addr, _, _)| address.is_none() || address.as_ref() == Some(addr)) + .map( |(addr, pk, vk)| + object!{ + "address" => addr.clone(), + "private_key" => pk.clone(), + "viewing_key" => vk.clone(), + } + ).collect::>(); + + // Clone address so it can be moved into the closure + let address = addr.clone(); + + // Go over all t addresses + let t_keys = wallet.get_t_secret_keys().iter() + .filter( move |(addr, _)| address.is_none() || address.as_ref() == Some(addr)) + .map( |(addr, sk)| + object!{ + "address" => addr.clone(), + "private_key" => sk.clone(), + } + ).collect::>(); + + let mut all_keys = vec![]; + all_keys.extend_from_slice(&z_keys); + all_keys.extend_from_slice(&t_keys); + + Ok(all_keys.into()) + } + + pub fn do_address(&self) -> JsonValue { + let wallet = self.wallet.read().unwrap(); + + // Collect z addresses + let z_addresses = wallet.get_all_zaddresses(); + + // Collect t addresses + let t_addresses = wallet.get_all_taddresses().iter().map( |a| a.clone() ) + .collect::>(); + + object!{ + "z_addresses" => z_addresses, + "t_addresses" => t_addresses, + } + } + + pub fn do_balance(&self) -> JsonValue { + let wallet = self.wallet.read().unwrap(); + + // Collect z addresses + let z_addresses = wallet.get_all_zaddresses().iter().map(|zaddress| { + object!{ + "address" => zaddress.clone(), + "zbalance" => wallet.zbalance(Some(zaddress.clone())), + "unconfirmed" => wallet.unconfirmed_zbalance(Some(zaddress.clone())), + "verified_zbalance" => wallet.verified_zbalance(Some(zaddress.clone())), + "spendable_zbalance" => wallet.spendable_zbalance(Some(zaddress.clone())) + } + }).collect::>(); + + // Collect t addresses + let t_addresses = wallet.get_all_taddresses().iter().map( |address| { + // Get the balance for this address + let balance = wallet.tbalance(Some(address.clone())) ; + + object!{ + "address" => address.clone(), + "balance" => balance, + } + }).collect::>(); + + object!{ + "zbalance" => wallet.zbalance(None), + "unconfirmed" => wallet.unconfirmed_zbalance(None), + "verified_zbalance" => wallet.verified_zbalance(None), + "spendable_zbalance" => wallet.spendable_zbalance(None), + "tbalance" => wallet.tbalance(None), + "z_addresses" => z_addresses, + "t_addresses" => t_addresses, + } + } + + pub fn do_save(&self) -> Result<(), String> { + // If the wallet is encrypted but unlocked, lock it again. + { + let mut wallet = self.wallet.write().unwrap(); + if wallet.is_encrypted() && wallet.is_unlocked_for_spending() { + match wallet.lock() { + Ok(_) => {}, + Err(e) => { + let err = format!("ERR: {}", e); + error!("{}", err); + return Err(e.to_string()); + } + } + } + } + + let r; + { + // Prevent any overlapping syncs during save, and don't save in the middle of a sync + let _lock = self.sync_lock.lock().unwrap(); + + let wallet_path = self.config.get_wallet_path(); + let tmp_path = wallet_path.with_extension("dat.tmp"); + let bak_path = wallet_path.with_extension("dat.bak"); + + // Write to a temporary file first (atomic save) + let wallet = self.wallet.write().unwrap(); + let mut file_buffer = BufWriter::with_capacity( + 1_000_000, // 1 MB write buffer + File::create(&tmp_path).map_err(|e| format!("Failed to create temp file: {}", e))?); + + r = match wallet.write(&mut file_buffer) { + Ok(_) => Ok(()), + Err(e) => { + let err = format!("ERR: {}", e); + error!("{}", err); + // Clean up temp file on write failure + let _ = std::fs::remove_file(&tmp_path); + Err(e.to_string()) + } + }; + + file_buffer.flush().map_err(|e| { + let _ = std::fs::remove_file(&tmp_path); + format!("{}", e) + })?; + + // Only proceed with rename if the write succeeded + if r.is_ok() { + // Create backup of existing wallet (if it exists) + if wallet_path.exists() { + if let Err(e) = std::fs::copy(&wallet_path, &bak_path) { + warn!("Failed to create wallet backup: {}", e); + // Non-fatal: continue with the save + } + } + + // Atomically replace the wallet file with the new one + if let Err(e) = std::fs::rename(&tmp_path, &wallet_path) { + error!("Failed to rename temp wallet file: {}", e); + // Try direct copy as fallback (rename can fail across filesystems) + if let Err(e2) = std::fs::copy(&tmp_path, &wallet_path) { + let _ = std::fs::remove_file(&tmp_path); + return Err(format!("Failed to save wallet: {} / {}", e, e2)); + } + let _ = std::fs::remove_file(&tmp_path); + } + } + } + + r + } + + pub fn get_server_uri(&self) -> http::Uri { + self.config.server.clone() + } + + // DEBUG: dump the wallet's latest Sapling commitment tree so it can be diffed against the + // node's `getblockmerkletree ` to localize a tree/anchor divergence. + pub fn do_sapling_tree(&self) -> JsonValue { + match self.wallet.read().unwrap().get_sapling_tree() { + Ok((height, hash, tree)) => object!{ + "height" => height, + "hash" => hash, + "tree" => tree, + "anchor_offset" => self.config.anchor_offset, + }, + Err(e) => object!{ "error" => e }, + } + } + + pub fn do_info(&self) -> String { + match get_info(&self.get_server_uri(), self.config.no_cert_verification) { + Ok(i) => { + let o = object!{ + "version" => i.version, + "vendor" => i.vendor, + "taddr_support" => i.taddr_support, + "chain_name" => i.chain_name, + "sapling_activation_height" => i.sapling_activation_height, + "consensus_branch_id" => i.consensus_branch_id, + "latest_block_height" => i.block_height, + "difficulty" => i.difficulty, + "longestchain" => i.longestchain, + "notarized" => i.notarized, + }; + o.pretty(2) + }, + Err(e) => e + } + } + + pub fn do_coinsupply(&self) -> String { + match get_coinsupply(self.get_server_uri(), self.config.no_cert_verification) { + Ok(i) => { + let o = object!{ + "result" => i.result, + "coin" => i.coin, + "height" => i.height, + "supply" => i.supply, + "zfunds" => i.zfunds, + "total" => i.total, + + }; + o.pretty(2) + }, + Err(e) => e + } + } + + pub fn do_seed_phrase(&self) -> Result { + if !self.wallet.read().unwrap().is_unlocked_for_spending() { + error!("Wallet is locked"); + return Err("Wallet is locked"); + } + + let wallet = self.wallet.read().unwrap(); + Ok(object!{ + "seed" => wallet.get_seed_phrase(), + "birthday" => wallet.get_birthday() + }) + } + + // Return a list of all notes, spent and unspent + pub fn do_list_notes(&self, all_notes: bool) -> JsonValue { + let mut unspent_notes: Vec = vec![]; + let mut spent_notes : Vec = vec![]; + let mut pending_notes: Vec = vec![]; + + let anchor_height: i32 = self.wallet.read().unwrap().get_anchor_height() as i32; + + { + + let wallet = self.wallet.read().unwrap(); + + // First, collect all extfvk's that are spendable (i.e., we have the private key) + let spendable_address: HashSet = wallet.get_all_zaddresses().iter() + .filter(|address| wallet.have_spending_key_for_zaddress(address)) + .map(|address| address.clone()) + .collect(); + + // Collect Sapling notes + wallet.txs.read().unwrap().iter() + .flat_map( |(txid, wtx)| { + let spendable_address = spendable_address.clone(); + wtx.notes.iter().filter_map(move |nd| + if !all_notes && nd.spent.is_some() { + None + } else { + + let address = LightWallet::note_address(self.config.hrp_sapling_address(), nd); + let spendable = address.is_some() && + spendable_address.contains(&address.clone().unwrap()) && + wtx.block <= anchor_height && nd.spent.is_none() && nd.unconfirmed_spent.is_none(); + Some(object!{ + "created_in_block" => wtx.block, + "datetime" => wtx.datetime, + "created_in_txid" => format!("{}", txid), + "value" => nd.note.value, + "is_change" => nd.is_change, + "address" => address, + "spendable" => spendable, + "spent" => nd.spent.map(|spent_txid| format!("{}", spent_txid)), + "unconfirmed_spent" => nd.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)), + }) + } + ) + }) + .for_each( |note| { + if note["spent"].is_null() && note["unconfirmed_spent"].is_null() { + unspent_notes.push(note); + } else if !note["spent"].is_null() { + spent_notes.push(note); + } else { + pending_notes.push(note); + } + }); + } + + let mut unspent_utxos: Vec = vec![]; + let mut spent_utxos : Vec = vec![]; + let mut pending_utxos: Vec = vec![]; + + { + let wallet = self.wallet.read().unwrap(); + wallet.txs.read().unwrap().iter() + .flat_map( |(txid, wtx)| { + wtx.utxos.iter().filter_map(move |utxo| + if !all_notes && utxo.spent.is_some() { + None + } else { + Some(object!{ + "created_in_block" => wtx.block, + "datetime" => wtx.datetime, + "created_in_txid" => format!("{}", txid), + "value" => utxo.value, + "scriptkey" => hex::encode(utxo.script.clone()), + "is_change" => false, // TODO: Identify notes as change if we send change to taddrs + "address" => utxo.address.clone(), + "spent" => utxo.spent.map(|spent_txid| format!("{}", spent_txid)), + "unconfirmed_spent" => utxo.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)), + }) + } + ) + }) + .for_each( |utxo| { + if utxo["spent"].is_null() && utxo["unconfirmed_spent"].is_null() { + unspent_utxos.push(utxo); + } else if !utxo["spent"].is_null() { + spent_utxos.push(utxo); + } else { + pending_utxos.push(utxo); + } + }); + } + + let mut res = object!{ + "unspent_notes" => unspent_notes, + "pending_notes" => pending_notes, + "utxos" => unspent_utxos, + "pending_utxos" => pending_utxos, + }; + + if all_notes { + res["spent_notes"] = JsonValue::Array(spent_notes); + res["spent_utxos"] = JsonValue::Array(spent_utxos); + } + + res + } + + pub fn do_encryption_status(&self) -> JsonValue { + let wallet = self.wallet.read().unwrap(); + object!{ + "encrypted" => wallet.is_encrypted(), + "locked" => !wallet.is_unlocked_for_spending() + } + } + + pub fn do_list_transactions(&self) -> JsonValue { + let wallet = self.wallet.read().unwrap(); + + // Create a list of TransactionItems from wallet txns + let mut tx_list = wallet.txs.read().unwrap().iter() + .flat_map(| (_k, v) | { + let mut txns: Vec = vec![]; + + if v.total_shielded_value_spent + v.total_transparent_value_spent > 0 { + // If money was spent, create a transaction. For this, we'll subtract + // all the change notes. TODO: Add transparent change here to subtract it also + let total_change: u64 = v.notes.iter() + .filter( |nd| nd.is_change ) + .map( |nd| nd.note.value ) + .sum(); + + // TODO: What happens if change is > than sent ? + + // Collect outgoing metadata + let outgoing_json = v.outgoing_metadata.iter() + .map(|om| + object!{ + "address" => om.address.clone(), + "value" => om.value, + "memo" => LightWallet::memo_str(&Some(om.memo.clone())), + }) + .collect::>(); + + txns.push(object! { + "block_height" => v.block, + "datetime" => v.datetime, + "txid" => format!("{}", v.txid), + "amount" => total_change as i64 + - v.total_shielded_value_spent as i64 + - v.total_transparent_value_spent as i64, + "outgoing_metadata" => outgoing_json, + }); + } + + // For each sapling note that is not a change, add a Tx. + txns.extend(v.notes.iter() + .filter( |nd| !nd.is_change ) + .enumerate() + .map ( |(i, nd)| + object! { + "block_height" => v.block, + "datetime" => v.datetime, + "position" => i, + "txid" => format!("{}", v.txid), + "amount" => nd.note.value as i64, + "address" => LightWallet::note_address(self.config.hrp_sapling_address(), nd), + "memo" => LightWallet::memo_str(&nd.memo), + }) + ); + + // Get the total transparent received + let total_transparent_received = v.utxos.iter().map(|u| u.value).sum::(); + if total_transparent_received > v.total_transparent_value_spent { + // Create an input transaction for the transparent value as well. + txns.push(object!{ + "block_height" => v.block, + "datetime" => v.datetime, + "txid" => format!("{}", v.txid), + "amount" => total_transparent_received as i64 - v.total_transparent_value_spent as i64, + "address" => v.utxos.iter().map(|u| u.address.clone()).collect::>().join(","), + "memo" => None:: + }) + } + + txns + }) + .collect::>(); + + // Add the incoming Mempool - incoming_mempool flag is atm useless, but we can use that in future maybe + tx_list.extend( + wallet.incoming_mempool_txs.read().unwrap().iter().flat_map(|(_, wtxs)| { + wtxs.iter().flat_map(|wtx| { + wtx.incoming_metadata.iter() + .enumerate() + .map(move |(i, om)| + object! { + "block_height" => wtx.block.clone(), + "datetime" => wtx.datetime.clone(), + "position" => i, + "txid" => format!("{}", wtx.txid), + "amount" => om.value as i64, + "address" => om.address.clone(), + "memo" => LightWallet::memo_str(&Some(om.memo.clone())), + "unconfirmed" => true, + "incoming_mempool" => true, + } + ) + }) + }) + ); + + // Add in all mempool txns + tx_list.extend(wallet.mempool_txs.read().unwrap().iter().map( |(_, wtx)| { + use zcash_primitives::transaction::components::amount::DEFAULT_FEE; + use std::convert::TryInto; + + let amount: u64 = wtx.outgoing_metadata.iter().map(|om| om.value).sum::(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + // Collect outgoing metadata + let outgoing_json = wtx.outgoing_metadata.iter() + .map(|om| + object!{ + "address" => om.address.clone(), + "value" => om.value, + "memo" => LightWallet::memo_str(&Some(om.memo.clone())), + + }).collect::>(); + + object! { + "block_height" => wtx.block, + "datetime" => wtx.datetime, + "txid" => format!("{}", wtx.txid), + "amount" => -1 * (fee + amount) as i64, + "unconfirmed" => true, + "outgoing_metadata" => outgoing_json, + } + })); + + tx_list.sort_by( |a, b| if a["block_height"] == b["block_height"] { + a["txid"].as_str().cmp(&b["txid"].as_str()) + } else { + a["block_height"].as_i32().cmp(&b["block_height"].as_i32()) + } + ); + + JsonValue::Array(tx_list) + } + + /// Create a new address, deriving it from the seed. + pub fn do_new_address(&self, addr_type: &str) -> Result { + if !self.wallet.read().unwrap().is_unlocked_for_spending() { + error!("Wallet is locked"); + return Err("Wallet is locked".to_string()); + } + + let new_address = { + let wallet = self.wallet.write().unwrap(); + + let addr = match addr_type { + "zs" => wallet.add_zaddr(), + "R" => wallet.add_taddr(), + _ => { + let e = format!("Unrecognized address type: {}", addr_type); + error!("{}", e); + return Err(e); + } + }; + + if addr.starts_with("Error") { + let e = format!("Error creating new address: {}", addr); + error!("{}", e); + return Err(e); + } + + addr + }; + + self.do_save()?; + + Ok(array![new_address]) + } + + /// Import a new private key + pub fn do_import_tk(&self, sk: String, birthday: u64) -> Result { + if !self.wallet.read().unwrap().is_unlocked_for_spending() { + error!("Wallet is locked"); + return Err("Wallet is locked".to_string()); + } + + let new_address = { + let mut wallet = self.wallet.write().unwrap(); + + let addr = wallet.import_taddr(sk, birthday); + if addr.starts_with("Error") { + let e = format!("Error creating new address{}", addr); + error!("{}", e); + return Err(e); + } + + addr + }; + + self.do_save()?; + + Ok(array![new_address]) + } + + // Start Mempool-Monitor +pub fn start_mempool_monitor(lc: Arc) -> Result<(), String> { + let config = lc.config.clone(); + let uri = config.server.clone(); + let shutdown = lc.shutdown_flag.clone(); + + let (incoming_mempool_tx, incoming_mempool_rx) = std::sync::mpsc::channel::(); + + // Thread for receive transactions + let shutdown_rx = lc.shutdown_flag.clone(); + std::thread::spawn(move || { + while let Ok(rtx) = incoming_mempool_rx.recv() { + if shutdown_rx.load(Ordering::Relaxed) { + break; + } + if let Ok(tx) = Transaction::read( + &rtx.data[..]) + { + let light_wallet_clone = lc.wallet.clone(); + light_wallet_clone.read().unwrap().scan_full_mempool_tx(&tx, rtx.height as i32, 0, true); + } + } + }); + + // Thread mempool monitor + std::thread::spawn(move || { + let mut rt = Runtime::new().unwrap(); + rt.block_on(async { + loop { + if shutdown.load(Ordering::Relaxed) { + info!("Mempool monitor shutting down"); + break; + } + + let incoming_mempool_tx_clone = incoming_mempool_tx.clone(); + let send_closure = move |rtx: RawTransaction| { + incoming_mempool_tx_clone.send(rtx).map_err(|e| Box::new(e) as Box) + }; + + match grpcconnector::monitor_mempool(&uri.clone(), true, send_closure).await { + Ok(_) => info!("Mempool monitor loop successful"), + Err(e) => { + // If the server doesn't implement GetMempoolStream, stop the monitor instead + // of reconnecting forever against a method that will never succeed (log spam + // + needless churn). Other errors fall through to the periodic retry below. + if e.to_string().contains("Unimplemented") { + info!("Server does not implement GetMempoolStream; stopping mempool monitor"); + return; + } + warn!("Mempool monitor returned {:?}, will restart listening", e); + } + } + + // Sleep in 1-second increments so we can check shutdown flag + for _ in 0..10 { + if shutdown.load(Ordering::Relaxed) { + info!("Mempool monitor shutting down during sleep"); + return; + } + std::thread::sleep(Duration::from_secs(1)); + } + } + }); + }); + + Ok(()) +} + + /// Signal all background threads to stop + pub fn shutdown(&self) { + self.shutdown_flag.store(true, Ordering::Relaxed); + info!("Shutdown flag set"); + } + /// Convinence function to determine what type of key this is and import it + pub fn do_import_key(&self, key: String, birthday: u64) -> Result { + if key.starts_with(self.config.hrp_sapling_private_key()) { + self.do_import_sk(key, birthday) + } else if key.starts_with(self.config.hrp_sapling_viewing_key()) { + self.do_import_vk(key, birthday) + } else { + Err(format!("'{}' was not recognized as either a spending key or a viewing key because it didn't start with either '{}' or '{}'", + key, self.config.hrp_sapling_private_key(), self.config.hrp_sapling_viewing_key())) + } + } + + /// Import a new private key + pub fn do_import_sk(&self, sk: String, birthday: u64) -> Result { + if !self.wallet.read().unwrap().is_unlocked_for_spending() { + error!("Wallet is locked"); + return Err("Wallet is locked".to_string()); + } + + let new_address = { + let mut wallet = self.wallet.write().unwrap(); + + let addr = wallet.add_imported_sk(sk, birthday); + if addr.starts_with("Error") { + let e = format!("Error creating new address{}", addr); + error!("{}", e); + return Err(e); + } + + addr + }; + + self.do_save()?; + + Ok(array![new_address]) + } + + /// Import a new viewing key + pub fn do_import_vk(&self, vk: String, birthday: u64) -> Result { + if !self.wallet.read().unwrap().is_unlocked_for_spending() { + error!("Wallet is locked"); + return Err("Wallet is locked".to_string()); + } + + let new_address = { + let mut wallet = self.wallet.write().unwrap(); + + let addr = wallet.add_imported_vk(vk, birthday); + if addr.starts_with("Error") { + let e = format!("Error creating new address{}", addr); + error!("{}", e); + return Err(e); + } + addr + }; + + self.do_save()?; + + Ok(array![new_address]) + } + + pub fn do_new_sietchaddress(&self, addr_type: &str) -> Result { + + let zdust_address = { + let wallet = self.wallet.write().unwrap(); + + match addr_type { + "zs" => wallet.add_zaddrdust(), + + _ => { + let e = format!("Unrecognized address type: {}", addr_type); + error!("{}", e); + return Err(e); + } + } + }; + + Ok(array![zdust_address]) + } + + pub fn clear_state(&self) { + self.clear_state_from(self.wallet.read().unwrap().get_birthday()); + } + + pub fn clear_state_from(&self, height: u64) { + // First, clear the state from the wallet + self.wallet.read().unwrap().clear_blocks(); + + // Then set the initial block + self.set_wallet_initial_state(height); + info!("Cleared wallet state to height {}", height); + } + + pub fn do_rescan(&self) -> Result { + if !self.wallet.read().unwrap().is_unlocked_for_spending() { + warn!("Wallet is locked, new HD addresses won't be added!"); + } + + info!("Rescan starting"); + + self.clear_state(); + + // Then, do a sync, which will force a full rescan from the initial state + let response = self.do_sync(true); + + self.do_save()?; + info!("Rescan finished"); + + response + } + + /// Return the syncing status of the wallet + pub fn do_scan_status(&self) -> WalletStatus { + self.sync_status.read().unwrap().clone() + } + + pub fn do_sync(&self, print_updates: bool) -> Result { + + let mut retry_count = 0; + loop { + match self.do_sync_internal(print_updates, retry_count) { + Ok(j) => return Ok(j), + Err(e) => { + retry_count += 1; + if retry_count > 5 { + return Err(e); + } + // Sleep exponentially backing off + std::thread::sleep(std::time::Duration::from_secs((2 as u64).pow(retry_count))); + + } + } + } + } + + pub fn do_shield(&self, address: Option) -> Result { + use zcash_primitives::transaction::components::amount::DEFAULT_FEE; + use std::convert::TryInto; + + let fee = DEFAULT_FEE.try_into().unwrap(); + let tbal = self.wallet.read().unwrap().tbalance(None); + + // Make sure there is a balance, and it is greated than the amount + if tbal <= fee { + return Err(format!("Not enough transparent balance to shield. Have {} puposhis, need more than {} puposhis to cover tx fee", tbal, fee)); + } + + let addr = address.or(self.wallet.read().unwrap().get_all_zaddresses().get(0).map(|s| s.clone())).unwrap(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let result = { + let _lock = self.sync_lock.lock().unwrap(); + self.wallet.read().unwrap().send_to_address( + u32::from_str_radix(&self.config.consensus_branch_id, 16).unwrap(), + &self.sapling_spend, &self.sapling_output, + true, + vec![(&addr, tbal - fee, None)], + &fee, + |txbytes| broadcast_raw_tx(&self.get_server_uri(),self.config.no_cert_verification, txbytes) + ) + }; + + result.map(|(txid, _)| txid) + } + + fn do_sync_internal(&self, print_updates: bool, retry_count: u32) -> Result { + // We can only do one sync at a time because we sync blocks in serial order + // If we allow multiple syncs, they'll all get jumbled up. + let _lock = self.sync_lock.lock().unwrap(); + + // Sync is 3 parts + // 1. Get the latest block + // 2. Get all the blocks that we don't have + // 3. Find all new Txns that don't have the full Tx, and get them as full transactions + // and scan them, mainly to get the memos + let mut last_scanned_height = self.wallet.read().unwrap().last_scanned_height() as u64; + + // This will hold the latest block fetched from the RPC + + let latest_block = fetch_latest_block(&self.get_server_uri(), self.config.no_cert_verification)?.height; + + if latest_block < last_scanned_height { + let w = format!("Server's latest block({}) is behind ours({})", latest_block, last_scanned_height); + warn!("{}", w); + return Err(w); + } + + info!("Latest block is {}", latest_block); + + // Get the end height to scan to. + // get_block_range streams an ENTIRE range over ONE gRPC connection, so the batch size is + // the dominant factor in how many TCP connections a sync opens. The old 1000 meant ~1300 + // connections for a full sync, which on Windows exhausted socket buffers (WSAENOBUFS / os + // error 10055). A large batch (25k) cuts that ~50x (~52 connections for a full sync) with + // no extra memory — blocks are still streamed + scanned incrementally, not buffered. + let scan_batch_size = 25000; + let mut end_height = std::cmp::min(last_scanned_height + scan_batch_size, latest_block); + + // If there's nothing to scan, just return + if last_scanned_height == latest_block { + info!("Nothing to sync, returning"); + return Ok(object!{ "result" => "success" }) + } + + { + let mut status = self.sync_status.write().unwrap(); + status.is_syncing = true; + status.synced_blocks = last_scanned_height; + status.total_blocks = latest_block; + } + + // Count how many bytes we've downloaded + let bytes_downloaded = Arc::new(AtomicUsize::new(0)); + + let mut total_reorg = 0; + + // Collect all txns in blocks that we have a tx in. We'll fetch all these + // txs along with our own, so that the server doesn't learn which ones + // belong to us. + let all_new_txs = Arc::new(RwLock::new(vec![])); + + // Create a new threadpool (upto 8, atleast 2 threads) to scan with + let pool = ThreadPool::new(max(2, min(8, num_cpus::get()))); + + // Create a single Tokio runtime for all block fetches to avoid per-batch overhead + let mut rt = tokio::runtime::Runtime::new() + .map_err(|e| format!("Error creating runtime: {:?}", e))?; + + // Fetch CompactBlocks in increments + let mut pass = 0; + loop { + pass +=1 ; + // Collect all block times, because we'll need to update transparent tx + // datetime via the block height timestamp + let block_times = Arc::new(RwLock::new(HashMap::new())); + + let local_light_wallet = self.wallet.clone(); + let local_bytes_downloaded = bytes_downloaded.clone(); + + let start_height = last_scanned_height + 1; + info!("Start height is {}", start_height); + + // Show updates only if we're syncing a lot of blocks + if print_updates && (latest_block - start_height) > 100 { + print!("Syncing {}/{}\r", start_height, latest_block); + io::stdout().flush().ok().expect("Could not flush stdout"); + } + + { + let mut status = self.sync_status.write().unwrap(); + status.is_syncing = true; + status.synced_blocks = start_height; + status.total_blocks = latest_block; + } + + // Fetch compact blocks + info!("Fetching blocks {}-{}", start_height, end_height); + + let all_txs = all_new_txs.clone(); + let block_times_inner = block_times.clone(); + + let last_invalid_height = Arc::new(AtomicI32::new(0)); + let last_invalid_height_inner = last_invalid_height.clone(); + let tpool = pool.clone(); + fetch_blocks_with_runtime(&mut rt, &self.get_server_uri(), start_height, end_height, self.config.no_cert_verification, pool.clone(), + move |encoded_block: &[u8], height: u64| { + // Process the block only if there were no previous errors + if last_invalid_height_inner.load(Ordering::SeqCst) > 0 { + return; + } + + // Parse the block and save it's time. We'll use this timestamp for + // transactions in this block that might belong to us. + let block: Result + = parse_from_bytes(encoded_block); + match block { + Ok(b) => { + block_times_inner.write().unwrap().insert(b.height, b.time); + }, + Err(_) => {} + } + + match local_light_wallet.read().unwrap().scan_block_with_pool(encoded_block, &tpool) { + Ok(block_txns) => { + // Add to global tx list + all_txs.write().unwrap().extend_from_slice(&block_txns.iter().map(|txid| (txid.clone(), height as i32)).collect::>()[..]); + }, + Err(invalid_height) => { + // Block at this height seems to be invalid, so invalidate up till that point + last_invalid_height_inner.store(invalid_height, Ordering::SeqCst); + } + }; + + local_bytes_downloaded.fetch_add(encoded_block.len(), Ordering::SeqCst); + })?; + + + { + + + let t = self.wallet.read().unwrap(); + let mut d = t.total_scan_duration.write().unwrap(); + d.clear(); + d.push(std::time::Duration::new(0, 0)); + } + + // Check if there was any invalid block, which means we might have to do a reorg + let invalid_height = last_invalid_height.load(Ordering::SeqCst); + if invalid_height > 0 { + total_reorg += self.wallet.read().unwrap().invalidate_block(invalid_height); + + warn!("Invalidated block at height {}. Total reorg is now {}", invalid_height, total_reorg); + } + + // Make sure we're not re-orging too much! + if total_reorg > (crate::lightwallet::MAX_REORG - 1) as u64 { + error!("Reorg has now exceeded {} blocks!", crate::lightwallet::MAX_REORG); + return Err(format!("Reorg has exceeded {} blocks. Aborting.", crate::lightwallet::MAX_REORG)); + } + + if invalid_height > 0 { + // Reset the scanning heights + last_scanned_height = (invalid_height - 1) as u64; + end_height = std::cmp::min(last_scanned_height + scan_batch_size, latest_block); + + warn!("Reorg: reset scanning from {} to {}", last_scanned_height, end_height); + + continue; + } + + // If it got here, that means the blocks are scanning properly now. + // So, reset the total_reorg + total_reorg = 0; + + // If this is the first pass after a retry, fetch older t address txids too, becuse + // they might have been missed last time. + let transparent_start_height = if pass == 1 && retry_count > 0 { + start_height - scan_batch_size + } else { + start_height + }; + + let no_cert = true; + + // We'll also fetch all the txids that our transparent addresses are involved with + self.scan_taddress_txids(&pool, block_times, transparent_start_height, end_height, no_cert)?; + + // Do block height accounting + last_scanned_height = end_height; + end_height = last_scanned_height + scan_batch_size; + + if last_scanned_height >= latest_block { + break; + } else if end_height > latest_block { + end_height = latest_block; + } + } + + if print_updates{ + println!(""); // New line to finish up the updates + } + + info!("Synced to {}, Downloaded {} kB", latest_block, bytes_downloaded.load(Ordering::SeqCst) / 1024); + { + let mut status = self.sync_status.write().unwrap(); + status.is_syncing = false; + status.synced_blocks = latest_block; + status.total_blocks = latest_block; + } + + // Get the Raw transaction for all the wallet transactions + + { + let decoy_txids = all_new_txs.read().unwrap(); + match self.scan_fill_fulltxs(&pool, decoy_txids.to_vec()) { + Ok(_) => Ok(object!{ + "result" => "success", + "latest_block" => latest_block, + "downloaded_bytes" => bytes_downloaded.load(Ordering::SeqCst) + }), + Err(e) => Err(format!("Error fetching all txns for memos: {}", e)) + } + } + } + fn scan_taddress_txids( + &self, + pool: &ThreadPool, + block_times: Arc>>, + start_height: u64, + end_height: u64, + no_cert: bool + ) -> Result, String> { + let addresses = self.wallet.read() + .map_err(|e| format!("Failed to read wallet: {:?}", e))? + .get_all_taddresses().iter() + .cloned() + .collect::>(); + + let (ctx, crx) = channel(); + let num_addresses = addresses.len(); + + for address in addresses { + let address_clone = address.clone(); + let wallet = self.wallet.clone(); + let pool = pool.clone(); + let server_uri = self.get_server_uri(); + let ctx = ctx.clone(); + let block_times = block_times.clone(); + + pool.execute(move || { + + let r = fetch_transparent_txids( + &server_uri, + address, + start_height, + end_height, + no_cert, + move |tx_bytes: &[u8], height: u64| { + let tx_result = Transaction::read(tx_bytes) + .map_err(|e| format!("Failed to read transaction: {:?}", e)); + + match tx_result { + Ok(tx) => { + let datetime_result = block_times.read() + .map_err(|e| format!("Failed to read block times: {:?}", e)) + .and_then(|bt| bt.get(&height).cloned().ok_or_else(|| format!("No datetime for height: {}", height))); + + match datetime_result { + Ok(datetime) => { + match wallet.read().map_err(|e| format!("Failed to read wallet: {:?}", e)) { + Ok(w) => { + w.scan_full_tx(&tx, height as i32, datetime as u64); + }, + Err(e) => { + error!("Error reading wallet: {}", e); + }, + } + }, + Err(e) => { + error!("Error processing transaction: {}", e); + }, + } + }, + Err(e) => { + error!("Error reading transaction: {}", e); + }, + } + } + ); + + match ctx.send(r) { + Ok(_) => info!("Successfully sent data for address: {}", address_clone), + Err(e) => error!("Failed to send data for address: {}: {:?}", address_clone, e), + } + }); + } + + crx.iter().take(num_addresses).collect() + } + + + fn scan_fill_fulltxs(&self, pool: &ThreadPool, decoy_txids: Vec<(TxId, i32)>) -> Result, String> { + + // We need to first copy over the Txids from the wallet struct, because + // we need to free the read lock from here (Because we'll self.wallet.txs later) + let mut txids_to_fetch: Vec<(TxId, i32)> = self.wallet.read().unwrap().txs.read().unwrap().values() + .filter(|wtx| wtx.full_tx_scanned == false) + .map(|wtx| (wtx.txid.clone(), wtx.block)) + .collect::>(); + + info!("Fetching {} new txids, total {} with decoy", txids_to_fetch.len(), decoy_txids.len()); + txids_to_fetch.extend_from_slice(&decoy_txids[..]); + txids_to_fetch.sort(); + txids_to_fetch.dedup(); + + let num_fetches = txids_to_fetch.len(); + let (ctx, crx) = channel(); + + // And go and fetch the txids, getting the full transaction, so we can + // read the memos + for (txid, height) in txids_to_fetch { + let light_wallet_clone = self.wallet.clone(); + + let pool = pool.clone(); + let server_uri = self.get_server_uri(); + let ctx = ctx.clone(); + let no_cert = self.config.no_cert_verification; + + pool.execute(move || { + //info!("Fetching full Tx: {}", txid); + + match fetch_full_tx(&server_uri, txid, no_cert) { + Ok(tx_bytes) => { + let tx = Transaction::read(&tx_bytes[..]).unwrap(); + + light_wallet_clone.read().unwrap().scan_full_tx(&tx, height, 0); + ctx.send(Ok(())).unwrap(); + }, + Err(e) => ctx.send(Err(e)).unwrap() + }; + }); + }; + + // Wait for all the fetches to finish. + crx.iter().take(num_fetches).collect::, String>>() + } + + pub fn do_send(&self, addrs: Vec<(&str, u64, Option)>, fee: &u64) -> Result { + if !self.wallet.read().unwrap().is_unlocked_for_spending() { + error!("Wallet is locked"); + return Err("Wallet is locked".to_string()); + } + + info!("Creating transaction"); + + let result = { + let _lock = self.sync_lock.lock().unwrap(); + + self.wallet.write().unwrap().send_to_address( + u32::from_str_radix(&self.config.consensus_branch_id, 16).unwrap(), + &self.sapling_spend, &self.sapling_output, + false, + addrs, + fee, + |txbytes| broadcast_raw_tx(&self.get_server_uri(), self.config.no_cert_verification, txbytes) + ) + }; + + result.map(|(txid, _)| txid) + } +} + +#[cfg(test)] +pub mod tests { + use lazy_static::lazy_static; + use tempdir::TempDir; + use super::{LightClient, LightClientConfig}; + + lazy_static!{ + static ref TEST_SEED: String = "youth strong sweet gorilla hammer unhappy congress stamp left stereo riot salute road tag clean toilet artefact fork certain leopard entire civil degree wonder".to_string(); + } + + #[test] + pub fn test_encrypt_decrypt() { + let lc = super::LightClient::unconnected(TEST_SEED.to_string(), None).unwrap(); + + assert!(!lc.do_export(None).is_err()); + assert!(!lc.do_new_address("zs").is_err()); + assert!(!lc.do_new_address("R").is_err()); + assert_eq!(lc.do_seed_phrase().unwrap()["seed"], TEST_SEED.to_string()); + + // Encrypt and Lock the wallet + lc.wallet.write().unwrap().encrypt("password".to_string()).unwrap(); + assert!(lc.do_export(None).is_err()); + assert!(lc.do_seed_phrase().is_err()); + assert!(lc.do_new_address("R").is_err()); + assert!(lc.do_new_address("zs").is_err()); + assert!(lc.do_send(vec![("zs", 0, None)]).is_err()); + + // Do a unlock, and make sure it all works now + lc.wallet.write().unwrap().unlock("password".to_string()).unwrap(); + assert!(!lc.do_export(None).is_err()); + assert!(!lc.do_seed_phrase().is_err()); + + // Can't add keys while unlocked but encrypted + assert!(lc.do_new_address("R").is_err()); + assert!(lc.do_new_address("zs").is_err()); + + // Remove encryption, which will allow adding + lc.wallet.write().unwrap().remove_encryption("password".to_string()).unwrap(); + + assert!(lc.do_new_address("R").is_ok()); + assert!(lc.do_new_address("zs").is_ok()); + } + + #[test] + pub fn test_addresses() { + let lc = super::LightClient::unconnected(TEST_SEED.to_string(), None).unwrap(); + + { + let addresses = lc.do_address(); + // When restoring from seed, there should be 5+1 addresses + assert_eq!(addresses["z_addresses"].len(), 51); + assert_eq!(addresses["t_addresses"].len(), 6); + } + + // Add new z and t addresses + + let taddr1 = lc.do_new_address("R").unwrap()[0].as_str().unwrap().to_string(); + let taddr2 = lc.do_new_address("R").unwrap()[0].as_str().unwrap().to_string(); + let zaddr1 = lc.do_new_address("zs").unwrap()[0].as_str().unwrap().to_string(); + let zaddr2 = lc.do_new_address("zs").unwrap()[0].as_str().unwrap().to_string(); + + let addresses = lc.do_address(); + + assert_eq!(addresses["z_addresses"].len(), 52); + assert_eq!(addresses["z_addresses"][49], zaddr1); + assert_eq!(addresses["z_addresses"][50], zaddr2); + + assert_eq!(addresses["t_addresses"].len(), 8); + assert_eq!(addresses["t_addresses"][6], taddr1); + assert_eq!(addresses["t_addresses"][7], taddr2); + + use std::sync::{Arc, RwLock, Mutex}; + use crate::lightclient::{WalletStatus, LightWallet}; + + // When creating a new wallet, there is only 1 address + let config = LightClientConfig::create_unconnected("test".to_string(), None); + let lc = LightClient { + wallet : Arc::new(RwLock::new(LightWallet::new(None, &config, 0).unwrap())), + config : config, + sapling_output : vec![], + sapling_spend : vec![], + sync_lock : Mutex::new(()), + sync_status : Arc::new(RwLock::new(WalletStatus::new())), + shutdown_flag : Arc::new(AtomicBool::new(false)), + }; + { + let addresses = lc.do_address(); + // New wallets have only 1 address + assert_eq!(addresses["z_addresses"].len(), 1); + assert_eq!(addresses["t_addresses"].len(), 1); + } +} + +#[test] +pub fn test_bad_import() { + let lc = super::LightClient::unconnected(TEST_SEED.to_string(), None).unwrap(); + + assert!(lc.do_import_sk("bad_priv_key".to_string(), 0).is_err()); + assert!(lc.do_import_vk("bad_view_key".to_string(), 0).is_err()); +} + + #[test] + pub fn test_wallet_creation() { + // Create a new tmp director + { + let tmp = TempDir::new("lctest").unwrap(); + let dir_name = tmp.path().to_str().map(|s| s.to_string()); + + // A lightclient to a new, empty directory works. + let config = LightClientConfig::create_unconnected("test".to_string(), dir_name); + let lc = LightClient::new(&config, 0).unwrap(); + let seed = lc.do_seed_phrase().unwrap()["seed"].as_str().unwrap().to_string(); + lc.do_save().unwrap(); + + // Doing another new will fail, because the wallet file now already exists + assert!(LightClient::new(&config, 0).is_err()); + + // new_from_phrase will not work either, again, because wallet file exists + assert!(LightClient::new_from_phrase(TEST_SEED.to_string(), &config, 0, false).is_err()); + + // Creating a lightclient to the same dir without a seed should re-read the same wallet + // file and therefore the same seed phrase + let lc2 = LightClient::read_from_disk(&config).unwrap(); + assert_eq!(seed, lc2.do_seed_phrase().unwrap()["seed"].as_str().unwrap().to_string()); + } + + // Now, get a new directory, and try to read from phrase + { + let tmp = TempDir::new("lctest").unwrap(); + let dir_name = tmp.path().to_str().map(|s| s.to_string()); + + let config = LightClientConfig::create_unconnected("test".to_string(), dir_name); + + // read_from_disk will fail, because the dir doesn't exist + assert!(LightClient::read_from_disk(&config).is_err()); + + // New from phrase should work becase a file doesn't exist already + let lc = LightClient::new_from_phrase(TEST_SEED.to_string(), &config, 0, false).unwrap(); + assert_eq!(TEST_SEED.to_string(), lc.do_seed_phrase().unwrap()["seed"].as_str().unwrap().to_string()); + lc.do_save().unwrap(); + + // Now a new will fail because wallet exists + assert!(LightClient::new(&config, 0).is_err()); + } + } + + #[test] + pub fn test_recover_seed() { + // Create a new tmp director + { + let tmp = TempDir::new("lctest").unwrap(); + let dir_name = tmp.path().to_str().map(|s| s.to_string()); + + // A lightclient to a new, empty directory works. + let config = LightClientConfig::create_unconnected("test".to_string(), dir_name); + let lc = LightClient::new(&config, 0).unwrap(); + let seed = lc.do_seed_phrase().unwrap()["seed"].as_str().unwrap().to_string(); + lc.do_save().unwrap(); + + assert_eq!(seed, LightClient::attempt_recover_seed(&config, None).unwrap()); + + // Now encrypt and save the file + let pwd = "password".to_string(); + lc.wallet.write().unwrap().encrypt(pwd.clone()).unwrap(); + + assert_eq!(seed, LightClient::attempt_recover_seed(&config, Some(pwd)).unwrap()); + } + } + +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient/checkpoints.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient/checkpoints.rs new file mode 100644 index 0000000..bf5741a --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightclient/checkpoints.rs @@ -0,0 +1,1021 @@ + +pub fn get_closest_checkpoint(chain_name: &str, height: u64) -> Option<(u64, &'static str, &'static str)> { + match chain_name { + "test" => get_test_checkpoint(height), + "main" => get_main_checkpoint(height), + _ => None + } +} + +fn get_test_checkpoint(height: u64) -> Option<(u64, &'static str, &'static str)> { + let checkpoints: Vec<(u64, &str, &str)> = vec![ + (130000, "000000001c4a5aa11e6c142931463fcf7a9f5b9fb41061d26c18ff1860431881", + "" + ), + (152000, "00000000d7c0f7d63b7e628cfcb9fdd45c9d2a244326532c61dfff7fb0d4af45", + "" + ) + ]; + + find_checkpoint(height, checkpoints) +} + + +fn get_main_checkpoint(height: u64) -> Option<(u64, &'static str, &'static str)> { + let checkpoints: Vec<(u64, &str, &str)> = vec![ + (10000,"0000f32ac2bd4581ef4b651f72b27d92dfca04d4dc721c505214f113e209b10e", + "01156306d59b6e6850ddd946ce81adf53bd2e288a2ac082c68b3a3c7387bfbea01000901653965032ba7ddd1566346e7fd672e5765c60ed5abc5760acf30910a22f4dc2000000125da72eca34fc7cca85fb32e47c92245b753da2394f48eee43f73a24cbb3835c018d356df2aa50ca4495a960a8fd6b3cdce03ecc2c900f452851acd11f2528b55a00000001e22eca8fa9487f1a8bbbbd1d668d725e1c1bc0412fefc54e1ca226babe46ba3a" + ), + (20000,"0001b4057aebdc835c63dfb581235d5917af697fff962ecfe47e8a26c0b3ac55", + "01e8ff3f439de481d31cd558cdad2e16663aa462c5146d5a340b9dfc7e020798030108ebe122d987c41e1b3d8f55378f901a8583d384e5ec9a3ed8ce1ca760cf674b0a00000000016d2eb30d3c419f209db42909a25933ce70cb826f64447a95e15f32c6400c6d490000000001c892813b23c165ed57803b8725fbd36752295b2360ef76db61371496e5998f42" + ), + (30000,"000075d2e08eafd62f4633cc1cabf281de17e7e0bba7965db75f3cf268b9a980", + "01590489b5bd1ffe63d443e4f48e2757e33d387b922159d2476e23552cd7e97e190164dda54d0d240349f08af49cf6a662e4f909b3a2103cba0abdd006d928e360440b000001213fc6a11d20b38ee7d7ca0e7af41c7e4b5211f49bc33e096678af89c5a21d2f0000000000000001846ad89a127e56e82977450ac16a47970a8b79c64679ec82316ded604911c73f" + ), + (40000,"00015fc2f6953e67578c6484c3a5342d883fd3ef051456edc55064e544f04df2", + "010bacbf6c0fceca1eed0360a4cf0b7844c6c3a388bf1ccd76749755b0e78b3c40000b0001677175093d864bdd3d67de7e60bf2455d83558c4b8e1a029b15f6690eaebac1400012bffc6e35ff265c55a850ae97c36e247c2e45a57bebeb83696c29013714c4f11013ae6744c0cd47154fe796d24c56d4dd34401824922b5794a9f088a99e0d32d31013046f9b62ff744d5c1461b25c6fc4ac7f901ca2f922442e4784f631b863bf63d0001ed227802f58cbf3148820a771fb5c94fd519a527aef5defe280962a00a3b2a03000001846ad89a127e56e82977450ac16a47970a8b79c64679ec82316ded604911c73f" + ), + (50000,"000207bddb3ebb7cb8000f84e63ae7be40e74dde52c8bfa4f0cc913b048e5767", + "01bb3d85b0591521d3b1a3f5dcdad69f7ed167bfd6848361f685bc479fc788951c000b01bf8ec332dec82368c8dc08264b2ea47748b5b2605bb32eee430de1d20315df0001392c11fb70d061a31347ea360bf3ebe9225df0ca1e2ecb0b0d7a5653217a286d01d6c270c6c895a9b5663d7245b98cad3ba0fff4a26c7aa6b9709bd11717249709019d566e3988577f9f395df5b0b9f2cfb1837eccbf98149e508abb172fa63ae042000128c3bbbc9ab11905d2a917d7004d713df507f118e796402bdf12b52f88824a270001a6d8c220c63e398f3e01408b4623b4d98fc6b42520b1daa8743f9370f398870a0180b14f4757e4908b5d9ff2efba400e767235afb5b1e92e20722672f1edccf1650001846ad89a127e56e82977450ac16a47970a8b79c64679ec82316ded604911c73f" + ), + (60000,"000071cd818c3fa9f155ec8ac65333f43e0d86b4f6ea98fc702ae0fe1ec3126f", + "01422baf876b52509244d102e30a620759f0b7fa9cc15942ac1f14e5269e1b2509015c6833b1afa46359710cae20e131a8f9077e16d7c20c980f58c5e40aa35a9c460b00000108bfa78dd11766120ab7724f82e1efd9dc90399c7e69ff55cf87d71b9c7eed19014009ed4b479e97234e70a9fd747c1a969cca8fe2685107e15eea55dfd818c74601d4bebff4602bac657eec7f20d5741c429b8777b76ea130b67b5fe634e6deb12f0001421a3686ae5cce2fc67090cf3a443ac6be26c5149ed0312d52e7b91d0ae33b140000014ac717db9bd5ba897696b84945f177789ab407666584792c9c8a266b4e9d376a01846ad89a127e56e82977450ac16a47970a8b79c64679ec82316ded604911c73f" + ), + (70000,"0000bbfd71d248074239456adf756eff007bcbe004b8c02eed8dd1b1656d9270", + "018fa96fbf4e52c5632f009cf036c173e2377191be52cb1892c3e850f82dbf7629000b01b46279a17bb4686f6b55d0772003d0ea902d93ea32c6107c6429c490ca4c7e1b018d995cb94ee123dc4d3581812ee491c6215bab98367d92ad3642b6f27dccd61e0158dac29b09d07c0a15940e48a3826d7ba69358588ca5ea48baf1a003418f9a2b01db2b63cbcf2cc75987ff7a4b9d325d7c5bf8e6b3ec91fadf0b14227112dedb0b0001007148751c52ebc015e5adde8ef32acda728f8272e0abfa7fb44dc2688c9a55d0001552704f277bee30cc463aa7502fe4832e53dd4758ced6bb0491926875995590c00014ac717db9bd5ba897696b84945f177789ab407666584792c9c8a266b4e9d376a01846ad89a127e56e82977450ac16a47970a8b79c64679ec82316ded604911c73f" + ), + (80000,"00001d995acdf7ac796829dcc869dae073258e5c608a8daa709b4c512c3a3343", + "01fc6aa0e42edef0b4bb7b201f1736755668e3aee22ed3188426b160b83b51f4270102422e7b5ff09f43cf6cefd018bab44607fcd2cb3a6c1fddce258974195f550c0b00019567034dfe51a3432e045f93f7e41f0b5ac60a091d5b597062229430ac983f2701a68aeaf01ab84e9fbc1ec9d36e684e0b3458adb3767cdfcb791f09aeaf6a3f6e01ac48e1c1930f73a81902dd06fe88081dd7bbca6b520936d97061492514dd8d3901e578312504c53914c21d29404639756ab12c0c7969288d0ae76ee5f8445121690001a2993398bc56e7bbf1269a338b8c704decddce9c4cc412fdb43750baeb37820301552704f277bee30cc463aa7502fe4832e53dd4758ced6bb0491926875995590c00014ac717db9bd5ba897696b84945f177789ab407666584792c9c8a266b4e9d376a01846ad89a127e56e82977450ac16a47970a8b79c64679ec82316ded604911c73f" + ), + (90000,"0000d841407e0ac42c190ca65319bf2c4ec056724c75774bbfaaaf28fc022fec", + "016bdfbacb257915548da7dc2c1537dd8667f7fa192817a4c6b8b6c6c24c641a4b000b00014f16ffc18a9cf071fda0ac612c9702196d5b7be45ff7c83495d694e6347e993f0104508cd0982b3695cf4a25a6bbf8c2e08fd863e8387c8630f5db704e6807c859011f602db3d408a80ab31a631ca3867a0010aa40b4c5845b80187ce18c7064dc38000000000119fc9f69d0e06115f46ee93daf6be191aa95e383c988e4c0c123574dc2e26614014ac717db9bd5ba897696b84945f177789ab407666584792c9c8a266b4e9d376a01846ad89a127e56e82977450ac16a47970a8b79c64679ec82316ded604911c73f" + ), + (100000,"00005af032270f04bc9be8072ee0b70820b88303dc25770a8dfb5c6f00341ae8", + "0144177b42c82bceec8874e4d706b1ea36fa1982f007782c0b6522f382ffe79c19014c5763e0948c7fcf2bb9fd8ad6e7eb5c03f0b36eb2779a68b0a5217327eb541d0b00011b47d0138b1ba4414bf0dd0c28c8f03a4a5a6921772ed01bd276df6b4e9fe21d0001855b9487f738e908ff30a22cb791e5216d344f3cc64471d08e6c92afcd8d8f07013dbf24f1ca27b6cdbe9aa06d0659161cd39fa1e603c089ce57657767bd481a45019255b37fab5960570693246f984e15d92e5871592b173bc6817b92edc3ddd53600000119fc9f69d0e06115f46ee93daf6be191aa95e383c988e4c0c123574dc2e26614014ac717db9bd5ba897696b84945f177789ab407666584792c9c8a266b4e9d376a01846ad89a127e56e82977450ac16a47970a8b79c64679ec82316ded604911c73f" + ), + (110000,"00007558a72be2b93ff9c32a3c8c8896f4fd7b8d59430a1917755412307bbcde", + "01969ee1ed1b74e6e82104035ed8221bd73e339cac192061a81e6b1aeae4b6f62f000b0155fd453523904f1c256f80a59bd879e7025b075eac26725fbdb4aff4f362dc320001e277e51fd6a308d57197fbb3974c1c2b96719aad6f7207feb7315484dbca9527000179073260de92c17379c9badfa67530b5b09fa61811b8fb184d9e8a5d9fef123201b96452551b60999a8017f6cfa93607d9c833dc7ff306899a136528f9f75b9229018d95a23b5d89fd59af5d1fa54d106909d1fdec3aa5d6f7175a9ccf906d35ec33000119fc9f69d0e06115f46ee93daf6be191aa95e383c988e4c0c123574dc2e26614014ac717db9bd5ba897696b84945f177789ab407666584792c9c8a266b4e9d376a01846ad89a127e56e82977450ac16a47970a8b79c64679ec82316ded604911c73f" + ), + (120000,"000159aac0c2854248eeaa7717b5f9defed70ec7eb56225f74650e2e4dd14ab0", + "0119c8fcf468478c496fe10d63c7c9db5b527f689560c04f590046aee7ae8cd56b014565795bf698e0ce31f33190933714c25869fa83dccc1cf409944c1731b6922c0c013eba7113e0993ceddeff4a16c0820528457569e5b6e03ba552fc0820ce1f5b4b0133381e95c597d5668f6e70e88a3b75f9f5afcef9a7295489c6c2f450dce87e1e017c19cd5cbc6392238eb5cdefd256c89f54411984f67d99adf6ae00391edb3207000000014a37a25dfce543bd56935536191e2d19125c45609190012afd069e3ce99b1346019b5c1c8d37532c8fa2732a7a76355e544cbbd82038a87c5c8975d1b3bb7fb935000000018bba0174a21e825ffe20efdc0a07ca2966230803a2f19bc4eb10aaa31c020737" + ), + (130000,"0000674dcced99276e684e9ac19eb2412e55c968a2bb524069384647cb45eefc", + "011ed4cba814716d106819847e7a3970c5819f50794d6759f4abcafeaf4ce7e90b0109818b40f2a4230396005e5e080f29a9abc02bd16061241f9edb27759e8c122e0c01c09d0e3ac8497eaefd8db6189020ef27053c20d8a4669897a6440ced3dfdeb450001949b57003d64583225a6736638b73e8b9fb250310fed2e92b0babb687340d36f01044ab7d8c84db567b90c55f8660dd341cd45931b075a7a79e4851a7523c35b3f01da406aec7631057b3caeb014aebdf442961385dac0b82fe028674528c6e4b86b00014a37a25dfce543bd56935536191e2d19125c45609190012afd069e3ce99b1346019b5c1c8d37532c8fa2732a7a76355e544cbbd82038a87c5c8975d1b3bb7fb935000000018bba0174a21e825ffe20efdc0a07ca2966230803a2f19bc4eb10aaa31c020737" + ), + (140000,"00012475b06db9b0c4befe66ed3de054b993bc787092488df2383c566155921e", + "01845cfab464e705256ac4d3cb23727768fd9e3f329a8cfffa5711b8f218cff259000c01a8b6b4305f80d8539808c1b7d27fe73a235e15d3c1dee76d7e68d8ebfa55bf55000105e2825b766a60c9886ac52fabfadd42f3ae95cc25061c4e2701300ecb78652800000114c71675e4c10aeb5547443ec6fab0d5c607fe3acc7202a26adfcd2830f24f20014a37a25dfce543bd56935536191e2d19125c45609190012afd069e3ce99b1346019b5c1c8d37532c8fa2732a7a76355e544cbbd82038a87c5c8975d1b3bb7fb935000000018bba0174a21e825ffe20efdc0a07ca2966230803a2f19bc4eb10aaa31c020737" + ), + (150000,"0000da3a58875dc171e5bb9747cff5dfa2880d39e244d3f10acccf5232aac81b", + "0114c80c4346048ddb45894455f00fa84d239a8f37ad0373e484c49ccf1d6fb422000c0000000001e88d279dd721de8f92d068059c4cc07edc407810b3252f2d4009699844d56a66010c27040366c5c605c174f87f6af2901f46c7bf8d023cd7c9d5393b44626d721a01e78d35157124eea354c8522174894391b100d2957d22fda73296443c9a04ec3900017c1ec396216ce2f904ef16d602711a59a0b4cb9c8d46cc6800249292a93df9300000018bba0174a21e825ffe20efdc0a07ca2966230803a2f19bc4eb10aaa31c020737" + ), + (160000,"0000419b54cbe8a842b92201996bd74382b63db7ab0b1989b932ecb19bdd6f4a", + "01473cc24d339eff03e4dcbcea8d3be15b7ef6cba9d799d7b409b82f3b7bb61e71000c00000000000182e8e6a9012f29cf442781ceba9abb8c212ce15555286058985475281541c8080001af319d6c24f7c64620c4eda81316530c970e1ef5153bb8a0e6ab489bea05b010017c1ec396216ce2f904ef16d602711a59a0b4cb9c8d46cc6800249292a93df9300000018bba0174a21e825ffe20efdc0a07ca2966230803a2f19bc4eb10aaa31c020737" + ), + (170000,"0000f8e02172873f12b2b1adf8709b5d1990ef64b674fb0e5ed1ea08db9b6a48", + "01bf5a074718b31acb6064174f216409ddf6c6d3f47aef90b1bd33beb4fede5f21000c015b26175f27accb988abe1a76af2661b151d75f53f8c47e1b68885fed4605192501761ce66d3e582757be9eb7ef7b78caaf0bcc7acaf4b63d3be8b8979eec74791101dc4a4c740fff5a1f99440fa599f2f400bdca79c93e614a0beb20d50c5af53c2200014982a12f32279e4868ba4d34135ead1bdf0030bf66d0fbe968a7de73b3fa3a4101808ea84e6b83ff8ea8c7b07411f967f1892537a23c44098b878264a1205d454300000001dfe16a1c138863d27ee0494b40d8de6e8c028be9b2c226e14ba8e1bedd70f73800018bba0174a21e825ffe20efdc0a07ca2966230803a2f19bc4eb10aaa31c020737" + ), + (180000,"000131b050c745484f85e2c8422bf4624f23c69969d1c7d2f6760ef62a864746", + "01043ca9cda76c368628a8e1d52a4777e56eb89d6adbec5a79025da12c67b30b13014efb2db195855089b1c6b435fa5bf9de25d278f27b0d9b5cbaa07dc01de388730c00000001a38d03fe8a873e91be7ae426550d3848142dae4a52ad097135d0620e425dff3201a56f1042091f1f1724ea2ce3d02db5a6c38dcdd6e68a31ec83193661add58d09016280e7c096b4615ba3d1086b1a3e1073450d4698cdaa4c66d4fffd2a36918c18000197d9c25daf32651d062f77d2cfe87df0e399407bd5f7543b584293cd80cd97310001dfe16a1c138863d27ee0494b40d8de6e8c028be9b2c226e14ba8e1bedd70f73800018bba0174a21e825ffe20efdc0a07ca2966230803a2f19bc4eb10aaa31c020737" + ), + (190000,"0000572751fda233f466c6ad237184ff6a7a15f8d84e5471e3dc5288ad74f592", + "01bae44425a3b7ceb9628402c4890eb46adc4c0a4c3b4e836d99b927e90dce042201c0384e4975577e631ace703dfec392429f124b006df895340e5d6cf9eed66e000c01f73380e29b29657ea18876f4cde70f4a2c68124a0f534a6cc0baf21b984ea8300001227d2f613a1a5964594717cb242c39da21e6f1506bdf7f2bfbc0d40ec335d21500000001217891b1e1825819c9ccfdba665c75ff9683379312d2161f2a00bda51af3dd390001591b7ec7928b25d0a8b42b36e90b409f649880d31509b5dcfb6edd5a6790b63d0001150347c4a3cdcf3a73f4279a78d2533928f214e97b36c0c4622046ff8f9fd760018bba0174a21e825ffe20efdc0a07ca2966230803a2f19bc4eb10aaa31c020737" + ), + (200000,"00009ca9950079d5cd882b615fc068df25beed137642b751b224d6a939a87c69", + "010016ef8fe3a2fa29c3b1ccbc1142df525909b3988fa28795afa36e19dd3d532e01ac7bdc40f8d39c58456360580f1babdd37324f6229e27397461ddd032cb86c5d0c0170d5ad64d15717ad48bcb7bb5526790550e628e78f3fbf333e7d942b622e0c2a0001025118593289a3e0e4d87c71f5319ecc03c957710f1abcf90a7567f986c3fa0c01dd246555537b3306ccfe29b73502818aa91a8c097565ad0c4cbd28e0342ff42f0000017d5818ef618d70be4d255491b2b575feb6d08a84b1a6ab70ba45da552571ad4000015543ea71e269d2db346f25a02bb9a0aa10131884af122e7cac092aa685dea86501c6e19b457b5760627fa66be699575ffcb0f20ac73c29eecad7c4ffe6ea98a20001150347c4a3cdcf3a73f4279a78d2533928f214e97b36c0c4622046ff8f9fd760018bba0174a21e825ffe20efdc0a07ca2966230803a2f19bc4eb10aaa31c020737" + ), + (210000,"000097146b9280bac0344dc886cc4c77141b7467e2ef68e657525d789a9cdcc1", + "01ef6fb489d5ab6ec15a1978ee0c52b005073563f0db8661abab2389fd34b32c38000d010561aa1c66be2bb1c8651ee1fc2d52530115139dc62e0355bba10a8801ce0b400000014fca6b0877e5b98df46f89c5e0e83c420c78ac980621446580d21784799d8f250001870f2155d5bf135f229539394a3448183b727c61c648e016562e339a66a1964c01975816ecf87141c3343856744e0afa2ed1e15c532b050f2073eded688afa8f0d0001e73913b3b90d2bea1bc26427cb528687e5f963087b90038d901178b87a5b162d00000001234be37bd6058b0555f191a003ec4b28b47d355dce97d1f67adaf96056b81370" + ), + (220000,"00010f0dfb2aa26bcd90f8769709a91e0948c03b56ca24740a94f59d5822a815", + "0129f7aec27589a8eadef35292f8e1659237096724574ef8f2c91110cdb0a41a5301ec999a45360425447ffb0c76c3af1e8d5c24d88887e1d9e1990bab70a501b4530d01ab334169cd79436c2a7e4ae3bc41e34d3e011a58c8b9dad9d279fa4a97de3b6a0000019b0d21a61f8eca3f858735d5a6a130f5da2fd66529c3df195fc5fe866b2a9850000109b6a36cf5b782ce4028ab00e800525fee0d91de24891c3b4c085e412ca2773a000100fae69ad4fb2a9bce86b31fc0d18cd06b990830d95c874dd138355c20b3ad4e00000108bcc4aa8091a5f730c1363b23a0d9ec34b7bfbcde857a321b88ac4530e0bc670001234be37bd6058b0555f191a003ec4b28b47d355dce97d1f67adaf96056b81370" + ), + (230000,"0000ca632b73f5b1ca9d60ede2010290712fc2f2d28d08ecb31b0affc5b8284f", + "014005e6f3cb4b27efe0a30a310b98ee999a979b66a3587803d21619d49c62672301f294872824774227cd60a4d4b295844862d10f25ed6b200eac133ae1b8129e1a0d0188b8c8488e67ee28452c5bc2badd78ac6ade77820be99ecbbea72f1876d74e1201e995f0668a55a8f24bd212fa23b0bd4411f4accabfb54ef29f5b20df4f77c65f000001525346801ecd3ca156015507b6762f4084d0ae039f4a0fe97a3e0fdc67d48c35019057ddce2f06bc4f861463a56f96f848c675ca1e8df8a0d2b7ee8c11a2a4b716013d170baf41d6b11ea77e32b131c570f651c4e06c22e94d21cba6b35ae23541350001afb485fb6095519d2c8590937912d34fe0de960d5cacfb8d2de9ffb803c99c3d000108bcc4aa8091a5f730c1363b23a0d9ec34b7bfbcde857a321b88ac4530e0bc670001234be37bd6058b0555f191a003ec4b28b47d355dce97d1f67adaf96056b81370" + ), + (240000,"00004827b75431d480812d39ca8e0925e44889c18b08778b2171c78aacc800f9", + "012b4df9aa042ede81772e1bc81eb306c6235f6c564f7caa726927f06465eb6759014de2279f173cfe1a41fe4ffd2725286f3779bec5c49b2efb0e2261cea909ac720d00014cf7bc0475ea37a002777f224cbe600e50aac42c05eb26516d1bfac00d9783710000014d5367463c39a7d6e153ff79a571815b05f79753ef8c931015ca70d5e80069390001d5e572cda2af802a9c2003404dc591b86777c1ff18e7ce9443f652ece6be9d730000017c821eb898cf379dacbfae7e81ede793e1535ea7940d0171233e6a323906cf710108bcc4aa8091a5f730c1363b23a0d9ec34b7bfbcde857a321b88ac4530e0bc670001234be37bd6058b0555f191a003ec4b28b47d355dce97d1f67adaf96056b81370" + ), + (250000,"000090c5798405d73f400657dcc5685172c6b2608809a9ec2e82297716e5f5cd", + "017d683bbb9d2477dd0a2fea27bf8180537fd03d70039b3e801d2a3e4683d36b4f000d0001a97deb920d48e635ec3656b9de17974fc513627069a7609ff7c89d7edea12e4c0000017bcb3affc40348b04b4825bac5994ac90ae215ae1909369e78fdc9630ff27d340183252314d286fa0d904c02d6b142e40d137fd545b5637247a094f7070efd403a0001bb1a6b17e577f412c3e925df7f92016d0dc8cb22c8960296a321ab304b65b62301ec35fa68c2c978d6e6816ec206d584fed1064901f44274a236db181852a3b73c000001548702442421de0cbebf296b7acb87a12086b172d510e901cfb90dfb7bb9f42c01234be37bd6058b0555f191a003ec4b28b47d355dce97d1f67adaf96056b81370" + ), + (260000,"0000263c2baec9d5575580a30787d4893c1517b64ff7294e41f2efb16d21ef85", + "01c5fbafc8f775d06f1d67438c0caaba88d4a30b28570e23cdb2611d2787e0ed3f000d0001a2f0a0dcd330742e7f0d53483d1f6c29caef6a1002793ad26f96f957e8f23845013d0fb89f22478aac8a3933c506c51b57a75aa7a30c0a70397b0c583068af9359012432b20d9bfbe9d50a78b10a10de3cb41ef3c694e16b26a529086d08a5c01301012613192935098ce0d98cb89fc68d884f018a98761c3f3edf62ce9b2657823c1a00000192659206c1d360f0106c812ecc1751b1a6e78004dbd70a9e4584c108ce67cb600000012e9b5648f78fecf40cc5a2518c4d871458a70f1688ca1eaa8a809ddaaebc505301548702442421de0cbebf296b7acb87a12086b172d510e901cfb90dfb7bb9f42c01234be37bd6058b0555f191a003ec4b28b47d355dce97d1f67adaf96056b81370" + ), + (270000,"000027590b1638a77fbcf91aa46bb6fe044bf24a63a79ad6cc74b46d2318368c", + "01c47f10639139b5cfdb2620cc69c623edfb93f53f09597fe1fde67e7cea97a715010be939026b9d648de19fb86b2aeebb4cf8c5d97d8194faeb47cd2e418824bd3b0e0001d4a87cf78f37c761d5721ead090074742a8db2e7f08b79264ec2d7ed8b78b4520001eba7ed681e5396dac7e4e0e7224262badb0e137035bda166df50d68fb7ce4a3a0143278a079bc1fd9891637337f2c1d69a1ac0ca2ec033bf9b34971c4ea171d04e01d26fa7cea0930689733647b448fa66301b912426654d580a87aeecbec3f288100001f642ab36442805ffaaf33f84804a4974c465cf6ce9047d11c8df6f320c22f1060000014a74da6328d128ef6a6373b45ae1f30f7ea50b5cc7c8d5047deea8dba306bb0a000001d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (280000,"000041384c4c769143b1501c7c6f892a4ae5e295ca471499e08c8299f5162099", + "0196697b241dfe52a06871f3596f9772e46e7a0806f3ceda01d8d895f6dba2423601d0941320831d40260b45dea9c3a72b903cb847ff775dec28140c7c7bed78306f0e019f520afca6ed300fc6d8e5a31d4eecf4971a5dbf30ab8261a71cd6949b147628000001b2d4cb79fa7d98ac9a575fd0a1326a398acc8fd0574320b23f2b94047bd25d34000000000127d878c5dd1c01c5a60eef9e3474a712bcb64bb299ed7a5cde5d946d9b52e3410150189ddcd6bb10b8891ce5c5d995257d59d0bb7bee6cb454d99cc2076ff07a73014a74da6328d128ef6a6373b45ae1f30f7ea50b5cc7c8d5047deea8dba306bb0a000001d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (290000,"00004240e011ef34f0b6015698b4baa2728b2885a69284afab0424fc6b09877d", + "01f4c50df250832efcb2e05b378f4a31cad8f187dc4b1a3f47b9522a84c3dff55001bce0da0b6d70a3ead877d309b62299da2b269ec2ab7d2f3003b2819d1f9e4b650e0001691d648bbcd17ab4ae9ade57386c089568c862e65833d2defd9a1bbac0a0bd2e00014f69974fc9672930ebe9ee073c93f81e495841ae7f00ca6e02319ca917c18209000000019eb9d07b0e24ac5a41ebf6b23021beea00386d6b27b8542f5983a7bba19b8f50000123fa7417067dc33e1e165367e025909a1d65fcad586a1cb64c703ed49ab2a10c0001b07968261cfe63f45f0b22c4a59d5dc6b61186eaac6d86cb476e8b0ab1ffc3090001d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (300000,"00000c72234e026b3ac4be4a972889fe48810cb27d551058b77d4c5e9b32575c", + "01ef41ca7c9423b9b723701c71b33c9116016bed819ab04aab12bb5727feff4d3101eb0929d5c298f44241ca7e71202be712c94cf2cccd46afb875887300667fdf0d0e0156cefe9e5e0e45ae51df6f2368dbe06acb808b9562261dc58ab7f91ea780a92c01ccadca43623e3feb6bceffa12bdaab7cc7f050e569419ac9c51fec147f2c6c720196ed23faa5e608d1ebf93cf0d51e7419f1e131aab2faea7d8a6cf6e3c4e18e6f00000001c0ab8f835b50e2327831297ebde69b6eaf35f918250a7e79a5c2d318528c191401b48bb482676fd7b695991c80fb3a03cec28413b236b762f54dd31bcdfb7c2b650000011fd4c27ee4b11fb31f34b248a56188e15371090f1fe85c92092bfc4cfdcff73f01b07968261cfe63f45f0b22c4a59d5dc6b61186eaac6d86cb476e8b0ab1ffc3090001d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (310000,"0000765eb51eaa30265211672e8311dd40e265ad4142f4a03efd2f4089d9e186", + "01c6f616ec002388e9d97da12ec7083bb9ef87ff683858fd96716234d191921d08000e01d691a145d3d8c5cd860191b727762f969eff8bfa4e2820860f260ac1183ad46701c2028fa4471376082dfdfecfa3313015b9fd51fdcffea50c45f999440588de39010122ca54fa7d2cab6956dcf93ca4472e0e169d3321a610df668d7c457fc8546e0160f94b5859407163c60ad308da9c623afc885b28af1bf07c5ba5f55721876a14000000011312f4baa39b132f1dfd45eaa93ee19b634f84329d8c5ff37147ed47d894076c01ec37d599691d6dd4f0d4652965040e94aef2c5fe5563bfd757c2324bf60b555a0111ca21239e97c2f48fd687fa43f1ebef7a53bec7b7665bb13ae24f7459eef318011fd4c27ee4b11fb31f34b248a56188e15371090f1fe85c92092bfc4cfdcff73f01b07968261cfe63f45f0b22c4a59d5dc6b61186eaac6d86cb476e8b0ab1ffc3090001d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (320000,"000023b752e4c077f231d05026220244ca10a603324035b885e1b0dd6443c148", + "015cbe7ba68e248071af2a43eadc9eab332763a3aaf582910167c172a74e3b301d013bdda1f116ec7d7814477dac430161c3f83dcf02d2385fcb0b118da1c65f773a0e0001cd648261d4cea6b8bc979d1c813c3a8b88760ef09a476742ac5a6a345fcf735401ca916765aae63b3603c868ba24ec8ede176b78f5d279b2a8c052e1dbfec4b94a00013cfd9a47d8db85a7749271d89bb79f1faa5d9b2eec11e444c4962ef7931b655500015b2eb81dcfb58b1d0e6c6ce81ba65e8c9205876fcd93b72e02eb06f786cfed4f000001ae3a75db06e9be78ea3f3b44e32c4fb4ba19e8577c859058529af2cd0617382f000001957c61c63888002f9724392201449d6cf8e2cdb6ea6effe2d62542ea2e5b3f6301d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (330000,"000069d6c21607c368128d4586255f315c55380759a766f961acc35a2e9cb166", + "01bf6e5e366351752ce3af48125e268a152e79355de46719248ced04ceb6cea97301c794e424270a4cf8580f3c7331c468f0dc2ab8886956106201feace6585f056b0e0001b8267b38707225543bed02c1b6f4eb828aa2a13f647130ce29423cb88ccea6730127184874c3a7496ac5ed8d18a0b602f993d61e79a2258a973df71f05f39f1d1401d56dbd037b9828cc96bd2f7b7309cafbb819d35cca70068f2e960b9a2ecbbb2f019b9d5a7fb060652b6c5fec2955c90d647f05bc1ee5a6736470bf92a44e82e5130000017ec2ec25b2e7e4437c9389e12df7983df91796898e93e2cd05b58ffaa7be380b0151111ae130ea97bdf7df169de7b42fc290af200c62d8e61d273bd1836a983d1601ae3a75db06e9be78ea3f3b44e32c4fb4ba19e8577c859058529af2cd0617382f000001957c61c63888002f9724392201449d6cf8e2cdb6ea6effe2d62542ea2e5b3f6301d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (340000,"00002d001410cef77057d66b49c1949ca1367e11297a595b13f17ff1676e55e9", + "01d9ef7ff0e8d21f9b618fe0c3f778b0e1b1ab2331d9ea42baa15ff35ca55b7c1c0140f9a811a87014edf9e0a4426377819a09632ba1e3a5bfbab145926b63dd865c0e0001512a0f3fa4fc3bac3c1f41c2477cf0428cf5400fccd38640728d692d92d4b12401b554328b2443517eba6c131979344d23f83250c40b03f012c64dd42b51c7ea24000000000127bbd95bc26a354d91d42220b8846591c2ff2e7cd7ef6e009b3c5ec36906a90300012a61fa759d7652ee59932cbb0c4d87eb0a1b3aea52af79ffd98f05c6e3030513015f2d031592ca5289235afb50ebb1fa6121a28d54cf15a8de9399e7e0d3c7c0450001957c61c63888002f9724392201449d6cf8e2cdb6ea6effe2d62542ea2e5b3f6301d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (350000,"00005953abe841392b72e8fab12e3cbceb18c128f73e1aa4406defd18543fe4c", + "01588e71eb8f6e3c63e88e121054027e1e4bdc09b5187094f88c616858a2bcf31201ffbe41d5f7c66baab3d8e1bb0663f7b284846826aac6afe1a533afd67fbed23b0e0000011329b28648e2451175c4ad860ce40ffa7c72dfb928d71fa92c135f92216b2c6d0001bb914de8bd862f77ffdcf24b3eea1f47fe9ff566e3922e17ab36c680a4542214015d6ff4934ed4472d9a5772a572e4c2fbf462fd9468b8ebaa3a311f14c4cffb5701b2ab7011a6a3bf6da0956d89a706a657490f5833c0758a01bac12a812172aa330192b1f01f031cf56fd339c5c3f8e559de91be932823cdfb36646c923c3aa8aa2c011db1c92772e10170b0bd80180d07f39cca598bbf76670c164d18cc7afe776300012a61fa759d7652ee59932cbb0c4d87eb0a1b3aea52af79ffd98f05c6e3030513015f2d031592ca5289235afb50ebb1fa6121a28d54cf15a8de9399e7e0d3c7c0450001957c61c63888002f9724392201449d6cf8e2cdb6ea6effe2d62542ea2e5b3f6301d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (360000,"00000f11a3a78c71d8a7c722cfb52f51006b3656b707cf94c0b08a80c4fd4dc9", + "014fa98adcc8b8969891f0b4f9be97a83b23ce6aadec3b4ffc7e69f6677014884d014f346ea0f5890caff4c93050952d5966a1893a10aad06164c3530db188382c480e00015cdb9137e384d2686096a34e444f9178a008a9de7021be7c85de6752cdfb20480001b16d60ce82fc40c9f8887ded9272133bb26cc530a4c79894dede430bafb765020001c421cb3fb379c4bb29ee42a9128f0a18eadc5ce5be3c6cfef2d5df5fa2d7041501d7aa0d5d5f7b28f2abdb1d5b0bb7e888db7a3cc30e41e3c24346edc22f17ff1600015bbb5996b597d4e4fd937e4fd2303353d34033b684f8d7e34658a7077d728725011ea54bcccea7564951f1efa8f445c7508b8f1536ed22c03dbdf12090c9146440000122828664786acb5a974c20352687b6e98e5bcb350a3f00b82c2c6a15b195874201957c61c63888002f9724392201449d6cf8e2cdb6ea6effe2d62542ea2e5b3f6301d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (370000,"0000810057e4efa124db97d9433372af35b93ae61ea4b620f7ac3ac26658a5aa", + "018f16d67d9b0ba505cd4bb8a883ea1039c336056b583dd4b044b45e553510c429018edad308f824774ae4f866c6bd1f5fcac582b0ee05dd897a1591f247c5688b100e000001fb7fcbd59127ef14234a648d1fe1da144433d991303b58d241c0ddc94b6ba91a0000000001c70f3e9d1095627cffcab00e25862ea53f3ae2ac7c64b401ad1d733129b66f3b0118abf566238771143bda4e103f214d68ac489246f55c40fc3e290277a7ac7105000101bbc5a1dd6d6262c4285f161273d326b7f3f642c11438c51797660a4ae663670122828664786acb5a974c20352687b6e98e5bcb350a3f00b82c2c6a15b195874201957c61c63888002f9724392201449d6cf8e2cdb6ea6effe2d62542ea2e5b3f6301d46f3fd17f4ae147d36986791cd7123aeeab230cccc9885cd5b7f642a919b910" + ), + (380000,"000046b214d061a9c237e42bf42493f9792cf80b95f89ef10f432cacee23eba6", + "0133b195afb4f104ccfe8fdabdecd435289188a8e4958d5e9c6347d6502caa1002019b13d871a0c44f6fc9afbbe5a3e7171cd1df22c79d2725c781cc18b28824ca060f00014e2e4849da6453686fbe1de1810dc39cd016e4941b8487b372f49dd9abfb8b0701181bb07b7f29c2aca5fdec2c8ab0c97da68ac3c723943f36f45d654089e1136501876f7b75142a43240c47b4b062803ceb3fcfa93cb136d6d3907b2f0ceb47d81e01973901d908e77491316a6f1d82a40cdd7de8bd9b309bb50fc70b523793d738090189f41f524f1ffc804d4c1dd1c040834d10e5af0293484d8d895cbcda24abf24801c01be84ebf207b14947092d7b162c6f4eff383f35a93ef7fa67e6cb7205e135900000001ef5b6c57a4bfdeb3c395a174932f49bb8d409529cd9fc9f8cf48cfbeca09786e00000001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (390000,"00001dce3a9cdd42f97d3268d29bdad179375f61f7d4e729919450996917c583", + "018798bc605b4b9456a24bd91ae93adcbddbf3c19d7cf5ed6747f6e58ff7614d47000f0118e01bbd7fc033c26d35be53817aef214fd11771474bd29cd73bcd60ab8d48300174223f909ec8806518281e9c27e1083de405f59e720ec5cdce169553ce8ca34a01e043261b85969355e66e34d0a12e059c40da44f13b717a74ce53025b429b480a01cffcce60f2789bb05dfbeeb4c28a54c411d1296b362641b992a48e2477218a6501c1d0e37bdc87c7238fe81da0c8a3186f9e861f3eacd790807464ccba5f291c2f012786d933c253fd2036be2eec6da6882eca2f00ef17086520efad5f99ede9513301ced529d4d59794f7a71e2902ab3fe213f073b8e1db8f64e33ac47a3f833b5d1f0001bf90ff52687de588b9f60da50c81589861600f16f53b9ccc3e1aa14b6f89fa610001ef5b6c57a4bfdeb3c395a174932f49bb8d409529cd9fc9f8cf48cfbeca09786e00000001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (400000,"000023fc6e679339fa72280568217dee1ea0c26790c13f80d0092f9b12ed5f38", + "01f12389e6884a152b7d15ec45cc288301dc79c1baa0bec36e8fb220143fef3a5201ffebe266530756914610539486674ac73480bc48d3f4eff479cf89c02ae514490f0140740ef9c30b06f27470234190ab5b7abe30e618f54f34bdc59360fbb203371a00015a535b3ebdf380a9b271a77ef5026ffd3ece2477cbb98ee0ecf731353e18ae7100012e0905ffd89c8b95abad0b6253c1808fc5652a8eee32792804c029a55db18728000001ae011842bd244efc8658c4c65505588278ed1e63b8470a07849384c0a22f8c4601192d9b999b4c377a694de00479aceb169ab85b7eec16d9273573f8b08f62f16c01d963aad1b80b976c8d64f84fad87dd9ebfd2386644d631da7fbce707f8db5d1701ef5b6c57a4bfdeb3c395a174932f49bb8d409529cd9fc9f8cf48cfbeca09786e00000001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (410000,"00007b63162192a0b9c1015a227df9fe264591b5842aab61a637c06768a5e3d7", + "014364f58b64f1d35c01b6d7b8855cde64620f29c8960d8c45830aa4f4d851492401258c6393c79247c8e92fb4dd80f1dcb8523f16d01c89a5f9245f4ce03964f2460f01bc50e6216e41031ce3bc40a1fb6cbe93dbce9404344699a01533be40d9ab744e01a4aab6880414232e5d17ea625de2ae76e2e68483c89da98946d58a5f012341180000000151878c3920ccddb3b99f190001bec220a4115572b6ca9e700123f5df0ebcf25200000102f9994d03c8553e27d0dd3e0e18730bb3cbcb84c28e3b6abf9d5bc403b8620b01ac81d64c5c254ab0772928be582c3829742e7f889d681f88684c273491b69356000167d3c9e2c7df30fa4ac5a2fc8d39ac329202f7af6e3da38b7abfe5984f09c753000001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (420000,"000040207b5dc0f5e2509b1216b549672c58d68bac8224f67492aa803b2f2570", + "01158605d7c890f68562d3ae98cd0a520641530d12bc853537b1dd27b1dbea076b016e424ac40aef7ffe6d23a96f2ab1ebde9dcc7ffc436e5faa54ce9ffd916a8a660f000149c180c0f3fa212fed270b5dd292ee2469e340c365579a3c63da1f7ffba3194d00000001e057bb97d20840ebdf1faeb82c94a870beab73415ba92db78665c12332871d24014e5a87ebae3e05bc2e0223f7d5e552025927c674a653358ea83dfdc9cc305551000121f92946588f6c393c9ea89aedd165982aac8cd17da9bf700206f70d85bb5e6e000117c95324e5cb6fb3c0625dc6fbb3cafeb59b6344123bed28eba831f01c124d640167d3c9e2c7df30fa4ac5a2fc8d39ac329202f7af6e3da38b7abfe5984f09c753000001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (430000,"0000305e76e05f96396d96187e6d35e900a4d735aeee6f105168eb45846d7055", + "0163e7f723ff1235cca2d4c3e9617b2739cf9f65bb8f4290b9ff46e299972d6733010ad9c4c9541c55f24e87afd5a3a6f61c9672b4e606b69b60f185525196beed200f0000015b6cb6de26d36d8b1b3882d1637339b55459cd6cdd4430c7a7c28cb72394b42a0000019a0f109f31261b1843b44e39db21d4a965dbb4117cfcecb2b21ef233ad307d2200011b7797bb918f8b35a3ee95a2215b09b110a195a1fc9179a56ed7ab7b2a5ba8260001656ded6a93231d4513eae7801afcb0b3b6195400559b14c0607020e698097d6a0117c95324e5cb6fb3c0625dc6fbb3cafeb59b6344123bed28eba831f01c124d640167d3c9e2c7df30fa4ac5a2fc8d39ac329202f7af6e3da38b7abfe5984f09c753000001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (440000,"000036b5b2b13908454ede21c5826af6331b728a66da7fc67bfedca910ff9b80", + "015dbe801954ab2e92af927b6b00e9abe7d13f81085122baff1d08545d0efc943001eed2d5e617b586382b04f54770d1c5e89c91bb70e420e61bb71abf439a73c9640f01c8ab5eca64a8779774aacb15511f4cdc45e5533745ee9d7f96242fbc7cb9716c01845e0b2919d2f412511b705e95f956fa60b706a5163b62fb8aadf1a2d81e261d011fb47ebb6ef2a866e730cba011d5c575e7d90605ef7aede99d4922e725232a1401e4fe317ba82d4850f31aa3f345cfa2dacaf55dce89e4494c8db312553019aa66018a849f9e947c29d8489958260d4f32c89308234fa3d4f28d30bd9313e44c3f6d000001e26ea142503feced4ddb8aa1195b300cb58d68187c5d289248570b02e7704d3f01d3ac27f170119e03108fb3fff4531776edbc516ba611e7217cc849cd3d49863d01656ded6a93231d4513eae7801afcb0b3b6195400559b14c0607020e698097d6a0117c95324e5cb6fb3c0625dc6fbb3cafeb59b6344123bed28eba831f01c124d640167d3c9e2c7df30fa4ac5a2fc8d39ac329202f7af6e3da38b7abfe5984f09c753000001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (450000,"00001579df12ce8d38b8f4416f221964af6aa54bd065201c578bee6e03596463", + "015926aafa5776ac2197dc2a996e373a98da8498028c438cfeffd9476316c1e85801dd8f38456e2f03dde3efb7725e7af4d09ee670a432018ec05cb5b4f42684f3490f000197e22edacfacd4fd09754e2d52c03ad35eb1f278d6ebf8c4adacaf25b25a9d1701150ab81993fd6695da8a1b3f2e00c7229ff5d4f8e8359c455973e069da62150e00000001b77fa8385c53ce7f575963a90d6a954cbd6bda3effbc45aa9eb4c97374f68616019b42bacaea04f62d69a77e98f27e9b87b580f9d157145ad4f7f15c1ac7a88d4400000000014e9bd58f1ffbc75c82bea012600189f35c0d2e169d97c973f73bfedd99f0684a0001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (460000,"00006487396edc5cb9bf6f5cd62491d1d5a47e89257ac0286cbcc8f428239fb7", + "013df992b5d3e09dbbae8f8c552193d8477ecc1550004906cf419a17dbb140de53011645b1afa15ec0044813308033520912becd07259bb9879e24a1db746d1d9b5b0f00013aea7fc438ce337561dafcec03e850b0d44bf75b9d0bd5e723a101c645feb60e016c35562d74ac2b444a194e786bfb797704e481264f8907b051d91b3d1200424a000001ea3a9df228c1b64bcf4dbbae981aebc6437ebb5a6b18af59771ad903b827e62300000001ff72788f9cfa5e1bfbba2b6371f6ca39967d40404dd9c5f22076ca2158c9ab320000014e9bd58f1ffbc75c82bea012600189f35c0d2e169d97c973f73bfedd99f0684a0001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (470000,"000016f55342090f2f7dd1013c1780daea3e18331b9e481e44cc969e42267c9a", + "016729011e17f68a82388f66c074a42585ca2ccbb69ccceccd8fcb4e10bd33240f000f000001f6399799e2bdb03f2e4db6da31792e87e6d12c60a6f5f36d3ebf78c1e6e8a6350001b2c78d592247de4a39660c11d4d4ba5da7695164ac78ecb928b68312f4866f1d019b5c629cffc898de3e108096cd25d5cb8c8f0885943d29f0c885b19714cec45c011c5f4ea682a544e78f2729171c1d54c9b06e66b21cc96fe53cfe04ec352095280001e6a37433ff8a293265cd610211cabcfe5f61b73dfd4eba1aed952282b9f34f5d01ff72788f9cfa5e1bfbba2b6371f6ca39967d40404dd9c5f22076ca2158c9ab320000014e9bd58f1ffbc75c82bea012600189f35c0d2e169d97c973f73bfedd99f0684a0001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (480000,"00004fcf6a8c13044beef7a176c1b872bbec53cc00aee74a4cbc9f7cb332ad29", + "01b9ad273e21b506286688fe570ff4b525e2e09499c121771ffafd420bc1bc6b3d018a44a7e3a43c4d94ff559173885c9ff39769d48f998217a575b3afef685dc6010f01844d5e859f7c9c52719a3ce5315bb64e3ebd7f705caf6429c22d4cdf39242d68013c9778ddd6073fd7264cf322c7b32321fc125c65697c121ce5f53b828a0d200a000136cd721522a3900e8f779b318a5446534919acbf5b50221b064d9a6fa0590a3b000000016b54a117cbb3db51746bcc997436dd9074289d3b124e1c60599534751768d5560001b383d8708c03e7318994d39c8f755f85199cc45cf8fc7035e649dad5cc398d01000149477698c0e94c6dd780a99a0ca3035f76e9dcfe693b2412e4c6555d3367f852014e9bd58f1ffbc75c82bea012600189f35c0d2e169d97c973f73bfedd99f0684a0001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (490000,"000021e70faa40c6b1cd9ed3f91b8ad43e2570a00e34c54c5ed74218ac33bf72", + "01123e42aa9b0fe473c31eb50776e2d208e6094e193344f95690e46829c9f1944301530c6fcc86be810aa1408db9012065212cb07c13bd649f46ebdc57b842273f4c0f01c1de3a7db31f6dd95fb2cd1a12603c5c31c2edc9d5111ce9aff2b93e8310ce0901977eb4dd86c3c2229bc4c206c16744fcfe08bc9cad980cdc0d76415faa929b6700000114e8140138e5446bafefdf843e4ede449a9595ec8be805c58baa6f8a43e668070000012510d4d42a30a0ed05f049209fdd3e0de2def32a2e6b202f1c9373603b83e63701a0430a94b83eff3d3fc757980c7e94bfe2133e0fd208a81fa7f23a697dc5c5460001c69d863b9b81af6a30c648b267f94dcb81899a9b157c976d35c88aa611bf615f0149477698c0e94c6dd780a99a0ca3035f76e9dcfe693b2412e4c6555d3367f852014e9bd58f1ffbc75c82bea012600189f35c0d2e169d97c973f73bfedd99f0684a0001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (500000,"000038889c413584ecab16aaf0ae8f050fec136484416084187a2fa27ed1efc4", + "014a6cd8d3d006668b6f0db403d62a2dbe80c9de1b9ad76cbc20545e4fe2799d5501938ad6e7c2fa79cd0eaaaeaf6de75176f5497f3b3beeea2025fdd217b904bf070f000150071cbe4d641e7c794a8d938434fa4301f9d21cf73154d14eefb7870a31ce1f0177a05b121b0e01f779cb236cbffc7bb1694b4ed8bea78ed601b4d895a2951873000001ddcff3cfaf5a9df1f0274028a8f590e9589c16e18753bd0669d9a8bce9a5bf15000000010dd41d7659cd01bf18bd0a4ced2bf98366b13f3218e6f3a38eac00919f00a55d01c69d863b9b81af6a30c648b267f94dcb81899a9b157c976d35c88aa611bf615f0149477698c0e94c6dd780a99a0ca3035f76e9dcfe693b2412e4c6555d3367f852014e9bd58f1ffbc75c82bea012600189f35c0d2e169d97c973f73bfedd99f0684a0001b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (510000,"000045c957443966535795a08bc5feffee0f0c59b55d7ba727593dbc459a1c34", + "01e80024eecea4f447023db3d6d5e1d32faa7cf14b98cc6f3ab7b8d83cb7527a67019b8ae283f952d4a61bab804fcf18a3e61c2c921d738dd1d9e6c77f56f6a08c060f000001f68d963a781e0f6e382adcebab544556f889f6329af7427b232545667ebaf85c0001baf909aa71d1c86796f553b640ea82151517b29a2f0841394591c73fb34dd13800000000000000000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (520000,"000065949a8cd500872a3961e2d5736fee4f84ddb909c1f34969223b0f896fc0", + "01595404d486dd5512a338ee58c0841403d0645c9fb8a6e3fd6f96c63f013dec1b000f0001b90a66f8036230aa954a12954359697c682ef23fe4e8c46b30c016b14fc1f84a0000017b454eb6f9a6d6d2850a1a3a781c21d2f2782d913c48601b429916eac70010430001fa307e8543a1ca593f82a0d7952b2baa0e9c48d2d03406d3a053c7665a1f9b5401ccfa9090a0373783b5c19b89c962a80a2557f699b9a591e032245baa9b382e0d00000000000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (530000,"00001b24c718558a4c4e08ea1605fc5851706f39b163d2b90e6f89d3bb633330", + "0148b948176d92107194d005a95f9b228288b67646d54ae0138ba607954b9198650182fbaa2b7b4cc3aeb7e51c059687ed42bd619b2c48c974bff44fc98121932d310f0000000001d779625e9bd58d138c358d0e5230851fe04c25d9e398eedbcbe27bf31a260b0d01ec97f5e603cccba4cf4ab9f69fff04fb0d257be3eb30ffd57fdd647c1b713839000000015cbb27d57dfa29ee789f2d77458d8689220ad9106d200a595bd0be7dc2e390270000000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (540000,"00000b8dbe1fe69be0c9da5dc22153a6de62d31971e11b991fd69485a81ecccb", + "0181c70ddb80424c7e8e428286e12c50337a438a3031b5370a86cc493b0efa5806000f015978c2dcec4216057eb9815c1312d86e0a8906411069dab9e5139e523ac345090001be48404f3d0d8ffc07a51b4e32ab55faba0addd3773694fca57ffa0fc9ab3c2801ad93c58bd3967078323880b20b1db320c39aeeed89118733f4542dd03add7a2e0000000140b4791f57c2ea2d7725abb19f77369df1b6aab2eebda86166841ad8722d4c1500015cbb27d57dfa29ee789f2d77458d8689220ad9106d200a595bd0be7dc2e390270000000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (550000,"000029c2f399d04f9bda73c132727d12a5c101f20afd7d7d020d5abe8f35d2df", + "01f8b88caeeb8db5edbf8fe39b3d9e23a2c536adf13553b2ad52690945696fa01c000f00000001a816874ab9a252dc90a22cea107917ae6802eae5dc992474194dbee1a94c234201a8c5130511ab495a2772ffd09551dbe605da05d2d315f3f79b252ac06bfbe96d017936887c541abc81b981f8f78a3649cd2134d34329f910fe280caae66525342701e3b23b097d40fa1f276a75653824b2294885e8cc0397e8b03ad02b06410e5b360001e224d40210c02f33b91bceaf383587db0fc28958d9569cddd2dfedfc4089c946015cbb27d57dfa29ee789f2d77458d8689220ad9106d200a595bd0be7dc2e390270000000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (560000,"0000437b6d663bf1bd44299280d96559c7fceee80dd15909bb430f04169e75e1", + "01823dc3625c9d76f5e96d354c43eece31f46785aa950469d7a3d20254e5a9bb170144806d1953380e845e43bfab2b8f922568c1faee7870ec58d1497b1dca79e30e0f01eaf5899b4a25c93f4722c49db3e151206e514f365ef667052db6fb83e6962e110171a24bb9c9026779751245421e125d13223a83c477594ef31dd12bdcf1b86d56000000012d0adc64b816899f8d600dff4f8080f95e66d2d841560ea6587632bd072b323a011116b6afef53a74d45c28a57d54935d5527505304273f3ed2989ffcb35dd152301c5da2bc30be1a8328880027cbc85576b91704ba8fccedd1b894acefc51e2e82201e224d40210c02f33b91bceaf383587db0fc28958d9569cddd2dfedfc4089c946015cbb27d57dfa29ee789f2d77458d8689220ad9106d200a595bd0be7dc2e390270000000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (570000,"000070c8008bcf3c2edeb60484503d4f38a6c1790e7a253173edd2fbb3200e8c", + "01b811b1ed2d0e47884c5161822f0b2ef140ccfaf022db16cf809599ee7c141a6701d5cdc6a4095e871894a8143cb9e57440ae33f0f40f674821e6ebfe848e12dc4f0f014e6fe5f351106baaa02f0b16353e105e87ec2502dd17f6d4faac3ff2523ede05000001e8b2a92d01579bb383a74e106673dfdc0fdc30435e2fc28a93fbfe981c7a360c017c88daafb93c7476bf5e3f0fe2f21149bfdd61c03ca3671a4584c80dd3e5096a00000180427978161dd84f8076bff555d34c8048ccd431a4154cc0ca4983625258246700000165a5e572da27aa3c0aa5d119ac7f1c4dd79a340b2cdaf5c6c8542d82d62cd36200000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (580000,"0000313f09d83c59644907ca6036f88f82b2bdbbfe5dc91c248a7c9b786dc9fc", + "01dfa88e5c19ad34c3dc46149bd3a53fd405d84bb58a67c921cbd1ec12b678fb32000f0177164389b8a02a3de7b8307019758faae455c381fdc36bb567effb037cf17f3a017bde2dc1169b2bc974b713ccb3c550aeb0d77b82f6acd039f933120d9422282c01ba4a9ef4c903c62c8bf1c309700ea5aaa2ef3f4978ebf84871c0f62c0518b0700123d90a8b0570ccd7598acc90aad942993be6cbe66ab82c679ce2fc00b710f80900013c778932fd8958d68073eb777ba8ced6009efc5c53781efbf8f6ad5705ca3756000159473b4b5ef07b8df92704ad5bdd48116649f2a17fb110fcfc39c4507cb12a07016e9205938303515512ae29c00518a8db18021537a9789b2d562affec8db7944e000165a5e572da27aa3c0aa5d119ac7f1c4dd79a340b2cdaf5c6c8542d82d62cd36200000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (590000,"00005d849ece31d523ec22768684fe8f06826fac681a5e53515799dde5f3c47e", + "0150fad9c41963b52f7c9291ee84dde371ba80df69265ffa1884e8f29f5893351e01a3c7ee847352133013c208c5fe06af6fe6aaaf6e5ccf4736f6f4746d92fc694b0f01570a16926bbd0d26f836afd893e1de8bd47d35efe07a1947be48c19529fa1b620001482b79b71cc92392717231801a1ce3f3d4c0931ff8da11c1f27484132c900e3101ee40a5804cb012e944b0cba9c4733ec72884c00f9654003ceb5143032c6f560f01c497d87e5e2c98240ed78f2a5af5ac9c33eac3aa37a62177c449233b51d51572012c078a1ad819d777c5e93bcd14b00b41560bfba233293584d42554b351f4503500019742dfc12cd622d2a98607c3e7d1400905687f5ccf9de3a1245b51c5acf19f1500000001358ebda3606e626a8228cf73b69127d0af003ef258c4856d2af243e274a90b3d000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (600000,"0000436ae9e88636c65919c5966f9d34a31dc4b65eeaa11f0da4f0dd1dee9149", + "01078982bde1dc9d9373e5da6ccece6f968fa8478026da10864f4384dee7032e11000f000001af0040de38e077ba9c92405d2a71456cc74ac60069bd93de75a105214159201200019529758be4f545c48c2175d4d517cc133efd7b00c08741c270b7819a21f6732301fee78eed798ee17dc63632336624fbef264f20097adab50912de9e7b120c606a000001e4e44ebac86344b1b6a7f0e4e2c69baa607bb061a3af1e46c9b163bd5a3a3a05000001358ebda3606e626a8228cf73b69127d0af003ef258c4856d2af243e274a90b3d000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (610000,"000034a883b6a81b1532959b63a5365343362982bbf37cadcff99d8e60d8e825", + "0106ca20d495b45a80e3b3b638f6224e2cc9ebb55f5c1a22ca3c04e89ff369f46c01838415df4fd8d6e933d8d08fa6f21a7985bae1be754db529406ac6fe7833995c0f00000107c6a697d8d5e7ce9ef0bc7e6bf7d9f2622713d082d8346cad39ee0d10ad0f5f00016dff21be57a0a1632e9720a1347cafbb4eebf5e19a34d8ab4c545833cbd32710013fa883a09833f59afa163944d45d6233d325ad39b9b1547bdf49d84926f98f6c01c689d4f4eb54bcbb939e37560f0d63bd51f054790e2bd161b229eaa7ef518616012ad6fb46beb5272afdd17e0239a96ffd5351699bf54630bb80b7bde845d6e34c015faf55e5406fd9769c0da2627c25bba2b1e9e7af81d67a2e755e9db9b429ae3101037833ec681804434db7669f4d9bf526af5fbe649c96032081f07bd9870ae9580001358ebda3606e626a8228cf73b69127d0af003ef258c4856d2af243e274a90b3d000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (620000,"000083e3d11d57f10ca889db4f39a5a1ffcfbc779fb147e2d78c003488a56937", + "0142a41be9a8e958c4f45a17e8c89a3dc855ae3d7fae32baaeedb51ab2e847a54c0141952fb77d4eaf6ad3115cd43da836539cd48fdbc328e1ef6234f8b9ad17f3240f000157793efa4ccf94032f15045c7c852ddd3aad2580413303d0141f14a6379d032c0172a1b75077221ff4e406c2920d0760e47e678145d844399af91a3e39642ada42014a676e235d8f0fe36f48534f8f7539f02dea8e981cf8d2ac002adeda057e0f5800000001e69e38b8ff7dcd0d04bfddcb4a3b3ea146aeed4bb8327b44ffa7c46ed95d663b00000120931a9bbc153e3b071c66a0490e2e96b45beb148db3305963eff65d8c04a53c01358ebda3606e626a8228cf73b69127d0af003ef258c4856d2af243e274a90b3d000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (630000,"00003cae63886df0b0d10fc0915289bce98117b17c1bd5fc4e005f75370c470d", + "0100f19cae15a1cf1eb070277dccc0839b00559026e9c810dac740b82a47d43b600146cfc74a2d3782b73f6b550bf5d953468c541697d373f3fc0540e8a0eb4434310f00000115abf8223f7e2a45f6e9c631b16ecb8cee282f69bdeb1e4ddad3b1dedf57e13300018564c563147a043735f41c83b5fbd9e12f597b8d18d26c33737193fae91356710117938242e5abba43f842016cbd606b2692f00fea6ee0a2249c5508e4bcf27e0b014ee543a026fbd378de09c4273f73d6640644920ad6341356dad493ee7e03fe5700010c3528996875e59cb64c84cd96736ae127951073cc08346f2b888280df14ab30000120931a9bbc153e3b071c66a0490e2e96b45beb148db3305963eff65d8c04a53c01358ebda3606e626a8228cf73b69127d0af003ef258c4856d2af243e274a90b3d000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (640000,"0000266d50614b362c3c45f59a016843277cfe0905db3cdcb15a2accd89b97f6", + "01e8364240cb4de299e88f7b1b77ad5b55e4581e7814b0779631a35063b518125c000f000001bac0377e576d6a3838ea32a981e3887d0cacc116c54dcb1332702e9fde28f75101aacfeac1f492e4aa5aa4b9b38a426d6fc254b47e01e0cf8d361630d0288fe85b0167911ddd0627808b8fb6b849f19d5d3b9bdf4ded26c964dd49905049bc36192401805a3426fcc632a97a40a645ddea1885c6763ae0dfc4bb91ea14e5edc04b126501f7e68bdd4b1bf7c6eb3d25096f994a394c517b72354db01732f84efc6711bd600000019eb9bffe49a8829ffee319396eb6f339cdf904b9c99e3894405f1e55e430840b0120931a9bbc153e3b071c66a0490e2e96b45beb148db3305963eff65d8c04a53c01358ebda3606e626a8228cf73b69127d0af003ef258c4856d2af243e274a90b3d000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (650000,"000051cb54c7a3308052e8136db2c9615c94bfec0979697f251f20847cce4963", + "01919a096447af2178b211079eeea1d5cdca95c4051f9d0d9f39da9a6e2048b641000f00011c336ccab3a5d62246c067abfb5b04fdb19d604af28ba2ee1c8a2e6ce1efbd53016feefb21d1a0a3be7af7ed7e92d90f01cb10cd80e2f278fecf8974b81e836a6e018297c16a467818171a430300c1b32db2fa04c8a4ae29956204e4b268faa2e41d015295874f4a69ab5310cb728ea2a7540319459f733a07372a2f1f55dcafc7aa2101771da71c74ac931d752f90dbd6f455a6224850d97ef0bd2a0cc1f76c314197060000013a0818372bdd7d116e063280a48db61ce11949d625afea7230b8afdac8329a2c019eb9bffe49a8829ffee319396eb6f339cdf904b9c99e3894405f1e55e430840b0120931a9bbc153e3b071c66a0490e2e96b45beb148db3305963eff65d8c04a53c01358ebda3606e626a8228cf73b69127d0af003ef258c4856d2af243e274a90b3d000155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (660000,"000040122c9700917a83fac1ce3790ae0a31d33fb1a9f7327d5d60932c2d61a3", + "014ad5e80f99745a3b7ba3c78ce444b2b5a80bca9abe8f100c4b09b763ff889f32014cd243f2113d4273ae95e434c32fa34150bfd516bdf88fe3df5ed61eede19d2a0f00000176a06ad43dc2752d87b9c3a9a17c26bfbeb873e18d8b19648e01788390330b6601279e46099dd4277139f58a99c69bca8fce5380b10d5038e5d0d0131c6108cb67000001d6e14d3ab5c70e4f2b2b07c158fb1515b4f48108344f3f5de46a4a34ca611002000000000001cdc8210e623767bb07ddf67d2c7900b8f6dd71b78f9f39792955579ef4b900240155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (670000,"00000487b1a11a5d1c671e788d8ec240585b35d43e67f4a157c95d2b874e05e9", + "01428e7896087a8ebec68f4a6cc35f087154be638c83c81466aa881df129a62d3b0154c89c7a379411eced229217865af6614b24250b7fef927b311a8d4896b15e120f0001ba9a421b8963c0f7502e8e9264c61665d4f03cb41c1ed1a6f03e876c1958971301c01813e3aafe7f6bafca36404fed2c564b080596a13b02d3912b8fe18b57271201b75f8c5db4ad4aa645c244c6890641cb820a93c13fa5fe5c75322ea1640cc15900013fd448092b68c4c5872204877e80fe7c0af2da937891c777948f344cb4c6373b00000000017d79a73d5595b435315d8732f0aed43f5dabc9e906a3dd9a5a71626d9b73dd180001cdc8210e623767bb07ddf67d2c7900b8f6dd71b78f9f39792955579ef4b900240155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (680000,"000068e3988fb8e32f2aa408d3414cb9da9748da7d8250fae08dc246317ed65d", + "01c54dd8ab27caa2ffd6b2250dc95cda339070f07e752e2e214c3e7f5c510c895701d3d86d87d5dddb8fc47c6d80a8b49a3435a71c2dd479cba69fc684f79acc4f460f0001995ba8de8a831df83ed19122022bad951544dbb61d77e7c9b43b5f7bc7f0520001d226284a017bb7fb0e3d58629d6274996e14e922ffd32b00d8d3db4b64514e3d01252f2701607ec5c966f78f68a8a566207f2153c8e6f93af1065289bcecd7e6500141db6616886f727563551ed780ff5c893db5c75d907e318ac820a1f2ff16066200010629afb905f34d15a0ab7e5af8e3e46cfc66ca5b545684698126454a695bf861012eb9fa88009f4ef31f94f2ec049515cb0b6ab71dcc286866e34311bb39a97035011003a8893b46d8a6282e79916c12b33d0032a03fede5ec17291a72980fcd795200017d79a73d5595b435315d8732f0aed43f5dabc9e906a3dd9a5a71626d9b73dd180001cdc8210e623767bb07ddf67d2c7900b8f6dd71b78f9f39792955579ef4b900240155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (690000,"0000084caa9255d78ceef4411cd69d319e78eaf98f5062157944682526eaf889", + "01ec50b70a08dcf9cffeb16385acb9d80e258099e6f1013fbee470f649a98e8e6101b907a9a961b5e94adddc544f7ea4453941f9c013b330fd61f3977447bc1e92610f0001ec01002ec9153ffc1479e0111825ab3c714142e83e4398c5f1794ed574c8a72f0000013b7fe23b36c80a40475f675e5aba57a4495bd3ea85634cef5c1665cfb794c42f01c065a1a1ddfa3d098623b811816fe03764f8e5874bc619213d1db68b37a22a14018a9f5bbd9fdef1f58f193d97e3d8659a0599b044b9dc27b0f72ddaaf4730472100013465ef1afc8ebfaabf29a88aafdf1b1bfed935f421fb5c4592228503dbd2f80601d275e2a9aac9c486b247594afa9130e98d74c36dda1d7ed1628706c11f7e1d10017d79a73d5595b435315d8732f0aed43f5dabc9e906a3dd9a5a71626d9b73dd180001cdc8210e623767bb07ddf67d2c7900b8f6dd71b78f9f39792955579ef4b900240155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (700000,"00008edc47c5bb08fa7e565022caff0de4735bc6b3a50ce6ce50df4ec667700b", + "015b3ad1ca7c3e83bf39b72691c3c3e6f93b7cc1806db8f0223c558c931fd1dc4d01365604c38e4efb0ffcde7016256be69bf98023776a8b05048a631d78467eb6330f00000000000118097888cc7a68c042903ff525b48f4c0c8f8839dda7d03a871be4de0ac88b3701b77b4cb1d4d6961dcb30db191d35ff6b9b1f1b4509c6d65e715e3f8313d6e529014a3ca625849dc80d39ca0fd8c0e0ad598b9bd23e99bc9e747d76bbd681329f6301e014403ea6bb4dfc363ed6da9ff6ddd95de9508832405175838ca86fc4635626000001202de9f616d97225222a37c2351108fd7fd73dd155b2700a751b38b33ef5ed3f01cdc8210e623767bb07ddf67d2c7900b8f6dd71b78f9f39792955579ef4b900240155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (710000,"000078982d79cfc589556fb27f548885bda5e146b1327bc5bdc0eecbb58c5f46", + "01776ec7608d93ba60921a0997f28c925938a0ccfe15d61110c48e503ef5fba23701087eb1d6831e0be9b9a26505f8539fcd036ee1ddf7935d8b3b582ea4645407030f01fd643d6c79a2bab830e8ff3f8c3613c37c45ab985435e21b37bd0915a48b661e019350991ec60402430aedc8bdd2a2e653934ca14f8dad3de936d7595e4ab284330000000000014409738041119cc40541a4e17adf4a643f4cc0b33eee10b2e098735a92aacd4c011161e9e0a8bf51f3513d245fae6104c1458ea5659662cf45cc136b1aaa9f9b590139c9d0e66e440e5a609b989baea1e3905a6d613b53531253ccb9c9cb114219670001202de9f616d97225222a37c2351108fd7fd73dd155b2700a751b38b33ef5ed3f01cdc8210e623767bb07ddf67d2c7900b8f6dd71b78f9f39792955579ef4b900240155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (720000,"000048a83b4832a27349ebbd2f73414813c495eca71ae7db3db46ab01606ad79", + "011d794b040f64a63f734d6eb8bc5f4035cac45d4b448279b76678f93c3af1b641000f012c98f73f9cb41f61a9adb1d71da3b7d62c890e578c900c24b74a086f66ae1e720152450f700f4eec1ca68ec40570ff298813fa6970af049ad4e02f6adae79eab6e000001fd40894e49281fea15e5ba30c45f97ad333b1bb2dbc970aa6dac3eaa613456260154eb642cfb3ee76a53809d32f9f8a862eb6f6ca9676cf66563dae7540d85935300015caf3b4808143fd42ff2218b1ce2ebb7ec457df728e6325539d99d6f27d4613b01d24c8cd6d4e4dbf069f5cbc17fcc09d6e68b6999aae2979773b39bed4dc09a1b0001766f69542e22097e659a4143d220c4bcc99dc88bcf141271fe7c9b99df0c1a3301202de9f616d97225222a37c2351108fd7fd73dd155b2700a751b38b33ef5ed3f01cdc8210e623767bb07ddf67d2c7900b8f6dd71b78f9f39792955579ef4b900240155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (730000,"0000592ff5248f0fbf7ef9e39e0560f95975803312dc0d7c8de61cb3183c452b", + "013b229dac65555ed8adb87ab48583b871766c89964cbd83161b1b3a78293ff220000f00000169d6e38488cc132bcca171e186890ff5a4fb6080475d189146d9a1b856a6a93e01c94687526cb09772f29122c2220af9fd5aecd0046cf756002e7ab701f8963701000000014c482c4b2171ae0c8556a3dd4a8c1691826fecc015ce54c4f1f804f1ea5d734f0001fd4eb3e6b49f38e92bef13c9a5b46ec2081259e387b37d81c4f3280235c34d2901766f69542e22097e659a4143d220c4bcc99dc88bcf141271fe7c9b99df0c1a3301202de9f616d97225222a37c2351108fd7fd73dd155b2700a751b38b33ef5ed3f01cdc8210e623767bb07ddf67d2c7900b8f6dd71b78f9f39792955579ef4b900240155b7e4710e19681f04c7aa34741e8e8e21f4d2b9be4688378c965f6275594a0f01b4860c8b019af1565d84f5d9ed58a549b04f31d111a49037370311946f35dd20" + ), + (740000,"00006901558a74158f7b154635dc1ca20db88135ad4bdf5a27b7110e131a9ad4", + "01cb3c3d8f3c1eb81f90b4105b345a4fb99196a2972a54b59f315877d467923e730159abbc09c5db1c6339ca410961d992b2216c7867595bb1446ecb06a6eb1a8c0e1000018cad9ffdce44f80c856eb18cced9cfc06fc82a2d8ab003c92bb9614719724b4d0140624f8beffd07dc7989ef9db79a666da4660bfde7a6188d946d7b422d75092b012fa11ce864e3b18e59a806df20305df6faa4ebf1cccaf5bc5bf86ee83dc9140101c0ac464375ad3f1e73663b5d1fb29fe2afee516cfdb89816b509aa5c0a69b537019191ed2941a1e043a00946d731351bf9ba5be551ea595d0011a18fa44fc2ef0a0000018578d77bd984942ed5829ae9bfd22d4cbebcc8a229dacfcd02ae50db24852c2a0000000000000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (750000,"000045d2c9dbf992768e4855d0b533c8066a636c72773c4e9de29c167f255f6b", + "010cf0121dd552c6ef902817d014abe5b5396830513ed10232e3c89ee579b4a41c0155d7f612a022bd3bee60239dadba69a02ae597d6dd6b85652edb4811df818b461001fe437ec50581d7b60020311414db508b7b26d94880314416b14b2f04850ac90d000001b458fb1c303f0d3ae829134c67522af0d22994d75b851f780e452959523df73f00011ed5753c2256ef302447c68356a466f3041325f9ffcd18d645a853c7c587064800000186fac1d30cf867832fdfe58737665cd68c93a0ad7702ab316905c3d842532c16013ecde153cf2109b6583a36d6b69d2aaff9f31f6208794eebc2fa538fce2f056200000000000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (760000,"00003bed363545a3db6a54e6ed60e0494b73e686d138fe60b87253cf64e14b49", + "0154be672bbecd7f889cb11df7c13a2948c28f4db0936d31f0819a5cd5f7c26634001001978014562ad91b7dd37e9a3d36e0800083b2fa7cb21052a69e50e26f04a5bb730182eff9739e91a7a755f97f32d75595c5a3d57e4a774fdde5d7ccfb8d116b3131017e238f0c2949eed90fa18887f2514b90e5afd78cc5f9f526cbedd7b7eca8273c0182a3f21892b67889356787c8b595fbd2d53ff1ec24d7bd4f691e5f43351d7c000127b9d5a95403f8a0bba9774fcb1016bb9caced1b6cb5c3cb8300e5c62aba341e0001629bfff0124f6ebb67449851b42ff9c1a4090f3b3aa39f015eef8bf518976a46000145ad1754b8464743bfed0b3ba02d8f420a848c988a72fec1b44837c0a0e99f0f000122bc5400523ed4e6a7a18e48c677115854b19ff4f9827dc87adf51071d423814000000000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (770000,"00007385edc0ff7a856aa6921cf5fe7860e915a5d46f6c1676685dedcb8f5271", + "01a892773ac2486f36abb494d1033afa86944202ef907a967cfae4a6f7e896f63c001000015f30d50e25b57ccb2967dfd4f6db295b3c29c91e337344ee3c3def30e457e80001ddf443463f2be664c11ab96bd73acd93b212d139f09c78ab90df14279aa9231a0001bc90ff4569a1af3ebc473c916fab6d71fe265a72ff0efb5db6c176f3ce0bd4630001379456a0e9a7bf40c7e949ef85ae1cdd44414ce1f3edf949046b385cd829eb4b0180570b76de471cbc78d635e1167f225704117348b21a5afc5ac836a040d2fa580145ad1754b8464743bfed0b3ba02d8f420a848c988a72fec1b44837c0a0e99f0f000122bc5400523ed4e6a7a18e48c677115854b19ff4f9827dc87adf51071d423814000000000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (780000,"00007078727c38b3dcf92001100fd25f90feede5e8a908ec9607b112cbc073d7", + "01af167786f1bedb1ebaf5fd4cc07a5dfd587ea18db858237943aad0642039ce1500100160a35eb16938d7f91576497ea7e1d198c8a4efee81fb8f6b4626739d8888064101567990a1d12b6ce9186bc71624ce24c692e05c86959f2b2e08e661f6c43ea12401107b7cbfdb94b27ecea9de45e085c1b1609280fe734fd26223d422327467c004016f7eefb74eaff06c39681d10b465bed5fa6d907b4f33ff5299aa9d633c0fce370000000001dac3e0627e8ce9e742794c69563d6d1c23dad283df0d6e84864d645a4d87505c0102b30e32b32a20f2ff04019ce8f305531c210418059e6353c0a81e9119a517240122bc5400523ed4e6a7a18e48c677115854b19ff4f9827dc87adf51071d423814000000000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (790000,"00004d295243452c2946e9205cf3bc59475287c1718676f6e50e67eb9901425a", + "01d36e4c8f4b92a97fc6f1b1ba4fcb30a392f717df3b8bfe123145bbf5094c2e590010010d6116610bb8b514ebcec584c03f578380134ca3ef29a646050cf20fb60522010001cd0c85e085bcf56873954789844e30f9376f20bdacdfb95ffedf094c7023e61300010cba7e15044cb4749974e3f43d00e156dcd59d22c3a99cfff8f6c6dda2f38f59000000000000017a29e3c9a4266ffaaf024a825c82253ed6ca2d8109b01340ca177e1474670d380000000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (800000,"0000640fc41f9dc199d59fe773bb333c13dc2d908ce7ace0a1d066e1fea1e13f", + "019c5da60bbc39bde22b7193f7a2bbcf84956e380ac7e2367039c69d9f2f64406a017ec17bcfb269d1cdd84405a16daa7c582d358d5c008c8f5da51ce02f9281111a1000000001b33a7b21edacd754d9a1c260c259d365dcfc65ae5ef1dd1a2b99f0c464cfd30600014c0ec2fa1078517e3abbaba68b98a890c5373f53dc3774d12884a12492e2202401e08139cd21b86de278ae76f19e11e1acbf52134263578c4e3aea388d616eeb5800012ee16d261e1273a94bf666cb60cee84bf2bd923feb9a0ebcf36e214df0ae5d2a0000017a29e3c9a4266ffaaf024a825c82253ed6ca2d8109b01340ca177e1474670d380000000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (810000,"00002735c70731b9ed94dabfd5473144a535af478ff04f07e7a198a5062c6516", + "0129c35db5e988886f5af218862c6e782ff4a1cedd48c0a56d8cd94914e37a7d680113480a9c31c88e29196dfe5b0e1cbb79b50a1ce51e91f34d593f8f2dc97b092010000156cb83b755ffef43fd574d679a9852a253b75295020d37ba63a6376daf0640360001f6a7ec1931b843697681a872ef898d72c20e907db5f6f7a90f0f67dda91bb74f00000000000103df92dbbb7238ff306bd9abe03f6429a19c6a2347de3d6473d5486d28048f0e00017a29e3c9a4266ffaaf024a825c82253ed6ca2d8109b01340ca177e1474670d380000000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (820000,"00001df952c416cba92cdac45913475a1e2494e95a4190960e334240efa3d2c4", + "019c518f0451c0f3ab731c069a907e6c38876ce93448189e27f571f9896c69a3560010000000000142fb3b20e3ca21827e97218f50b37959ce4606985cba2ec1055bd873dd428a6601d521ef8b9aa3980b9cdbf3a4813969bafa5d6c4933e748b7991216fd8242c433015be7bf3b504d93257b3e2aea842fd805eba5efc2cdd85153218971be5e5f01160001a83e523fb81488e16bd602169f5cbdc42742cda6b23d7685cee6dedda19ebd6d0103df92dbbb7238ff306bd9abe03f6429a19c6a2347de3d6473d5486d28048f0e00017a29e3c9a4266ffaaf024a825c82253ed6ca2d8109b01340ca177e1474670d380000000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (830000,"0000313d0909651b5f2bcecccf811de988f06ba779ebcec92ddaf733f6e4b2dc", + "016ef27c3d098be0780c75275b6d23d01db5394c0887eb852aceb78202af32143f001000000001f7a4b3cb96e6d6c11e9add4efd32c99df29e5472a4a922ef4f5a285b76755a2f0001ef099ff8fc9e330523be88164c78f48d3bed3c3f26d8eade55a96089061c596f017543d1abdcd3fd22a970b15fc5dfc02c8a6bc7fb7eec1d25eaeb5ce8301cbc3f000001c8a7381505b1f3762593d108aee945732a1282f0628e192b73b1f47829e0b45d0100e4c0821b69bfa7fb1199e1d331c497996356bca329a7d0d569d364bdee8d350001eb34e694456f98319ec6e7f249cc3a042443cc15ba9bf8a9be0b2c3e0319084b00000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (840000,"000067813b167220e4936fef9a0f283e56d6fc83a163fdc44b2b6f003f2dc660", + "0162290985acbc63eca481c8b23ce7199c7b9ea924774bb38fa160aed5a7a1a128010b46d5109e6e6cdbc117d368830657b0243bdf99d914ebfce8e4e6f15d4bea4c10000000000107622d1844b4d16269ae75e31614e05d6c872505d762f6b0339a4e4a8cd2002a012dff05e04920fc260f26004487fa3e3b2c4de92157c12bf6f625cf411046736f000001c5b2bb728c0d1b760f2f25c1f22ea571f8a614c270c57fe54a426aceb222c81401c8a7381505b1f3762593d108aee945732a1282f0628e192b73b1f47829e0b45d0100e4c0821b69bfa7fb1199e1d331c497996356bca329a7d0d569d364bdee8d350001eb34e694456f98319ec6e7f249cc3a042443cc15ba9bf8a9be0b2c3e0319084b00000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (850000,"0000612f3aff24eb07160699d27a4469a11fafaddaa323010e88bb3f69cc7316", + "012415064f09fd2782c04bc62d9c931a65b6dcc0ec356ae94177836f91f57bcd30016cacf0edf401cd31a78d789e829c7c008b26a5e1d5e12723418ba4007618ae0210013cee4076cb38d3731a95bbf64d2af80d0d31f4bc2f86a86f36b260360fd8aa4e000001dc62761010ed6ad227c2b18db5f2648db11ab69f469097e289246c6ffcb01e4b00011ba555e2681204f2c07c110166669a957f908f9541eaa967c78efbd9122cbd630131afff717146324d0cd0d0fda50d741900238fe3c769c3f62e8ce60c5e11462e01b97510af9e863a4879568345aad13eef8d2cedc801bb2750f5be759c13c6ca2401c5b2bb728c0d1b760f2f25c1f22ea571f8a614c270c57fe54a426aceb222c81401c8a7381505b1f3762593d108aee945732a1282f0628e192b73b1f47829e0b45d0100e4c0821b69bfa7fb1199e1d331c497996356bca329a7d0d569d364bdee8d350001eb34e694456f98319ec6e7f249cc3a042443cc15ba9bf8a9be0b2c3e0319084b00000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (860000,"00001999dd005c53141c1728f40bcdd4b7a528ba65b180567300979194b196fa", + "01e1f5e10506c8c35c6565053bd4fa661b3c68fc93231b7ba20306f6f5ff8b1f510010016de0a26e58312a57104e2d074a72c26c421583a7613cb26ef0cb270089de5a11010ca3c0376b5f4384f700f5444be8733b66de32fcbd801df30f9d13adbc4bb33300011ab50b9a8a46261d78782aedebd45585b42291c93feda6dc83ef1fb89df5ef39015483f2893511421b093df25e4a2559f6f5c0566ead349f6dd3bcf28bf6946b5d01a8fd914509688b484f540847e3dd4c2eaab31b3273690ca11f43cde406ab7f0a00000132227a0b72f9fe1a209fe2d3bc20c5c3f9e1daa2193fbff9fdd370a9ddbf3726000001bd12e53b3f34157dfece160c92fc10c66cd09dd59395a0223159bcc31c318b1e01eb34e694456f98319ec6e7f249cc3a042443cc15ba9bf8a9be0b2c3e0319084b00000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (870000,"00006f4e756f969adb221afdcddc65979660e39799a9a9103d9d883bcc4c9f72", + "01602e3754022ecefdd78d3699445facb36dc021cccad363026d6bf8b3be804a23001001781859c2b106c14b063001bfe84383957f11ca59e2f498eab5ba330b62980c5200000000012644fe599cd5721f73d636e391011ba7b8e1fd46b1a3be35a5edd7c4f11e814100010ecf184cb21e3b5ee0ae21e27d9c9da930fec29df8b6f44587e6ea5db424885d000127f2f97daa4717900cbbdb28afc38134b4b97aa0a9313e517d311169cd9f7e0d0001bd12e53b3f34157dfece160c92fc10c66cd09dd59395a0223159bcc31c318b1e01eb34e694456f98319ec6e7f249cc3a042443cc15ba9bf8a9be0b2c3e0319084b00000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (880000,"00008bc77e18736b7876d995db87837974fc4e723353a5339f52361bcceb632f", + "01014101161c2baf6be8493dbb71154d4c184fba2f4577885c39c2cbe67113fb5600100000000113b50240e1c3813a04657b19e044db2a3719cdf4f86c4acaf18dc5fb50559d5701d1b5e269a61a8d5c669bd315fae4ab92776a057801602532c1bb83d3f69bbd680001702e531d07140beb79dd2db77f78726391b0ed26e667fa33df95257e106b624e000157f346f5f4b0824d90b2ce029445e9a3961ad0def1c30fc178dea3ab99f5b94f0001d676c59d31036a8e48aac679bf1fc89cdfad294afeaa6674a7f6462ac09c6c2d01bd12e53b3f34157dfece160c92fc10c66cd09dd59395a0223159bcc31c318b1e01eb34e694456f98319ec6e7f249cc3a042443cc15ba9bf8a9be0b2c3e0319084b00000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (890000,"000081163a16d309bfbce119c27c23cff28e99e2f5248d897de84a9d04a7b0c9", + "01dc26ace1629e312ce38d8e14b0cba22c56c7310cfa620c4c4f5179331e66031d0010013f6394f47459c22b07d10de86bd18a5c613d9a535343cb520db400f5d149b55300000193b68b70f59856f1f74db8dc01d1942883f65b36c36d321cd58836dfa80ad044000000000001cc99318e50d832010207d1417b46936117a8662720bf6baee314797cc45f133f01d676c59d31036a8e48aac679bf1fc89cdfad294afeaa6674a7f6462ac09c6c2d01bd12e53b3f34157dfece160c92fc10c66cd09dd59395a0223159bcc31c318b1e01eb34e694456f98319ec6e7f249cc3a042443cc15ba9bf8a9be0b2c3e0319084b00000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (900000,"000023936469313daadbfb35bb1af3ca9c3a0a2f7341e3fd54c73e817bb2b586", + "017d485dd048f5af7e82568428a9831d0549e69d8b112b4c40169f5f22cb16d4730155191a61baa9b8621e3eec643ab20b2202f7e7bac3dd9f32344d3781d6ec76641000000000016519a740807ab03510758defeabf703a5631abb1998cd9c9ed0f7627baf6731b000001cca00d49ac45aa57b585875b06f10def7acf48e96afcde91615aa6bb7ca28b3b0001cc99318e50d832010207d1417b46936117a8662720bf6baee314797cc45f133f01d676c59d31036a8e48aac679bf1fc89cdfad294afeaa6674a7f6462ac09c6c2d01bd12e53b3f34157dfece160c92fc10c66cd09dd59395a0223159bcc31c318b1e01eb34e694456f98319ec6e7f249cc3a042443cc15ba9bf8a9be0b2c3e0319084b00000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (910000,"000033733d3f6613409f56627cee647bc85066a57b789b248d94fcafc3ba0479", + "0133e9cf73be3741f7b8bfb67f8716d640ad2e52e045d4a01c84217e8ecd5cec43001000013af097b098c9b13bbf607ee1d74ae6007ebacddd847f0f7ac5d8d385771486290001b9a5f383f0cbec6e1bd62a5a5a8efee737e0377a4a2d0104e171638a3c231e4200012ecb660855d89204f2d8f058eb7cb2bd455a8d24b8d882e6fb5254cb1e2bc14101ec5336b93f7c62a870aafc1b481024e6b63c99b6ec6e018115960eb24f8edb700001fa2f166ce1b6bfce3976aa1214fc915d274d135fa1cff5206bfbd8fcb65b412001cc99318e50d832010207d1417b46936117a8662720bf6baee314797cc45f133f01d676c59d31036a8e48aac679bf1fc89cdfad294afeaa6674a7f6462ac09c6c2d01bd12e53b3f34157dfece160c92fc10c66cd09dd59395a0223159bcc31c318b1e01eb34e694456f98319ec6e7f249cc3a042443cc15ba9bf8a9be0b2c3e0319084b00000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (920000,"0000660a4c74a8c9d65df6dc9c7d22ced45d560c1d22cf3e248baa02cc773177", + "01e5f0a46b5acb5f4f9beddb1fe9ec9764c8a62b0ab9966f0106b67d176556141a0010000147f6f229e0155b53dab14a4675ac1f25edb9451c53897462ca2ac597c5d3224601147d9f7858d0a2ffdd88b906613a62597f58369f4f7e3a4a2c05db07fd1d2e1301a63ac2eeb611121fbfb64f5bbc2fb2d1fc3794a5b4dc656eb2613bcd1a0f0c4e0000011e50b8f0091d7ca544fc4a61b6f40f43bdabb8bebb5cb4f40938510579782e01013c3e8a5cc5a6d05ad93155b3b2a06bd6e9f8ab55d381a1ed758d333a797545050000000000016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (930000,"00004990787bb80cd418e82f5ce14197ea53e4803b88d35233d0a9df06f08ed4", + "01eb9d7e8f7b5c8e4bee09138d998f8b667162f70e81f62edec9b8dd7fe415930801e61776653289a93e1467293e479865a943e64244821a7f4223396759225b301f10014345ca7c70d7a3b5cd72661f7cba10d0535f007879c7815a2fe1cac15e8a2a670000019dc89723304b4ded1e8f093bfe67f1e1020392dbd17aaa0fdbabc60e871bfb5f015ab20c1b1beb64e996871eec69b9e86b8f780bc9ca56916aa350675220e89848000001de6f115175af644db7190a80fcfcb344efdc44d327973287300e4f88104061290001dc4b63d89c8982905234d280eafedf74f49b7d59438ef249a2a249aaa5b2af32000000016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (940000,"0000541d565f69359359a165f2d74ce18d098b439794e158da4ee6f3e1864bfb", + "0160c577e7c7eb765466616cdb634e457d93078d9e99c92ae3ca7c5a0e5489a25701864dd9705d12494782875b78731216b36b69360b7a3d80918bd6f9156169002010018b1b097d407cdaf52ea49791bb8b0de324c07c4579c38383f05c0606338fca52000001a3dd9ed5249d145c45b6beecdf04220d31b6b39196554a687d8d8795c61987700001057cb0c1ce824c13ae1fc0171301daf28c3e62ecf0bf32e4d872a478c5945140012e45380f929eb260b8b8342b6190973adfba5a9c9eee541e2c42c549cb8af56a00010a64dd2e1e18ffaf82c0f0a951039c44aef7b0006b8810782fdd10c937fa082301dc4b63d89c8982905234d280eafedf74f49b7d59438ef249a2a249aaa5b2af32000000016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (950000,"000034fc8d33f91264f615b269dc33b5d47b426fc6754273ff7a459081b732dd", + "0194d12979a50fcc7e03d4b5d9c8ad603f58350025fb138d4c1a0881622b289e0c001000000000016b3baff6c8dc0839abe578eab21e6c41b20f83e8789cd310d6740a209d872a3b000001cb9a078cb8f0cba411807f7b7747a5000e2c7afc5d60cf5b264664d83ddd135a01cd4f1b79c9f5bd71d22847bd1954894a97902ab322f364dcbf05f29edb690c2001e4f6915e7d5020ca0d108f8da97d2896b62e2f4b517f75ddfc885ef7b902e1250155f27f4a60661e15380abf4de27ca7a8a1dbbefa1be8b870df6162d8c7fa400b017f89dc47df78645a86a7bee565b628e5cb1b2843698c7314df8c0757afcb210500016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (960000,"00001a07190de23bd4d3dd7c80d47b921c6814558afd639dee206d59db732432", + "016c3002c4cca632274fea5435438e9ac7a0f1d9e5e42b353e6b807782f052906c00100185ec1c170642d3b3d640e98c78751ffbc64008cef16d2bf787c5e6f3c9a2f14e00000126326323d5a9ca1d49ba1b0811d59ea560cd9a629c54728874f13b669cb9552f012c1fb22e4b464a3dcb33cf5be5b298eff15693dbf04b9137a217defedc37d94300014ae39e6778f567b87283b4361685cc18fe0e2960c6497ff65eb0d25f5a59f42300000000000197e816132d23c8e42cf8f0c2e73e3d625aeb7b3dcfcd3c9702ed44ce9c143311016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (970000,"00000611c1529817df9ae09b4198578d920e2abced48945c4a366aee86e54f84", + "01bd80efe85fd56c0cbb36ac5e5328c3830273ffdba87c9a049ef7cdfe74a8a2670010000000000145cebd0cc005376d8a21d61fb4a92928d1cd2d7f904b63c2301864f52a79d7130000000157aee3b807a229fa811c1e6eb7d7c678296be2b668fd334a73d9b12dfc08f1450000000197e816132d23c8e42cf8f0c2e73e3d625aeb7b3dcfcd3c9702ed44ce9c143311016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (980000,"0000017214698c757c38cee40a6c14c6d2f5000dcfdb2de65e47956443d03be4", + "01bc70b3143e9a4faff38f2e2830e36fd4c06a381d47073e22220c9a16496c0662016c24548674b56bba1912e4d1bfb6873f2b98a9ec23654410791dfd27a9efd162100172337c92b248ab5c5c5dcbef9802a25016263cd5487dd57279a65b91caf08c120178cdeb8d7bf1596fe26b8d552184b5d6fa440a40e717093248ff05e909a17b6f000140d7744bf373c1eb8cb199651fbb65df7a3b645572ab33f30da539530509bb6901a5663b5f56abd88b60d3c33bade05e4ad635c779f37c72de8936b0850e07f010000001bffe491f61a07943d8022d986056698db9859eede04e297c3374ff7f4e5cbe060001ecbfc06cd956806c2cdedf23ef02ef0a2317fea9c8155514f16acb73eb8a072000000197e816132d23c8e42cf8f0c2e73e3d625aeb7b3dcfcd3c9702ed44ce9c143311016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (990000,"00005340e6855081dd3fade7baa8db18b660ffd272373e67c812c5cf69006300", + "01b6d8e3cb2db3dd3f8b13ae3de03eebd1c2bf7ef5138c792ff76f22ebdd5a896101825944b409845754e1b802b96737108a7f0a39ae1314ef870ff1ac15200a7b3310000001c281652cd50fe7a79160755ffc1cf2042ca29d094599972f7997accfcbc58a490199bed965b93f38995781e966a6bcc1d984c6a2624c9522f44bec55305992a81901f100aa8fe23991cde8a427c315f3e6a8fbefd7772b45e6c385cc8d0c3328173a000176a54a79eb2e15b6a73cab5db77f5ae7627d62ee75a70b1c74052b06fdedd50b01e44972a439782199e2a434ce43b1cbb885cb8c3f938bf27a82ae1f23509ebc2f01f6bba5487e2434bfa0d96668fd485bd4e4a6fd01e5c538ca662c43ea662c235401ecbfc06cd956806c2cdedf23ef02ef0a2317fea9c8155514f16acb73eb8a072000000197e816132d23c8e42cf8f0c2e73e3d625aeb7b3dcfcd3c9702ed44ce9c143311016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1000000,"00005efbc0c1342f7604617830dde40956343dbfb4737502334c54bd28387855", + "014569595eba402e1e946f1a3a41f4dd6006eb2852ee09f2fdd5958c303669f66d01f7fde4884d37844b8aad362347d4953efe3007e5b7525581b1760cfb8a4c6b431001f97fb9402befed2cffc8dc3f26b9b189aa391bf0a0d0ab2b5fc731925de6c502010f97b7ac593b7d4637119722a06f7304aafdc3a68b8d5966f67a22a8f961ba3000000001c4a264c60df77b143b489c4b6b79a6862be87048785d41acec9e4c94d421fc1301ce19145722ab523df40b88a5076cf24dded0b9016077bfe9f15f9be4e4385e5c0129707b129122c53d8e42719ffacb423a6d96d3f00953bc49d39001659dff3f6600010b41d07600752830fc90e3933818877831085ded4b8d0cc38680bebb397c8e1301e8e8e0471fe89f92fdbb3124621ec3b2efd82651a046137581cba810813d5a6c000197e816132d23c8e42cf8f0c2e73e3d625aeb7b3dcfcd3c9702ed44ce9c143311016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1010000,"00002ddc10f8190a9bcf3c6bb218f5b168db3b1aa025af10ce5f5b7cca2368e4", + "010d76fe9c90dc91ccbbd63b37f84b9c5e10107c1866fa496ac73df7256bb871240135273391cc7803ae03d5a0b1b62cccfaf01e506af940d8358b8f7ea6ba270f21100001212932d7d11eb593f2c8bd21ff7d9a698bef6e4f60134be44c6f35ab8993371701b7425f9116e28746879f7da315ff52be2d4efc6103f6ea5b7f8912cf135c382a01af06ebf40ea7b9f5ee2f38009e02238461b9827eb4a5c2c8e395d8d4092ed81601e893249b1e1f3fc1889984f8392b4fb713182f63f7b46c98e5d88db58e3bd945000001593ae1febe47c4999c75b4922e161117d1aa9e27e0c2cde990cf3ca77a95843b00017c756eba9267604ab8fea803a9e0ff473cb1cd755a1228d7ccdf44f6f2cca05f0108d60459bb270bc740ccd9fb37f05e0931dfe990b7e0088fae97c64110bb6a00010e7b7e092c3bfe1be4eb2a9fd45d31c38766768452dbfbd7c2521e0075b2490c0197e816132d23c8e42cf8f0c2e73e3d625aeb7b3dcfcd3c9702ed44ce9c143311016ac4d543d5eff96f492a3fe0f21bb24284199605e3fd86d31c8172e1194a7b5f000112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1020000,"000008643ba6ac5974579afde3ac22485b4aa5b475011420ea4232cd2e4aff20", + "01612b9918eff69befd1f97776261dd9ae8c777938a47d44e406d2f7e768ec401c018a77c34ebc04b48f8bbcb24b8d4579749148a372b1471c53b91d1a77e6226714100000011b01e6197d00db99d8af0d24e3ba3d24ce59974a3113aa2dedd64a1d838d9b0400016bb08de0b1036a3f452b3a24c211225a812d65e86fd7e31877f39f658824e11f01c146fa3265d6cdd17e744a4d038e706417f8c6f25eaa3b65e6f44a1aea4bc502000000000133c709502e966c2035717bef05e1f25c0656d24769a5bd519a03ce8a15be135d00000001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1030000,"0000808ca14d72c3d5ce2dba95a43d7cfd0a5c0446dae881b4e2fdcddef29553", + "012bffe4253e078b06528ff3ff80e080510dc096724113857ac72ccbe01cbb5224001000016bd8482ac9c57d280edc2bb28eb5cdb94467c97dc652b4ffa691f853b7e1e04100000172126b2973b2f0ea1797a0bf9ed5df91b9cad7bb9199ebdce496ffae3d64631401f8362c235360c8123ec594a0a9126aa8fb326c8fb1228c03f95ebac88875941901eb1d2335bfe8629d9fb7681e41a3aaec560d0943dd101ee8cdffc40dec197d04018d17dccf6bc34d4409ab9fe477cb0db56e84a79eb8846e168f8253fbe13e3656012661601b77c2d14f937b7aea60aface8b850ac4d647bcdf417512c5d8e4e0051000133c709502e966c2035717bef05e1f25c0656d24769a5bd519a03ce8a15be135d00000001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1040000,"00003f7008d4c2e3163b73caa2903ffccb00331ccb5bcc6fe1e272103b4b760b", + "014654ae3c004aa14f1a5c920558d2dcbd6812f43b0f72f531589df8e8fe65af220010000001bbc503e38835d7f96e22714b5730a33cea1f95cf97778716b3c1f2d2ba3fc91901322d03c4605c4ac1c57397c81f25165e8ad62f7dce95a760535985cfadcc000300016ea182d5a704d04e8dc33b8f7749e38903bc0a35edad269a639ce942b8c3476b0001e44320e6bdd9eb7b8f2b5b1727f650c098679e6ea75f264aeda39ffc8a09496301317e12b97025f5ce20de7f38195f112da2af2e56761479ca44baecbcc1e19b6801a1d63692cff1d920ed5c333348c46273c78665371ec622f46839429bae92164b0133c709502e966c2035717bef05e1f25c0656d24769a5bd519a03ce8a15be135d00000001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1050000,"000065e2f27bf9c47650dcda4235236daecea24b8f6d5de7ff3cd12b36a130f9", + "01b7008c7b09a6eef07a69e7c66973810a4509f4968a178770f988c19d88ccfd5b01cec82f245bbdd2a7939e48ee6df13016620a4263bf286ce4736704376d090e1a1001f7000e5c0f4c061a883a6e60acb9edbda3330dbaf37eb071cb4424b4a924730b0001c73f6a1ac50aaf5969550f64a599e599df0148bd58bd94aa76ffa99963feba190164d388745c16acf526d6331871197065df16c9b793616b7d9f9b292e91ba8c6c01b594ab3934cae74a4ba0439b024c28ea000cf0f355381bb8b17d832e7fabe268014fce274a218a1504f588c0bb69eb6e98fbef9e10b8c1b36cfc482cb6ebc96d2401fcbda3c3672d28a3e6c7c07ecd99a03f98b580d9e6bd333517018a9682203b400132fc6948e7f124ffa28dd0ed95c893660743afb2aba0450c68f303c004505e5b00015af757cbbe7b3f156053b05ccf93f50e8a7b5a94fcdab0a484e248e5e8d67e0d0001ee9f6596cc9c0e9781bf1ab6a52c1e4b6e371c4778b618304aca1204959a1f0a000001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1060000,"00005690db6a3f997a039afc7739035cb4d0e6d672a9c18776223d66388bfc4e", + "01e668d188b7f47f8ce6858fe050bd3dcda18a2f1b38dbffa448d04743e0f406030010000001af7a52723478c7e91b6f9ff14d7f289e61da75b8a31816384b92df9cb08b4703000000010a8e317f6fce82fff0d94d329ba729710eec4921e30d448d10e33e62b342f80e01c428f369cf7e118bdfe44e242318b9977f660b1dee5c9e69614ce7d0e89b82540102800aa4a8ebb186d9bac6549e83acc2fe226c451411b3a3cad82ec4293dd42e0001a5fc3f0241326f4bba2405d1ee57345c0ba219928eebaa93ab6eadc6d403f30901ee9f6596cc9c0e9781bf1ab6a52c1e4b6e371c4778b618304aca1204959a1f0a000001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1070000,"000063990f169d83f00357097d194597f077e5faab078795240eff231b6179b7", + "01f333e022eb86298bcceb6caacd2f7840d527ca6f8480ed771efc1b83884a664f00100179c3de8c738ad5ee144eaaf755d658401c3071468274830d9b207f54e9a5162b0125474720958747d32ed3a4c3b39fa053127ba961c303a9dc1fb4bc36ad631a0101c1249ba229e02e3a3b134f9d38bb8f9bb347a4a7c0f5fc0bb0a9127e1e2e210e00000162985a83105dbcb154c44d1a1c5de389e54325c4cb26c68d672e1b2ce9302d5d0000016fbe0085632bdae3af677c66316617f16a98d7c55be30154f562699cad955b2a0000000118af15deaf4f23785398d31d305e0364e2bd125d50670ee60158da62856ce9710001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1080000,"0000686623e37caf1a5e5ff9e0740eb0c7e57799f70a5adc8b88d81cda45e723", + "01c643e70509a2a9d4958dcb642d5f1e3fbc554c731932ed4c1075f79dfcdd0053001001a0ad5869392de1d9cd8f9009cac711ba01b0e3b1a36182e8bc46805fe297b64b013113c63525a69ae3738ca985ac7a5732d7d0f9790472048a110e153f696b45450001216c978d3d7347be91a5e0e5ea4fa76efb0f0e5e71939b5c62d0e4835d30733301c639eee7bde6bc4cc2c85223bfbbccc13f2ca83438c3edc7900260d0a89d611e017ddf14c1561cb4f893abc33e4e01443c4c208115ac8842faeb114a6c0b4eaa4001ff656e3734b2e6047020e38398288e19b2149c2d2a55f89deae17bd190bd71660001bb51c8390541cc136ea282c8442159d834e87968b2085eda136c49ce7f05193501abdbef07588564e4b9e63a03665c38e2040104c7434949cb9fc4387d18e06f6000000118af15deaf4f23785398d31d305e0364e2bd125d50670ee60158da62856ce9710001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1090000,"00001d164a60ba32049705b3de3cc156145508e7864a6c3359a5554d35f1cc02", + "011415f9a43a7a3a5ca18d43993a9b121e022518d05648cc21369464f1c954b82e00100001eb8da84775484e7d202d9295c96ad73da5f9e267b471f3125c1a746b1ae2ec5700013002cffec91782bc3d7ce386219de002360cfdbcf47b4d054d8aa30372cdb34e0000010599cd86fb28ee11fab77d81c7580b8651e193edbbfdf99ad8a717e84abb543c0174cf862a59b5686e73c56dfbd25a400f384c09330079d4b28f470e2722d57c5e00018f44b964ca593ee2c0c7f48dfc8eaed9f29c0f7643cca397320f66935ab42b2c0185ff04bf954d0e621f8d388b21e06e6d608d482cf97a02cfa45689ac526dfc30000118af15deaf4f23785398d31d305e0364e2bd125d50670ee60158da62856ce9710001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1100000,"00004505211599b14b8b94fde5c1ffcce88901427dbc1ea633917160d4188f29", + "014d74f8e5b0b11468a0bb95e491ba4c18f4d9eee224a76b5f28bd611231ece66200100175e5f566e4cdfc566590e981f65183f9000e8f04e8fa28b602b5783426f2a42e000151075573701dbf26e22241a29f7764a05484373108e28c9d0fe0b6767d2c305a0001461fb9155d6706fd3e9e8e91181057adf6b458776736cf7ed4e1c613c8e5b52f01615eba3a55f8e8a46674f86820f1fc5035d37271aa2a2dc3906d92bd9ad40a5d0001071b5607db3aa7e86864fe73e062d0422d1942ba32d0709449c544d5ae976c2600019d259a5e6d1c8dc47365fa2e34d73637c15184b6c0d5d1328189381211bcbe3c0001636097b35e39a2c8f363a2919cdf76280176810c1162b3f150b6f76b34272e270118af15deaf4f23785398d31d305e0364e2bd125d50670ee60158da62856ce9710001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1110000,"000010f822b1664ac8042ec066739dac80c2996cd2494f613926defbb4f76ab5", + "01b03a19f8b63407585427d047328054c2a61b35b4cc842f157f81cc5a813b1d3701b6056fc964cc1aaf6af31895b519e64d832c63c30def7aa9182b1026b63de63c100000000149ed3f1ee7896cfb4c0025e279462c5466669f7a0921d5b20cc5c51b0e65f007016dc6451842c5975cd02dfe6b6828291109d451ccea28af530b278ee937728b1c000000000105b716ad594916265704940f00c51c368e0ada8f1a7f3efc0007f82338b10804014cecc02c546e13ff21c429ca48790ee21da9ca3a39f3369f1e36154f3dcd056801636097b35e39a2c8f363a2919cdf76280176810c1162b3f150b6f76b34272e270118af15deaf4f23785398d31d305e0364e2bd125d50670ee60158da62856ce9710001b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1120000,"0000409d4bfdf323d3a9a85b65765e90084218a776c76a1cd07829cb7f5b6d05", + "01e68d9631ac6c369ac6771eba6abb55a34fa02eae6729bfe60d1a62b42214c930001001891c2c607b97daa841f1cd6ef10430661324f68d29e8084cf5633f81d23a810a01bd3d3dc8350627812e28c339ca190d75422b5c2395fc6f0adeb81d4eac0afa2f00013ea1dcac7bdceb4baa8d23e549a8a48608158cd26c2f80ade2b54e32945e3c3d012e9b39aca15a927aed2cdd1be49f8b92ad72910f5dc393b766c569b4404b490f0000000001cf0c47f6e7bc5c22e020e1495ed3add4a77d20cf18481fd213b381f37b1a3873000000016f7a6a17eeb728b599718d67670a1d28b2fe5e9d6384824935361a5626d1c74b01b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1130000,"000082de552f3b0dbd1a805e08db29f9f60fe020ee88944b0266e55cda2f7e78", + "0129114283e10564198c30a1105a13646eb0739aa18a182213642371e1a7126106001001f7cc1db7368461deaf1db392ec5352cf7fca634ee62fd31d57ec607b3ace93370000019bf68fd2da9875be23688aadfe4a4a9d96223ec4092405c3103a047300c17f3c00015ee9c98d3e263796b80b2c5db72a6b34e0e7ce0c573876bf6112599cc507c54500017c21a7f41faae9b27baba1e9cdbfe2990098b494d517b61375548db44a4b341f0001ef0c325d4434df461aba2c88def6929564274b3ba15ea0c5884928edba35661b01816e55e9d75361aa212ff4ec8e0f5f2e50b667b0fbcb70edf2639ffea84d473f0000016f7a6a17eeb728b599718d67670a1d28b2fe5e9d6384824935361a5626d1c74b01b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1140000,"0000012c87cc87745fe70667c7a2eca8b03dac27a8424b0830c06a18fdf9fc77", + "015af4befe22cc56da4cebfcc2bb3fc50b3691c188c8adfb39856043bad832c50a001001376eff9c07dbf6a3be3498c6bd1b41b1146ad4507bdae5f44a728d39251fe908013982092058ee6b53235adc07757e7b04671b15475c29fe8135b87ae0faf9665400000001de56c54ffaf949a6e7943b38d2c21c97a08e77881b819135e8150d78dcf7f73201186328aa793f460addd5feeef4172c30f78d6d5b0ffc5d1ba35f21fb3667ad2b0197c47d7af5b9ba56062dd1363ba37639030816884b8c71b411eee00880f6245801509bb841e2c7536b42d4832b5bca3103943ec64921e269a4b7a6cc58f77bfd63000001e51bf10a100ef8951e13771772f4311d6f8ba6d211ba36e8e3e980d9411ecf1500016f7a6a17eeb728b599718d67670a1d28b2fe5e9d6384824935361a5626d1c74b01b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1150000,"0000383d666b6039ad272fadc0e6375def512cdaa0d958ef03369eb286f7f790", + "011b8b5fd382f8cbec0d8bb1c1c32c18fb355435be536e56d8f20d2c232b349c1201aed961875764d7765c9c12034677991ee8e14687e38004fc7efca55a6792830e10013995e7ca0a379f848643bf6e77223e996911889135433d6f7bb15765034bc102000000000187bcb5816f70f95fe793bd035b2c26c7d802ec4a40a9511ed26eeb02c11b090b000165c9193456ea91dea4383a767283aff2d10ca4c82fcd01392c09e44440582f5f0150970f5b784e2037ac241e235211fe376c793adc7ffc92d41e037e45245c650500010409414d33c7da4242b3fb75e577dd0ed43e13ff15be2af8b237dc46e5e2ad4c01e51bf10a100ef8951e13771772f4311d6f8ba6d211ba36e8e3e980d9411ecf1500016f7a6a17eeb728b599718d67670a1d28b2fe5e9d6384824935361a5626d1c74b01b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1160000,"000033a737dae6e4c83b3cc5eb9cfab2d69119096dd958a620b3d4d28505461c", + "01b770f2192c4d76983bab3d0b9ce1e28914e14ce8440e4bd1e43fbf9d8c5dcd080010000000000001ba3dea7e84c3dc1a79c733bad7d2efc62c6f9a1ce6d76816c01f445edbac1644012c31c93d4d56d483f5b102d191dd19481c53720131fd871e2f0c2d731bd1f83a00011b245a29ecb903fcf91ddb0a04581d0e55a7c5aa3ef718c114f29bcd732ebb2f00000001f12bfb8b20561b54c8c0a65d8211228b6bca6891c3cf8034f08d5ab55702a449016f7a6a17eeb728b599718d67670a1d28b2fe5e9d6384824935361a5626d1c74b01b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1170000,"000015438fdf9acff1bfb51f49362ae1c1573c278f5048def1dc5b3f6ebb4d46", + "01b6ca89432d3c1ee09a31520da593857739c1db0347fd9d8595d9c544bcb11a2101f1cec765ce02b170e54652dd174cca73dc844e67d46f757373d9ec373baf4d211000010ed554548a9307b5fae36491ec2c743ef6bfa238715dc3593f697dca6d7b643c0170ce2dbd747ba7d581453f420dcc13ad361b8283bb87dc986aeabc5aba31ee6601a60e8bb96949a39c072cb9a61c0fb6c909ee493b042dbc27286c17658d08324f01b70f2a9f5f97574494e6595cc6f9352b4eea047aa8294b4ab39a65b5f51bf92a00013325621a4d897a6145b198402830be8128391b5037dc526972e17a50b856773d014e7b70d5772bacf890f3e597eaf7872257cca367c9e6bf0ffd3f10588d1ae934000001b7490ec9ce030a772817206c2c6dab929cb11b44479feea611015a56807831290001f12bfb8b20561b54c8c0a65d8211228b6bca6891c3cf8034f08d5ab55702a449016f7a6a17eeb728b599718d67670a1d28b2fe5e9d6384824935361a5626d1c74b01b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1180000,"0000649ca5e4674aaafd4a4ba0471871174ea06c10509d112eea15ee99fe8daf", + "013fecf09cee4ae2a24c6d89b6e3861622cf7f3e4e1ce18a6857620a0ea5648366017847747ef1ad679ddf733e5a05a804498bf194d9d1ba575fcbdb365529201b2a1001c2f50148338515ed676bdbe6f6b186a79fd356232a1f244eb0e51397388fd5500155b768b5e0fc9280da95ce7e97b6b893455e4d90f5c2edb085dcf8cb4de6f9130186f218c7b06a479e15fa12b9b4838d9ac10d36112f538f3e135de84f0dee395401f1cf781dccb3e184e6530ca822e7216576b81c0ab1b5812148266191dd82fa60014ebb95290be2baf66af076f60b1e14d94ab2d9545dffd708b1fe2ffcd8f850200148f34733539f1c8d4a6a63253cca24f8891588890a73383f81e1b972f2e69a5701d825c1c8995363ed2b5e8970fca525b6e3277ce750668e7f3ee837ab7131fb100000000001f2a18f4a40661b73dad41e3418ca83a16cbf24556e741b6fbb418a4de9488c2401f12bfb8b20561b54c8c0a65d8211228b6bca6891c3cf8034f08d5ab55702a449016f7a6a17eeb728b599718d67670a1d28b2fe5e9d6384824935361a5626d1c74b01b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1190000,"00002245226551549ffa162c710225978f840faa0970916927ba949cfc9abd11", + "014b8f430131770580e7b6bdd24274a19d40f29567223148753218e7e5d3cd53720155884ece6909de9762b38427c740ce151db2eb3015129f586ba11fa8ac786a5c10000001838511d02a99847ed37c7db09b4e62bf070484bf1fc1599e1767c2057a3c43020001329ab855a6ce18bc32a7a9b2106e3409524f4347b5488133d760d95765bdb01201581c244c7146bb4dec337d7d6215967b11b231176702e91e896674621447dd290103a454a2fd8caf564e96beb48c9025ea32e46fe998c513306c1387a94e1bf26b000162397d5a7503e50dbc6d492a14d28ddc39e1abd2ca74adde479f8a884d2ce1250167c4618053fe9e209d44986d1a3de4fe202adb6df3422890bbdab070377277080001f2a18f4a40661b73dad41e3418ca83a16cbf24556e741b6fbb418a4de9488c2401f12bfb8b20561b54c8c0a65d8211228b6bca6891c3cf8034f08d5ab55702a449016f7a6a17eeb728b599718d67670a1d28b2fe5e9d6384824935361a5626d1c74b01b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1200000,"000013ad8b897aa023a26864bc8499fadc17c37214b46f0d4b84dc4744a3f270", + "01937e566a6d5664127cce69287d8d36fb8f0becbf583f7e063bed3bd615bca04c01eb82911b65ef74f2bcaaf804bee86b3d91119211e8f70da67219138a6cfc975f1001f452a7ebf710b1dd7384a4dd742393049d73a20a729eb83cf9e068b9d2362e660157d7b92a886995d2fa106a3aee8c77b32da387dcf06981ac95f5bd6f2e87042c0001dee5cdffa72cea442d825cbdd174a145b86bbd037e142507aa0744d961187426018d517d663b00416b7cfc9cd9b25127937c15566877aab60816547ecaff11d050019426f2cb95a92f164d39f0f4901e6ea727ed6f478c69f51a2d942bf3b864d76000000113ffd2290ae796d8beb57949a291a4b7eb29f2910197907cf2b5ee5381f73b0f010a5efcf85d275410fdfb2ae51c53aaaacc6f53365a30a54ffc351fb0a821816101fe12d9ce08e492cc2138b7e6fbaf23a05966e4f0f0d4ae030f957fe1e4c42b4b01f2a18f4a40661b73dad41e3418ca83a16cbf24556e741b6fbb418a4de9488c2401f12bfb8b20561b54c8c0a65d8211228b6bca6891c3cf8034f08d5ab55702a449016f7a6a17eeb728b599718d67670a1d28b2fe5e9d6384824935361a5626d1c74b01b86bd876790786b1166359eda3472cbb67d2f6c1266dda95c07ecc1dad201a6d0112a0aa9338eb060de79e24135b51343f8c996aba1044c19926bff3ec913d8401" + ), + (1210000,"000014155338aa216d649ff656fa174fa2ef31f709ff6733a6dca2cac5fbfaac", + "01946f099344db08d6970342bf6056f8859d699cf671edb5e24922c89f3f75a80001d11a0b34fe0ea56c11fe501f971847a8813a6c41d5502495b0ef40608b091f261101c33ed02cd94d18e55fdd38e0bfe8440a0842037fc995b8625dda7f5d542d45230001c96090afe4c5fd18fe75be9f0b70355a72573f0f9748c7f126ebd3ec95fc836c000001715a801d94585bd312ee416dd5ac69656db1be541a4eb78392a790bb2ae52d01000000012a6b0030d88413a163bff66bfece7e77ba21d15749911dc16c38f82bf563034f00000000000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1220000,"000032c41595c1d0856b5f42cc223f718fc39730a8ccc7da12979eeb16843b17", + "017a8194377f4f786716758ccfe75e037ed9b99a5170c0cb4b617b6c075ee8c026001101b7390b3e9df12399824a182bb64a1659a763a915926d5da9ce14ae045473106c0001dc593109baaebd440ecc38dd765187337b33498bfb27c435eaf5a8db95c0163601f7d77e6a7192f206ddef6b5567ceb1ab2a02d06272c7600a97856d58fb99a94c01a26f281eea2dbe4f8eefdcd1fca472c01510289b7fef40755cf115da9cbf4a2000016146da4a7b483cc6bbda0fb1335397c8b455e7f067a0e87ed39562bf6a643039011148dfc325cb6a0f2c7917a106fc0513938b0c36dcb9357bd164966c993fb72300011a4ecd32e61d22e82eac64897b61bac774dc87c522f1ccab8b82dd4dd1e92a280108ef5da1716cbd1b8ef6bd28dfe63a07cc08d7603b2f0a6f8d27ab9c9135834f000000000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1230000,"0000172fe9a0cdfa70d80e99586ac8a26034d7100a74c45aa5cb832be780b602", + "01950ecfb016afcdd2a592aecdfb547522033bcddda866fa2594bfb970831b9c1301be3905ebe6385949b62d92a2a5486a67f80772867a1326b1d7b8883c55333b5b11018b3355704ee3ec7b2ceb1d300e5dcbc2269f9d52ad0930338b665493f21526230180dad20d6433d231e27f478fd6d87b504426fd1136395517287366ebcb1d7020000126aaec4c85e36b0044247e535e1e695ce318ed573e62622410c9358240c12a64015057992ff32e68c45e6a52d61acb31bb7520febabc2ddc6f2ac3eab011038121000001933069f22ff9b093f0847f6fb7e3b58d204972870ccb0556ff4ffca146345c0701cdc922f1fcd07c414ce130cebc6e92c23a5094538795ab75960e6c91b89c27500110ea6dabaf89d8b73ad8f794f77538216e023b7b304bad01041e049f2889a03a0001f6338d92b5bedf6a4d14c683d37f83787bf7cf12dee297ddfc355e1e9c5fa7720000000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1240000,"00005dd97b8aa7bba0d0a2743f39b6a483884db7397b65abedb866bdb27acdb6", + "01a006ea40b25c813bd2edb72458c2f4bf6f60beb174130e99e0c41a9fc52885630165a0024f028f1658e6d8e1ce840e335e08ec9ff3e396ed3f616615e303c5236c1100017e10847b26df14703c4a0ce1570be93a645105849cf7c11c7d599feb9cba1e0a016cde298958a09704b51948458769ce0c21f3f1dde8ada99ce1269ff65ca1805c01bddfea07833d489433dce7829aea085d1d05b984d5cd801235479cb0fdb5062a0187a9a4bfde3d57d018a9e19ef25ca3940942be10adfb95c471652ff1c5a6f151017fdb660694b5378b1f5e2344ac6163b2a2441d2c53f7bcd27965a17cb6a9f318000141afd9490a9fd685cd68295f2daa82e36cfb771a535c8c58da82f54c77ecce6d012e5ceb158c74a1ebf1657cd48c8eeee6522c02299d43fd89f63125721823720f01c4e76a525c644f74900f7b17c0d28e08eb16c7cb56ad1d685f3571a725e660390107435e794218e0149a13b078111e36a3e1594321943a4cebe7c8f46198b5744f01f6338d92b5bedf6a4d14c683d37f83787bf7cf12dee297ddfc355e1e9c5fa7720000000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1250000,"000040801b1c63452f1fe4a0549d5062b24f9d96ae8813e38e5310f2feab28f4", + "0169a039edfd7fd7a52bfee3b9f22e45ce2fa92f0647b8ebee6369d03361dbec63010e2dc8b1c13a35cb9aebf979505eda6002212daadf8f045e8dca40eff1181963110101be95a602a76e4ecf0079196a74007c08ada0c45c7782d863d7601838acb75f019c298cdaccd62292e414493e72d79c7cd645c58f1f7de84d1e9adc77043e075c01adf4689f441d0ecaffabe40c5e1bcc7054f47dce82717f256fe29930644ea5460001d99b3bf3eb72f224bc6185fe0622177270c0eed15a62d876ae3f09c9146ee31e01b1c1290a7886d4d2a195f486c5a99925dc91a5d43aebefb627d1358a5dca1a57000001a7f5c68127b8024b2540796774264641e7850bc76ec9a4846dab97e93309b24f000001f7241a0ecd82618b35656013ed349344cfabdfac7237a571488fe7d1d91a785d0141dfebf8493e95071c05cfcc393e2f1d5b7330e675b01fe4e79a651badb4a74500000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1260000,"000012c037027810296ce25642167daf3d0495e1ef9292544f47599531244bd2", + "01c0b9124293756652de4a966de99695192012a940d7d6b992d7d0346f41d64e41001101b270f184b0bfe49109748b3f29d5803fc7911048e04e522319e44a7cd5ba36180000000001185bbb439d00a1608df6dbacb787bbffe024171b342625ec48feb6a19249624b000158d94397ae4e0fb55e0d485e99efccddab71fbfde351b48dffa947ed4dfcd56601799228074fa7b27cedbf89f9e87d8618735307d65b82534420d2da8bdcbc065b00017a1628099066e14ee40b33d887076bded1275cf5e5ca5b04826f952bf00c7f5f01f7241a0ecd82618b35656013ed349344cfabdfac7237a571488fe7d1d91a785d0141dfebf8493e95071c05cfcc393e2f1d5b7330e675b01fe4e79a651badb4a74500000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1270000,"000022d4eefaaed23d27ad85bb00e92349b53c3f81d4d308acc53434dafdf731", + "019cc20f81681c1f6b2f69498744fa21c7c630c0827876d1442575bd443314844f01f9b0c5aabe8e2b3548989f491af0f6233e3a0544ea3c1cab1d4a6a787d97161911015877f6f4efcdc2ab2289406bab277d3d64bb556529aec1c52e506c90391b702101c47ea8ffc001d811dd6acd8abbe9f0a6efbe826edbfbdb787c38ba4f7d6c4954015922275a51b3cf0429324c47e6a5751d408db698ae422e5c1479b7066a9dc008019b1abbf3dd6005ce7984e84c487e216d2f2686ef7d3f1cb57cd4f4da3a3160700001618322b94b1925077a361572a79c9c0829aff3a40cfba5dc9c704f2f82c7ab5300000000000000018e4fbffef00d9093e763988e6385e0954e9ee4d989d9cbc479b6869e5b8f2706000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1280000,"0000187b49dbd8b87d68adc0070413d3ba904df45d27d1837aede0aa50993df5", + "01f84a311a0545720362e007c6d24db4a441f8f233ab57d20a33ff6ed39413ec200176ffc2d684d5dac74187e9c12b8fa6f2e471efd9911fa7d622b76caada02b53d1101cdff847566fd208660256467a6808360821ec413ba71c0f5fff76addbce81e63018e472958a2a68888fff76c44fbc6c11ddcc501dcdf890e2abcb0ebe5668f900d015355fbbebb6bf1af4fafa21923bb56874f996a37960668ad49182355c33e2327000001c7791791076929b3b8a2942d146cede6ac9d41baed85909f516054ae2bc1a73101afa4ccb3d9dd9786231bc7f655ad7be6b9c392168f1c01e29fd2967b8b3ed42801d1de653a26225f1688e62ad68d7e5e03dad6ddf28e57228ab68e931df0523735000197b5d23e6a73d18dc7c4572e84b54919a5d699848975cd758d244903d7798d6d000000018e4fbffef00d9093e763988e6385e0954e9ee4d989d9cbc479b6869e5b8f2706000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1290000,"00000ed28be2c4c802b598a19ed177364bf86029e8b20e43338c721f231e4279", + "01dcebdb26692cc0ce4c83fbc149d28b4a976195f883a27b43c81085e38677dd6d017bb9efb9953547d9a551b52945a17858d7f02ac2dd88c6db14f461cb69b1ad0c1101a1d26604a28e70822d2370b95d14acf43033c80a7a295c994c4b0caa2821b1130133617ca8c9b5c253ab581af7ead440f7594621678f2502218274a3ae0f8c022300000000012813f466c6091789b4332a7a1e11c4a1dbb6ac3937661aaf7a13c089f69a513800000001c7b4318b29849cd650f192570e2244fcfca63be5b129e69378775c813e085c3001cc15ebe4b78d4c4c88a964040c4f04bc8ff6f0b60c29ee081180330770a7994a00018e4fbffef00d9093e763988e6385e0954e9ee4d989d9cbc479b6869e5b8f2706000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1300000,"000009b6907f7923dc9b784b0077bb713a8856ca9d940f57078b44d0f83ac312", + "0172d679c03efebe3eb7acd4625d82a45cba1a116b5e2dd88ff2c3f1014d5d2a1b001101ada81e94f47bb19ba9ec4bc86ca0cb125a02f1d8af1e0b234bca8b4d737f2544013cdd184113354fbbd7a2d4f5ae1ff07166c3725bba4e6f26f187c75411aae60e017649d03fca8a7edd89b0a45381b325d65014c9baec109ad96d09b1a84909d0370117c32b5121b1ff1b0be9cd77f79969f47d5b1f6f4fc1672668412d59687793710001a35bf73c53dcc3bdcec94a0b85cfbaf82426d50e6044978aef30f3c172bc931400000001a8b7759632f7ae59f36717854295bed52a20237617d1f1cdb7e0c6e5cd36b73801c7b4318b29849cd650f192570e2244fcfca63be5b129e69378775c813e085c3001cc15ebe4b78d4c4c88a964040c4f04bc8ff6f0b60c29ee081180330770a7994a00018e4fbffef00d9093e763988e6385e0954e9ee4d989d9cbc479b6869e5b8f2706000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1310000,"0000136813f4498435a9f1d740616be5aa8a7dc5fe1fd893a86400959f2667c4", + "018856f47c75ba5e8509496a617f604a2d690a2617eb81d53301d4438416ba2d3501aef488fed7d9cc36b3db042149742acaa4b2899a27e3e99ed75e5b1d04dfab4811000001248dbc7369022027bdb000462cfd8b8a13b2818407f1e75b4ea0fba76836e661000001a641dd227706e334f2990c1fb6d7ae0ab1c1cd7bb13d1c19678cf77b34292d5f0118a1a61c3ff6921a6c99296129da63589de9287081a6b15ca3d4dd7bd3b1960b01f116cf97aae60640d0db8f6a349692c6359b00306f77a3766b0bc3f870f4cd6600000000011907ef5341493597c1fa3b58a666c886155e5bf7c494b460185b9dd8b2c4ce21018e4fbffef00d9093e763988e6385e0954e9ee4d989d9cbc479b6869e5b8f2706000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1320000,"000054f5e7a90e9b5e70336442e07c569da53095163b6e6029c2cf7012737654", + "01285cbbf70a4270ae98bc5516890c51dd51cccc744ec4d1e519db17eb41a5b1560176347fa5b5c83b7d407683168b1502ed7f3f7c3464c521785a656a379950e02d1101d0f292cc5be7845b20d8fa90cf9c65e60ff95585d20a5e10b2ef03e8afaf1e3100016f44279cb93389feb890bdb78d9936177a4b78f3d8c0e78e1af5629f580dbc3c01958f2a6e2a77dc96a96725d9b7a1f80f954821541aef48e6c00f8d2d55035313000000010b30ca9af3283cf9b016e57245cb05dbab61d4de88fb90fa7ba093d1c6b27466000001586dd16ac1d5d62303cf664e89b6eaf3646699b842fdcb7775a4befe0a0bc10100011907ef5341493597c1fa3b58a666c886155e5bf7c494b460185b9dd8b2c4ce21018e4fbffef00d9093e763988e6385e0954e9ee4d989d9cbc479b6869e5b8f2706000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1330000,"0000190c2db32da38586594ee3c67a95fd1ebf4b6fa288c8407decefc23eb420", + "019d9eeeeb354b992141dbc2558d064868be5d58af3e5533f6071bbeb043160d2001a9704ecf79d4a8cc1b8ad5fc835f8f94dac5b923144e46301caeb35b9c025e501100000171aa99694a34498a07b98c8b97fa28f563f6b9e39b1216ff95d926d9d83be4690183551e05e88466451018abe04d62765a70d8c00688cd77aa753984d70465cb0200011a0c6acb23b21ac48a49c30e12c0c2ff236c568aa99298b21d6e3b2184fbae52010e03fba1d52e9446f5cf49d1be96b511f7403edcf9f56d5f98fbfb51805e4c65000001d7130cf547b1fd0e7d22c0e65a8ffb399895b264ce38e4654ed862eb1cd3905a01586dd16ac1d5d62303cf664e89b6eaf3646699b842fdcb7775a4befe0a0bc10100011907ef5341493597c1fa3b58a666c886155e5bf7c494b460185b9dd8b2c4ce21018e4fbffef00d9093e763988e6385e0954e9ee4d989d9cbc479b6869e5b8f2706000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1340000,"000025a875bb614824947cfefc70701ae1bf573a38f5fd5255d97238947d4d46", + "0143eb695bf2e88af899a984d03b9668a498dfc4a8b2a8b14e6440f012b4d572100126e51a81650da751a9143aed2b5c1b925b6a33298ff5351c98a88863a2e4095011000000011c8b2204d97e7a649d87155ea6c8a3acfb4a84319c51c13184f2e5e60ad4ac0801751bce1a5d0f785ad3e2d52a9c0ff68f96c5c794b35e796f53800bcb179fba280149c5fdce00ca88cfe93977e277217d430d8408cfe0b158e6a9ab52eeb616170e017ee06c533a537c63d89a7eaabfa6fec33781c238d6eb872a22c4cce89208bf2b01eab734586c73041d27689f308ae517910464a9c5a68c387af18610e4e739345201b77a8929fdf0706275189ded91990f6aa2ba4378643744fde8dd00c79dd88c3700000153b56330f42d2d4e08dcd93144ae4c285ac745ae1db18c39bddc72e9ecb87f0d011907ef5341493597c1fa3b58a666c886155e5bf7c494b460185b9dd8b2c4ce21018e4fbffef00d9093e763988e6385e0954e9ee4d989d9cbc479b6869e5b8f2706000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1350000,"00001b9b8fa3e8f35dbc95a27c88ae13b6c5d70ad81c7d4353e7f2b0096d8cc3", + "01c4066fd582a09bb5fbcf66059861ed9f36f1691d7c1fcb41ed7a82266fc853120118ceec161ac411b5ad6b3d7cd82b1f5bab08bace07c0a16603a92f5bbdae143f11018b87a5cf03480b6d690acd18faff6a7bfab53efa674b780aca0c95bcd02d4f6a013006ca9ac50eb90d3523db16687c069a686b54543e28b3d6a4692c1731369e0c0001eb12d00a7c659429d4975dba3dc7c2ed3e07b43f6017011f183f062ec44ced0c00012f09b6c0150c82fbb04a2c231ee8fc772da891f5d4ded356ad2cec5a1f22ff6900013cd5df6c9332673bb41b58927841006313e15b5ae20c58601809d4649fd4565500011698d6c38883b2cb126d40494739b37aebaf967f18c48606909c22004a48720a01e333672f586b25660b3bb2c17a428e4babfe04971ea8a2f681d25496890313480153b56330f42d2d4e08dcd93144ae4c285ac745ae1db18c39bddc72e9ecb87f0d011907ef5341493597c1fa3b58a666c886155e5bf7c494b460185b9dd8b2c4ce21018e4fbffef00d9093e763988e6385e0954e9ee4d989d9cbc479b6869e5b8f2706000001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1360000,"00005be02850d4d4fcc57f1af06934459b2beb0c3e218e41f31b44383e3bc6b6", + "01678d0b7293edb380ca50523867be5ee7051d232ef599623d3d61924e4aca321301417fa1ebb9d2b6da2f0dc0e25ab9ede24bf310c577ba0e093abd10a230671b5811000109071e2109e3be6ed6c0239edd2056d9a2e5c8fd64ae83856b4755363c0f680a017efca9a895ab8e227afac6082c95dd7a3c5ee4fe4a2b4cd93fa8f12403c1185f00013d770b7aadf0040517929fd57019882682e5f011202c811e9e0de9dacb08a70d01a6af4788ebfa845048416f53ade210250f92b296ea5fc6665a157fbea31b1c0f01c6bf084301108d94eb495b9506741161fb5f355c4dc6c525d0b1381bca0d812b0000000158cd6bfa8d42d2279a192eff4c3c0adae4acbd538dc598ed1aa89d3649b0606400000001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1370000,"00000156aa005f16d2c49557dde9ab4e8c8f7988ad3d742349ac7d8e5a540dbe", + "01fcd83cef5a13411cb200d63b315684eed77b927f04b55fa27c1c40e047afad2100110001385cb0c14f7487f79f58329f56aeb7b2b5d233a34c498a03f5f5ae68105b284e011469c7667a9ebf546f41566ff05f1236c5351204daa247a33dedf4ec3b0f0307000001ee5fe176288d4580268fecb68cd5d241771844dd295ffb6d6fc4cdca4e48ea6c01eb7a238c90499e5270337fd35ff163f881cfc16b6cd91d1ee25c525f8c2fbc6101e535fbbf1639162cf3e3ecec356c92543cc726fa448fcf9db6a6db1f8b10d8250000000156b2023a46cd01c35aa940ae27f1b2e163dbc974b6555182ebbb5535caa96e00000001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1380000,"00005505fc6e9964c5046bfdcba3ed94b981c39d81057fd1e159066d86503b0b", + "0190efb171cd6b68931ddc66af391fa6d249949c9a3d7676986a9e7a8cacf50215018b18c0f76fcf6f6a944392986670b7621bffbd1ce27cd35ba69aae48a1e97439110001ea21615a89de7c8e30aa92fd290137326a3b5f03fc0f383d7a4bdfd1ffb880040000017aebf1b220218fabc5847d048d0395a27bd6c33f2a498539f2c238734d766a4f000000000001dfb72260361bb9da3b657bbdd64a3a2f0ac42b25778337193b13bf449ac3b7380156b2023a46cd01c35aa940ae27f1b2e163dbc974b6555182ebbb5535caa96e00000001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1390000,"00007250c54dea4de6ae66a06b19a5538a64a98475c1f890e2845cfd0b0f6f87", + "01315e0c9890f5018dfe1e6eac97502621d3e8f5df1ef1361d08d6ae132849513e0011016439b44bc67c8c3a32c85d9e530fdc5546894e0f0f41cf0ab8fd277e21a5e00a01d8f7c8494c6239390ceeca2eb219c417fe24969b40b298ad27b564cd929a783b01091b1315c3b38d68441398b3e96dd50aa03b8cd37b294558f09544526019e63801e4dd518e1eaa4abff66692a503e00d839fd3e39bae1f6fb28879eb48bae3ec360000000199230f511e4b4d99c329ea47b66a369ae5096e315c6dfb8e12d8b567364d951e01f6c584695368376b5efb029fb4a5b7ade882f817d50bed947f05f2197d9d8d2701cc2e30361cf766c305343db9b7351b44cbea0aff579e4797e1c353bb9cd11f3a01dfb72260361bb9da3b657bbdd64a3a2f0ac42b25778337193b13bf449ac3b7380156b2023a46cd01c35aa940ae27f1b2e163dbc974b6555182ebbb5535caa96e00000001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1400000,"000011096de8cdd5942f4bdb443bdb8ad336342b3173ef8fa05307a9c8cf58ee", + "01eb356b8f01023aacb479679019281bb502378837b529e4c84196b4bc2fe1dd5101bfd7661da8dfbbd4881027bc160622da3bae02ab8fe12177cfb5a806b361e80c1101b526343927e3311e270652277e8ef07a1e4bc13232b036e72439c0e2760a550a000199457bb896f835d2c127504c2f1154362f825acdc983c6c5de02014d5f750c6b01c1993dc26ace60786dce4e86a35dce8b36f1738b729d14e6a9c54f63f8928c03000001bf4f7c13285b53c6f4d31b65356c66edcb4c1b83d08cf64176ebaf4733608d27000127ba4ba033ce8c7635c71c0f500ed0c3834251c3d515ce4ab9e3f2cbdf1e034b00000001b8eec26b0c2d9852ed154420fc2f85c44f938ffd32fa393c3a8a2c9c9491545a0001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1410000,"0000405c1dc5e1e9bcc44a2c21c1bea6c2fe9b61ce3383ca2bc8051e954bc7e9", + "01bf7707f5950a83de2fb957fa3b9633ca1552ec6d1094add1fed03624c3fba87201b6dade53310859a23433655327fb4e992ba00be2fac8449b12374f147e9bed73110001e340d134292d60bdb6672410fa6cef6c490b7b9bb5175262ad3c1dfa6e76c30b000190d8a30acb425b33f454ec3513828c3812d73118b44907087afe0ef168541c030000019fcfdf574e5807deedc3f8b4361ec376e9e764dc1647b5d323179c8b726f7e0a00017d59b5953757cf07c1220e7663814c1ac65eebcf2ee4be99e33d8adc1f9a7d340143dc3d051726e91ef137ac122c200c902f6e63bad15893a787cec4a53340ec01000001b8eec26b0c2d9852ed154420fc2f85c44f938ffd32fa393c3a8a2c9c9491545a0001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1420000,"0000716e6829dcda9d949cca382e41034a038b018a418bb6cec025bd7aa6b6a9", + "01fbb03c5fc14119c1e3e24b2c66d4cc1a82ace508d3467ab819035fa89997565b01011b40ee0e3f6b518ca806700c2771de389eca9a195d9d14f9dbdec9ba27c33d1100000001d027210398807f90b73bf31bab12579283a5ea67ad6d1b92998515816ce13d080151074087d18a17ad53df0a28d9305642a4634de09bbbb373206ef5ffba4b9e4801778dd801d13a860b37d093f9bdf3ab072a6c5488ae22914a909a67bac6f86147000173ba88bb921a56caeb6726652bb0cd56523fa696850ae5511f4d44bea277532100015888f49abc2b90639eee6f94afdca5a7e0de9e0e79faccb8816ccb7f22c31e34011ffd1fd0a3c9d18738d3929c8f0695bdc9751435cf0908119e921193f29e85190001b8eec26b0c2d9852ed154420fc2f85c44f938ffd32fa393c3a8a2c9c9491545a0001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1430000,"000009d80d24169a4bc873c9f878ba7b08a0fefc85a40c1048721b343f249dbf", + "018c9f10105f0e5206e14f8b1124e3e13a0088390d7b87bf9dab3348a1b38e8c62018341feb6afc028676f6bcbb48093bd429105452095d92f87f0cd12feb283b90411018cb46716121a921f1ec57fb754eb2314aeea9b27ab76f99c716cd54dda2fcf1301e28df64ca7aa5a871f7166a9581c29cf4d19d6113695ce47b916aadbe6f1b75a01545677d3b5ff42d36c28b38462cb568cb4ae303af0b9a92f322b4dfd289e587100000114a174297311bef192c53ecc1d1da36af003f757646494503eaa5e8716d82865000000017acf7a5758b20ede7ca8ad7711f0c32abffc98c2f6befb03bc94076ce1414b680001f15c9469160f9e689fcd36eb042d7ddc9c985e81626642cd66e7ee447243674e01b8eec26b0c2d9852ed154420fc2f85c44f938ffd32fa393c3a8a2c9c9491545a0001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1440000,"00005242530b3faa4c464a684aa360213d185b5532279e50048c22478b587e4d", + "01c7b77b8b27564d7251f0aa0ff6b3d0b38d1c70d856add73b4b8c34253d65cf34001101fd0fa5c23f12d67188dd524979e8fdce83273a0d49bb649e1e1d14767bd576690173d5924f5349b002f9e0ad6422a3848a4690d1124dbad999a6de031bf16a1623016449a679a83e371fa8b710a2c0c3ed0c1a8269b6218da463f0ca8bae47abef5f01ea9c4bbb88f83794228c17c3bdf3b5237bb0c95e5ebced5b5cf9625f5e3de50301b0649721cf1ce11427c1b3ad5d2fb93557398bc16380e1db9de1cabff073b91f0157062b0c347a7dc8b367a6253ea547c97b639d6948627f75fe5569480ae70d1f0141077918a6e143b54b3b479739dd84d8a9a865c02aafd1a75c8b5b8dc74f1c2500000001fbda73698f629045391e4ba596c88e834309ec144b67b9edbc45f994cfedc40a01f15c9469160f9e689fcd36eb042d7ddc9c985e81626642cd66e7ee447243674e01b8eec26b0c2d9852ed154420fc2f85c44f938ffd32fa393c3a8a2c9c9491545a0001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1450000,"00002ab142f116f52000cacdd3f05da45dc7d1a188d04cf57095dde1e10e7c45", + "01fd0fc68f6837deb6977c0bf4d8aab2c88b36ca095ba708dd9afab549a680f34100110001fabcb4b63194e9674b608eb18bd211a396db0040c353ff13f69388f8a09fff1201f987d9deb641864370983d216f21aee81ad28c3d9feb270a6adcfefa9715b81b00018bc9b65375bbcf4e15b823cd46e6abbcf1f17768e521ccef3c8ae83c081e724e00017a2078f9c68a9a6d3134173fe4a8b1ad7deac2acd5acb8f77bea45bc689e324901eb1868d0ba0637b9788737a4f9286d63691e0abf78b877b7f3ac2d256f4d44530155319f3f25a6e15ecc6bfec4aa8eb4f4a678c7567489b5c9a89dcdc0dd763941013e7c2e14a194f80fb5e1615d96167605bd0699116a1ba50598659f92fad29e5f01fbda73698f629045391e4ba596c88e834309ec144b67b9edbc45f994cfedc40a01f15c9469160f9e689fcd36eb042d7ddc9c985e81626642cd66e7ee447243674e01b8eec26b0c2d9852ed154420fc2f85c44f938ffd32fa393c3a8a2c9c9491545a0001a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1460000,"0000457bf2734cdb31c828d467d6678140942cd27fe84091543a33c1a786ff0e", + "0175af74b7ebe50e2866a32518348fa51db1ab28e590fc45a8e1af2b0525431c4300110001353095563636afe3ced92178885d5e9f126659ce7937706dccf2e3ba35890f3001f15886d581609c2efd8d7cbc36157cb6a5b8b52f13e3283d63fb9a9c512e1236000121f9d63102662dc3f4da65dddaa94356e48fa445df398d028a93facd9b1bfa10010165338b634203f04eb18bb846b32ef2db1c1b3d78547061f7f65f291926883f01533ce2fcc50bf16b177033915681902edf6f8792c16406c6cf254d4da027ce09000001c13ff2c41e27b8c7b29a2411b51091c491d13096734b52a0c4c5198e2dd22a2700000001f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1470000,"000027cf3973376f2e39711853c969763b3a9a5f614c479d36a82328b4034714", + "019f8e4755a2a55ba00626cea1a890114cb7011688cc27d3c73be9d1e42920a705019423ef697b58aad06889ea2567a75d4a160098cc4a74a65010fdbc6da3b0ff25110106031917dd63583fd6bae58bb95f120d64609ee98c47236fb990058924eb071d0183fc0f4b67db4b6dc8e47bd9b48c3b835cf39462577cd8800820ab5523319530011769e7cd28d4b02d5ab61c7fca21bc89f6e603b2298d8e776ebb5b010948f503000165ef0b969c292eeb8611fc18c2f655336878a62f7c62904bc77fa68c55c1ee07014f04f72e562f1a90ab6094a16ce585a65d11fe592e493470295c4d36792b5e5c01a2e9bdcb4b82fff4829b6cdf6c3f86b944e063dab789a355f50e1262fc8c8a30000000017f686f02f5813deb57fc2d5a5548e19127c6cb4b35ff50f924b1281c9aa78f6b000001f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1480000,"00006f49e804684d1c7e50bf8e1234fc1fbd123af27ba7459f20c07203b93054", + "018084171bca95b61d5758f62abd76a8d37d19549ba7df0a5212cedd6ec5c30927001101023659c63bdc4e618ba69de5c930231085e56a342ab457801e477c1e762ad22a0171c37118d547f7de19baeca66dbc45150c63cafd496a786c14ae72ef6c6c8943000126f6113d437b3ccbabc9f9b6f12810f87f9a43d5feb3e41ff8d4bd38ebcf3b1101e1ca1d868733f00962df197625d303979d9160feb995756e1f3be9d8ec9e1d6f0181e64b840173db97351cbc0114a0f4176252a9a01e5a85dfc9f0eda6b37ad00401f33858db8baae4d41b39f0dc823930ba32ceda990354007e9769a27c6c5d9b090186da13bf8b4e744b1d79053d8921b1dc7a87a9d92909d2f05d169f3e44e8911a00018ceb7f1b7fe1ce4ccb8ef2e96576cdad316dd00e3aa2723d409ba930821e6e15017f686f02f5813deb57fc2d5a5548e19127c6cb4b35ff50f924b1281c9aa78f6b000001f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1490000,"000047018d94461b91f86e2591f8a175b54d797fab29e8cdd07a38d546cd2ae5", + "018204a6bd88cc59f963464518cd32669d43968373826097b75cae73886f6b4b730188ff9e586915ebe7cca78afc4004f0fade70620ef9f9c1c9e96cb58c26b6285511000001f8e133076387895d6999d9c0eca1afe4a690beffacef90d53bcf668d26e37e24014b7667d7ad9c5b96b06b2775c562f820d24983966557ef03e0d2060b0fc3933501312b8f8c3e24e5a562572d6bfd8f0ff7dd109406f4fb7da867898900fa974733013abaf22f0cb8be7d30c026755e6c2fd071cd4141495c773b20987c6edc7e0b3c00000000000120c98065b8f9bd1d9fb966a21ef0ae64894ca77d346f39b69d2347fcb1e9495e0001f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1500000,"0000324248e01dbc3a4dcf9d3065e5e1eb6c81fa8586d4b82b32e97cc43d8356", + "013df2e8af2a777f4f7c236b490196275689c1f9f71e90b5cc869a490dea279a4f001101fdb8f6a0a1b8391d816c47e577d37fe9fb778239c0967a0da5fc6f41787ae506000001d6066c7e703b11efa05ce2a9a1ec7cb631b8be2078888d1bd997ca4028a5f6200102c6972e8905cb719d0fb3fee0ad9644d65037f524fceb77b253b5e93c43892e0101926882f9e5da06906a0e75006114afb906df5121a1c817c67d2f3defa2486301faf21c97997d74216676cd3f30c7e40d119aebcf5f3691d9f74d4c3aba4cec30000001f72ec1f9708aae1e182170f836b8fb18efa29c5d01f946817bc895f44619a164000120c98065b8f9bd1d9fb966a21ef0ae64894ca77d346f39b69d2347fcb1e9495e0001f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1510000,"000029fd6680e674dad7ad6a5fd3f44e73287062d47bb97c7644db3489a4fa36", + "011b70d80a3fd898c976e9bcdd7963af6fe5e2ccd4032d02944487a7876744463f0011019fca72a391e6d3ce4691f063df3aae4575580fd0a246832cc249021e9d81e81101549daefc81255a954366231f54085fdf9a41d50750fda3331144d820c2ed0b21000001d57a3aebaa94fd255a834fb254266b5397acdcf399a58aba1202181c5ece060e0176091ee8447518d2ede521fbdcffaaa8dfc0ff010842f88808f0195ea20b0a2c0000000189c1bce652768509c69e4897ab771ee3f11ec0338ed8cbf296de0db0b73a2f5e012b1ffff398b65a8ff38706f99bac811b4ac5c615650864751a78c039103719060120c98065b8f9bd1d9fb966a21ef0ae64894ca77d346f39b69d2347fcb1e9495e0001f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1520000,"0000037445ddb55d5c28f2a4446780e4be0c7f47cccb626bb0fbb8a3a10f728a", + "0167fb58755e6fed3b2d4abe92380429b45cfbe77c928cca1b798e55e38c57ab28012a0a52189b2d8eaf46a741f96f36a7f01e709bc2167c255b08b9904123cd3f32110146507d2f9a9c49d0a3d5be03f1a48fa6f67498863cc94df7cbcdddc7135bba5c01fcda7d6bc07006f6bb765a1b54654b67fe675da14b32a994c5d0019011408522016ea9c7cc13a546eb4708a0916194345e4904eba62107aec7f666213571269d5a000000000134f819fd2803bc96a32665a9194b62802a7d21228d76f9f61e5c21b640cdd36101c6fa3059443d0229839abe0b789484c967fe479ce23ad2bceb6cb1496449f24c000000017a2fb4dd5532a290ee7d9ff786c9c8987b6d162c747657316943150ed325ff4f01f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1530000,"000059d5ff92b926dbff5b3d54f35a544b02501851c58d1abe75bffbd41dfc5e", + "0114292fc7a8c583596777dd3c1782df1b21afca3921b4dfb5165e7cfbcf78ef0801ed7b6267a82bdec3baedcefade053090d3d5e3dbe82caddf9da6b4fcc4cca45e110001dacbc42d44935559987f040b24630326536baebf09fef3cfa19fdbe266ba2d3101f43cb33503dc1b60b3301802c675f1efa6253e6fb18f939e3788ac61a37fde5f00000000012ed20127bda53d86f68cb8522fc7ce98194b6482534b7f5895b3e54e20e34a000000013fc4c32fd10d2cf525adda7f963a577cbbefdd52b83866f4fa0d4017cb47e30800017a2fb4dd5532a290ee7d9ff786c9c8987b6d162c747657316943150ed325ff4f01f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1540000,"00006e5ef028324567cb084b819d13b8ff2ba8d2d1f4d682cfba675455007a3d", + "012d05dd4a51d01d74705bb9de21b40566ef0cc4db00359cdf3d98898d2eb57a61019b51839a2465e1dca5cf0ec4d5325cc68c736ee23dbfb2de15c5ab652faa614e110147876035d5f388dd9b5c5c96c0d5720f35c5f0799f0676f1b96c83abdb25c0530001c4d8809eb506a58e75eafb361ac6142999b7fdb78273b96ab119519ab8d70e1901c50698e857b87e911cc435d446e4c8bc95c1d7db0fc601068f95289e9d848b2f0000000001e120efe64d6ca95bb63ea93cb884e3abc40d741bde187463085cd6af7203c0150192f88ea9efbf9e3904ccfecf82bd175cd81e56c6ae4903c618cbf32f3996c73d013fc4c32fd10d2cf525adda7f963a577cbbefdd52b83866f4fa0d4017cb47e30800017a2fb4dd5532a290ee7d9ff786c9c8987b6d162c747657316943150ed325ff4f01f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1550000,"00002f2255e089937ae2c11e0bf2f096dd2e4602b9d1e2c10747ade07629b45b", + "013fbaa592b8f3d72b794de2c90e04d66c487a7966ff2355d19ef7e639106f322800110197e2843b45d58f4f5fbe8fa6b350b72c6d98aec9b8a82166f5c3dc1ff112884a01718689f6f5397d7acf54ab84634e16346ee0be0e4e3811afb56ff3d786990e1a000001794dd0583851ad641ac4139c16429e4468e326799c32e420bf595c484cb077020001d82567815f38e639498ed4f766f1bd48b93ae50255ae6a4ab9bd4756d56c31420001780b8d98a9a0004715af8ae0b00312475c17daf3cbf0cf835036c711c6f9172c0000014317855418e08b972cb4afa51c885518ff305d670157022a7d0c5a699093553f017a2fb4dd5532a290ee7d9ff786c9c8987b6d162c747657316943150ed325ff4f01f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1560000,"0000280433109582ea0c96fa1867c86f70600dac6f9914a91ea049969594b809", + "0188f14e43b77cb56acfbfc5f998c9d74292ee87669a89f5202cd3bed476cc501500110001a77533e5c60c34510e712e543a3df54e416886b21926e543e8328f29f65dc10d000001e50885ce62b4b5ec855f4791c9900113dabc4275b5f602721f33525b5d2003040000000121c19fe72397530dd36196fd6c114f46e096ba21a9ce47da5da1570d7b151964010898faa9946cfd21e4007f750c13993b41ef5597cbdb8d09bf502aaf5422044f00014317855418e08b972cb4afa51c885518ff305d670157022a7d0c5a699093553f017a2fb4dd5532a290ee7d9ff786c9c8987b6d162c747657316943150ed325ff4f01f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1570000,"00001e8f4d2fd86654189f494ace0a835f855c960358d901b51a0eacbb391543", + "0126b968e88a841853250c1efbd515588dc75b3fdf0277bd08364f904f1097924a0193f9f08be372fe84942633d84a6406cbb9f5247382c8c7fc4d60ebb4840cbb1f11000001a632b27a796c02314e40a4027edd9549544947130e11357f624dfd383dca1b6b01b1334be908001bad453b53fce2b3d609427cb1fc975c2de5931680b32930ac2301052a43fb28154a2cc6a1f089f986c8eee2ffeed90931d34f13501c805456965901f21749daa0209e0615432183181334a3ea8e9e62daf5826601f92022c1bdbd4b000001ca235ab3479e884a41f513a8f18b4d07b9f04ecaf52a14dbf6a7005c1718871d017a5da502646a82b3a627a68aee56c5883e2a88c1e0e30d91ebd8e9c59d9aef620174f3648ffc20e7a190e7ce3ac2047ba266c2c96701bd17c257d22fd2dbee615c014317855418e08b972cb4afa51c885518ff305d670157022a7d0c5a699093553f017a2fb4dd5532a290ee7d9ff786c9c8987b6d162c747657316943150ed325ff4f01f701702ce85b47572d8e82e1fbede9edead61b540aaf05c6c5c7fdf760ebae3101a76b8a4f29655a92725e524223d4be4c7c2a7a24e53818607d224aa94f0543230001a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1580000,"00001e5033df33216358904bbbf6cb9cb3b03b2956893cfd8c032b4e5c4ca08a", + "016f3796ce2c86ae8a58ba45211d897fa48386272d89927314e623d60c350a290b010074a2f7802d0f98b5295997ff963f10493180306570edb89ef17e21f44b970d11018753574a9ffcc4cb054d443e13381a7ca4e92d5db400b0ed214583e27a47b24100018301bec012fede8ee4978afbdaa53117c6036e9405cd326083ab0fffa382e06d0001d582be32e483e5de0e6a447f9353b730febd0d8ecf4f20259a8642495597d75b01bd92c770424657651e90a00ef3dd88c3e5c8f32341964a27d113e4aa63117c360125c4608cc8232e0edc2d2b4ecbd1553a683805dfc2a3fb0b523ace33d351281f0001d06bca84cd8ceb863df83e466840ccb1a09da919772cc78f78077cb01d57c41c000000000000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1590000,"000047707a12173afcfff8dfa0ab9790c84a2f233630adb35187656cdce4dbfa", + "01c16e082164d1b1cc17393772e6fbdf4893372d1dd36dcb2e8cc5099278f2da5a0011016e0ccd67696f26957dd6d3e6af477726fc76268194879f431761b1c2d812e555018fbf4a0a9df262da1aadc701fcb27b92decbdf2a954215e28f0fb10f24176b1f000001f6440ce3ab28371d6823ca81e695fc7d67b2fb91a45de520f4cd2cfdbbff572c00000001b6bcc9566a8de416f5be71eecac0a057bbbb060c695030f3f63f73e19cb99d4601e5140ef34406530728f8cc2b665ad328cdd82df7cbd067c1061dedf06bd430380000000000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1600000,"000026c04739309ae03a6578613b71fca0572c9793521809967ada579cc1830b", + "017e2423f68a06a32178e3e8fa29d711644235e51da7d90615bd95ad890f3c7d0c0111b9b89b883702676911620be3924fdaaf5e4711eda37392dbaa0c7f1bc574131100000001235886ef97cad5176fc516f8b2f5dd3c192a9aae0f93865c2e7f482a96e14166014b767f576c7ffa085f5d4ee00ce3e644b1f5d1dffe96e6974f7d3bd2ba403e1f0001dd51ff2baab5d0c254a3260c51c0fe7acc7e065c308743af71b9775b24d3321c0001444bd27d7765722239930535dad5efa655e6850a88910f7e77340a8937bc352d000121362752491a51b43b800de2bb214f1cb5d454db8483d4514d15ed457fa7e15a00000000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1610000,"000011718a84324cf4835a0c8542d104f15c1be0163a62964a2c5aee9d74b009", + "0149bacea003fa6a732cf5212574fb9330c75fc9f477064d7807d452cf6bd9a362013c8dad684553f990d1fc7269bbb45197accf7590e1979b9f2a50f2add3b89a5b110000017127930799c8ffb01ee476cf50244641730dedbe56e3d16cf1663a5cdf9ad6330001a45a8a6c5328f85b1c488fb97c7aa589eb1f2f01dc1eeb90e5eac5755fa0f254018982c3547718a5cb43b9a966e9d7830cae58a525473036f2c5a4fc462141bb530001c58c9ccab9bfb4c039daba04fd136ff5e09aafb8fa459a2e32ee1f28436e7f67014bf530ae2cd2120b9b94497d5097a8912b62c83f0fea7f1b7c519e61b2200e4f01090ae990615eeeac4eb3ef858d30a76a360a3ddbd581f45f1967f6d675f6360e0121362752491a51b43b800de2bb214f1cb5d454db8483d4514d15ed457fa7e15a00000000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1620000,"000064999f826e0d9f30f02b043b9a4201c1e131780a3ff66d942cd77382f5ea", + "01e00d2d04e9827085a584e87c8860800a48ab463f7ff5a9836905fd041cc890590011018218f690f58e286a8b594d03ede509ae7910908d5a60d64757f4f1ff9aaf3c4101c8852f7bb10e07196ac1de5d2d375fbd16acc024a2811e2f814279d6dfe6a743014bbd79ae66e50b3d826c871009c7676eb47274a16282dfe5571c1c1bde53012a0173981524e831e610d3b314645f5bbe9e948b39941abb30fca1081f569b770e6901af4b73ed63d8207f8fc9bf13769317dfc9f2e7e3f7e5e73e952f6c6d29b278060001bb64d85bf374846afcb4bf49f51e3867f469e4e63a78ba77335f4349712fca5b01bc54af8df78685fbc8bcadcbbf9a22d1d16523ee50d466d162bc58f2f96fec400001264d5472eb1c8c907f65200cbd9f8440b45fc919789d0652b0fce23b38d1c80100011bab2553b8142c703ee617ec525ff34bd4e098ade947cc8343390aad500c4215000000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1630000,"00004965b5928360a6dc4e6915f09aa1b649cfca8bc86bd34852fa7db5ceaba7", + "0122b5d5201b80a8c2b500a80ef363c38e7ccfa12fdb18b5836789f58a77a0252701b058b380df871d022d20cc97735aa570af45c1e2eede0091cf84363c52edfa5311015d97ecce68354d9d96bc10a3e339bae0aaa4b4a2987d5bf15e9a045725cd6764013087822a862b1eb7ed670c1ea47e40233566e4b8494da026bebc87a35deb0a27000115f28cea5eb123691ea4628cbde33d4879aba244176adde96972b1a883d110310001d31402e32343aec2197ce10ea373a37be0ab9a491c328c078553361ddea11d5401f109dd62721af4e137efc9aea2052e073e0084ebe2960e4d024ebd59858c374b0001c6f1b3b87eb84f77b5c4bafc7e16e73917642a5f4ce4ea37a9834cdbf281b5500001c537776f934e9a8c95be2b59208ea0f7e3138495ed27b28ccabd91d4f8166110011bab2553b8142c703ee617ec525ff34bd4e098ade947cc8343390aad500c4215000000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1640000,"00003b7b3cc716f0273150d2f6a8ba1616290975d847df91929dee69f730d5ad", + "0144c4ec8b8d11a086db89ccabe318ff31cefc9e1a2131f40887c73baf571c9036015541dfa6233e6ca23a574ac0e6d1eabb8294dbc2d4266f473f3ad74c1acf7c641100013cf97c0bfe7e809a19431b535719a52e26dcd5168bc0de285a3d82c4d890c5600199588e5c88a5d2edf2014b3fccfed91473109c3b98da8bb8f9e762b3bc1ba73b0001691d248730eb513ac8f0279abc5ba1c5a313bdc340d43565c32f8c040adadc51011350cc589d690155cd43d84d635e27a20810ea5d138cbb38c9ebb60f91db4b2b00000000000001f04bc8db2fb5e4993ffb1eea33fbaee2428b492302144ec69f6cbbe1b61fe4430000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1650000,"0000341800955e594bdea120916a3131511c32d586590eda7a18ac1763a9434f", + "010a6754c345b05498165280fe64d70b97e1b902beb20091e9f716b3db35bdb74c019c926c9c81a062c25976474ac89f78bb07f9f6274e2b7229f44372a320e22413110157c5eed1456ff634ed2e338803215f64f3d0e46b1bf8266ce441fdeca7fff11101f54bdd00bedc51866b128e185502cfb1aa0e2907c3f9b8686500bff189ea841201868c43bfbb9c602f88fbebd232acf83c32c09a5de3ebaeb2f9e7ad1bd6b1621d01e9abdcd38a947b67279a66947a6c0c0a321aa73225061eccad44a0da554cbc1e016017c91738e7d366b64a8d23ee2dbc294c767c6427fc8ae6631be682d497f61101cf2e2b2e4dd0450af81e48ac4d7d1f3821cc5bc1527c8e4e6f43801568034e4100000001344cd97d2fbc6bafeac55325adbb5691f00113aebfbc1af3f212d4a85dd55056000001f04bc8db2fb5e4993ffb1eea33fbaee2428b492302144ec69f6cbbe1b61fe4430000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1660000,"0000123057165ad76c5765d63c078527b8a9a7d3ddf80c781f0e07d58375b8bf", + "01ae0c581f115cdac41d5028a468e3518175b81a57d65c51164447bfed7320b50100110157fc124fe64ffbae371050e7b3f8c78cd0694849c5ba7bfa786d03c232c0c1220000017f60d0135c00cc3284bb3d0ebadb0ace57e48a2343b40f1d27877f04bf66b05b000001d8791da9f17bc3486c0f2c9fa616c59107b97dc40e68c08ded3955a8bfe7505800000001191810d8a90f177f8aa3e3fd15a20f6a27f97fe226ee89bf8cd4f08fe579a76a0001f04bc8db2fb5e4993ffb1eea33fbaee2428b492302144ec69f6cbbe1b61fe4430000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1670000,"000060e6cfa6e71aab1d9d44aa1adf456c33396edecb5418fd9e2849762cd2cc", + "01f1186c201c01893cc22ddf8eef8fc521b8d5bc1ebcf63f3bfcfe7f1d6a45880201ce350da52d9d9764fc195ae6a358a0413c5ba32e0e2ad1bdda242380cc6f690e11015f6f01ee10b061750db1b8e60000d2779c311414c61579680b16f2ecac638b370000011a1c6e87d832e7922d54f3c0d3b6d1f13851ab9d3183f7dddf39afde8519c4250001241861013536d2222d3adefef5884bac420d36cf97606176d4bc96f30a93ac4e0000000192bad32334bc19cd5377d96182f6b3b06a5ad1e6d64575d44d0cea203d75b62b01191810d8a90f177f8aa3e3fd15a20f6a27f97fe226ee89bf8cd4f08fe579a76a0001f04bc8db2fb5e4993ffb1eea33fbaee2428b492302144ec69f6cbbe1b61fe4430000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1680000,"00002721896465b0ec8af7a2069a193eacc5a7a9c7047456f6001fc2644169fa", + "0106fa634e29411c219f137623e2d5176238145b13f48913ed90f3c39dcf67495f001100012fb4ef6e19c92be061b4e05063506fb0843e58f6cb3396a68df7e4bfcaf62c1b000000019a6e03ce31c11c61fceace9bb3fbcfc8a508e357d833de8a6ce0a9a4ff37b726000000000001b0a9988f98e923f4d1f7e47e5189129437acf786083ba93e478393a5fce6125c01f04bc8db2fb5e4993ffb1eea33fbaee2428b492302144ec69f6cbbe1b61fe4430000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1690000,"00002cbcb844d35a17f8ebd41a9a01540c7a16b92e640c6889965ebc349026af", + "01c624811a5f9fd5b94dce74908100c80005fe43b28e4834da4fc613ebf732c12001a9389beaed232c95f517fb73049dff4e7ac56ea23a2edfd5f3aebc8a7259c473110001d44601c31afbe35aaeab21c7cfecbcfd456c309d3a11b1f929109daaeee2bb33015885a277761b3ad13c1898e850211cc333cb43f95deb5ea1469be4eb399932160113153e36269b2e4e18aad7377e6f8d13099dc9e345e57ecc692c74bd86d80b660000016519417f8a7f0eb654d037cdf9ea1cf6fae27842c5f27c24302abefd416b541e0106a189f1fc7e29338ad90f2c1e28e18b96ad422902c0b790df7592b6731f2d3a017779f24ae8aaad7e6156a0c8ac25e697e199c417043c46be0bad98c39305d25001f9c0d0f206e7ec4a214e6dc7bf3e8b3ab12026612cf3185eaa707fe1389838540001b0a9988f98e923f4d1f7e47e5189129437acf786083ba93e478393a5fce6125c01f04bc8db2fb5e4993ffb1eea33fbaee2428b492302144ec69f6cbbe1b61fe4430000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1700000,"00004a95516d76f6982540fc933ccdc1a44ec8f1edf2cb7c965aec71766ba482", + "01c856760104a3a18589fef5f87928430dfc292c0ffd3f0425585f865811ec371b0196affdbcd70835156fe7e27328895f12c6d99cad76e7dae9998a841bd5185c1e1101336f10ff0b1789c4a75b5d8821798b2d0f6ae31639867ced4587fef903f41b72014a5f1863b910dc5ce1beed3d0a40e4cc0607f3d8c525ba1d3a06181c6c03c7450001bb73e365268613a2e247e5f640ccf21a06f57842365553fb7355061f3e40cf3901f4a28a1436e5194214a81758f60fa6dbccbae0cde6a3be4922642657e7988f700000012c5517582874d2352684e0aa44de639d760d574c65c10eba6de6ec5f4b2d071201b5234164d45b86cc758fa56eb934d6623fd31a50384abb524d33051ed35a0c36000155098199e121a6a5c8a8c1fe44493f86ee1613f1ea18fce926abb492e3aca84101b0a9988f98e923f4d1f7e47e5189129437acf786083ba93e478393a5fce6125c01f04bc8db2fb5e4993ffb1eea33fbaee2428b492302144ec69f6cbbe1b61fe4430000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1710000,"0000226ba88350d59493cc577192bc5634bb6376f91a55eb8df78f9bada058f6", + "01775c291d988094e3491121bb83d97e0504e67f9ef41758c823a34fb2a51a8d5001041ad1f7818f05cbf2fa5be1d7d569da7f17646530d0498d9f3e1490ec2c6b5e11014131433d58458115ab282e8ffd8172f27c54ecbbea6499ca992ea1168629da2f0148ba246d4aa9d5dcd1926c8d0eb75348685d665e4a99f7b6d0febf1064b53b37018df7f87458017abf93868e1d57bb0767fd1fe1931f7de6b5f3fc4ba2fe12123f01c177db8a450ac6ec99a025ffc45c57d926a0ce280a7192e917978ed61791f467000111cc976b387187a2daecc70f310c07cb7fa49292c2c706e2a95ec50f6f2e245001c0ae142eb9b16c5d069213a714c4a33c3b50bae4cd4255a916818086b7a0284001c8bf3c2b4823a06ef78440d692162fc918d59a0e32503e09183167df6cb91c290001f41e0d37c9664cf0218487de26b7c55eb582b516807406c065582ee5dcff223b0155098199e121a6a5c8a8c1fe44493f86ee1613f1ea18fce926abb492e3aca84101b0a9988f98e923f4d1f7e47e5189129437acf786083ba93e478393a5fce6125c01f04bc8db2fb5e4993ffb1eea33fbaee2428b492302144ec69f6cbbe1b61fe4430000018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1720000,"00006ea2d6a01d1090cf17bb92e03bd49b87e7438fe78d705f587488330b537a", + "0155d5337c0ce2581931758c8aa403e5cc2e724991f607795109a2de1359603d0b0192c6ecdd568d8474fc07ec0cc9aa9ecae918a3aca80717361c7fde57136525121100015ae324b8d5745f4a6ee292655151259d4e70fcfad0d30f253283d999bb645107017d9e5f4e01e6a05260b8f54c56988d1d24da4ae91500a5ba34d40e4ee6c6a1460001b3a6d3db3d331d4bb05b06f966bbf4c3e1111880d8722b06e44d0be47b32894300000104e8d66b26da5f65938dc4470fa8d651a9b0cc59f0f4fe932d885ed5d152eb1401037c4dda17a00ac96720d78ca5017d1ff430e09d288bbd4d049067891a3c467000018325e7641e7e980c5432c2e82712dc399b5a090d895cce8119f2dd4fdb3f3d73000001cea78be33468f15f54695d160121cd8c1901a0f195272d8a949b6e0ee24cf20d00018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1730000,"00001e84c6ee75c149141d2b89d4afbd5e96e0be9310d4f50e051b69a4259e09", + "01e0587b584277c4d16ccab04b7841cab16385602b406e6f95e92045723f6f3f220158a6dd3e449701d914daa991206db220795101c0a7458401646965ef41f95d26110000000000000001f8b5dd43e659c41edc4b951429dac348969e7115d453fd4a4f6550d2b7239b1900013164f78b29b0ae193a749a535f2d1de7ad1ebf583e646ece63c2378851158c1f0001739ae05b915866c82502779f6a7d95179b0801920bcd7c0d21f4bdab09da814a0001cea78be33468f15f54695d160121cd8c1901a0f195272d8a949b6e0ee24cf20d00018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1740000,"000052f50002d0f09d7cf561a3fe8750f4727c3de2b4878de7aad0ede2b4d2ac", + "0155a266f1580cf7db6e05b547c4e177ed4f9a9c43056edd896fea2ac02994d26901958d01775cf6c5272effc33ce4253c9b8ed081a5b4bee568bc1def55b278e1351101567410220c3e0ad41f61be89ff267ab9d5f764f1dfd185d5ebacd4d25203a86901a46514418f4257a0b0822dea7e83593d70d42080262b06da91b5e6bd4d271524000170f34db45507cb8e859dd87e6660007c0f4979c28773458f21c9b765b63b4101017393f956a5a528de7f7da9ad43633c7a0107d6f1e69282edbe0fe4e39fe9b041000157ab459bb17d72c4a45813e26b9014e1f7e94ee13621dd9f49d56740fd19d1090185cada08202bbdab9cd53a8a7d2308a3f66b57dbc1cb70f7a1cf769a37a8521801f35c0254c4292db842410ecd19bfceac7b5f6af8208307025942903c6e0eab68016c79c434987952b98747820dbf22f6044bd0d01999a965b823a28f597d43241c01779536bcdeba0f7105ec450e8bec3c9204aeb3cae17b0fa386b83332acc4015101739ae05b915866c82502779f6a7d95179b0801920bcd7c0d21f4bdab09da814a0001cea78be33468f15f54695d160121cd8c1901a0f195272d8a949b6e0ee24cf20d00018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1750000,"000043532ed42651b85e839a7f0069955ad773813b4d9e03769a3457ee153bba", + "016baf979f9576d41d0537611c9039fc5e9c6911bd477182b953255b98490d992200110001ce274d95730fc5a7ef3496394bf07faddd87a37ca18adca45f319c018d89820c00000131487a8ca9b48a163f26276811c5a2a09e4f9e37842235da43b56ca41129cf5b0000000157b177c41eaa5a20256c578e5726daf92105d56d29c6917abb04cd9ff347075f0134a89f60187b6b9755a84c9ebab284c5cfe630deb3edf4f111810043b4de7c48000001b845ba458057badfd0d240d3a6b3a2caa61a88fad6ee892055edaccea8067e4001cea78be33468f15f54695d160121cd8c1901a0f195272d8a949b6e0ee24cf20d00018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1760000,"0000545a45b8d4ee4e4b423cb1ea74d67e3a04c320c6ea2f59ee06c08f91a117", + "01c1e00d09847e164ddf1d6815cad2ceb3caaa5a35e008bebf846e65aedcce5f58012c405c629b7e711ea8e1d0bf4e2631c0cbc5a9eaccc7898395e821c4f0840e3211014babc14bfd59c6d17f3aa1e5fa7e13ab759495a71445e8f03c50d81912d1e60b00014f227b6bdbc98996a41de7ad57be2e26cca701d1f0efd39a1d6461adb906352900000001aa2225b6debd1a2b29d86eafc35de6bebeedbc36c4580371a67e7cee75989b4b000001ee44e084c71666ab22ed8d300c70ed636d18b8d60d565c874faea8e087da37350130de3f21de078c25aaf3df32a26c8c1075006b81d5c66dae0ae4836c9e68fd53015430bc2b9dca7e8b71c18d485aec3bb3297c5ac94ec488c1f432a2e91fea950701b845ba458057badfd0d240d3a6b3a2caa61a88fad6ee892055edaccea8067e4001cea78be33468f15f54695d160121cd8c1901a0f195272d8a949b6e0ee24cf20d00018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (3093000,"00000043ac606b0dce50c3e7ef9709084e99e6ee7abdecd698a0e8e8bbc764e9", + "015d19f4e2430c7130fdfcdf0652ccca1be9c01e7ad23a906b7f967b6a1d368e0c01a52d4656b9a1bc1debb4dbe841e5b45224fe0375f835ffb07f70e291d8d970571300018f2ec8b576da8535d8b29b6383f3a90d0260b34d5a23a323cdad03b08cb5a83c01fc29a2d8800ddb52397829ababf51ab953be6ce92af3478361030109ce9d8d4001bc94966b856264dfa5aa7383e206a809012722ae2ceb752b09fb6b75e26f584601f88bc475705e4b04a38037a8b889a17c667e79093a15b2fd6b5857b7c1384530016d86a7965dfe0f2ee5b3a4a4b2a747625b6d9f7566339bda64f0cca722330a260001c8482eb60d78bd0ac75fa089b040f7852614e7bc1f0df641e132a348f4d10b5b00000000000189bcfccf37da1e24271d922d943ccce38165e586dbb48de291707bccdfd18c300001d7ef39194b479424bb8fe70bc5690a629609c10a9643e000c74c68a219d9f95701273790927730b00a19d377d235ee0e03d891ab047c68e657bc24ae5d7f67e45d0001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (1770000,"0000179300c15684316293c7c5cb2aebc79a63fda0b65f7d87968a3ecbe23d11", + "01919299da83cce5642ee07383dd215a2411a787fcc2bd699dff0ef112d307c46a001100013caaed7f18e6c835004ffe1f7f81f21303673de115af4e66765b791a6ab2873a019863a23007a75f374660163a55289e537339a2958754fe8a1005f930f978014c017d51c25f60d370f2107bd149ee00f9907eedf00e54859c017f1a0ce9da2ce94f000001e5590395a7274c750f048b03a407a14cb571df54ff3be3b9e69fad5d18d64c3f0001513db2be53aa1e5c680461232a1f44d6fe7b0b623198bba553a2523ae886163701d2f66126df2e5c07031c9e36e83a78d2cc31246e7e43499d596390ea7311f6220000000001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1780000,"000031393466be34251e6351c1b11b10a8685c0326e2435865bbb51980d34288", + "0187e1498511d3657b056d66f8e7b871c584c90fc283b36fd9826c31715c14042601d88b6b9d3c44dc30a030f2c3083533409483469912c793595471f2295422c2641101a3fe3629bc29fe3cba610e709c4f7c7266e6d0cd886e3d91ebbf1c8c8b7512570000012a952e1097dbbd362ccbcd2d869684af8add95d569b932db4a14eabb83fc5f2a0167444960e63d7c8a0f8a08a20995dae5e8bb7ca3b38d0d176f86a5b29dda9d690001a1dfc7f50e7bb0f258467f438a9237feb45db2b9f5d136e2571e31bc64e31c0d00017be1ae512cc0f602cbd64c44fd4d94689233e0eb5c5e7b1cc78b4a8f53ab1b59019bd65b70501afee45561d51d9e479d2bf6412040f29b191d9bec40ea7e4f27040114011701d10879f7978a87432e64e19777c7d08b72e61f751ef128dce3c36f3000000001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1790000,"00002d304896196373a4c26a912a6c0a1d8d9ef463f473487c577d947f001b44", + "0167ab1f114fc1131b1171159606b54ab21cab441e907eeccb54c84c529d0e0002001101472a59595b12d697bea88b245daf4b8dfdfdbe09a40b36a5b94928ad1b13e32600000001997a75537c9c872f6961cf432a6af4213b3fb2209ae30204b2c120636fc0112101e8dfb1c8c02ce42c3b91bc67b467bb85091469bafdb92b3d239f97794f89510101ed3c8ee7d61d803f25a84e7182f3ee1ae3a980a7f6549504a946809acb529306018052fbc0b0b77c162b96c23d46eb366ea8d4a0cb6a2e67e156db414a936f0f7001592e9c1b592b6cd40c4f200585ee838692f80544506f6734a68091ecff2d6c150000016c4a9d2263ad884ef77ec5412f6b41f7650743b468aa74003024419368b6c558000001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1800000,"00004d177912834d21b37de3b59c494adf644e4aa01593ba80c9efde53d1d602", + "017386b853caaf8098e961e41739e94dc6244593bb0f4a8f345a156d6b0f3c0f2f016a22610f3692ce8c281d2d9cf91ac62f340e253b1545f6fe0da29723801b163111017f93b36900ccd5a7769891eab3410e6af01da74162b75e3835bde25e2ccd75250001af9bf538cf183fb63a1f38aec14e63f632d872930eb94deee11e671901174228011bbd60751f3ef018e067283edade0c459c9d62c6e56fd31e6cdd5a7b317d7243018576cec926f274ccf6effaa2724160bbbe9f6f7bd5428805f2ce7f5193d4bd120105375f7d7d6c1ef355174e13886c930cc40283c0834d130fd9005126e58b75200000000001ddc8310b0a2d79e78270bb626f1021375f71eb47c3592bc9392788ad08a13c0c016c4a9d2263ad884ef77ec5412f6b41f7650743b468aa74003024419368b6c558000001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1810000,"00001327b6cb0ce0db96faa692490fcf620878cb38a5a2380b0c4f21bd85eade", + "010874dd5cc572a620543cc86e69126b20a7964cd60152b5df016be89833d9d13100110130bf9eb97a827ac2a79272f21742bf5fa2744962272dd7896c4e75b12bb40f18018a3d6fbd5ef674fb10ea63cec79175a7ddaa6108768efd63fb7aca800c68131901e43a2c51dd6404bd0df0536603f9c92c75addafed3528ba447a3727824ac842e0000014ba4cc6bfc40833401c2b7a7befe11b3175fa37dd3fdae278d52ca6e326f76520001d82f892760cff7bb9b4240978d2de70f28a251f451e608137ac6c845fcafa7590001781a73f793413ec55fef5d95333a7b2d2ed77037bd421d35a0c892ad994e066901ddc8310b0a2d79e78270bb626f1021375f71eb47c3592bc9392788ad08a13c0c016c4a9d2263ad884ef77ec5412f6b41f7650743b468aa74003024419368b6c558000001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1820000,"00002d0a09117ff1a90cea9a00209084fe368119b389c3262847679a324b702f", + "01bcf69d8601c08a3962da96e8af0def2aa7f27b3a4910b3982f882a48dbbd1f0a019fae912db0c9be270bacd8dee555c81b6209adc5ef52beea0a77cacae0f6a3321100000001dfcab3930009e192374cf2ae5428b4ec8723dcfe80dd305baf824635acd60a0f0157e716d3eff0d0174179f92dcfe3748b5e9254ae399edb0eabf8becf3919ae0800000001e470aa1c209ebc3869bcc7461cd9c9188d74b404190afe78b131409e5a0272390000000157604268c8d80c44af48119ba0d8cefc22c0e78c00ff41051e4ced31b86e8e600001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1830000,"00004bb3bc3f7022b560c3a9bf1946e2a113050f6d3009cffc6b5cbeb3e17b73", + "010bd5bcbaf185ce7b80bcd407022bac3ff1673e4fb8cfc5ae70aee097b246db700114c6332363501bb59a321ff50f129633bc16197b0332bd0b11103f502825461a110000000001fdefb1c8161ee6d25e1011c0e0c1e5b4a8030b1dac3162695bd082b8b7a12e01015be3859cf4995501ab1a90d3bbe3960fa911bef08faca49b7d8be6a5693b330101eb446a009992832682b0d30a0c6811c0ba6aced41b18aeb799671f7f4dc46765019db8c0082d7a273874b7e6c1f9d8fffd8b6bce322ca80c681bbc335f959d562a015c24981797aeb4a3c0c5fab702387bba7971723d009ddc65a86e43a824d87961018f932941279d223408e1826bd1794316ebad469bf72995da3e9cb942ffa24e2600000157604268c8d80c44af48119ba0d8cefc22c0e78c00ff41051e4ced31b86e8e600001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1840000,"0000049666c76067a8eb150b1c5e6c4140748a64eeccbaf03d958d1ade2badb5", + "0198c2b43f97e0b337f1926fa59764d244a0b66a6c39e0662bf465d0b656d2045c001101fd3428e426238009f84b473281401765b4162e76c1b101970146db9a20b73c62011f1470a5875a057d0c25a5946978345c63d81ea752d238b803abc97945961168018b852ff4eaed7419fab3f872ca73d1542fac4708d6db1bd9629a6de540d3ec5a0001f7fa35cf6106b7b894fc2842d650d05e565eba30ff4fa59d5f42684619da600d000000015d6d0a2eef319552f0196b442a409d915ccdca53e100e4b090430ef2604ec630015cf9676f4b981778ec6a1cdf625c31ba095a3cfb05f593e140d3e90f1a24e81601d9ac50ef001e828c19eb0c5cdb38ac19bbebe98cf7ed2d071bd5a5a1205e5f0b000157604268c8d80c44af48119ba0d8cefc22c0e78c00ff41051e4ced31b86e8e600001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1850000,"0000424fa1d6768f88f10563e2aa69c2ab6f97fe37d57b029a169d72389233e9", + "01752da8fef48a8be91b4103e20d6f1e3bcd492a22bac8b8b19fe295e45dc4703f001100000000000001756d1fd68bb87adc3f094f59609a84f0c8e0f29fbad5add1d4d55f2c7f362330019da12e440107600a1727b254d353cd11b1fb64082e7dea3aa64ace522a7dac0101768747c777a5d8e54d20880c74db5fcafd0847d1775869fa74962c3382277f6801cea2c51fd209a77b49623428d1a644de56a79ce8b6569c91a4b350376048c84700011ac502ac0af72e26210054bcaac06d3df8ae51db2b79deeb967b0837b90fcb080157604268c8d80c44af48119ba0d8cefc22c0e78c00ff41051e4ced31b86e8e600001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1860000,"000020f6f29bea2745ffe0cadeeeff215e3052fe0b61f8e27f3e9eea7ffb779d", + "019a32f92e4594936507ae0a7170836b6ebc982b2afe017ed5bb0647ef1e50cd2c001100012159f5443cfaef14e6e3cd0dbd1f1f9f5191d1e63bc4051be0b23dd17e4fdc680001aefb838d93287ae5f95f65045c1298f4572bb4adcfeefb4553153970838fc80700000102c7893902ab4c527af789f2c24a6e7ad43671f1511ac812d3ac0c279aabb41601b49a76c12e7b657368d7f1ed6d4f92ef104817a9e6f91ac1b3685d7700b9621601c2d589041c04475f53b54f3cd8ef649a35884f30a12a00296227b0cd8877e3640001b03522281d81aedc2a749fe7c5cea1b8897bbc5c0086fe008bb9297d9ec9d858011ac502ac0af72e26210054bcaac06d3df8ae51db2b79deeb967b0837b90fcb080157604268c8d80c44af48119ba0d8cefc22c0e78c00ff41051e4ced31b86e8e600001a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1870000,"000004043ce0bae57b6fba659cc0619eb55b7128c2fe14073e138c0b160a0793", + "01226021f996e858ab01f0ba4314b80f84809862973398a62cde6fff357bf77e4100110000000001419288d58a03108b5d09f4d800a16508c31dd154bbd0be5f15063b45408e9572000001c626a36fed125f21b8506384dfa50400c874c1c62d128d9031c453ab91dda72901d71c13f79908300a91f4b04b96c1a106799a1611f32dc4a6c4fba2382392ff1c0000000001e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1880000,"000049b5bef92c9049093a875b5270ca10f6b4e1a77632bb614c989212e7f1bf", + "01d22a26efdd84560cbf90d995fb614b780b0e254241fe4ae3f2d0adb88a1a321a001100000000011ac73f071cd23a175a4424e04280ebbf1a3cd387dd20c99e9783498c458d793801c2e0e8a2e601dab1cadb19d8726e41aa353367ac9528d64691e8b4e27b34c50301cb20e7c0a8d9cfe160ae4837126d02b86f57e26b489b155c1a41b30db01d9562000001f3b510424fcfb49e35e573f382c750a2ee8f9c82141b3be0aa7b5f780a6d844400000001e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1890000,"000014aa765fdff2c4f02beb2c701f5f3d3c0767c7f56d1954f5cb84bafbd46c", + "0107965f98f75f8ef3fbd76b1287a28cabf935eb231f8bb5eeaa09a236592c81390154c3f850e56df4400cfa735d4491b5c148577c76ef586e1eb5d9e3da0dc7d40a1100000001f307cf2f66ba92eb439ddb5899160bbbf15c3ca6dd99c6790746b338ef39465d0000000000000175ad0f7b1631b2daa6bbbe3764e595bdec29e890865e787d2a129263ff5d7c52000001e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1900000,"000044711e9a4b2036ce4f56af9452c4833fb310707740e5ccd04ca6ee0b09c7", + "01273d6adbab31c8cc61413464edd6e042cfc950e60e7c762f08d3c534bba36b46001100000157f2900a7329c9a80dabf365e05d0c812c9c0ca514d4510f14558ac34b34d07001c27fbe9c9e43fde0071cab6d371c54d9d5f3753cb2608bd18dec43f61c66340a0108d47db6ab2317075dbdf98c595aaf91ff7e5e8a89fe705329be9cb5d64ba52400000001defbed8e40833c561875506cf2821ac341d5657326cd225438b40413b6757131000175ad0f7b1631b2daa6bbbe3764e595bdec29e890865e787d2a129263ff5d7c52000001e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1910000,"000041a3c6506cfcf05d18100e23805847f8056da0862274c99b73598f814efa", + "0181812cd05f97d11b683b884651616d4ee0b72f69f46d5bc050f7cb8341bbfa620197d6fd9bb760cf188c5eedb796490dc83722f7bb8728b07eb2ab22e638308a3d110001a58464b01cbe85cd48a88b3dec500b6df01ae6f225b0dced27ba842c48967613015ef4b3f8d2d2be18e4e9f5481762f0a5369947bf25d93b394cbf7c401a8d5e4d0162a1d237170d3476b9b8efedc1abf7fe15055648101c7b836a4827e7846bd84800013b11535b5e23ae205f9639860b631a15189c206ec4eb67e055b07ff511880848018895d17880ce343b4971d98ed56ae39ff32ec9d70662bb3a0e8da9d8f73bca000122856313a80e6b983d06f80de16cdbad94180bcb2e651d4d3cbfbe3cdc155b20000189d9a58fe2458e4bdf18222ab1919fbe5a86b0c2ab3a4bae6fdf7aa9651cb7660175ad0f7b1631b2daa6bbbe3764e595bdec29e890865e787d2a129263ff5d7c52000001e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1920000,"000042010e0a2012d386e840cb217ae2565b9d7550ba8040a7621a8490127a26", + "0172025a2ee0870d75cf80c400bfb836af14c047ffd71808f5a6db5e948625e34d0154972087e4cb7d7b6b446489cd961833ee887cf0fcd71b2c09eda3c8c92ff46011000000000001d98ba4f8fb7ce16c045144c89b71f04b45604cccfe515b7043065e439940570601f4fe2b3efac95609be8bd396363f3c202fabcc73ec71446ac6a76691ff32a66c0000010ba87a2370750edb9a07af64ce369b006fddcaa286e6b97814b19f35320af01a0001ef1a9c0b44b17867be6900e7c4a34d3968492ce16d8d83d19c405b32bb12a90b0001e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1930000,"0000260f1170ff140086dccdafd86778e336caa8cefd59432168027d5172de38", + "019a2ada7fd24bf319671fa1cd6f1c65e06815b1a6e1e93c23fb6db90d07c9f626001101c5de9dff43ef1e3c2529ee3fbb5ce3bdbd2971e64c6dc1130e955588bd49bc4c0118287235403eb7d88ce8a2419b2b32ad1e44d9d553b67f2312107e31c3840c3f0000017590efff7815ae35a3e756521df9ac183b3d55ee24836f7726534bcf26674749019f2ad684ea924050b96a9053b95c1b34d195f6773a38958573d3043e713b8425015fae5b3cfb709d43f6138bbea97f9c62c48fb088f62d030acdd725527bdccd0000000001ce636cad3e4361611c242ed9dd68acb520b7f52feba0b57ed9f893ae8f7b6e3301ef1a9c0b44b17867be6900e7c4a34d3968492ce16d8d83d19c405b32bb12a90b0001e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1940000,"000003e23107a3e520eb2c6526790c2371f97081b9b282d1eba8cdfef27682e1", + "0145f5ae6e2fccfeb889b48440aeecd87b783eb153bce64bf940cf2629088a6f7001edbc4d2a29cb40ba4b73fc415ee69b83251c443f899a06a02ae6455804407650110175e7d23d87e48d682a6c782a0e0125cb85fdb9952c907413f38bc895e5be1327015485c6d21b3c2175c70e4fedac1a93b722ecccbde38d4871be567c13db902b1900000176eef0caa24d87e08ffb5f731e8c6a71d4944f25bc12114d5f337b2f0087f22200000000010d1309776a25debd736263762dd6711429f7fbcb31a13897d78ed834369b345301ce636cad3e4361611c242ed9dd68acb520b7f52feba0b57ed9f893ae8f7b6e3301ef1a9c0b44b17867be6900e7c4a34d3968492ce16d8d83d19c405b32bb12a90b0001e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1950000,"000021b8dbecf18fb352fa1ab5e4ab191b634defa59dc20176bc300e9d299a74", + "01ffe695eda9075bc9b6554eca18c7cb7a346fc70a0434bdd279e32b0a6b10e126001100000128c6f8a4dbae8d4c2e63f68699291833aed0ca153710dc1a043956a3f3659d3a0184af29c4361e95e0d279f6e7acb5afda4875c71417285faef8147ecefd551251000000019bf9e08071db3310ec751b0c6dd6b8d53110d8f5f84d1670f67053921231f11b0000000001a938eae1dc3910d0fdcc1a5296e05cc16bf98b87bc7d42bff7081ef6f51fed0401e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1960000,"00002ae5ef0edfe28e4dbab70c9fb472f4f2e23b884ec75352d39fc9a0f8655b", + "01263649890506875e474ae2954993c5d810564c35427b0e7aec35867dc18290350198f5012278d588af5dc5dadc4c25ce08ed37d67d8f275298c2d007bd5e4adf5f11012b8de45730fafa0665df987efe8f32acda1b8724b7a5b7d83f3a698a4d108d380139a5a34aefccbbc378b446f0c19894e31758b037c4723f020f9d0e08d49f7c6001d092284b5fc7da40ecd0105dad8b66a6c64cc7c8516ea189646182e9d8ca4446019473222c095b0123b0ba2ef8e8287a86a51e020c2ee651b73f1709aee6834b6401aa3fe5868063df4feeb4144ff9ef4b78e50618ec75ce14b6b9c3175e8883a73b0115c01b844ee77bec3e1d75708b8cff387a875fbf9f74a58b4963ad89cd0be00101d5bfad1fc2bf75f235868c633f387dd282bed44d81f574bc2bd37bb16d71a26f0000017e42054b0d6d60e9be41d98d1658d72491f5039c2bb856a7b384ee9e25625627000001a938eae1dc3910d0fdcc1a5296e05cc16bf98b87bc7d42bff7081ef6f51fed0401e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1970000,"0000279c2bf4f1599442a9594fc6800e867d529ac0d115264bffa3fc7d3f9854", + "01615d3a6797304cff736fe9fbfb76701bf1d1d72a53894d9bf1cfb727e634d238001100011572c6af9f3e10066ff4b916e2d0dacea262a2280f543fb2c8deac92207ee355000001a112636305ed4989d2a144f306bd31d4aa5d08d4c7647e400db2f221a6fbae7301275eebd481e3b5df459a9f1472cb6b60bbe8686f0fb926ceca0215fa69bde421000158ff51ebb17dc53e2db3869d969f2a0aed2233090cecefa547f66034f365516301db7a208b7127d3bf1d9cfe78f5e8361f3897781762ef3624cc6c0e668e660f1d017e42054b0d6d60e9be41d98d1658d72491f5039c2bb856a7b384ee9e25625627000001a938eae1dc3910d0fdcc1a5296e05cc16bf98b87bc7d42bff7081ef6f51fed0401e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1980000,"00001f8a03b724f540d4b43e7ea872e350624845bf97a5ee405a6666d59bf265", + "018ec97d029ba6556967f1050ee36bdc8fe956f369cfb14530936f3462a3977f4201c9f40517174c0354ae4f37232efe4a25c5d8f7c2d01830154157ed832e2d62671101b324167977df82034be0e3a03c60d94289b44d02167fa39f614e3471cf5b0d020000013e6ae3f0e0fb45f72f30f30bf1ec04bb01d8d025672ece89c0edd8f87078362601f30139286239d78729e0eb4d8489f25530d12d3d9f7aa0f2d976ced17e57483401061edf311238c4f9ceeac7fdbaab67f226f1eea4d4d95d33919c7cb6674311110001d38411dcc1096f7fefa4553556c489c6d9f2cda32ef4d5f041d84698d2bd7e560001ecd76a2ce9d14c9692a44b831ebeb3fec471a659831c8c02bca5a0f3a7581e6101f1d8b2247a0f9774a1df6a598d2cec866d62c9f0f1dff12a4335f14f52b7d94a0001a938eae1dc3910d0fdcc1a5296e05cc16bf98b87bc7d42bff7081ef6f51fed0401e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (1990000,"000029a2bd9eba59be028bea999c261cac695417d12c425d21884475d4b1703d", + "0149f73733e5039122162c7bffe1da4f24f7da9da0d6c8bda4322f99260e9a6800016d625ca9ec5defe1ff1a25cd5411eb842efe43b07e931869de830d46b0c1d50d1100000000017d7c27982a59838fb573d4dd97bb09df4c5c206c2c6e22c0dfa5e80d355b291a000001cd7f3503002fe8528197d93222bfe95236c384c493b2f92a779ebfa076b8d60401cf69564e6f0de0c0210e45092321ddace1816a1e56336addb96c98ba48e9d32d01ecd76a2ce9d14c9692a44b831ebeb3fec471a659831c8c02bca5a0f3a7581e6101f1d8b2247a0f9774a1df6a598d2cec866d62c9f0f1dff12a4335f14f52b7d94a0001a938eae1dc3910d0fdcc1a5296e05cc16bf98b87bc7d42bff7081ef6f51fed0401e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (2000000,"000003fc9909851c4b887dff5a36ed039efc20eb1e682967f72a1074053a0b27", + "01fc7f6d907eecd84e48eebf32361472b52fdf64d1b59a9582ef366fb09ca8f41101cd39f19a4fb0c417946fe3bb5183c89f16ba7775d5baa866d99430796eb9f263110001140c70de76d4cd0dcbd74643d72ad57e9210034665e8c2daf422657190f3136d000000000001d32dbe155eb4f833838f3f061d4cb8b1c5fb2fb0137b836a8988f7cb003f7557010d3775022df9b09e29ec5553d344f33a88e2e64e4340369809bb8dee24d0f868013c29e7a7df09efdab29cce9be688c17caa201f6cd814a265c812c8ef7643d1680001228af3782ae61a040c60ec630f8bfdcbec62a5ee51f5d0486f05e373956b2d5f01a938eae1dc3910d0fdcc1a5296e05cc16bf98b87bc7d42bff7081ef6f51fed0401e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (2010000,"00002688aa0078409e160b6cd8fb1679bd5939689aa3ff4fc534fea69a4989bd", + "01d86ae9148276f8fc0030ca0bb4167feda30b8d945a081f7ed0a1d6ea1ce0e34501e33af4daeecda5f305e196f54096ea9681382eb714abc44bf7cd6288e8512753110138158216b338c95d00b03bd3ec17334d289634df369c0dbeaaf4e2c7b3cf813701a45de9735557e8f7400a0df47da5f7842041a4d2e5b0c401a5dc01280439372101982208cb416aee03509b71cb2c86c0ed9d95e0af5d952fa65f06c36ae795800001764273165e4db2cf1bec3d135a64e6044e9d2764953c47f976dc331b1e6bd25d0000000001e6ced1a6cff1c110d8dece19a1f580fad1b101c53f6f65ad84ca8753c059ed630104341dce27be20838d8f9c07085333c8a1cdcabc5d7ecd7a4a64466f3e88655101982bcc6c9aaa9560b9b3f8adde091a789dc524a9f2cf61f4d237fd4e9f71ad5301228af3782ae61a040c60ec630f8bfdcbec62a5ee51f5d0486f05e373956b2d5f01a938eae1dc3910d0fdcc1a5296e05cc16bf98b87bc7d42bff7081ef6f51fed0401e332e148bbf1f06fd04bd9098b9e9a163819c76b91515a67f7d689b68a2a2e1901a264e138e0262ccad3eba18ea5f15d41873cef91f1e22c6ef05ffc2afe97415b018b004040cb51deee36be9f335d8e54f7af775259204cc9ca2222f083f2fb896601a48d1cf04baffc3d3974b88f32e28c87ed2b6e043f771a15e89e6118cc903836" + ), + (2020000,"00000d372734ed0d800fe00c4310313ab033ae4536280aa416391764a68ea6b2", + "01de5c280c8b95517ec26108ce34bd6544c3850e2c3593719a6bba3c64141d2756017453f0981c6b8751cec62be793cc858a7b1ad0f9e32bf2eb42baaddce09e790012000001da913d7096211bf728836bd4331bfdb75f4d9e3a8594a9e8e6c472167c754442000185bd70c18a4fefeac1b28086fff65d51395baf18a0d10f3262c89c89235bc23401597ec08455eba10984b94af95ec7e66f9cc2c20be1d56a9910f46ac3c619bc2700000195d5a89b8e582b46d32cc014f3c60f81fe994d237568c9965fd6e79b74167d61000000000000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2030000,"00001355501d7d0129ab267a1345f0f70bdf51b3cd5e50b9b4d78702ef87baa0", + "01d5aa0371c4712c4fd177bef80ad456e0a8ec6da8e517cd4db346ad51fd98ad2601be6b9136d21e503a26ddcc1bc53b676805491d89bbd8a0716ab78cd0e29a27491200000169228fcee97d3b42ba68f7a9cecb8e560c6f535f52b6d21a552c202638198b130001eda4ad07f12858e10354b268beb9a9680c9120f2ed00611e83c17ec88d626c3301b1ae043a9366c4408bfc4357ebb129827532bdf00f8b70de8ca038d702e3952f00000001226900ec540af73aa1fcaef7fd118319b264abf560bebeaaa93737d95555d94501edc320d1447a78ef00e74d4202548653604ea824a9fa7c628a07131d677b0b5200000000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2040000,"0000497976d382da22e1848624d3e2129b5b2ca52ac36674494c5dbafbeef589", + "017fc28113cc2c884a9c2ed5b9e371739a25a2e15ed61c74521281fcef2bb80516014d84b5b20c6b97a66f0bbb021465e90e3c011dff8add735603331974f45fdd5612019fa2c28ea4cd22598d75c0d21390784082c10f20e1f3ae18b443f1800a981c2d0001ce9016c6426848182d494d419c296099e24709bd46f7f7ec1f2e9bd162dfd36a0000000000000000014594052a7568869489db065a810fcc5ab607b1eecf390b27b9333966311dec35000000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2050000,"00002c4d7ce579578798ad759b01cb05a429cc936eb638d0325021cb2c708f42", + "016b4500e186758c56e45e60f9559ea3bba086cc6cf3c951c133501d8cc7cb43240012018ce2b897d6b46bc808a566903dd40258c4fe0da39e2d6a154b363fb076f8f54001c4587b5c60b188004ba83c7c836920b55569a83eaa7363af40ea05bfbed91a32000000012cabcdcb9e1bfe3550970d0883ba1c0aeeeab5ffe437ddd8444fbee2195d290a01b5be3a5c7660b3f233ae54a805eb07c57b8c850a124c275af7460e5dd50007350196a085fd0b5ac2be522101efaa4f6547a50bb80584053fbe7d9b04bf8b33ae6e00000000019e0970d3198f5a295d434511d214f5ead0cc18136c4737fbb86a5b4fe23ea7400000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2060000,"0000295db07b620d5282a7d9fa3862d76817f182e1f4a720e2f3d867bde0a43f", + "010e29067c5614c6f8625ab2c990b96a20e82c524f803ec7f4940f836ff3d8cc21001200013c5fb890a86c9e820a7013a9beb861847dce294cb18b8ba34a9d46f273ec236b01f6d65a5f1df5a5106fa124d4f4a5763d7ccd4bca23c77b260c1fe42e7daadf6200010a22e36f9e522c8fccfa8ddfa9d15f6e403029ca9434802abdd09accd83d03470001eaaedab1fd33317c485f5231b8116c881b5f56ef3ab905dc976cbbbdbb7c681901d3b0a94203e36e1f153112122a974c7642b89cf8a33992c2b5f90699f0167e5a000118c3e95e0ac1fa52881298df44f7a8541cefd1b39ab9093d202c8f3fac13a8100000019e0970d3198f5a295d434511d214f5ead0cc18136c4737fbb86a5b4fe23ea7400000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2070000,"000006e7be7783f4aa24f15bdea567fd9e5eba0d0deca6f5bbe3a7b6fc140421", + "01157331809ab561ff7e13c171d2e04db2eea0abf759ec08afa7636387c2149603015dcd59d3a8d0ac913907e3058535a46fc9c9d887f9445573f122e0214289b9391200000001d992f0216e54ce18cf3ad98fa2b168dddb85a7a96719121cd4d5682ce058e44b000001a02ae3a7c2dcf21dd84c46b7282e39a61059a3f5183fc41d3f1fa6aa90e4b76f000000014eb7f495ae82ac3a2e38580086fcf92403b9e1450096d82baeefdaf93165fa4b00019e0970d3198f5a295d434511d214f5ead0cc18136c4737fbb86a5b4fe23ea7400000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2080000,"00005443c0215ff5403ed505785af8f38cc50a2733761c2dc145b270b4c944ad", + "01b071c2f44afedda6d6bb0d7b0b045f0cafcaa997e8a094f746660832053f5b04013bdf2a1049e477d5f240a8c0c50fdc42aacaaa9692197960cd0824609afd02381200019bb0372abddb7b4a3f9c0d61102875e46e7db8455d3f1919356862780258445d0000012222bc0da006082cd650da1e9e7b3b29e51500877fa836d7aa3fdf851ab4943c01208b537f3fd2c1f5c4673599d4c0cf3a565f2d768c2347e1a94f6acf79ff890401ffb3f9bfc029bc3a9bbab311ae52cac72cd5b2d1b9ec8cd4dcb8c545f431762d0127b2f7b943cf39a61585be117dc5a81f5b21325b037a8f309ce7c8ed7f17dc5f000151ba4c631d89a732c11eb8bd191dcb00eca497b3e668dd7b84c91f4117ae9009014eb7f495ae82ac3a2e38580086fcf92403b9e1450096d82baeefdaf93165fa4b00019e0970d3198f5a295d434511d214f5ead0cc18136c4737fbb86a5b4fe23ea7400000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2090000,"0000336bdf163dcabe6f2cf1504e6489c578ff6e9ddefdf091da4a5f898bf1a8", + "0104402d30c04920c3ffeb559b8e6873da41175f297f85dc626ec8b4e2d76c981800120000017751a539da4141023caf3034ff7a253f69d54e8459147b66b1bdf6c1d549280000000001c2b25c124f397e6e114185f76919a3970b6977a1bc6d8dabc9847f5f7361b6320121658f3bd8338516db02e71bb0e2d4ad3fc001f56f3ba98d777567c8b3debb1600000001ef3b218749df914c3c4c9f5b44ceafe99be89b9186cebd05790a9f8ec63ace1a019e0970d3198f5a295d434511d214f5ead0cc18136c4737fbb86a5b4fe23ea7400000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2100000,"00000a0e32f09b35098f22aeeb76aea01c35479309900efb04954b61f8b7010f", + "01889ef9b4e74c0b6b3614b99348be8b6573e701a7134b7146fba7fc97b42758730103843335de47047e3783dcd7748b7f0298cc332aa02475dd582efdb50bbcd4611201106604450ba0bd765dcb513fabced763bfe8661de281678a5704ade81cbdd91f012dd6399e70de78afed3fed93d989cbe12952bcab6fe190dbc63511bcf7abcf6a0162557ba65ee5fb4569fb4f9e9e1b3d168b745bc892a83c4123ab2e6a0de3476601473cd31197d8cb075b1e42e39ed006d6b661f484dbbf8fc523e5e5a66e850007000001e509c90c0cf434e257c7ec4047c39890850c0d30e6c87a89e84818814e6fc9130148a219ad72f640a077e5f3001bff85149f248e09142aacbcfe66ad2c14e3a53801b5f573504710b2cd69d4fc35bcf97aa116caa3c81a1565522a5c66bcb21df015000001ef3b218749df914c3c4c9f5b44ceafe99be89b9186cebd05790a9f8ec63ace1a019e0970d3198f5a295d434511d214f5ead0cc18136c4737fbb86a5b4fe23ea7400000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2110000,"00000b05f05abf620f12425d13c5479ddb1f0d11a21bff33e879aad584741ffd", + "01d9769942c8da8be2514e44d786f8eeac4164e8a97c27c2f2f607026f4f009a08001201d01c9f0cdee4b541d0df386a153768a3c586cd4314e830851c4d1d6e9c6ab30801a4cabffeec437ea422c18812979a5ed577cacbe05da4c74e50c20dfff3dc2132012f56756c190cc09d39ed14d0da2a16ac805b09f7406f93cfe28e2c504067303800012614d1b6c2bcd58e8478e8c57c2cb485e644d95257c689be1a528c20da959c110123fb75ba7680ce107a25a112476787df76e2d6c9773b67f834502d8c8e31095100011fff194f9bfd78b4aff29ec11cd707c97a4838234abb94fca2a1180e94d2a14001ef301f313a214324e847bb701ce37c671d3f7b12f49e8065f14ed4f65775127101e75f4007fe00f49a90cc8b22c97f3a0a2b679cefc82a6e650e5178efaa2882520001ef3b218749df914c3c4c9f5b44ceafe99be89b9186cebd05790a9f8ec63ace1a019e0970d3198f5a295d434511d214f5ead0cc18136c4737fbb86a5b4fe23ea7400000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2120000,"00000ac463ee0b4a2abec658b98365a03737761b9492c83148bf54868ed10027", + "011569b9f1f0348e4ece12f05880a94c2ac4a5eebef63e84c2bf1261065014b24e0142fa67b1e4f430f798f09a87a8b3463ec2bdd01a2f2cec278ad6f57aebc5274d1201a8f3c327bd289b27f1938514433b7f28df00546fc30fb973eb52b4d91f4b942700012a7feab22f3b2ba66b55bdfa587f0791aa66ca8a3aa1cc8b68dccc7ffec8c15d01410fcb3eeeda48d5d44fd6bfc260dd89f81af17c6476288a52847781256669090001f4cf0ef5acf9c489234ba38a34b52c9040626f8f016ca54a0bb598825d43c72601243d9d30f050cad3b24786f344a1bf69833a1527fda8d8d4a7bc93e661f32d400001bfac9fd13d5515faa31723c1032b42172a9bbba53fa536a11e5b1d34920ff9440001e3686ac4392863dbd0f3c00e6324e487544f1ae163d3bfe416b2b9b9f824a53401ef3b218749df914c3c4c9f5b44ceafe99be89b9186cebd05790a9f8ec63ace1a019e0970d3198f5a295d434511d214f5ead0cc18136c4737fbb86a5b4fe23ea7400000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2130000,"00002a7481591850d6f271c6e9cce6e9e9d5aa72a66dff5cacc0b2112b84a184", + "01ffa6f276d18e99f297b1a5c5fdf056bc5525031d7e0ee9cd078be604c299323900120001127746f750b68222cff5a12c69211d175ac5f3590560e115e2346836f5d1ac6f016577167e53bcac17c9c58bcea0c03dd823c036af555e8047a96230a0f079c03400000145b3b569f2a7a591fe437047388a52cca9b3eabaa5169b33581e1521adfcee6601c05171df00074e1c422cb784fba1d015f7f391c4b9c8b3854b11ef4b4ba5ae0f013b7ebe6b642dd124e7b25ab3fd84801e0e9697ff7fc00f352ae7aedd4af2c71a010fb472ad015f23ee2f98992568d24831727557785949c4762e5467d36187a013016780caac21e481e18b0234fdb6eaefb19264742aaad66ae90090d16b300aa65a01e3686ac4392863dbd0f3c00e6324e487544f1ae163d3bfe416b2b9b9f824a53401ef3b218749df914c3c4c9f5b44ceafe99be89b9186cebd05790a9f8ec63ace1a019e0970d3198f5a295d434511d214f5ead0cc18136c4737fbb86a5b4fe23ea7400000000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2140000,"000028262c696e8f1d36d289058a4894632eed255a1e3b0a413322b53735af55", + "01f6d946f7426ff16cfef21c7912f3bbee3d845abe683a0e007cc71c52686397090012018dc6c61ab5367ff9b272fb00bee166cb25913f0c6fd8c8627e468408e1ec044d013b4021d8e6fe3d21b365bd4f5e5f5c4d01351a93a318900937b6ff17e0edd31401a77b7baf059b0be0378565f6c489c9dcc66c82f75b745c899cf6e62eb762b130012007d8b8e5e4fec48995a83724992a0217e78e112e41adc4cdbe6109f1e48e4b018827aa39c0e455f6b6cd58ec31d9c0cd005776a170d6abf2a984e76e18f50f1a000001d2a0891b4a91323f030624497c3d448a41cace1180e65be3c1393b01a1e32d2f00000000000169acb3d026b12b2b2e92ffb2d6d682be2873376e7b1712169e9a7729de56505700000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2150000,"0000117dc7a047855b1d35c2a59f62de19f8a9a1f08c7e481c00b981cd69ef18", + "01b31bc76aba08683d53b8f7708cd0c8d243247342f38dbda1378c07291b0d346b0150f904fbae402fe6a540d86d461bb1a24e580a0d26ecc1bd38b8b220308406621201a0231b6d633479816104e6b527780107541c4f159a55982e87bea1d4cd5ff06801cc17e85294ce258b083a3ad03e2ef636d1845b9b7ccadb33565ecefd7fe28a4701b52b5fc10f339ac74dedabff4748c4a3978528981fe15ef69e1bea7bc92bea4a00000001d71cd85ae67e1bd10ab9db591e6c26e05776c8c02f13689dc21aacb1b57d126801cae42f966ade95c040e387024dd0c452021a3fec3ea0bef6cf54c2aa94510d410001c8386f7c27e62df088633a024b61340e6814ab6f7e89ba86622aca9d6da6f9130000000169acb3d026b12b2b2e92ffb2d6d682be2873376e7b1712169e9a7729de56505700000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2160000,"000002f17a78820444246ae5c27302f2a597e34097a95720128488f70980f618", + "01cd19c43a2d3f19c86292620bf2d3d789fc9f985be583f1ed2f4373345d861b5d0012015243593b856f44f265d5bad5acd92d4337da13fe342e511f1137cd126aee334101bac38c99d5e77f29952772620d8fe5f107ee2968db749e72b92a4a1cde0bb3400000000137122629d77ed221e9c1912f095ae804b0be6c58d03560510b4908b1717f700f01fe9b15dba66f62512394b98298f4155125598760a5096fafb14175ba5d5f385901675cbcc266be30ca2570258a757596ce20eaf12eee15ac5923c43f67943194650000000104cede4f1b6cc87945eae713b1f7f39049c104e879311f37f62dce9a652c84060000011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2170000,"000027c5b23f6f32442bc7f8e3d9f02c9dfb7ac79325ea16e6edc197176aea1b", + "01d1b0bdfaa86697fa4e8d4ecd1cd7817eae89c7375ac8a6d5c5d4c7baacbccd2d01446ff9e75c87de18f3d1a4a99752edacf2ffb8c3f3c52a81227bab556551395d12010412fc84a96d68dc638153f6dc59c5060e3e725538ac6edce5c92ba79d6a18530111c8a0f4f8284c89517df4fa96c2ba8298b88ad1dbb62ea551426162447070270176a24b721a6de664b0922d6eb700a671c53d7aab8cc5a1ca1efebe051d87e44f0168babed9c812d378985305bf88b74f016d6ec384cdbec058befd32e7810bbb2900014d869416698e7556d9be0860eda3f3a25c968d736a7ee00cb7421ea35d0c8f5b00000001835c809c7e8695429629c9d1d9e3c93806c0fe7108b77b7dd07f988ee816f902000104cede4f1b6cc87945eae713b1f7f39049c104e879311f37f62dce9a652c84060000011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2180000,"00004aa190fa6fa7e06f8c229b9c3f817135922767addc909e93052c4aef9e95", + "015ff1a2cd0bf5473adcbccec733afce4947801ea7a788c0ab919c6319dbefa0580133d034ea5e27e6ad75bc2a21708e542151ff9fdddd6d9261b745fdf7be60f3411201544eb4b998c87f93826d671cb2b305bdc33342a2fe3f0aee7e17ec995147fb05000001b7cbd84dce38dc590f010de2e4b3705c5aa7b92b861065eba55edf4f445849570159245b02c118fb068ba0622b8dd1c2ffe826e559bd66f04e167e3a7afe1fda720198679e8eada06329b250a092f9b49721ff07f731df426593857388ce31f9853100010cbf9b4cce4930bc1868f65c8d0010937dcb9966b03d5915af20854c2a17d10e000001619dc3eddd2f741d3da32d2f62de1cceaca98809afa56ce6f7be056103dc8a630104cede4f1b6cc87945eae713b1f7f39049c104e879311f37f62dce9a652c84060000011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2190000,"00005004683702dde682956fefaf447305f943463df5175e9c97a0a30124c62a", + "01024bb9669837275963ac8fc4d7bc55cdb850e1cbec68995bd15d01a5b713dd21016e0c69f63fd6b0464ec79aa53a5f4a52ba89a855b2b6ae92a2cbdd6ecdbd282d1201137348fb2836ba7fd39918a7ae2ac28a5ffe884e2b856590b44f3d78a3b415610000015ec1c7b71c7098e956175ccbff8d6d312132369314291d5e14b335e0e99a652601a64bc74dc8b5050215b06ccf1bf581149414da3f349ed47d2295f9e3a1ba0e35019fccbce323b60c6673e9cc1dbe51b261ff4f29f65bc21c5c895a6515af8d001401b2391688fb603b4baee46c2c996daf2069568d02b65c174914a77f5b835a67100001ba9f5e16629304b5df3247337cad104821412a9740347f539a03a9c75fb33f0d0001619dc3eddd2f741d3da32d2f62de1cceaca98809afa56ce6f7be056103dc8a630104cede4f1b6cc87945eae713b1f7f39049c104e879311f37f62dce9a652c84060000011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2200000,"00003c75b78e2c8092b05bdeb4d53365125bf77bbc06e1e76418ba8f85845fb6", + "0168943fdc6c1c980414040a6fd229e333e969cc823ea88622e918ac0d1b779606001201de7be5872b54eec796fe7e272d72d818cba67742108f7c8649dab16ed1f78d2c016b8d2684a1a78ea1f90d9cb3e6b391f20867f2983ba68e0603019895dc6db029000001cf2d02af91527b087da2a48d7de6cb9bc6924d95e376c0f2a105892b72709272010547cc8f46e44c47abd64459c4c2f9dc483146263e86c63949e7763adf03230a0000017b7a4d5f33d799a7f0b021472d21a66294e50b40e6634441f515e22f0299dc6001a09a8f6bb9d592de8a66ac73b73b21e8dd4443c4f0d785cad8ee973c979a8f4201619dc3eddd2f741d3da32d2f62de1cceaca98809afa56ce6f7be056103dc8a630104cede4f1b6cc87945eae713b1f7f39049c104e879311f37f62dce9a652c84060000011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2210000,"00000968eb429ede2e03af88adfc7fe56a0c066af88075694811cfa57fde1ee9", + "01a60915fb20cebd80cc121b4b68565bb17005969909e6e102eb56bb07df90da19018cc313c257ab03595aa33298a47f8d5b83fc2e1cfc65b46f95351d178ca4c63e120001005073d3336eef1d1fd09a0a8de5580b3fc28e84c8900932e28ce5116bcf5f620001e2884572fc94fa13f9213e80f195bf91a111cf5ff81ab7b15671ad25b78f885001855bc85cda791acc3f5739c2ba6613612c4573fcc669ad07906fbb3291fb906c00000140ff22f92cc2f600e4fa1c922932c4e12b7a3c47d7ce8b0511e4acdef79a942c0000000001d02644861569a0bce501965982c3df05d634d064ea371cb1c0fd615444fe6d3400011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2220000,"0000353926a6aa20f7d15a621868bc840cb35ff7a2d815812aee339b218ff995", + "01ed2ef8d2ef7731cdf129c358c3675e51fd318f0dc50dd1c24c950854a9a4d93a01a02ad97d2858bf649ab57dde6e82a246d0ac59f5b5cc07c1d694dc1097501a5d12016a632113c76d47ee7e495d959a8ad924ea1b3d7e95c5a174cdd41e65c977ae4701d8f12e680e54f17d84f6a70abba6e44befcb4ed75133365d9b01d2cfec47136a00000001974ab51c022406d85e6cceb5dd372f2eea2a75cb7250a87a44cc2e5dd5bee53c019b32e13a05e9d11d8efd66d5e4da0f29a6bd51c8610c7981e69f262eb5c03e5101feffd442b10ed042685735fda0874468c51bee698c8b4c87b15876c781e91b3c01f35ec78db2f3664817d3674467ab19d5f89efea69117043f88982756220d270300000001d02644861569a0bce501965982c3df05d634d064ea371cb1c0fd615444fe6d3400011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2230000,"0000410bd162fc58d18e26c1c100d28ae9941d1a35ed992b577ab1da6bdafabe", + "0194696750c79234418287a10af922a5a0ec7439be2807c3974c192060d0d3940f00120130a286eb9617ffb9de87fbbbf71d0ffcd41a57abb787b1bd72d6931293793e4f01e5f025bc7438aac61c00135771171ebabc86ee4e49a23bc28ab7d12e09c7676901c86c10e6b165fea1f01723093e3b149b3029ba5444f58e54fd775cba5b3bdb48000142323fe1bd46e460e2aae271dd5dc4ee5d77a96a1cf57c47a669306b53d6e372000001cd33cdbeca32e6ee4a8ab52de9b4580be870bb5e9fbe7e11a3f8bd4a01bec34700019f6376fbbb8e2ee03a6bc7785066bd5125d473ba2df13e0b744f8a65670c43440153db3fc2e6a33cbacfcaa8ac79d07bf21f44835fce61becd765a1ad1ae2841530001d02644861569a0bce501965982c3df05d634d064ea371cb1c0fd615444fe6d3400011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2240000,"0000623792bc7dfdfb2c47022ff41e4fd7a4fbf91eedcdfc09431702c3430517", + "01e4e1c18a20c56af089e0307d17a5314b2fb98cf70f8f8488bc2adcd7ffd65d0900120000011a4cdd72b36e72a4b088553c616ac25e163539e087373731902c91eb05ef3a1e015a7a71520de1424f713735390e6f08a33602c91588cc549e038fb4c46695d6400000000000000001ce67d97b42d5172370a4db0da66af6daa4e95fdb1dca9b1c0026d4f21cb9e03e01d02644861569a0bce501965982c3df05d634d064ea371cb1c0fd615444fe6d3400011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2250000,"000028cd62deeb5e810740499c3147bd6394d44a58c54e2bbff1b071804343ca", + "010051af79ef45f9354e08d03f33019fa9ecdd5cd1ef051bcbdc78c36d05e8745e001201be29cb31a6c8c01ffac0b5c8546a882161a9595fd7ceccdda5e397b6a19fbd4701a46849fe6a49cbe47da07de42ec6202927c11137f08f0f72c19a53cfec0a4a2f0001ab72802f5d38ad5cff393c6e8c8a085d2f602cd84d214325b43a8086ae0329410181108e586f58b8f356ad5aed1afd73a00ceea20162f76380ee41dc43f0b596300001f77a7a7b576b8e062a07da9ce214f931a6b7a6546dad83947d4cfc9eadf6c500016b9127eea06abec682960bbcaa82a91d9b3cdce6bd086c62a01218dff650244c0104d2766e76e787c46148e88b401e6e1dc7ae78b13ebffb8d7e80ff7d98855e38000001ce67d97b42d5172370a4db0da66af6daa4e95fdb1dca9b1c0026d4f21cb9e03e01d02644861569a0bce501965982c3df05d634d064ea371cb1c0fd615444fe6d3400011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2260000,"00006149bd7600d47de36462f1560e2adb294ba590e9600bcf784b59270cae77", + "0140077b039b3835f572b28e6ee945d405ae21665bf8d21ea2ac87fef1aab641390012010c2904845608e701ec3692948d3c06493a73f2e00d515ea717a670b3d468d61b0000000000010d4093d88ec4a51565c2020525e718d76f0d844546f4f495cb8a7e9bb07cda1c00000001e1d5bd64deff2c1f46906aba36db1d4fd0706acbf189db1e50cbd117fa3c2a6b01ce67d97b42d5172370a4db0da66af6daa4e95fdb1dca9b1c0026d4f21cb9e03e01d02644861569a0bce501965982c3df05d634d064ea371cb1c0fd615444fe6d3400011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2270000,"00002c8835060df6cffa87be9849faccab191bd4dd8719f75dac4b34e4fdb5b7", + "01066207c4cbd109e738103a5f9e36ae71e37fd133567bd6c99cb742b003885a5c01708e480da0b4e20d9fae7f1b0ffd87a8fe1782d3c852c236adbe10778c11076e1201d0e35fb466c27f9801d56cb4388c1cd1331f646c75788b2caf27dec5b7095e1f01568b72a5dee4905d0ac068419ae957601c0468003c707a3f8d8c09a0b546352b00000192d26fedadbc11a1a44ef448defa25f91dd5d4c20a17318681fccb55ae72891801c47c9e42d9ecee1549ef18bf591a8f9e293816fa8dec7d68d8d5455de1bd46370000012477d00f536e0fddbb1a482b55faa62bd0b9801d8be1be52bb3b4af7182638130001e1d5bd64deff2c1f46906aba36db1d4fd0706acbf189db1e50cbd117fa3c2a6b01ce67d97b42d5172370a4db0da66af6daa4e95fdb1dca9b1c0026d4f21cb9e03e01d02644861569a0bce501965982c3df05d634d064ea371cb1c0fd615444fe6d3400011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2280000,"000010aba066577d86f268a859a5cc85f467edb456b427c6320e43b367f25703", + "01039abe233058f79d3d8369526d66d213dbfad8e0166db4d9890324c7b143332c013fb94fc44a7847da4ace48e2bd27d7106c0c581f8f5ef39243f80165bf5841611201eaf5e8e10d2f3ced40c25b2cd0512d6138921013e39a5be3a9da9a3d5923222b011d259bb955d2dee2d6d4f5bbb8fc0925222f5798c8c0db688abf05cb2548995501b55027e81fe3d9a6dccc8b13c12411d47fc050dfd8a027c10e634baaa810aa3e0138e589d5ffb6a48fe24de00a7d1c6b7778d5c925d8d0702bc3dfc305a7fbdc700136cbb66fcb07fe240036a75aec727ef1241ce4b9979807489023d8e15ac69a0400000194bd192af3dd04e0964f42e626cb7b0fcee1cc153e15f3da1efad3d5433d83150001bb3ce4979e15b84775feef597f4f130b06a6527a049a06ac6744c72c91153453000000015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2290000,"00002fff9f32112eb71ca61b2419b34701965d7ff7cc0e88e1ebd5811adeacda", + "01df5b36971e8252e9bd59c8e1290be0941b6e9692060b12bfd23e750ed7ea474a0012015cb0e5a30cab8ab2afb9ec0157340596eb5b03986822b5e40ba3ba277a90661d000001ef9e0ec55a4882c653a2c625fd37a7bbcd412d21f9f05bd8ce372058ab181d2800011f624d62a12ec43906413aed4a578d8b4b53776c43e015f7566cd1f0b703cd390157787e472d97b502a1290a840fb0a28b83d888f0cfc935e1117381a4b225314000011e811c65920bc3b4ed80fbd811c88e86019ebb9330763fc3cd826f9b40e90b10012bb66f740a40a0cf43267a1e940039f966a9294394dd670b7eca7f31bdb8c438019b31a36be46eec9bf054a6665b7de481906b6adab4c27b4bd95b17990776123b0000015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2300000,"0000304e0dddc169503d5f0fd3c7b2eddbcf1a7f87a8819368d67cd1dffbcada", + "010d6022f160c076ce3ef26c3b85a13b68d2e96abe0778433188ff3b284bda9d69001201a7ebf2076fa16145a79432d13510b01abea17b88832eae8a4cfe1d5d2dd78b7101efa942764b517ed4f8187e111acdd4cf2e603e62eec81039037baa728ae87c67017f8cf3c7004747de6c09aa17a6ad3d0545110293c5561831ef2a43f8eee4c63f000162cc720b416032222d2225d10434735c8cc1b1628d602408e3a4728100af5b7100019c6fccbdfd9ef6d878b8d5e6c061dc2547007697616505078b8b5ec2f37f65520000016ed3236d4725098589593c431c553d6a5f8a8896c235894143ab4f91d3e6c00b00011effb19ac0fcbf98b6f7b210ba5a0d2ec7aed75f18aefbfd2f48ee0d2792496d00015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2310000,"00004cc8e0e130149a871c86ca29e7ca5eeb60684dd2ae9f91ff5b30e7e4e3a1", + "0129c5ef4f6d214ad31999888573c2d11338bf19588054f90be67f543ef9f46f0a013f0837d78f95695bb1fbe9c850cc9cb71e0d034679c523b3270301582a1cf26012016dbe4c3884f717947e71c79ebbeea1cc288fc42ce56140392ace6c8bca54fe01013adba6b0c9d08edf73e2fd71860cdedf2c38610c04938c9c0259a2605a142d2c013f7671ea4df1051c1b90e8a739ed758c71b2cda34bd466ee00656bd19b0f9a0800017499e0cb4cda4f80a459e4bbd160a1ca1943761d4ac3bb417c7454ffd4656c100001d6bf48657d389d4300f411ed16c07f8cc1f5fac598761061be355a1eb1bd8d4401e6582c46e0c2a10b1463b65180aa80988fde80f24fb21bd18db4be784098d16300000143f0c0ac4476c79871c72cd653d9e306945ca4b9e7f630f7480814e79daa615a011effb19ac0fcbf98b6f7b210ba5a0d2ec7aed75f18aefbfd2f48ee0d2792496d00015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2320000,"000000a3d41a59482b5bce0f8b0b6ca0c5c8c6148cd948571e577778fc2188d0", + "01a5955e2d0b4bccfbb8449315adbdf81d4781399c3b07751e2adc4d0df2be284401c4deb1ebaaa2a4a70e25a4b6ac07ca137487a545126645bbe3eda06b455aa143120000000001a863d98015c6f655871746a4e8f2f8c504267ef0feac013d7df19563036895180000000001f4e5fa0403a4e96e942332a2469147958c27bf7341e5bf4124b7db85873e97450000016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2330000,"000028dada7347ec97146c83c4e458d2a07cfcb56564e730074544bdd9c23c56", + "016c41574f453786284a40bd68831ec65b825032fbab28d07ea1c56bea3be2231d0161aa0e661e3c9582c7f18a250c3c666b15383e501d66a0a4289735ee7d63714f12000001e03b7d2d90c5402e3675b8d5488c8efee405eb3d457a23f370172b39f4217d2501fe5fd0e975002ba486149188a403d9030c0fa74f9622dd6390150564f7840f3a017f9fb2bf8bf13564939bc33b7702316e3874c3646b27c5ed03148a62eec24f3b01c52914bad69b84e8f3119b9218026eb4906bb4e95d9f5a389219ab7e6b529757014aaceca5eb7b494ae64fa48f183d8590d34b53b113b557c1e1e0b648a803ca58019ef39c5b4c7e6f9effebbbeb113e4d01f6d9fe5fc1814636bddb716c5595a712000001d8ca2fcd3193c1596c15840a4f2ca8d7d136e13fe1dd212c59c41902bd7f5b0f00016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2340000,"00000d029a8682bcd1148c5357a3b298c320672737fb01d2c298575a348b83aa", + "019d01e464794ce44d12b3c4f8b9c46e7520f9766f27fdf92016deb5f5665abe48001200000139297cb4d88dd7463594e027e271a0eff0513ecaeca294d9fc94d948cdbfa20a0001715575d56927a67fcbf567d0569f1273d0524607ea40e7d751fcf030dedae15901f97eae3a253a593ea9d72d2cf182f5b6b9f759be9a937a08c3642e4fbb349c3e0001257c0d418c0cc946e11e377cf393996d674bae579faa8e776f2c97a5382573180001d382ffc954f9e0cced7b7934cc03b3a0f13faab14573a29eb5a7712474523d2301d8ca2fcd3193c1596c15840a4f2ca8d7d136e13fe1dd212c59c41902bd7f5b0f00016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2350000,"00005edc83af71ce822a94f52207461eb22c5bbed6b97b4bb191ef76042af8c3", + "011dacb0b6120de346e99f7175163272b7eb9ec74d8b67399f332775e7dabac76a010c395cd81fee60d26e8adf2735adf6bd767c61981e81b7438273c30fa67be25f12000000000001b74e63290aa20626eef682a2c40268491530bd72708a764ccd341ef55a05b9490149271b307029fde6a8837b2be836cbd69347eeb1233a2db35949af128975d13b0001ceeadef71a79f7fd4db50fbb1dcfbf533667120da94f85e30f36057bd2669a0701d382ffc954f9e0cced7b7934cc03b3a0f13faab14573a29eb5a7712474523d2301d8ca2fcd3193c1596c15840a4f2ca8d7d136e13fe1dd212c59c41902bd7f5b0f00016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2360000,"0000258b0cf718cdc03218d4f503b866f4b267db46fe4a10719c80f672d04abf", + "01b92c8bd507cfd1098b8676d0ad72d9beef675892d39cf634910febcd6b1faa23010e4bf081278d68630c698b0e72fc616368e6a44d640f4c0b929cce4fdd12840a12016832491a526aec9f4ccd12313a14c4afc117c90d62d087f14c584f1e3b304a5e0179d315028537107bfabc508562a774f13167d4143f95494ad70c53e891f97d3901e59286c5dcca71b9ba6953ea182dca2626a3d76b895d4a328f530a07ec8b5e3400015ef8add0391bb35ce194d01589b816b0f35e6d7d5eb4a81db536ce8f063d0b59000110601b6856554669b7d2c42933cb2ddda8a19bbe676bbf135b64264c98c3062601431eb81537acaa3794c105a7bd49e945869e16a892c09a32891fb57b7d95f82501f1fc1082dd91616938bab71aac7b907669d7a07381f58c25d550a467c0debe37000001b92d7bf57f6c5021782b1b64e4aebf193c08a2bcac48727bbb451cb0a3abde6b016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2370000,"000052859d7d554dc460cd9135b462f28eecb3740537b936d2904887d1895bf7", + "01d2a015e6336369f091e49dff2c497e747d6e06a30d68c5d65a34e2cb6eca2c6c001200000001bbc72471d78c656f806e600f0c3ced14be64e62c1eea44c7dc25c205beb5e42e0001cb81c1a4e57f531cdd24d04ac4b6b7d02b8d4098b3a61ec2276ae0f92b950e25017f1204091ff0ed42e974fea0be4d4a8e95c94b341a78b82a330ecc65ba2e3733000001d726a60c44c1e85fc6719be23810a3aff8806bbbbe24d3f89edc3debf2e3d6570001b92d7bf57f6c5021782b1b64e4aebf193c08a2bcac48727bbb451cb0a3abde6b016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2380000,"000049cdbc8c487e694419e33b5fb7983535aed8b4b249c9ee9180e01abd2a4a", + "01a1b6f4eef99a42d3b7daebc8d36d8fc1fab6ae66443f73874b181801e438760f0135bec9506b25c9afe232d053df2c63d605a2add9a381e7785192fb6eaf1ce81c1201b9a6346ec1045626c0890e6206dc9d2ceb68a09b46d97eefa319efaa1b70552a0121ab65e42f3d96a8e3a6c7543eba3e69374a3fa6ea1b9f6d4ebe73f7c517970d000104d1fa7f099ca778639e2627fd6f31b5ea2b963e0d409b53df8ef46179a9c74701d84717e34a4c8b86c04d6323ca6399a6f01e61623d59bcbaaa2e0b0396ac0309000000000001716659911c967a70e514e7ab4121562a25608064381cba3000a76f91ceeeb92901b92d7bf57f6c5021782b1b64e4aebf193c08a2bcac48727bbb451cb0a3abde6b016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2390000,"000027acd3077f0d53fc3184e47479b268dbfa9493c7e1eb091c9ef168b76a1b", + "01b0c3d0063c6342210668d1f91b67c2c0bc10853a40c576604d2e0ea3c0960a68001201ccdd55dce911229823fe2d5efd4dc49eca72dbf99955554101f66ee11238df5500000000000000015cb64b49bf7b03cecf8321609992ef9c28694cb36e8eb1fdb9bae3c58be07d0b0001716659911c967a70e514e7ab4121562a25608064381cba3000a76f91ceeeb92901b92d7bf57f6c5021782b1b64e4aebf193c08a2bcac48727bbb451cb0a3abde6b016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2400000,"000044599652a8b2177d2ea19d35b0257fed071021812463616b58af8de02d22", + "01b123bb9068836f87b169bcb4ce77ec9dafa31baea117a5584001b643e573af290012017a794e7a1875b225e468bd7eefef69591795f03df5c82645e0917c09e9ca343901b847f74770b2f029352d2ac5751588c04a18910e60dca24c5fcd6d23f818846b014777187ebe0edf90707f7b93c9a33a166075841aaae81c29d42a97a42f83186200017431e2625e7aba4900e13042b2ba90f83caa404e6fe37bdc44dabaafbaf4930e00000000018fe5fead7a19c48d8f58bb716acc8678d99e869a6b00ae1b352edb6596ea1f3701716659911c967a70e514e7ab4121562a25608064381cba3000a76f91ceeeb92901b92d7bf57f6c5021782b1b64e4aebf193c08a2bcac48727bbb451cb0a3abde6b016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2410000,"0000a4901d4456384bfe9441970b5793bea4701d8bba828f19ed191309e67c3c", + "018135f2f18efa097ebc36b0dc8eaf21857190259447e97fd4d33c596267540c0000120000012604e5cf87a50ab1f1728fa5660570709a9679b94099b7f4941c59f9fe4a7f270174472a887bb1574c1118a673da5ad9ccb5a31122b1593b1377dfab52a1e59f4301bee99bdda530dff7158eaa01b94c2241dce42af67791e1bb5e93ebc7e75b665c01536bc81de8a1dcfa627e77d0cdb1d3173abae233064f880be7c9ddfb03f71d7001c83220d0de57fcf6ccbfdfce93f5f007f33ec00b3724511b57d3c2efebe4922b0166fab84421d9ec0db3bf9782d459c3295494577155ad92b2abc1345c3b510e1c00018fe5fead7a19c48d8f58bb716acc8678d99e869a6b00ae1b352edb6596ea1f3701716659911c967a70e514e7ab4121562a25608064381cba3000a76f91ceeeb92901b92d7bf57f6c5021782b1b64e4aebf193c08a2bcac48727bbb451cb0a3abde6b016305de66ad84d330124afae338b2f352e0956acbca7585a8fe0481700c225e5c015d3ef5b631ec713381b2da26b26b0ade8eb0e427c12416526752f83f1e8c345c011e4852648e4b7f89b1d1c08874b62c081ed82b6161b45774895929ee04ef9b6a000001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2420000,"00003e768c574d4175b594e4b50551d0eef76c86e0b37f82ecfbc639a29e9e72", + "01424322f6dcfb74932c8b5656ccd746a071f1301dc6a6231e4061cc17cdf3734a015e84c3e2d3537b26ec88d5437defe70dae034961baafdbca7bca35f6146efc2a120001f7145352ec75c568fea408612d17571e8a95adcd8e12185432feed677f05e20c0001876496d32c9587a5dd2d280e7be8062ae1be29076b48ab4b8e1fccbd66915b3901305de6928572c3d4cb75ea4ed339f782a93d38d942df683929d312fb679280440000017ba519f312a179e8c517fe7cc9aa968db53ceabe41dbc84e4238ba04350635690000000000000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2430000,"000029ede2b75980f177597d7288ef60b7c09288ec50891f4d5f5917a320926d", + "014dc91d6c3a377055eee59878962b607237f484bb1ebbb9551ca295ffbb364f110012000160f55e8409adb669e02f6a973bb2c2569c90a3975ce2138df614bd5f95e04a35000118f991f19f80f0564595ebb7c4b9e38cbd15e869a1c1df30dcbb8f7b29592a5d000000000000012167235964bd1e96231a69047f6cb9e85fd5a31aad631045a4948c03564052600000000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2440000,"00002fe34ab64f1478c22092fd53e739c2ebb8168534c15d239275b6dc7865ed", + "0196d753913054dde3a680184de570db9c23e2bc357aa77aa5daa3bcd4b1e6270800120001fec2b60e795cdb5348a7e31b5056669e6325328310569f7041c60ea021c23d6801d553a7084147a42d684fd3b86988d16d8373665faef56a773ba46ca66bdbf41f01ee6d02b839e193324ba889413b876c2f65c7f075559b0c072a72db9649b10a2d01e0bccf8afe4430677a8b14318af0103748b3e25fbd69dbb547bd98dd51b0ea1a00015ed3fe99021387c0e29bcb582ff1d9f478777f9a1e959132cf9ccb8ce8f7051a0000010c505b2d7d6cd42a7fe421fb2b39654f4951b0c774e840fae011e46d3d7aad33012167235964bd1e96231a69047f6cb9e85fd5a31aad631045a4948c03564052600000000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2450000,"0000204dd601eefd91fb4dee2dc3ee331efe092ea90bd54a736748b11351a524", + "012805c2ab9f12de7713de2172d3c71aa7a133252860a5d7a50ae5d6fef1061f650193aff0ae25ab83bd2c90039d6a62312090c7aaf469f2f06bb5b23acd210ebe001201e6975046b5cf89ff05046c0b72b6e15dbc09643e5577839178ceeda3df0b5d2c000001edc4a1aa3ac3a14ac5514db78d2d314505fccbea5a16b9ff5075979e335a002e000128631961298bc5d4936d25d2dc7d9be36587c9758366bb04363a41357016544701684c94020c287ee8599097912eb718a59ca77bd1e0d2f5f4ba41e4c668fe6b5200015e97dde3dc4381c9f1977e0f7c3b84060c583ef6ac141256ecaa8fe8a378f613010c505b2d7d6cd42a7fe421fb2b39654f4951b0c774e840fae011e46d3d7aad33012167235964bd1e96231a69047f6cb9e85fd5a31aad631045a4948c03564052600000000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2460000,"0000532232ebe07b5236e33f21109efa5b17c0eead01163c0d8910af6e03bec4", + "013dd76be22aa7e732a9fa44d5f45b071d75af2144e5398a19af636cb9440047530114453c205795f11992ba8922b3116458516e492736343f1316455d7de86d6d1112016ae40c053fe5d7e96778c8953f04a0218f447a64e7e8675e53a217fac50eeb5e0000000001b26672c6a86075d271288811b53fba222b162d7c58ae3e49c3175c6dcd8f742701a1454f32bfa453b21c53f92aca41008062e6dcb7ba9d5eb032a259e3725a6a50019b3979b96ca60f75be94e9703040bb85a51f30b939ac41a846749cbeaf65701200000001e31197abd4962ef6abdb258e3d8822874b55c6c2fe1be65379dd6c456450c61f00000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2470000,"000021d076b5cdf88f2ffd7926aaa2621ec11078e2bb6750f7bda1242e52548f", + "01270cbbceb4e9f42666f7032c9e8109acf9bc18df722d7a167367942195d1d658017083123e78ff62fedfb3b8de5bbae9891c8341f2d34ea3814f13666e3008281f120188d2e0e44e64a31df1d5e72b8ba4802437ca932ce76edf14b94e5a27f4a5d762000001741da8b08dfe3548bd86981681ec13ffd1b246a79bf7f43224d35fc3f3d0f2500199981b982604a53535dff8dc6d0f9e75ffa97c5e40f0330036897b9ef2e8d34700013c89509b3d8592d6edfb11a58d6442bb9f136e1bab02449603ffb9249438b91500000162cd73337fd45ce5f4b686300ec798cfc79858405819ec4621340593436b030f0001e31197abd4962ef6abdb258e3d8822874b55c6c2fe1be65379dd6c456450c61f00000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2480000,"00003f0b0622fa27044ff8b84cad62f386bac55164b7289142cbeb3c33b040e8", + "015ada5a9dad0c752a2d5e2fb0935729e2139bf902fb210e6bcd210fefcdea662f001200000001f83dda26ef73587c2c3ca2509ce21a27dfeaa6ec4aa96c40e9d7a360c8398e6a00012fccc84d0e834df7644910de71d520d6838e5afc9afe8dad9dd2c2924c35f7350001e0663d45f0439b2add853577a7d7bf54e2173541dbb770cbe2d121cce3f96a37011282b6cb50beb9febeddb865b46bab484901947bf214670fae743bbcca037e640162cd73337fd45ce5f4b686300ec798cfc79858405819ec4621340593436b030f0001e31197abd4962ef6abdb258e3d8822874b55c6c2fe1be65379dd6c456450c61f00000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2490000,"00000839d2fc8b3661307961b555ee8785ed55210f7dcd447f2b8c9e2e7abc46", + "01637eec7561191d1e5e02ca4ff2c29734e2969b805a88ae242546743860215b03001201ee84092149b1bb8785932d488366195034d2c7b5abbfd2e83c305af3229bec4900019016d421f2474a8c1b97b6dad340346cf22707dd4afbba8fb8d83eb3ed01c46501ef31bda82a84892a7bc0f6de62d3552896075efa3f3679bd6ea0c153162b7a4c01107a89a3c364ac669bd087c8b16b82bc45ef8168e5bda4fa417129363cad8e1100012cf914b54adc6ab04bc494d6227b94400020b7f78e3559da9e2876576410ba470000013a70e983cfeee4ab3ef676dc246a19b1869fb30c1ab5329f58335fe3c0b1f35901fbea45827a5d8876f0bf15ee0ab0cc6a6b01e8c74d362ec5a4af355cd5557a2201e31197abd4962ef6abdb258e3d8822874b55c6c2fe1be65379dd6c456450c61f00000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2500000,"000041125b224027528b4c143bd78cefbdce89c00d6b5e64fa5d287816f6a77e", + "01c813e9afa455a1a0204f33e7212d40ff1c87d6294b5bab40ed9d94768b2f435001cefafeb4d135934c2bddd0eb648ab40353c1f4edd80ea3607412ffefc3d1c21512000000000001eea963cbca35633d6c2ef0d07aa5dafede181757e8dc5e5ede2f18cb0aad2e23000000019175b2b0550e4b9037e4bef17acb64ea628592a6444dac9fda3e81128ad3131e0000010cb6de26bb354eca1dd01fd06d69cccfd5e84ee2c0d9d98ae1c94e6619b86f0d000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2510000,"000020d829e68be64a32c278058143e3d01dc907a2e28618a266ea524343d7ad", + "0156cbe6fd3d6354aeb662d4fdd9c43ce4d81da84d43ff3ac359f71ba495e0ee620150e1d322bddd95d0df170fcc5aaa37b4dae6688d5820c0a3d42ca1ea162c7963120001210c68632dcca30f40ac1a8a5165335b3af7aeb8ff364ff603b9ab173455633f0125748d1e81b6ee2bf6afb402f71007b3d3da2e44c9d00d2281870273b9bcd74d0000015ecb0bab0615d86562a68e4953741f1d0e7a59d295e4c608655137cd943a1b1c000191850356ae5915f0905390c75fa05adee95b39180355f7cc34af5adf2be3970f011f61082ea2eb7544946c08aa79f1f7c368b8c17331dfd01517722eb7bbd78b07019175b2b0550e4b9037e4bef17acb64ea628592a6444dac9fda3e81128ad3131e0000010cb6de26bb354eca1dd01fd06d69cccfd5e84ee2c0d9d98ae1c94e6619b86f0d000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2520000,"0000101c27b4829b6e489e2a4c4c0982c97a3d09781455ed2491fc99de16817d", + "015f36f9384ff324c9750407eeece00a6a887d69cfc925f7a1c171fb96a64d9e5900120001d1356687146f67866e7edb6b8cb933a042e5425e1996e0ada870fbc2e33564020128806d6f8a736e78a20886500e265280dcf0c5ca37940ff34ffadbdb2beb9e4e0000000000019fd750559fdc631baf96beb1de448e12834b060c3a4e875cfa9996001a5183070001330ec830251becad019097566c213f7cacca38fc1717cd9d10a9902775c1693100010cb6de26bb354eca1dd01fd06d69cccfd5e84ee2c0d9d98ae1c94e6619b86f0d000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2530000,"000007013180f945757d26c3aa3517821df8570392742cf021a8c79997e74bd8", + "01c87cb2c979aac7003e355c6c475528c8ca796540a557cf50e2bdf1c0f09ba23e001201a81711a39e78b5ac0b29b22237d69ac2932fef0ba0a61c90ab655b0caf0723110001e0c8b91cec1ca91e83da05b1af3ec730cdf7b0fe9769eca553a015dc630aa541000114d42899bb27bf0e3f2ec5447a1a385159fbc1fd96dd99221d4e2c4de7f57d3f011291b9049366d7af1f19cbc6de69e836932b2f45f241a9b09b686e210cfd673f000000000001308deae1d0bbedf7d47f1cbf2c431bbca44f903e88be28da50b8ee1fb29ea118010cb6de26bb354eca1dd01fd06d69cccfd5e84ee2c0d9d98ae1c94e6619b86f0d000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2540000,"0000351a602e2a6a3748d7abfbecbfa4bf1e90d6df12fbbef06fe91b715ac27e", + "0170bc6b8d02eda416312877338aef1f38acbf965178321e4a602b9b11120817160012000001c2f6f87a3066689f32108b9d36ba19fd8c3ae855aa96759587412d8d52f0873c019454aa560b3a3e4611218a67e4a6489bd45d2f931e48dd11084c4757a59166100001bf7ce7db00f3286ef717e82754b45298b2cb59e435a8d759dfd549e6d3a336130000017950fd85896d54af3f4a31937d535f6e4c53172abda9cb0e805b4e530c96c050000001308deae1d0bbedf7d47f1cbf2c431bbca44f903e88be28da50b8ee1fb29ea118010cb6de26bb354eca1dd01fd06d69cccfd5e84ee2c0d9d98ae1c94e6619b86f0d000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2550000,"0000001007b89ad0b3155f9b567824554437d63f38741163c968e697124a2a6f", + "017a52f15572bd19cbd4a30518d28d701172bd123a13e97ccdf803a1a6d295304301efe9ff9f362a43264f64a68dfea80fda47e49a638e048e3f8b09d5886d6218241201c8a16218faee324090b4dad4db536c6227224e0501250ef98b2141c64bb8ab27000000000000000196321b69223bc1f2e629499d3ffe4cb53e04bcdb8a61c6a8583827bbc786156101aa8a63fb3bd27c339714bc72e5317d0c0f9543cef6ed1f01ce848378673699720001308deae1d0bbedf7d47f1cbf2c431bbca44f903e88be28da50b8ee1fb29ea118010cb6de26bb354eca1dd01fd06d69cccfd5e84ee2c0d9d98ae1c94e6619b86f0d000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2560000,"00002f0c91a1ef953b8dc04e9905c6e06a598ab9f45b430c552ab88b19c7102b", + "01499da00a8782c663622f5d868da31ec8aefa59675e2d8f08b9e1bb9888242f5700120113decf0da7c6db0bc575c42fcbc90b2fefc4ce2dda250a790fe6f5f405ee3c0801f53b45bb25602342225dc7962e61ea0cc986b2f20470009de369bd64b9c9e72c01338991f67f3570e694f3331e0ae2fa5a12706f1f8a4e34de4e4402433841d13b0000000001559f08c1d8c1fed6429a1721d31762fcb575282be1742c162d3d01380496ca2401e14757f6cea45181f2129ac47adfaba94729e216d42686faa3e6d723c9d1ac0900019a16f8f374c8f6fb7a9b6399518ad9a43f97473f82978d849b625858c993577201308deae1d0bbedf7d47f1cbf2c431bbca44f903e88be28da50b8ee1fb29ea118010cb6de26bb354eca1dd01fd06d69cccfd5e84ee2c0d9d98ae1c94e6619b86f0d000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2570000,"000011e8a01b44ddac78b911f0c3e0f0242e84b405cb5291c505323db5999e37", + "017b53d16added1ffcceaad245d769780f973bb123abd99984c83439ad40a86c450012011bf4dc20ce637cbbb544b26e49acc77e4826fcdb981cf0f74f04cb5a2b5e600f0001a53ba3cf1000abd846a272a1e76d1921c802befabb7d9bf6fd645b8d2fa82a6300013b44ecf7e739272e0e89c77cc7235154e84a1d5d7fe48ad4a60f4ff95a2c2a4e00000001e2f01119f5ace4129d58aaf91bc7a75cd91dabe5a9734f812746fe612be7306e01484ca3ae9822ea068f849e099123c9f9c6c56c97bcb5ac1e6ac4358b9560d018019a16f8f374c8f6fb7a9b6399518ad9a43f97473f82978d849b625858c993577201308deae1d0bbedf7d47f1cbf2c431bbca44f903e88be28da50b8ee1fb29ea118010cb6de26bb354eca1dd01fd06d69cccfd5e84ee2c0d9d98ae1c94e6619b86f0d000001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2580000,"0000150a26f2a00040485b69ef27ff8ce9aab00bf4211fe2046d1eb654c333c9", + "017bb6d393967b7378f55ae509b977694bc498e205009805f2b883ade235b0a81200120191bdec65d1af8716ade192ce15c3f6b1bb86904f9d593a081e097593c21e50570001d2e2c73d577db82357668ad401007bee45e678e37518eb51743d79155b369b6401be9cddea46ff84639cab301a4f67f667802253b53e96b507ef0a0d1b4dd66b4c0116da411d267aae6791f2b117e48a2da603c1f2a2465abb8cb15dddf62223e65b011ae5fcd708245b71c52fb64ca7a84c963897df804210169fa974db7df11d900a01af6fe03032b87dce4c244f9cbd31599334f71e7c1952215bebff29ba342fa71b01441f4eccefa92321c58058c3fea44c77dec676c1f91c4406cbe7814609671e59000000000001ae7d0852fbf412c63417a9dbd1064e29c4945a0644de644dc4037161c27a8e690001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2590000,"00002ca403107c7578ad36ac4b80c3bee3edcc6d667dbb659a44a1898cda4452", + "0116511967e4888cc8fd6e316ef613faf25701e7c86e672875fd206b1630e773060144e3fef205cf5a83f88ef36758d10078ca118eb77364377891409a10e8e63b1b12000000018cb01c9f5b3b16356cc0f1f2d4db823cb3807963b4f07896a2f7ec28ca49cd07016ad99a8cee9b06c3366a47410a30b3433427784961a5d44094815c3da8d85c450000014039bba278bb388cb8e2d0040b88d0c4d594756c2f51e6ad8a0d93d38e8168140001366acd93da66a6f776d0eab6e787200a04fd5bd41c22dc89ef7e657d35016c1500000001ae7d0852fbf412c63417a9dbd1064e29c4945a0644de644dc4037161c27a8e690001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2600000,"0000165930a477a3084bccf559ae79b4d111292e40a78febd723089c50314eac", + "01390c0f62bc7cfe915cb19897a019f68ac9562f922de7f0efa50a77d549415f1401464b244af510b7c17b874618c22757f30875eac88bb73ffaf99b711d984f9a0f120143e13536446c6b96d9e749226885f6091f43be9331c48d7ba597c4fdc2e19e3f01ccdd7195c60a2c3b1d88272d14fa2ae7ac552d042f5cafb727fb88ce2510ea4b000001e4b58b3cd1294e39d898fb290a8f3af4a4c9b4eed30569a47382421a4cd04e01016d46925c8d0f16f93a74a978e5087ae099087adbb47163c1929093ea9983650d01e0c208abd73ec7ae892be2cb472e6e1dfbf59cac4d578849e3096139c4be78720154e3813567c8e70420d092f0f00c574fa4c0b6fc2e191701ef84b2d31ec85719000001af041ec0f6a78422a396fbeb8ad3e8997560037fd66dd2af6a479ce4e368fb61000001ae7d0852fbf412c63417a9dbd1064e29c4945a0644de644dc4037161c27a8e690001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2610000,"000030c922cc26442a261d349a7790f0fa97b3f0626b1bddf8bc45ec2afb9bd3", + "0132beb19404e1cc51bb3332e4229cf605c6eb1309918dd0b2956a0197b343201d01bdce7959f8dba661cb2ea2433ff00bd0786baf638eaf36516f73e1384fb33f21120001e23cee737a7a908192a46987e9350d51f6b8f618bc19c1d64940dba4ebf70a4d01be28e5bcf72c478a625b4ec220330b4f174a05bd91d629de6e8c97cb9ec2137301645af3c58b2e39efd7ea63d92e55f4e30e07983e19d76d16f2da6cd02f60041b000001e18fea2fbdb2036df64a2f32daaec094114dc3fa4207cafdf7f8c61bd9f9906e000001b4324e16be6ed21ed9930a92e711d4a28c09da43a461b7bbe0641d22bc3dd91b01af041ec0f6a78422a396fbeb8ad3e8997560037fd66dd2af6a479ce4e368fb61000001ae7d0852fbf412c63417a9dbd1064e29c4945a0644de644dc4037161c27a8e690001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2620000,"00001cf792ea2566aab5b74659cdef79e5d7a516a42d599789c1c12002455093", + "01631e0b17a6ec0e2235da932d02b9869b4c719312e48e4765a166c424a99ea61300120001ab0e23b393f1bbc2a89d1d9b27ac7eddcac427dd02568f14a25c1e55a298c36b00015951a0138f4b4a04b10de3f4082bbe3e318a44c55402ebb903bda9a428b2736a0119dea2592d7314210185f4184e041191ea6d2cc9709094ece38cce23a8f08859010d2d536e80469571d01f762081b10a2dfeadbff318e6261ac48f9d0c9acb3d2a012bea92322966cfb20cb12c2bce48add77276bfb5a56af8f663c7c83a86b6b61400013aca9133adcee5c404d3f69ce721ab44e2427fec4736b5461fdb445df2892641000001eeb64275ae35b267c245ba29133a678cde061ce7afc1cdb1691113abebc9e3000001ae7d0852fbf412c63417a9dbd1064e29c4945a0644de644dc4037161c27a8e690001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2630000,"00001c6345ddcfc7773aaa19194e562815546f4c5b3460ad43e766a678271009", + "01b0dd4869f80e6e4af8c5c4be202d41127ec407d4fafb5956f7d5251536b5882401c49699ec36b15ea40a1df59d17e885ffc2c66c0129f6d5e4685d484ae36492001201d02bb240014ac1164f076da797f021143218b213b50464eaf97e32f517a6dc070156e2f5c2014bd2a99b78785ed55016bd593c83887343cbb73bf3d16bbd8f2d2d0001841f5cb3960d9ae5ac022875288c1d050173b4706664113c4c33f742bb3d3c6c01a8c80868700a4c656092f7f5c072fa40b798e294b9b4e6acd7b955119f3d193701a9414b98217ecaf6184f0e1837cb4bb3c6f3dcfd27d24699baddcda2e47b4e34013a3be89a0fda846d0870ba807885855621aa7db0059cad40c39a9d1a6cae735b00013005a33f817c204c22ef1817c83dd46cb4d28df3f3fa580b56a37b42a717174e0001ed254cbccc86b34950f799640f8fb64fd73c70e06bf7112506c55acf25bd827301eeb64275ae35b267c245ba29133a678cde061ce7afc1cdb1691113abebc9e3000001ae7d0852fbf412c63417a9dbd1064e29c4945a0644de644dc4037161c27a8e690001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2640000,"0000143d0ae980df804fc5ca8a37e6996d52cbb22a1cd42e4d1506d044ed070f", + "01c64b575223757ade160af217ee43565b122f6f3b8bed7ef4afabf7c77768981100120001406a54251bda24c9548914aae0c79744bb6c782f6ce2b7b20547f8a370094715000183f4917d08a7428309c4be5b36a3e50169d5a3af6d2fc0dc7a945d7bf6dc001600000001a988ee651bc8317b69c110ea0dcbb876435073ace5df4c8749388ec108e1176600000000013e319e88ae2b39cbcdf97b066940ee5c06d2f00b0fe2263b9db787e0fa043a3801ae7d0852fbf412c63417a9dbd1064e29c4945a0644de644dc4037161c27a8e690001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2650000,"00000ee25dc1ebbd90aea13580b22a96f2a02c00b05e8f624f27f60aec3c2834", + "011d0345054af8543ce8a77306ca73a9b85715f0d6b1c68b57f0285272a6f1800a013d886387001daf2674142160003cf452fdcafd2d8e7f84803eef794e215fef0a12017f08878fd2c6b000be5655de2cf634cce3e2b57378f26b687430a6955eeee7590173a7ac9807acd2f796e4b25c20ac936347281f4bfc7ffc00edaa73782e41a5580001dd6f096f1dadfa086303dedcfc6980dad7cf43b3b2c5069d3438b01c4994266e0124bb550a7a47129760b1130dff16f2b40ad4b4cb7c3556260f834b41bde8ea560001a25a84075455e91de53a229f7a6a027807074e365623b5d97dd7091e38ef30480188d1a8121f87ec1d7c6d2dce194cee57482789db35118cd3fbae2056049075280123cfa6562c302f0279e0af798c11f2a6887f1a0a6608afc13de566b04f39a5690137960d64cae376e0de2dadb841a3e397bdf9c8e322242f18bbb4a090d6a57e0a0160cf5da6772db1d7a14fd05856da8ddc69da43af28c6b3e680ba2fc01535a02500013e319e88ae2b39cbcdf97b066940ee5c06d2f00b0fe2263b9db787e0fa043a3801ae7d0852fbf412c63417a9dbd1064e29c4945a0644de644dc4037161c27a8e690001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2660000,"00001e17e7a2b7717a7e35af767c462ef5a00227c52e0dcec2108c70bb576958", + "0152dd3db7873afb78f5c478e8a6cc76fb217a51953bcbaad0c8f2ef4357834a4001e60de482896b4f53d7a3dd4c433af297a4cff7a15b4b8b3a9977bb94d2ebbb4812013539ae752d795b2bafb9a765a53bdcd9023b32e474417bee1827e037ceb886480001a24683f1eab263376aa8e4e7f21bbe01819c4522792859226d63de56a1c4fb5201bce850116cdcf36db506d1b66ca5540863a588d71380b87701abc866923f093601c059eba696019f93bb16691fad3c181513fa2b2913462811e75e753e951d916601449ddf266b43196ad809b5b78d5b262b54c0739c33871f178034a7d2fa5f791d01a05ae9edb01f9ecb5e87ccd48852ac811c0544020115e984af38e08117e6bf3400014aec1a28ff2eb17bad03abbd1acf3d0748279cb59725be0cfce270880c0b04220001bce8326783338b4f96a652c3e1e9e1657ef3c6c4fb24ba6686f4f1d3288b881101ed11102c55514dcbce8bc7b4e291b04fe4d1f84346766206bef058ce8c2c774b013e319e88ae2b39cbcdf97b066940ee5c06d2f00b0fe2263b9db787e0fa043a3801ae7d0852fbf412c63417a9dbd1064e29c4945a0644de644dc4037161c27a8e690001f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2670000,"00000693708a12127af8a813b4ac8ba07b51d6f0fea831b3be88a4a6b251bedc", + "01bdec3092005b56b1f73c99a401de98b1f38f9e971c92dea47310e95492155d18015c2fab33adb958e7042b54194d55b184582f7fb5b2cd97100f24a408e358d43e1201acef7516173c47862cf428b60236556010c50c2b4f9c0c3fce8a1624c36628310001428698f684e0fade2a57d3ac44c154b64271a2ad41ba42a309e3af39875d57280001bb3bbb5f80e5bb629f8a555928487b8bd6048ea75e3fc991fa725f37f076f148015523475393d57001331f176ed34bdc04628604c95b8e5ba024336ff7b172871200000001e47477e6a87a80c708581c390476c6405ec5b76569a6e8397c9ad519dcf8fa4000000000016ff5c612732badf091166a6ca6bd8648d43c84bae63b2cee4a80c9e442304a1301f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2680000,"00000397bc0e7b5512708062d70c14d282df16ddfe5e754e21ab689e9dd5f606", + "01d22b5d2c0e01116dbe05262dc55c4b8481ba19056c1e964007f7d85f0eef181f0101ae0dfb1132ac1e2a64d6f7840a4f1d66dd038a93f49110c3e82fc1e7b77f31120001f3a361a065b9b3cf02d1fd7368e6e38cec6c8a6533d9354ab2ae32e91adf42280001920f08bffaa3174ea822fd5cf82f038b1a420bad6c4d46ebf76bfc75de005c3301178a297fe92ffd9b69923043d1ed60cd9b783850f77ff595cd67e043bd76364c0195af9950fcced7df504329be180b5144d1e731b38efbc3dae7f3c6debf2cba6e01be993a451bc8f40ab8bb6a7270602b44cee6c7af517ffe3045165c23e3670d4c000000017c0a1c88159a1a111b01be6790a5be6cb5bdcfb03d2f331dd8a5f3aa4276366501cc900339956306818325ebf0fca55a8d6a18cbe911a25a2590d01ed8526e646c0000016ff5c612732badf091166a6ca6bd8648d43c84bae63b2cee4a80c9e442304a1301f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2690000,"00000b984da7ced076667d836d3353b4acd5d7c896c1bba9d4301e25c78916c6", + "016c98b995eec04bb8c80b08544f7eeaa78bd9c568382fc3abe9882ac9d8b89d32001200000001933b518878bc6c3d42b8224dc8f4081b68ffb66ac3d13870dcdee42f6bd04b4e01b162caf9dcd4adca71c7a1d44014ea51ac73e929c7020f385d612e68dd5c3e4501bbc7906633811b102da0ee35fe3e0f12d247a8c0d684ba5eb71adb543f6325410000010e3b5c9670ff9723c3739a85209de80b06cf5303af307e584cb74678f1dc295501f8535188dbd8a72912a9cc81be80baa49c31b97f435846cfbd40c67c2c0c8a0200000190ad723086e127787028b1f041e0ffcc71002bef7cff831e01832e59230ae35000016ff5c612732badf091166a6ca6bd8648d43c84bae63b2cee4a80c9e442304a1301f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2700000,"000009e4db0964825728e1344dd19bd0d3c9738f7e0f5a6e90fe5fb4453ffeb9", + "0127268a9a7adc1c520519fe082203b0635657bb39201b199e4a3979309a38b45c00120001b849a985c16aa789d76e87c26cc67f14a7c0f177334b6ba2450be7796d00234f01a31e3afeca73fadea06781491e3e2963be6e97cd6683623217180c194c54e73a010d8222e71e7160d07eb8b52994abfbbc24665ba12e600fbb5a6fb61e435a026601abcfb0ae3d348a294491ec4f6d057bdda3a83cb3589017c1e70d95dd0ed51f0501159165b6e453440a4874a0fad712995d1ff2f06f0aa3bc88ca3a9a2bac0ffd53018f894cfd731161ddd1c783abaabea08aef6091fc2cccfd04cb6fcef5db4a054200014f50912acf267e31542b109ae6ebbc02e749015b93e76b9093fd614efb2acc600169510879fbe08385bd791e29d7a59cd9fb6f097ddbe0771f7034311885b8942d00017e644c3f92d5b53f2dddf32112396a9bddb5ffb868279fa873e1802d5330904a0190ad723086e127787028b1f041e0ffcc71002bef7cff831e01832e59230ae35000016ff5c612732badf091166a6ca6bd8648d43c84bae63b2cee4a80c9e442304a1301f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2710000,"0000036bfb6c17d2bfa334d27d685b86fa10bb0db33cc30bd69322021545b7f6", + "0169e83120d671c4d50c1fe0136340a1d726a107b35ce1d1d12e0a884d3cdcac0b01abae607fa9c862393a56ddc09182c65a5f3948a8b87daac73b4216030a7d3361120000000115c8c1bc7fe450bed3e656f69ab3f463ef1208a3d3a6f80f3689f8390fdb570601514c60c5167776c272385afddfe4a8ccaf07459fece05fd9f69fca8cc841e0650000000175aad0d0de751fe2e4ccafcf0d36e775c553a6762464092c748485cd71dd882001cf4cf8c362a5d63c2c88c1a4d966b08e968b60d68a80ac1c9c833a4a45e6bd4600013978f53ca99f0efe627b2a191bc56475297ff7e162f52d7a797f2b81b70eae16000137ca8a9e6330a19ffe3c1a86a5c3f6ac3e35e0ebe05b9406346bbc2b116dd22e016ff5c612732badf091166a6ca6bd8648d43c84bae63b2cee4a80c9e442304a1301f32d79b963b112d7a905a6131318c5a70daabc092d93f6a7f6eba715e0aa2a300001deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2720000,"000004f3e5856c5cf40649ca09372db7b6b12f30e74137c82d2ab0d600d1eb4a", + "01c92ac7c94d6439a4673e592fa9e5e24b62e2ec1b4a4c3af90cd1ac2848e0756c0165e586025d7069f71c90a35ca5036e7df63601f6b9d71ebfef8c62969d7941381201e027f227a6c72eee27ba085709046b2f6925926a50bad2ab789b628d9dab503b0001516cbb26400d3ceeb25258359df175d1d18706326915b3bd320dac86b2cfbf7000000000017d50c292d9cc56e3294b202e0f1d8ddc1a13545eff6c5d18313e7bff467f5a3201371f80e5b54005be12b8c21379fb10f94735129d0c1a71a7fe33a9d5f4af586901e040cc1793ef8dcb1e582bd04339485440df158e69fa8fd5415f3ba2e9dfd92e00000000000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2730000,"00000218832ab3feae9e421ca900905f650a90afa116291c81636eb185772fe1", + "01a5bda89c1f2315bec1e58cc55d5ef2aa257da6647097bddf4c062700fcccfa41001201be43261aa388eb873ccbea0da465128ab0de4320c52ab36bd79dc7c91404916101e54dcf5bdb3d9dd46380a3f4cd5bfd724ff224259b5b3a902e3f049e8cd95d4b00016b5d0591513ba6672898819c2d859738d30bd115582ffdf5c21a5447b1e0470e01e1bdc9b4a66cbf2646fb42085e6ab61f5cc9a0efb329ed1f825db25a73f8bf1701aabd5d1718a381441c9d97880f0b85aff65d29f3c5ee02b611117d4e26e9e4630000016220bf8190b2f554d68b5cea869a4aa2e929562deec1cc453d0eb4488336931b01678934609c933d85fb3fb2ba8fe13fdccf55d60f51cdae2d929ed83878f86372011129d5db298713893a63fec1e9386362198ccfcc1e22efb68e2c1bad6a039d080001ba9ca11efbb2887364f5cdd56aa3cf2851ed212b6e5ec8decc6ec6501aad2a4f00000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2740000,"000000355cac8aa108cc943f8d3d4544369e4d95a6f77e098296b4f97d787a7b", + "0183eb3840cabf1d72273f858ae2d711f679cde82b568771fe433d78c0b2bc5165011f1b4b09df201d4bd1f990753427cce8fcece3344d73677aa390c6f479ce536e120001fe8d77fcd4480393a96b2be1d62fd6c32b93849af22df4f71fcb241f119be35d010c9b8b8fc5f2a2927214fcf0ff27bdc121ac13525128fab570b705a64a80d22501b927f4d072cf6778c89513e5262fbb7b089e34cd46472fcd3162c4f850961a2a0116d246deb76e9c5aa001dfb3fc69288ff40872a845d2e0331db570d8b13ff66001eb8e4b27e8b87ffad9390921e13bc19401e9bf7a8a9d19bbca6b8292277646340001f85b6a2ffda765f53066a62105583b635ab3d108a0bdf245eafeff526548cc4900000102ef63cda1595e0d94b0046a8f3d4dd2b90664b434ec1deef1b9162146a1126501a3b3a7f441e1ea0e97d0dde3ba826b3819d599fc2641620e6247dfa3fb4f290f01ba9ca11efbb2887364f5cdd56aa3cf2851ed212b6e5ec8decc6ec6501aad2a4f00000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2750000,"000001ec34f345399524f4ef9c00b58654bb04c978b8827f53df5f1b896550df", + "014ccad22ffa8edd72ebacf571b3b053063e01bb1e62e54030e8c812f6c353756401e5ccbe73056c993807a579ec1a4a4bf9b30c66cf6600dcdafbf1df211972425d1201f4efe5d6c872dd29927a09b38c502e6c3b912922fb4dbd889284b75ef731df0e0122fd032013e96797a46cbbcd8f31ae25178a985208ff78f0087bef0c665dcd2f0001746172f73f97f00daf048d59bae1095c320c23655db2f1e6f0f04c5e3589ca1e0001d1c7bdba606800a19babdd02dc0075ac8e9fc66686e9882d58caac0098bd34540001f1ff28f5c418a7aed6d4e2069a32f0f9248b0c9b9a70fd84b589d33f4e9bb5350000015a4c0945b7cf91575cd634728eebe9aababca44c82c80547bb04aa4c796bbd43000001acfd740aaa590500f443ba3b5ff637c50ee8e3214935480d1def6acfdc789d45000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2760000,"0000015c87e80ec4cf9158374c9a148a4fafbdd77b3c17df5a3b1b9d94b47bbf", + "01aaf1ac666a8d5ca078c8869ef5602cdf777065d07cf1be9696a69b77a82b811701613a0ef8067a782e39a2d97924d4dfc68bf13f6a58e9a995d0d535c1df340e681200000001a4303a4de5e934a1a1cffba66d4bf96f0bf0abad57364fd5bcafa9347eac4170014a6984765f894116a1514b87b3c025c9864077247e6eb0bc2d048b3840e6a368000000000000013363f3917240b741e58b686cbf200c5ad307a8a811a075a47a463bea7229cc450001acfd740aaa590500f443ba3b5ff637c50ee8e3214935480d1def6acfdc789d45000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2770000,"000002207ae03523029a46b0a3460174a30c3099c6722b4f892459b2b18f8867", + "01b900fff5f523af025b4e369c571d4bd2d1cfdf16ce8dd51b7ff82366291f9a45016cf4a53e8e4c8389c7d2e58edf37279aac81167e6ab781ab886e1cfbaee0486b120001745c7db9ec94777777f171532f385e67a9b0df640b734af32486c9df8ac9ae6f01687929d7cc4209a3bbab7ef6195db2bd3a73d99c83525ce873c64dfd38b1ea2b0001e436412616a18d2f5aafcbe361d9a8347d8254e5d843a7642d17699bd398fd0801def26a73810d3b625785306a8f8ce94440af5c184e9e4a25e42b7b6a823146520197b38ba3bece1c9aef77c7146a0a7b1977a99d4bbcff3e585d525d287365461b01921ec4f961ce738d96b70778556b3c7bc7e2ea9aead91b47a6b690e68c0f862600014566738f8f32f264451f1e1a875669060ca58de6414f514342aea2045b2afe4900013363f3917240b741e58b686cbf200c5ad307a8a811a075a47a463bea7229cc450001acfd740aaa590500f443ba3b5ff637c50ee8e3214935480d1def6acfdc789d45000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2780000,"000000ceadb1559401eb4ecc5f97f315293876bbd728e2205876a21af5e6ab65", + "01fbcc2ee17008927daf343356e114eba3cf842d36f0c96614a15e51ade15b3c3800120001b982af0b123c8dd54c04248b329403062622a72e633775f513a4c68786df233200000001fb9c49e671764cbe882642dcd99686daca9331447e07691f057e685d80afa23200018cf277f719d8674746f96507213ee4450b208b54b4771fc4a84b57f73b4a531101872359c8ff2f9fb305c0eae9e12c3fcc11020389951dabc32180627ea47727090001f1f692ec8b048024510662b293afb104149acab1e25f33c0aa0dd33174d78702013363f3917240b741e58b686cbf200c5ad307a8a811a075a47a463bea7229cc450001acfd740aaa590500f443ba3b5ff637c50ee8e3214935480d1def6acfdc789d45000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2790000,"000001dd2f736dbc05ae15176b258158d9f1de8bc86345e7b97782b651fa4343", + "01adb73de293af6100cdb246665ea5cb847a2613426965224533046760a0d2ec1e0012011b8ff8e27a2f1eba3d9d3ffd6b3f489d76b2487f3c980f2a277f8a32f67cee0d000000014bb2ecb5f06fb43d696fabc864cecd1a55e6794547a8dec1ef256300112a563401adf337dd014c8ad0e55516b3788e60e3db17c89fba5a85eff6bb3f449b93d2340001e4e31cded3650a399ca60efb0acc027ac5108b9dc13fc90ff4522849328b451201d993d33cc7322e9f07ee6545dd713db02221b5c11d6a2ccf3c777e9640f5e11e01006609d3df19953e1833373f8609b3b6d7709bd14d828ed87f89ae887814224d01f1f692ec8b048024510662b293afb104149acab1e25f33c0aa0dd33174d78702013363f3917240b741e58b686cbf200c5ad307a8a811a075a47a463bea7229cc450001acfd740aaa590500f443ba3b5ff637c50ee8e3214935480d1def6acfdc789d45000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2800000,"000000949616d853a3d53b384fdbc000ac1a1f316644b2415b9f4e0da207dec8", + "011bc927e5901290d2fd42ab76891564f19ad1d58787970aecd96cc73c6247b5300012000000015450e100c5f4ca90721335172ab3f304fea83890a0f4eb97ea59cb0a99196d2200000001046a4a80fab1516ea89ccbe28276951d55317a6eef81bace937ad00a60b70700011c9bcfa1c1c1bf70fe1037b2ac5fbac1cdaedd1f37d742df08ebb9a92a39db5100000001ef9b5c5eee526c310447b609ee9aec5f94b4e0aae936f132e713667e7070875001acfd740aaa590500f443ba3b5ff637c50ee8e3214935480d1def6acfdc789d45000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2810000,"00000004764c9eb18681abed0fc109aa948ce44ecc59f72a2dae81977c822316", + "011c77ba807a5a49bddba1754d70ceb2f7f6d8333f0ef4f13e4c723244c3cd146e001201972b8789a04dead389ae4ff3b125241081ce367336a5c29ad0de306d9252006a0195597617746c0d5eb3680504dcdf5b1974b2eadfa082474e2b76f25bc50228370001d25e448dfa8001d29c0a78fa95265f67e6c2c31859c22d25fe32c0cee4e2421601bcf90f0c426ec10f1e38679ac022e11f702b15380a83ee9ddb9f506cb575c25d014c52145ced5d63f5ed6152a958defc94c15b81e66badcc8ebd7da0e875cba84e00000176a0d6c3fc32b04f68e65eb6644ececf40990442434d4117240e73af55f2454300011fd66a932ef32969b64c70777caa733fc8750da0292884807245645c00f332670001ef9b5c5eee526c310447b609ee9aec5f94b4e0aae936f132e713667e7070875001acfd740aaa590500f443ba3b5ff637c50ee8e3214935480d1def6acfdc789d45000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2820000,"000000000909f2eb98b0a769141b25d1fb2b31d54810f6bff9808f56a5021cd0", + "012391a95de0e6ba2cff03db4a6e044b176702291c909d9b1c87674a1bf1587544017493a2d4735f191efc95b99661b10b0d4b4269f381bf1bf4e8b0ebd36623ab3c12000179d4ae1a9f0c187af947705b685261b446c387481a0492a7ee4fc89fc2d08f5801d4726c6363660995e5ec97ef3f8ed4e14faee7afab04bacccd65eafa5f31643201f9ecb2b1efdd7ad91598298978bfca7120ab8def940ddb3a35c17e15ebdffb2901cb5866ff701365a799e5d899f4d6675ebaface510910e25aee5ac41305f23657000129e2622a4d0ed1c791696a4247df04120381226de436e0e164a95ef0f2fae850019007a4b2f97ed338a049718952ba5947e7a7eb63627f3a03ed63d0749475162201bb709f1cd2bbdbccd098a92cf4b7ffbb5eb8c609db7a5220ad499cbc9ed15b4c01ef474b1eb9020685b3af2301b887a3c55bc432880d06abcbf92b6111215d414e00014af164eaea05c37bb0e257b3d73d10bd7d46935dea9a2ef12bbc93a77447570601ef9b5c5eee526c310447b609ee9aec5f94b4e0aae936f132e713667e7070875001acfd740aaa590500f443ba3b5ff637c50ee8e3214935480d1def6acfdc789d45000001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2830000,"0000000001e33263d528abcff353e147022258733bffeb0b895a9667457d809d", + "016b027237ef5406f56d2f6da0d8752b3d75f332d8abe7e2d121a2b2fed52bf32d00120000000001fed94f2021c9cd1e4f61e5d2c5188043a08084d2b410662d8e03eb6c0ad1be07015b738221c0e48a149ed7272a71a532f5e51644a5fb235205191b20218040313101553b82a71ed86b186e03e60cc45860e358f955299b87196b5526830bd9b1e32e0000015e81f3e7f0160966d2bf142e195b5eae6cf018f5056ae00fcf5a309f325f2e0500000000012fadfcf51161ca212e87a9cc4de8b5a9f4abb41b7a438728f044830f2afc74530001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2840000,"0007994f4c5a67c3030e41980e8f28de88f8c0095a5148393e3ae11fb7116071", + "0107d50be3da94cd282c1033cd06d4913c04b54cab7ea6c6a7c82594f83f496745014e2a1af1f72355c8f1293de782a56fb9007c058e2e63b505a3bded3a65e1cc181201cb5eb770f64973f5029cfc459dbb985ca7917f043fedafde858e1bdeab71511a00000001eaeb0b40eb18ca3f8af17693e885d12e448e26be0e86f094a52bb2f64f478e5d000143271ff6478c32c6ff24c05bd4430c98ae5e03a600e9f95bf86e277dfd480a700157a929e7478cb7f3fdb07ad200bec1ebdb9817d70c9cdd5b8a914a0042c4165900000001116cb65649e8d1f5880cd5024195b2c824ca422229c99009e317e0afee0705420000012fadfcf51161ca212e87a9cc4de8b5a9f4abb41b7a438728f044830f2afc74530001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2850000,"0000003db45806f522f1cbf059bab8ddb1a428bcc9063bced294c7bf0289a3c3", + "0168dff076ea07cf4cd3cc7f56dd777d4d674fdbd7254d228745985f1519aabb4a00120126221599044cc36a7e419fa7846bd10fbc30906f7224781805c357ab1c4b3b260001bee4b3f7685b465bf30071b907aa0fd7a0168c76e3ff12d2296a2d3f8071476f000001524a94380a6df5ff5e590b6f62975104335c9c7c836874972cbf1b26493d8809000001fc01021cf873aacef3be35c3b3cbe1ee6ba7c9fc4ab67575c4001a2cc44ffc610000010a3e329004d0d955b825858e80c3d06dd9301d23fc0eb774b72ecf5fcecb5c1d011e2a82b2b0a88f073ed033f7fa62c13ad6de54f8fc4352e905858b98aa18bb0600012fadfcf51161ca212e87a9cc4de8b5a9f4abb41b7a438728f044830f2afc74530001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2860000,"0000037007777311e559e889930c7590814e28f0bd3794796658b6cf588a9c19", + "0168e73e5d880dd3c2966e114f418ba6dc6b53e44f05491f869ce2318217ae31210137745c31d7b8e3d3fba88ea47680741d663b9a3ea33afaf0b0a46575b844690612010b0c81eb155701f7e4436fe6b497cc9c8c32fa1d7f1d61f4b1dc2488f0757e6a01ec6e66e4c074e5916a694e7747fb880960f9d1f8ba324dbc7578212343096b21000142736729d2658c556d1b69ee1646d7d8d4a5583240705d23c79224524916be2b01e639d2ce0f8395272898d30b69fcca93c8b915b31504d67f18bda3785cec9a67012c095d3aa4c6ab00ed706d7f096e8742a0304904181ef5661f04c5101a4e061d0001b973dbcaf63a72b0d3845a93c159ea9428f14a5c9511e556553e365c9e633b4101194234a75c594ea8982469440a8a481eef834fe45bdec4dca0667b77ec14c20f01efd703f6d25928ae7f30f5077924fef1cbd05ca1bec0f6dcd418015393c79c1301ba2b4a2de08400466b89d3f9c3683e5edd49def656294417e095feedcb2e4933000001606e25a393f539e0c9f767071ffcbd3a89520c066cca03e39abf465cc27f7f13012fadfcf51161ca212e87a9cc4de8b5a9f4abb41b7a438728f044830f2afc74530001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2870000,"000001d04778f2526ee1427709e733843e18decf20ee71d482271f04152737ac", + "01b700232dc2a8b845dbdc09bc5bf62a949395fb0ca7652d951ed6ec4d1b0ad60901fca8dbbc594fed4fa5d8b0a7266b90ced68e03963ff6bbe11ae8ae02d2040c021200012e2697c8d6142c817daff6ccdc8dd54cbef945a928351a34fa8b45cb7ded4056000001ea6e9613fbf5065561856cff1ee13295548036ff7175983910244939df2a0f0a00017bb6e2918f2fc2510262ad74a5da98483f9cbb6d88cded2a4a1ad0f25985c308000196f486388e3e5040d05e0dd965f32a07d496b7a20a374443a5654f4385a6845e01c97359fb44410c344474f22c403f4af28e42c095ea5ee248b9ed43cfa3d1d63a011f97f8c22e50eb065069b15dc7fae1ba5c43ed03e0f08cf213620dcaf4a270570001142c8262e2414e2657201ba5b2422e17e8ce9c9b7a5f624823a01b93b4e0a30e01606e25a393f539e0c9f767071ffcbd3a89520c066cca03e39abf465cc27f7f13012fadfcf51161ca212e87a9cc4de8b5a9f4abb41b7a438728f044830f2afc74530001e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2880000,"000002799452afc6905050bf9f1e0688b644b118c78051836e19e3d959a62c6e", + "0134d6767340388d779376d872ed53873bd2734ecd4a1c29f1ed9577db4e85f94a0012000001fe2853fbf0ad7f0056f742fef1e475fb34df44969a649b38fde8a347053ac83a01de8684c96ec70237845b30b820b7a66fbe20a2d27d95b93c8f210d3b900310410001a8fd5de1c1803457363a3ed65006db5e52da353ad34d7de8566492b17ffb1b3600017bcf655914b853b9c1026923d296dcd5a41b0ab3d6aecf9c58b149b58a6c762c01f924785ae37b762918a4fcf2976430d3e6aac4e268ccaf320cc9d07f62691b3801a5fb7b0002a13b13a3bcb9d6e031ca7ca1658b7c6560da6e8507310df8ea951e0001e8ab241d4edd5d63f92a0d1e1e53413b824e68fe20f12d546bf973ceaaf8112800000001d9ceb621d330b47541a9e5dd4b8a4e4cdf939b2f1c75a7d7a8dc46b4c21dc00601e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2890000,"0000013c9ed9f4eaa71e4d8da5c986c6d031e0907afd134e8f1268b899141a16", + "01cd3ede11961f50e972f0d4899c4bcdc52c063f0e312ffe68a7629c8555d9dc2001dde4b70b708bf15cf431f7f60eb555cba5ca00c84e3457acff1f7625fa97333b120001728fe7efc69ea17af6347ae15befb5726c0ba45428ce35e99a46ebe9fe2a982900018dbb3a33fd5d88e9051e7800238066c2c82cb2486b80f26019e4f9160eeb380201f3f7f0eac419356ac5ebd87a41f3d7ca4d6a23df606035b0255d0a439a07c87201b293b920a1294e99f799e9d088f32dc4be4f2752536e3448c9a4f0b29f0fb325000001d8024deb97d58a3730f4cda4f85f7ca4a5c12684d4ab8885bf2fc8435029e6220001a7176e4a6232fc4dabd1133144eb5c18d9cd472e9d77e200d67d8bdec03fdc15000001cf52d1019bc39ddb24555d9a89c6ec36091f38ca595dd87d78893e78c3208f670001d9ceb621d330b47541a9e5dd4b8a4e4cdf939b2f1c75a7d7a8dc46b4c21dc00601e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2900000,"000001ad94f932e03125a5cb8b97d65d8456cd5c5ac6c0d23a1e30dc66b722ab", + "01e2a67335c0bc7fe1f992b8855c0d06a149c157e247767013110d155b35ba55640012000128e885f4b6196408b882c8e710e3cdbdcf234cf87f1b5204702c8711bd6b016f016b18a89c92e9bdca9b0a0a7ed8efa923edf4512a3dd1da49fb19637f78a6675801230a7ddaae8eb150c03a393189ab57fecc90a4c6eaaee1d5af94d3a3621dbc5e000001fb5f2bdb1e666a1468d8de35b6ff80b80ccbcb0d4c0a60e85eb9bd5fdc0cb4420001185d341fbfde52dae86610bd5448414ff3af149962c611dc61a7ee6e3ea8f5710000000000019cf08345604a2edf9d85e8e4c8f38ac48b496f003447b615cbba592b62fabe0901d9ceb621d330b47541a9e5dd4b8a4e4cdf939b2f1c75a7d7a8dc46b4c21dc00601e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2910000,"0000000bff875f2e5dee11daff9357824dab610b81a38f8d24bebf4590bf7925", + "01369080fa59b98e677b77286193562138082502528ae4c47b898dfd1336a1500b018a115987fd5e63788df662b9ac8bfd88075385474ec7791234af7b2696f7ae26120000000000011f37c15173e9e762f0267dbeaccf1aae544c2621aa2ac4501a49a3136070227000000001fb1d42825800145ecd5b744dd80a6b00fa725de330f97dae5ef1ddc02239be0300016db9fc56554a7ea76ca5f4a990fd8b76bbd52d789fc81adcf24fa069894d4b020151d01f5521d4c44221d1e0eaa5fdcf4bc6bf8bdd262484f0880e4d27381c442400019cf08345604a2edf9d85e8e4c8f38ac48b496f003447b615cbba592b62fabe0901d9ceb621d330b47541a9e5dd4b8a4e4cdf939b2f1c75a7d7a8dc46b4c21dc00601e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2920000,"0000012ac689d379a42028bfe52ca7b8ffe203202b525c1b0271ee2b35ad6402", + "01bb7f60e4e5863130fa5fbbfeaa19474ba0db17f3ed55977d42cc3b9f0a9b106c01df4e23de8ca6ff7d2cc0b8d8778f2da9e8e6289962eee8ba1c0934fd2555d6431201c27570ab9e394ffb3386d7b0e142ac948042726733ecc3c0f3c8c3d099f9c419000000000000010afa191534491483df8ea53be13eea213ec284a14efacb9a020ce34892e9036c01e8c0d059f47ca16a186224deec7270090bd4170d5ba0ee1819edf77ffbefec37019d31faae5f05b4ef166820399c8d88817cc7d4da355ec124b77f35abca2438480127a188c0e1a734996074731280204a668f422a84550a471bd200df8e01fa4b1e014550482e0543a58adda205c20881f00166659256f77092998b4fdef292af04180001c199f91cfc79195060597b36581d0281e1cf7929789f4f3b8eef6a7db8104b2d019cf08345604a2edf9d85e8e4c8f38ac48b496f003447b615cbba592b62fabe0901d9ceb621d330b47541a9e5dd4b8a4e4cdf939b2f1c75a7d7a8dc46b4c21dc00601e837c0b6c619a0e9e2ffc4878b776a51095cb64838b091e66d82eaf0d614f15101deb507ac169a3899880ae317e8145c76a9925158fb90ff905d0f56f99276534b" + ), + (2930000,"00000169380c6a08fb48b379a5ca5d866f18fd9d453a13d6e0b8cf3348f75df1", + "01caa1aa054471af7c5b94a952ec272093fec9cc4ef422942bb5ec6aa490507b5f001300000001df11d380711d897e08fba3db377d985e68dd70cc6fbbc835470a16e6c98b7707019dfbefea9f4bffe39c72ecdfe18a1f6a86d75fbe5f1f10e496783d3e537b850501e4978ab7b70f76599f84c2f417321eb855da7fcd14f4d984330730126a63aa47014eb0b841d88846e2804806a90df963cf38a12799b101fd4260d1641ce9497e4a00011bd437ceba5d8b2991a57ce603ccc3a7ced3787725019715057e0d3c6a84bb08000001f566e713aaa06c42d8f1bf16ce6a63912655a10d1275b12cd1f4189fbecbae4c00000000000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (2940000,"000001c936816ab5875198fe942e3a33bbb030ae8a5defb457e80d01246a5a71", + "01d96a1ad51ac333ccf1e3e1763d4816658b98cc446832d787d1df4c2aae671a2b01ec84f41c4ee6b31cc509c87be0673fa847226d58e064d3814cdd70097187de061301d6b58863db421e4ed0840beb5555f31c46d115e2435de069de95b7bb191cdb5c000000000161099a63e8987ffa4b7dd940479755afa4e1e16c82dc9f089bcbfbc3abf9db260001fd3edec6216c0239f1573ad4c8253c070844d5639e48a430f33426f6e0fcaf3901be85fd225c3d831f14408b332642e7627833837df57eb8f394ab43364a4f175e01d0b0b32b528511c5a367515a109cd6a38089cfa26688ed40a22bc47d01d1f10700000001088ddefc2d8188a2b0cd14f6eeac05b123df871124f08d5f228064ca24672e210000000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (2950000,"00000116cbd21afdd1da358c3953de242eb4d2710540338e3f92fa66e768420e", + "01b3a01b1ae8eaadd417d0a7a674e898f865fcc98b02fa83dc260fb596463bfb3400130001c1615e47b5cf42cb6fb45ff65df602296ce3d214ed3f892cfb1c2ec2731e2e3e000000000000000001b94315273ea71e8a75b30b6193322635d38263fa789c01c4c5aa5acdd1748a2801d706b16c156cdf76c6d60f0c689271e760345051a2afbe062f9525b23064a52201374e24815713e84803618a5d95e6141591764d9b22ecfb6efa0bd31acea5d11101088ddefc2d8188a2b0cd14f6eeac05b123df871124f08d5f228064ca24672e210000000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (2960000,"0000017cff8dc295e80f9ed4834bf017a316c2cdd0aff7f11976cc232d9fc30f", + "019ccf5303504742820150c1948f440c260f3742de97252bf7b28ee293e10c9f5d0115453a7080cb94262a56c818face427a76f62ac8b53d83d307a1bfe31e3b81291301e57cdbb278050e09eec854d04420d13fd0c040da4b75318f04a9015fccc55105000001ae382a449f7cc7ba745c2088ed86e31502a0bc0cc49f4b3b1fa1067cd499e60701520c387d687f8fd752db1bff2ae1b87ee6b89fa59477f74a80e696b29a71f732000000010fc93a475ac1000acc438f8c8137171e55891df14b4b7240f84fb9cdf821430c00014451038b62222840c4900acd0960a20e521376044f7b753c701d363820f44b73000131ac7e98b74490886413f6e8993acdc4ff82938585123b3ded511f44e08eb1400001ff908ee1c729dd8ec183f616b427f63809ec45400dff701ec25292b9d562c85400000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (2970000,"0000009c0b2841782b18762e4d14e69bd4c51e75de7c0f161150fc994c175823", + "01b55824500222edee0c976a3806392c8de0f56e79976edc823c81a29adb04f70d018d6f809372ba5f2d7c00eeaa177b8b0d3d5d705a5c0989ed040ebcc79c2036591301e4c99c6d09923471631f800761a1b9f5d99bf04eef797fbbceb25652369d406d0000000001daa16126e2f8651d0ae87f2f9ed41db500b4a5abd627e7a9b5486f3399bcda7001450ee873df9660c4cd2d1aed4139da5c8c140ed33f9d70b7c91e41a9e66eb12a011a58b906630abdf42bcf928d917194d3dab1a727b60b3a835fb3d42b60269d160001754cf3c2af9e37403eb11172cfe807da5df80824df24f032e43a2050c3ae8507013f8cecda387447e68834ee39ce9a49ecc3fae37ebead22cba4790a80e27af2600001c1f23e9c854628b008ded28cd7b6edb89e8bffb96da3ac3597ea9177976c5a31011bc69f37d9bf3a4ea166d8374411d5175936378ef4e9acba262df4bede2ac04701ff908ee1c729dd8ec183f616b427f63809ec45400dff701ec25292b9d562c85400000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (2980000,"00000207c9d63400d53e902d1ba329a3d80f2e19e412fa3642e21d8d4c642904", + "01d030552607c7b84b01fab552587f96c6de9261c4b1d47c20b8722ad0cef2ec2b00130161f67837502c05cd3f8fac8e38c23b3a9de96ae9e7cc0ee89e2208c9ab7c9912000198aab8963b8a42cb8d4ae5a87f1da2666321149252b6761d659a46d7028dde620000010b64791776964fb4b4c7742cbfeff62d8be4d69efd190e1f887503ef94513637000000000001cb36bf6b9843d4dc7bf980dbb3d10c2f73b0b27a0b8d4bdb0084be6c366f8f2f000000017e6728d72c9c68ed46b07e71cdc7955d96e9046af13fba413a7f4b104dad3013000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (2990000,"0000019e091bf24b8c9b8c8797288dfa9a93f1fb455250b536c048ca491972bb", + "01e72ca2c8c3cf508ff93c1ebbb12a32be20142f529f7f0f7a2ae01eecd7d5646901d060291182d7ea426d4f915763792c811ba56f5855f049dc2fb728075f56d06d13016ab8ff35209ae58f79e8e392ae543a1f0e44b48dd87e5c37abc6abf695c60d2001c9675e21d95164b9ac272160c246c329056ec397b7f4bafb4b7ad80ba8d1e16401e800294ccfa82798c4efbf5a0e8875f85c79d9e0417a1b4ebfe666bccb3db6200000000001933489bbb592e0f371a2d74bd0741bc6e151ead1f049ef909e91e96d1fd0385f01b41bd1fd31d551ec264d08038d131fccbd98ee17442866bf1e5356fd8fcb1708000145d595f4d0365bda7d7330de105b5af0c89a547cc6970c9991974d55cd87c30201e7069fba929b4536d0a558933253bf4afed9f5fc87ded35d74f6bc02c8461d5a017d53657e75ea0ee0b50c4689e2b0fd606dfb42ffe4f18f294baa7e7c8bffa3170000017e6728d72c9c68ed46b07e71cdc7955d96e9046af13fba413a7f4b104dad3013000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3000000,"00000219b1672bb1caabd27e2906287856a042529b15b15d05a2e2ff3efcfe41", + "016b533e145bb15dff34db0369faf08f2f54fb28f8d33352795fd251caeb700520001300000001891fc3039e096b1fcdfef70cbdc00b8ea77335e94e37ddd95d02c1a2aada9c430000016d04469ef19d8256128d0e2c4221e67280ef281b1fae31a29c7abf7a5e6041340148c672b1213329dfac8a9a22d5dc150374c94ffe14f508328ecdb8891f1faf420001d15a5a4dba528e9af859eb3256f4519079b9d0ed8948f8418d383a2add8fc9300000017aede95b24db93a4e430ed602f1fa89294e1e16a974e5d0487d5c66a8b21c46f0186035af34c10fb3eeb10b8b55740a822fbad56e18f34efd5745b0a4c96d62b3700017e6728d72c9c68ed46b07e71cdc7955d96e9046af13fba413a7f4b104dad3013000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3010000,"000002c5039f54af5d24f6b64463a6d5578c2122bc5393553557bfd991b8503d", + "01012d90c92054c01f4fd6849784889df2bafb17effd88073f875f1dd6c2ca6545017b79eb36b2c07edc81774a75356c1b404db17151c490e76a4690b32db299a1601301986a8abbdd6179630789122118da05d49c2ce142c6ee44bbf6d66ab75076045e01c73000f129f8ff81189fab0941bcbe51dc18d1edef9a20789685cce079ffb17000019a6a70056b2ae12d8a8fc5485e7ee1d84d1b81d8a55626c33e2bdae8ace45a400001d4d0e6960fe98437e9f939098b2310ae881b3269068a008ba3a44b28c8ce420801f091e6e86fdc29ca527db522a612542937f2cebb40cc8306d83efc17c499fc5b0000000001824f6280a09f446cbb8f067f6fc409840ae08e913e78204c0762e0fcc578c64800000141be3ea93bf8a0dbbfdebb78f77215249635093c3cab2cca5b187eeff4db3b6d017e6728d72c9c68ed46b07e71cdc7955d96e9046af13fba413a7f4b104dad3013000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3020000,"000000e8ddbacb95d03f440ba2943b1ce76ffe4e9f26103554e5cdc7caed3c5a", + "014c90424de82a2b9bf19a3fd1be7687cf4660d0661404ddf7c468f6c37b56950f001300010c6d28a898dc201b9a768f86a79c6239ff33f6c3f6c7f7b22021f3f219588a2d0001ee972ce94164e76aff3d2f82250536e9b3b530cf517447ff67da58a03c1f4a6801f77368aa4d32d736ab22796c8056e0c6818f262cfd24a82e55942c6acbe2f030000001b8832987071efd14e9503fc8d8041f70b704d22062a9a6d44a3325772ca48728000001ec78dc35b567fb3505f93d7593e0f2b9ccf540681839f3ce69bdd49176c2d32d01d727354bdefedeb830f1f78583d1c06ea6acd65e6aa0512ed301777962fb201101b7e0f4cb8e4237e27c35f90b04a9764fbdfb6c1cc47a2e8ed0b30f673a650d2e000141be3ea93bf8a0dbbfdebb78f77215249635093c3cab2cca5b187eeff4db3b6d017e6728d72c9c68ed46b07e71cdc7955d96e9046af13fba413a7f4b104dad3013000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3030000,"000000368719c99a34efc081085fcefb4bd3b88dc4a720d40f8673702a9862e3", + "01d7752276eacf7c7c778286dedc4cdfb72d478da6ab3ee397786cd9e99888541a015325f48bf9c54aa75714d79e14dff88d428fe7222534fa295bbef33e9c8b1546130184e7b68e37c4ec4f2f6157b01f2f769f753e71a3aa676bcd83755dd4f58462700130b0caa711081774f36f8bbc523c91cee681658ad7e4d1b5d2b7f4b625c62a5f0111c2b177e18a8c490c9217d4eb18bc5811acd9d8e1ca0743a924fd8206fd376c0129ff8bdd33760290f178f781aff7b79ef98710ddffa24700bc0bc6f92881ee6c000001891150d540bf705bea15b7fba1d6dd3d2b8443111a026fb2aee9df8732bbf739000000013d64be28890b3240b56c7ad0f5d359c09652d20c7d55729a691895f98754ff6201ddf61456808dc7ccadc5015327bf37b4cf0bf225359e18966bf3c26a3dd2894b0001fab7310c8aa8b60a756bdcf0c1f9254a5e166cd24f4ebb15511382dea4620a090141be3ea93bf8a0dbbfdebb78f77215249635093c3cab2cca5b187eeff4db3b6d017e6728d72c9c68ed46b07e71cdc7955d96e9046af13fba413a7f4b104dad3013000001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3040000,"000002160907e189c825f3ae55081e0818d3a261884e18d56558441ebf95726b", + "018886a7909e8454e511e11905ebdf27032be935f848b04194735054002e21361c0102222ae2db1daf72ae6235a749d034805617b4d27b407e2b24834438a4746c081301bcf6377f83b93f9c4adc1f6eff73658da3fca97aee77491c986290e221b9aa0b015db5152ae33ec8f0dfbbf814ea4e9c4440b4f7c512f51ab373c6548772bccc4200012f89e6b8f0f8e70071643edf7ce47d63e2cf01a45ac23985ee75b25919e91608012a75c41b6346950bf9f3771ea45081a7c14d523b4735f9a9e296262be0c0744b0001646367662c6598697921e21801614872fa44b781b0bf94d1e08357e2a627dc5e000001560a992a484b983458c9d24d6da4ccee1abfa6bd4dd21f68ccc8b8d4493bbf2300000000000001273790927730b00a19d377d235ee0e03d891ab047c68e657bc24ae5d7f67e45d0001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3050000,"0000043f02004859e2aa5f017529ac20e17bd0b79b9d87d0e7961095b2f68af8", + "0104acaf2449d89fde8e4c1458badf2c8eec07c2fed988ce107212f00728a9d7200184041dc09ba86ceb9cb1201b15e7286eb3040d01b27798a85761f32a42fde440130146a440b89bd9d8ce672208c3c23942bc4b738efbdb61cf4e8f1b0e6851f9555601132049ea83d46f4f7b5f385f43b4d2c86f7d17fa4d4ef581f990515ad4d2b8030001bae66ff6c1736f9b65ba6092c730818428324a8e0dee2bc767415b497aea131f0144729b67ebdb48ed7e9c8802663e2dd4eac46c2400811078553f54b7ab026e1e01dbe1d02a041d7362a8dbc0b8a1d0474e7578179d5648748cd4b0fba6c3070e730001ea913f73ec7ed772b3bf40df9a897059c46b1f7340bba49254911560d80c9f6a01a3bcae48b2d05ab21d6e6697791292ce9bfc6bfc2152cd06d52a17aed4ed555a00010d1da89fed45454a24ff61f3003ff0bc17c263dd927bf83b6c61c7e2f720510e01fc26c05042adb566191f5101b9c3cd4445a9ea633c9f89f4a54925ce98708803018df8043de951c74df552368e4b11d73c8c2792566399cd0c062295cbeffdbe0d00000001273790927730b00a19d377d235ee0e03d891ab047c68e657bc24ae5d7f67e45d0001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3060000,"000001a55a082a6b1d942b3b1b9cc28a03337aa256a1be0ac02d842acfb1466f", + "0187063b5cea9063ec478810b80c519fb5edb17fb6cb01df46fc70249f11269a4101a70e8aa4312b07e43dc4bfa905111ac366d3361974c677dcf44f37fdb456835613000144e57d5e54f1702e9da84b79630e57eaa21ada1302e56e5ad7c9ba61074e044a01a3570b04721b5de914b069fc3bc7f88e70221ff6d49d127d43e4245dec8d9820000000012df37854c5fd019c0f222162ddf0428001da7143f1fc48760f89f0f998e49d63017efed102d92e363b5574d90451f2510aa5df556099a8899aa21ce009a0c83c5a0155d7aba6a45c2a330493bc36c0cdbcfb25a05aa24ac9c11a4b47b6fa49e2fc53017b71ca01cde743f94db8aa1da834e12e1a98e5fe8a767c6b60c073de4f39fa4800000001d57f2ccb5840af03acf081911ebd8f4ebd25473543b6ed36856a374746700966000001273790927730b00a19d377d235ee0e03d891ab047c68e657bc24ae5d7f67e45d0001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3070000,"000000b5f47d0bedda6805322811da7960af2ba3fc44b327be7bad34b6e598c3", + "01daee743ff1df8c4dcd147ba7296f6e9fd280964e0565893bd0e25cbfb9834130001301aab679ff0d9f0e49e885de367027940881561a98406eace3c5ac8bc519d8123c000162f09ccbbc3e0dc2b44531ae46c80cc410f78a396c2500cecebb23d6c14db8120000019cebc4bd497288011e0ab854e481fa356f65eb35219e8cfab726db6568aa7470000000000001ff4f64cc4bee3fe5cebf9ac88c34244b8c78511bce1d363fc52b935b38f13c3f017f0a7a7c8b2ca221366cb94e7033c409f0def2138a9e9f826734a7cae76e4716000174a95c6c0c9e4ba6f754bd263779d575e302c4f4ffb9519c3efa4d03c5553d220001273790927730b00a19d377d235ee0e03d891ab047c68e657bc24ae5d7f67e45d0001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3080000,"000001188832e4820fe420a7ad184ea76c287073aebd09d5c2818492f8af2627", + "01d29772d689567256e5fbf0a88f8bea5289c20839b038b86faf289c2d39ac776a01e3a9e908dc6c1bf93bc628042960a25f329ce7a735e5087f846bff37d8e2ec1a13000001360295f0be38be4d0f2a55d8fc4ed30349f4966cdac52b83c568027d1e0c0f1b0001603d4cacd7eae0e51af0493141aeae2c02582a89a75a009456f4d805034c3e1c0001dfb756fabbd26743b301abc719ce352ca362df95fd27b453ae8012feca8b064e01767733344c8221737729fe61f4219343f028256f2bab20cb20022c12b2e5e36301a0e5469d643c943b575d54cc1fd2b17717643ed26ddc29870d328f6770ee57110172bc62c6b11d4faa91e3856ee9d599ab7f5ddbd927c089757446c47101aba315012d70269679ce03b77cbd1e649438541d7a5d7bc3e3ee129c378c97663123a01900011599ff8d1d11412935ee4715df3044c346372e923983dd3e98f3e95960794a4e01bf87c41c60494e6ed154b3da08b85975084f56e926be7b23566432137ba4c9320174a95c6c0c9e4ba6f754bd263779d575e302c4f4ffb9519c3efa4d03c5553d220001273790927730b00a19d377d235ee0e03d891ab047c68e657bc24ae5d7f67e45d0001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3090000,"000000529a6ab02121007876e3d940db0df8f66f0b6234170ed5d0cbdef6e85c", + "01bfecdd7c42e59871fa54288a8237d5c4209994a5967615851607a6ee543d154301027a09b2897d2859bd7d4b38df615a13b47c8cce3e24ff2090a2122cd62bc641130000016034ed881c0501c8f1e0eb7d70445e51dc9febae326502d28360a810e0c4da1001b7f38b229bab51064f690b2b150de0cd2dfa9344fcb8739b5d08fdd57b191e310001066aa2eab794ba99cfba1c4230c65f6afe59647910dbd30878c4c216c00f62170118dcea3ff5ed0233602d48b549f4a70dd73c54f9cdd0778d90e61d67b073423d018a18e05733c4715c389c22034baf5ae610ed07815030787afa82bf3924ae436a00010be659cbaa0fb7d13bb8429de773e4d456088b427e617045db63ff7100e6b84901044e6dbdadc2fab6d8968a25558c947811d0ac8f7f73617e93bab02804673f1f0001c105a65b233e1561d803f5653d4d2cba39be6257594854e0c90fef42f886af2c000001d7ef39194b479424bb8fe70bc5690a629609c10a9643e000c74c68a219d9f95701273790927730b00a19d377d235ee0e03d891ab047c68e657bc24ae5d7f67e45d0001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), + (3100000,"0000005a501d4322cdb6d67504586362ab7902d116f5ad226b6bfd3d8df23d61", + "013e716dd4dca529438618d006e9b65d881a508582de1a6df4f40bc56b8bf1fd4b0180f5db826cfaa58c3caddcf560bd9c260d105e88b6230336265c59c6f2ba6b5413000158ba2fe08cf8a332cfcb47985f3b27d4e0d08ef91e1c345bd5ed199733f1b6690001b70184dcd4634409c2a8bbca6eb0c5bcdc4bcbd5422edfd9ce9160742e04870b01ba5ca2fced6567276593a53cf979a8e9f093d1718a880c8d70d8ef282426fa4901cce0ca10cf7eab7129e5b55dc71f8ad31e7b019a29c18faa32f68b0d2dc56e230000000000010852159b8b73b42465d05a623ad3417c6f27a8a5b5dd4640d604156d6ddf572501d89d62c8033f9379c4e288eddec763bda6002bd58b3fed2491897c16e46d7d4e0189bcfccf37da1e24271d922d943ccce38165e586dbb48de291707bccdfd18c300001d7ef39194b479424bb8fe70bc5690a629609c10a9643e000c74c68a219d9f95701273790927730b00a19d377d235ee0e03d891ab047c68e657bc24ae5d7f67e45d0001f2c7b0a4cf29159dd25c94ec09cffe0fe16c67911821e877192d951b54b65847" + ), +]; + + find_checkpoint(height, checkpoints) +} + +fn find_checkpoint(height: u64, chkpts: Vec<(u64, &'static str, &'static str)>) -> Option<(u64, &'static str, &'static str)> { + // Find the closest checkpoint + let mut heights = chkpts.iter().map(|(h, _, _)| *h as u64).collect::>(); + heights.sort(); + + match get_first_lower_than(height, heights) { + Some(closest_height) => { + chkpts.iter().find(|(h, _, _)| *h == closest_height).map(|t| *t) + }, + None => None + } +} + +fn get_first_lower_than(height: u64, heights: Vec) -> Option { + // If it's before the first checkpoint, return None. + if heights.len() == 0 || height < heights[0] { + return None; + } + + for (i, h) in heights.iter().enumerate() { + if height < *h { + return Some(heights[i-1]); + } + } + + return Some(*heights.last().unwrap()); +} + +#[cfg(test)] +pub mod tests { + use super::*; + + #[test] + fn test_lower_than() { + assert_eq!(get_first_lower_than( 9, vec![10, 30, 40]), None); + assert_eq!(get_first_lower_than(10, vec![10, 30, 40]).unwrap(), 10); + assert_eq!(get_first_lower_than(11, vec![10, 30, 40]).unwrap(), 10); + assert_eq!(get_first_lower_than(29, vec![10, 30, 40]).unwrap(), 10); + assert_eq!(get_first_lower_than(30, vec![10, 30, 40]).unwrap(), 30); + assert_eq!(get_first_lower_than(40, vec![10, 30, 40]).unwrap(), 40); + assert_eq!(get_first_lower_than(41, vec![10, 30, 40]).unwrap(), 40); + assert_eq!(get_first_lower_than(99, vec![10, 30, 40]).unwrap(), 40); + } + + #[test] + fn test_checkpoints() { + assert_eq!(get_test_checkpoint(100000), None); + assert_eq!(get_test_checkpoint(120000).unwrap().0, 120000); + assert_eq!(get_test_checkpoint(125000).unwrap().0, 120000); + assert_eq!(get_test_checkpoint(157000).unwrap().0, 157000); + assert_eq!(get_test_checkpoint(175000).unwrap().0, 157000); + + assert_eq!(get_main_checkpoint(100000), None); + assert_eq!(get_main_checkpoint(170947).unwrap().0, 170947); + assert_eq!(get_main_checkpoint(170949).unwrap().0, 170947); + } + +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet.rs new file mode 100644 index 0000000..d18b2fd --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet.rs @@ -0,0 +1,2600 @@ +// Copyright The Hush Developers 2019-2022 +// Released under the GPLv3 +use std::time::{SystemTime, Duration}; +use std::io::{self, Read, Write}; +use std::cmp; +use std::collections::{HashMap, HashSet}; +use std::sync::{Arc, RwLock}; +use std::io::{Error, ErrorKind}; + +use threadpool::ThreadPool; +use std::sync::mpsc::{channel}; + +use rand::{Rng, rngs::OsRng}; +use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; + +use log::{info, warn, error}; + +use protobuf::parse_from_bytes; + +use libflate::gzip::{Decoder}; +use secp256k1::SecretKey; +use bip39::{Mnemonic, Language}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use pairing::bls12_381::{Bls12}; +use sha2::{Sha256, Digest}; + +use sodiumoxide::crypto::secretbox; + +use zcash_client_backend::{ + encoding::{encode_payment_address, encode_extended_spending_key, encode_extended_full_viewing_key, decode_extended_spending_key, decode_extended_full_viewing_key}, + proto::compact_formats::{CompactBlock, CompactOutput}, + wallet::{WalletShieldedOutput, WalletShieldedSpend} +}; + +use zcash_primitives::{ + jubjub::fs::Fs, + block::BlockHash, + serialize::{Vector}, + transaction::{ + builder::{Builder}, + components::{Amount, OutPoint, TxOut}, + TxId, Transaction, + }, + sapling::Node, + merkle_tree::{CommitmentTree, IncrementalWitness}, + legacy::{Script, TransparentAddress}, + note_encryption::{Memo, try_sapling_note_decryption, try_sapling_output_recovery, try_sapling_compact_note_decryption}, + zip32::{ExtendedFullViewingKey, ExtendedSpendingKey, ChildIndex}, + JUBJUB, + primitives::{PaymentAddress}, + + +}; + +use crate::lightclient::{LightClientConfig}; + +mod data; +mod extended_key; +mod utils; +mod address; +mod prover; +mod walletzkey; + +use data::{BlockData, WalletTx, Utxo, SaplingNoteData, SpendableNote, OutgoingTxMetadata, IncomingTxMetadata}; +use extended_key::{KeyIndex, ExtendedPrivKey}; +use walletzkey::{WalletZKey, WalletTKey, WalletZKeyType}; + +pub const MAX_REORG: usize = 100; +pub const GAP_RULE_UNUSED_ADDRESSES: usize = 5; + +fn now() -> f64 { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as f64 +} + + +/// Sha256(Sha256(value)) +pub fn double_sha256(payload: &[u8]) -> Vec { + let h1 = Sha256::digest(&payload); + let h2 = Sha256::digest(&h1); + h2.to_vec() +} + +use base58::{ToBase58}; + +/// A trait for converting a [u8] to base58 encoded string. +pub trait ToBase58Check { + /// Converts a value of `self` to a base58 value, returning the owned string. + /// The version is a coin-specific prefix that is added. + /// The suffix is any bytes that we want to add at the end (like the "iscompressed" flag for + /// Secret key encoding) + fn to_base58check(&self, version: &[u8], suffix: &[u8]) -> String; +} + +impl ToBase58Check for [u8] { + fn to_base58check(&self, version: &[u8], suffix: &[u8]) -> String { + let mut payload: Vec = Vec::new(); + payload.extend_from_slice(version); + payload.extend_from_slice(self); + payload.extend_from_slice(suffix); + + let checksum = double_sha256(&payload); + payload.append(&mut checksum[..4].to_vec()); + payload.to_base58() + } +} + +pub struct LightWallet { + // Is the wallet encrypted? If it is, then when writing to disk, the seed is always encrypted + // and the individual spending keys are not written + encrypted: bool, + + // In memory only (i.e, this field is not written to disk). Is the wallet unlocked and are + // the spending keys present to allow spending from this wallet? + unlocked: bool, + + enc_seed: [u8; 48], // If locked, this contains the encrypted seed + nonce: Vec, // Nonce used to encrypt the wallet. + + seed: [u8; 32], // Seed phrase for this wallet. If wallet is locked, this is 0 + + // List of keys, actually in this wallet. This is a combination of HD keys derived from the seed, + // viewing keys and imported spending keys. + zkeys: Arc>>, + + // Transparent keys. + tkeys: Arc>>, + + blocks: Arc>>, + pub txs: Arc>>, + + // Transactions that are only in the mempool, but haven't been confirmed yet. + // This is not stored to disk. + pub mempool_txs: Arc>>, + pub incoming_mempool_txs: Arc>>>, + + // The block at which this wallet was born. Rescans + // will start from here. + birthday: u64, + + // Non-serialized fields + config: LightClientConfig, + + pub total_scan_duration: Arc>>, +} + +impl LightWallet { + pub fn serialized_version() -> u64 { + return 9; + } + + fn get_taddr_from_bip39seed(config: &LightClientConfig, bip39_seed: &[u8], pos: u32) -> SecretKey { + assert_eq!(bip39_seed.len(), 64); + + let ext_t_key = ExtendedPrivKey::with_seed(bip39_seed).unwrap(); + ext_t_key + .derive_private_key(KeyIndex::hardened_from_normalize_index(44).unwrap()).unwrap() + .derive_private_key(KeyIndex::hardened_from_normalize_index(config.get_coin_type()).unwrap()).unwrap() + .derive_private_key(KeyIndex::hardened_from_normalize_index(0).unwrap()).unwrap() + .derive_private_key(KeyIndex::Normal(0)).unwrap() + .derive_private_key(KeyIndex::Normal(pos)).unwrap() + .private_key + } + + + fn get_zaddr_from_bip39seed(config: &LightClientConfig, bip39_seed: &[u8], pos: u32) -> + (ExtendedSpendingKey, ExtendedFullViewingKey, PaymentAddress) { + assert_eq!(bip39_seed.len(), 64); + + let extsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path( + &ExtendedSpendingKey::master(bip39_seed), + &[ + ChildIndex::Hardened(32), + ChildIndex::Hardened(config.get_coin_type()), + ChildIndex::Hardened(pos) + ], + ); + let extfvk = ExtendedFullViewingKey::from(&extsk); + let address = extfvk.default_address().unwrap().1; + + (extsk, extfvk, address) + } + + fn get_sietch_from_bip39seed( bip39_seed: &[u8]) -> + + + PaymentAddress { + assert_eq!(bip39_seed.len(), 64); + + let zdustextsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path( + &ExtendedSpendingKey::master(bip39_seed), + &[ + ChildIndex::Hardened(32), + + ], + ); + let zdustextfvk = ExtendedFullViewingKey::from(&zdustextsk); + let zdustaddress = zdustextfvk.default_address().unwrap().1; + + zdustaddress +} + + pub fn is_shielded_address(addr: &String, config: &LightClientConfig) -> bool { + match address::RecipientAddress::from_str(addr, + config.hrp_sapling_address(), + config.base58_pubkey_address(), + config.base58_script_address()) { + Some(address::RecipientAddress::Shielded(_)) => true, + _ => false, + } + } + + pub fn new(seed_phrase: Option, config: &LightClientConfig, latest_block: u64, number: u64) -> io::Result { + // This is the source entropy that corresponds to the 24-word seed phrase + let mut seed_bytes = [0u8; 32]; + + if seed_phrase.is_none() { + // Create a random seed. + let mut system_rng = OsRng; + system_rng.fill(&mut seed_bytes); + } else { + let phrase = match Mnemonic::from_phrase(seed_phrase.clone().unwrap(), Language::English) { + Ok(p) => p, + Err(e) => { + let e = format!("Error parsing phrase: {}", e); + error!("{}", e); + return Err(io::Error::new(ErrorKind::InvalidData, e)); + } + }; + + seed_bytes.copy_from_slice(&phrase.entropy()); + } + + // The seed bytes is the raw entropy. To pass it to HD wallet generation, + // we need to get the 64 byte bip39 entropy + let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&seed_bytes, Language::English).unwrap(), ""); + + // Derive only the first sk and address + let tpk = LightWallet::get_taddr_from_bip39seed(&config, &bip39_seed.as_bytes(), 0); + let taddr = LightWallet::address_from_prefix_sk(&config.base58_pubkey_address(), &tpk); + + // TODO: We need to monitor addresses, and always keep 1 "free" address, so + // users can import a seed phrase and automatically get all used addresses + let hdkey_num = 0; + let (extsk, _, _) + = LightWallet::get_zaddr_from_bip39seed(&config, &bip39_seed.as_bytes(), hdkey_num); + + let lw = LightWallet { + encrypted: false, + unlocked: true, + enc_seed: [0u8; 48], + nonce: vec![], + seed: seed_bytes, + zkeys: Arc::new(RwLock::new(vec![WalletZKey::new_hdkey(hdkey_num, extsk)])), + tkeys: Arc::new(RwLock::new(vec![WalletTKey::new_hdkey(tpk, taddr)])), + blocks: Arc::new(RwLock::new(vec![])), + txs: Arc::new(RwLock::new(HashMap::new())), + mempool_txs: Arc::new(RwLock::new(HashMap::new())), + incoming_mempool_txs: Arc::new(RwLock::new(HashMap::new())), + config: config.clone(), + birthday: latest_block, + total_scan_duration: Arc::new(RwLock::new(vec![Duration::new(0, 0)])), + }; + + // If restoring from seed, make sure we are creating 50 addresses for users + if seed_phrase.is_some() { + for _i in 0..number { + lw.add_zaddr(); + } + for _i in 0..5 { + lw.add_taddr(); + } + } + + Ok(lw) + } + + pub fn read(mut inp: R, config: &LightClientConfig) -> io::Result { + let version = inp.read_u64::()?; + if version > LightWallet::serialized_version() { + let e = format!("Don't know how to read wallet version {}. Do you have the latest version?", version); + error!("{}", e); + return Err(io::Error::new(ErrorKind::InvalidData, e)); + } + println!("Reading wallet version {}", version); + info!("Reading wallet version {}", version); + + // At version 5, we're writing the rest of the file as a compressed stream (gzip) + let mut reader: Box = if version !=5 { + info!("Reading direct"); + Box::new(inp) + } else { + info!("Reading libflat"); + Box::new(Decoder::new(inp).unwrap()) + }; + + let encrypted = if version >= 4 { + reader.read_u8()? > 0 + } else { + false + }; + + info!("Wallet Encryption {:?}", encrypted); + let mut enc_seed = [0u8; 48]; + if version >= 4 { + reader.read_exact(&mut enc_seed)?; + } + + let nonce = if version >= 4 { + Vector::read(&mut reader, |r| r.read_u8())? + } else { + vec![] + }; + + // Seed + let mut seed_bytes = [0u8; 32]; + reader.read_exact(&mut seed_bytes)?; + + let zkeys = if version <= 6 { + // Up until version 6, the wallet keys were written out individually + // Read the spending keys + let extsks = Vector::read(&mut reader, |r| ExtendedSpendingKey::read(r))?; + + let extfvks = if version >= 4 { + // Read the viewing keys + Vector::read(&mut reader, |r| ExtendedFullViewingKey::read(r))? + } else { + // Calculate the viewing keys + extsks.iter().map(|sk| ExtendedFullViewingKey::from(sk)) + .collect::>() + }; + + // Calculate the addresses + let addresses = extfvks.iter().map( |fvk| fvk.default_address().unwrap().1 ) + .collect::>>(); + + // If extsks is of len 0, then this wallet is locked + let zkeys_result = if extsks.len() == 0 { + // Wallet is locked, so read only the viewing keys. + extfvks.iter().zip(addresses.iter()).enumerate().map(|(i, (extfvk, payment_address))| { + let zk = WalletZKey::new_locked_hdkey(i as u32, extfvk.clone()); + if zk.zaddress != *payment_address { + Err(io::Error::new(ErrorKind::InvalidData, "Payment address didn't match")) + } else { + Ok(zk) + } + }).collect::>>() + } else { + // Wallet is unlocked, read the spending keys as well + extsks.into_iter().zip(extfvks.into_iter().zip(addresses.iter())).enumerate() + .map(|(i, (extsk, (extfvk, payment_address)))| { + let zk = WalletZKey::new_hdkey(i as u32, extsk); + if zk.zaddress != *payment_address { + return Err(io::Error::new(ErrorKind::InvalidData, "Payment address didn't match")); + } + + if zk.extfvk != extfvk { + return Err(io::Error::new(ErrorKind::InvalidData, "Full View key didn't match")); + } + + Ok(zk) + }).collect::>>() + }; + + // Convert vector of results into result of vector, returning an error if any one of the keys failed the checks above + zkeys_result.into_iter().collect::>()? + } else { + // After version 6, we read the WalletZKey structs directly + Vector::read(&mut reader, |r| WalletZKey::read(r))? + }; + + // Calculate the addresses + + let wallet_tkeys = if version >= 9 { + Vector::read(&mut reader, |r| { + WalletTKey::read(r) + })? + } else { + + let tkeys = Vector::read(&mut reader, |r| { + let mut tpk_bytes = [0u8; 32]; + r.read_exact(&mut tpk_bytes)?; + secp256k1::SecretKey::from_slice(&tpk_bytes).map_err(|e| io::Error::new(ErrorKind::InvalidData, e)) + })?; + + let taddresses = if version >= 4 { + // Read the addresses + Vector::read(&mut reader, |r| utils::read_string(r))? + } else { + // Calculate the addresses + tkeys.iter().map(|sk| LightWallet::address_from_prefix_sk(&config.base58_pubkey_address(), sk)).collect() + }; + + tkeys.iter().zip(taddresses.iter()).map(|(k, a)| + WalletTKey::new_hdkey(*k, a.clone()) + ).collect() + }; + + let blocks = Vector::read(&mut reader, |r| BlockData::read(r))?; + + let txs_tuples = Vector::read(&mut reader, |r| { + let mut txid_bytes = [0u8; 32]; + r.read_exact(&mut txid_bytes)?; + + Ok((TxId{0: txid_bytes}, WalletTx::read(r).unwrap())) + })?; + let txs = txs_tuples.into_iter().collect::>(); + + let chain_name = utils::read_string(&mut reader)?; + + if chain_name != config.chain_name { + return Err(Error::new(ErrorKind::InvalidData, + format!("Wallet chain name {} doesn't match expected {}", chain_name, config.chain_name))); + } + + let birthday = reader.read_u64::()?; + + Ok(LightWallet{ + encrypted: encrypted, + unlocked: !encrypted, // When reading from disk, if wallet is encrypted, it starts off locked. + enc_seed: enc_seed, + nonce: nonce, + seed: seed_bytes, + zkeys: Arc::new(RwLock::new(zkeys)), + tkeys: Arc::new(RwLock::new(wallet_tkeys)), + blocks: Arc::new(RwLock::new(blocks)), + txs: Arc::new(RwLock::new(txs)), + mempool_txs: Arc::new(RwLock::new(HashMap::new())), + incoming_mempool_txs: Arc::new(RwLock::new(HashMap::new())), + config: config.clone(), + birthday, + total_scan_duration: Arc::new(RwLock::new(vec![Duration::new(0, 0)])), + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + if self.encrypted && self.unlocked { + return Err(Error::new(ErrorKind::InvalidInput, + format!("Cannot write while wallet is unlocked while encrypted."))); + } + + // Write the version + writer.write_u64::(LightWallet::serialized_version())?; + + // Write if it is locked + writer.write_u8(if self.encrypted {1} else {0})?; + + // Write the encrypted seed bytes + writer.write_all(&self.enc_seed)?; + + // Write the nonce + Vector::write(&mut writer, &self.nonce, |w, b| w.write_u8(*b))?; + + // Write the seed + writer.write_all(&self.seed)?; + + // Flush after writing the seed, so in case of a disaster, we can still recover the seed. + writer.flush()?; + + // Write all the wallet's keys + Vector::write(&mut writer, &self.zkeys.read().unwrap(), + |w, zk| zk.write(w) + )?; + + // Write the transparent private keys + Vector::write(&mut writer, &self.tkeys.read().unwrap(), + |w, tk| tk.write(w) + )?; + + Vector::write(&mut writer, &self.blocks.read().unwrap(), |w, b| b.write(w))?; + + // The hashmap, write as a set of tuples. Store them sorted so that wallets are + // deterministically saved + { + let txlist = self.txs.read().unwrap(); + let mut txns = txlist.iter().collect::>(); + txns.sort_by(|a, b| a.0.partial_cmp(b.0).unwrap()); + + Vector::write(&mut writer, &txns, + |w, (k, v)| { + w.write_all(&k.0)?; + v.write(w) + })?; + } + utils::write_string(&mut writer, &self.config.chain_name)?; + + // While writing the birthday, get it from the fn so we recalculate it properly + // in case of rescans etc... + writer.write_u64::(self.get_birthday()) + } + + pub fn note_address(hrp: &str, note: &SaplingNoteData) -> Option { + match note.extfvk.fvk.vk.into_payment_address(note.diversifier, &JUBJUB) { + Some(pa) => Some(encode_payment_address(hrp, &pa)), + None => None + } + } + + pub fn get_birthday(&self) -> u64 { + if self.birthday == 0 { + self.get_first_tx_block() + } else { + cmp::min(self.get_first_tx_block(), self.birthday) + } + } + + // Get the first block that this wallet has a tx in. This is often used as the wallet's "birthday" + // If there are no Txns, then the actual birthday (which is recorder at wallet creation) is returned + // If no birthday was recorded, return the sapling activation height + pub fn get_first_tx_block(&self) -> u64 { + // Find the first transaction + let mut blocks = self.txs.read().unwrap().values() + .map(|wtx| wtx.block as u64) + .collect::>(); + blocks.sort(); + + *blocks.first() // Returns optional, so if there's no txns, it'll get the activation height + .unwrap_or(&cmp::max(self.birthday, self.config.sapling_activation_height)) + } + + // Get all z-address private keys. Returns a Vector of (address, privatekey, viewkey) + pub fn get_z_private_keys(&self) -> Vec<(String, String, String)> { + let keys = self.zkeys.read().unwrap().iter().map(|k| { + let pkey = match k.extsk.clone().map(|extsk| encode_extended_spending_key(self.config.hrp_sapling_private_key(), &extsk)) { + Some(pk) => pk, + None => "".to_string() + }; + + let vkey = encode_extended_full_viewing_key(self.config.hrp_sapling_viewing_key(), &k.extfvk); + + (encode_payment_address(self.config.hrp_sapling_address(),&k.zaddress), pkey, vkey) + }).collect::>(); + + keys + } + + /// Get all t-address private keys. Returns a Vector of (address, secretkey) + pub fn get_t_secret_keys(&self) -> Vec<(String, String)> { + self.tkeys.read().unwrap().iter().map(|wtk| { + let sk = if wtk.tkey.is_some() { + wtk.tkey.unwrap()[..].to_base58check(&self.config.base58_secretkey_prefix(), &[0x01]) + } else { + "".to_string() + }; + + (wtk.address.clone(), sk) + }).collect::>() + } + + /// Adds a new z address to the wallet. This will derive a new address from the seed + /// at the next position and add it to the wallet. + /// NOTE: This does NOT rescan + pub fn add_zaddr(&self) -> String { + if !self.unlocked { + return "Error: Can't add key while wallet is locked".to_string(); + } + + if self.encrypted { + return "Error: Can't add key while wallet is encrypted".to_string(); + } + + // Find the highest pos we have + let pos = self.zkeys.read().unwrap().iter() + .filter(|zk| zk.hdkey_num.is_some()) + .max_by(|zk1, zk2| zk1.hdkey_num.unwrap().cmp(&zk2.hdkey_num.unwrap())) + .map_or(0, |zk| zk.hdkey_num.unwrap() + 1); + + + let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&self.seed, Language::English).unwrap(), ""); + + let (extsk, _, _) = + LightWallet::get_zaddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos); + + // let zaddr = encode_payment_address(self.config.hrp_sapling_address(), &address); + let newkey = WalletZKey::new_hdkey(pos, extsk); + self.zkeys.write().unwrap().push(newkey.clone()); + + encode_payment_address(self.config.hrp_sapling_address(), &newkey.zaddress) + } + + // Add a new Sietch Addr. This will derive a new zdust address from manipluated seed + pub fn add_zaddrdust(&self) -> String { + + let mut seed_bytes = [0u8; 32]; + + + // Use random generator to create a new Sietch seed + + let mut rng = rand::thread_rng(); + let letter: String = rng.gen_range(b'A', b'Z').to_string(); + let number: String = rng.gen_range(0, 999999).to_string(); + let s = format!("{}{:06}", letter, number); + let my_string = String::from(s); + let dust: &str = &my_string; + + + let mut system_rng = OsRng; + system_rng.fill(&mut seed_bytes); + + let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&seed_bytes, Language::English).unwrap(), dust); + + let zdustaddress = LightWallet::get_sietch_from_bip39seed(&bip39_seed.as_bytes()); + + let zdust = encode_payment_address("zs", &zdustaddress); + + + zdust + } + + /// Add a new t address to the wallet. This will derive a new address from the seed + /// at the next position. + /// NOTE: This will not rescan the wallet + pub fn add_taddr(&self) -> String { + if !self.unlocked { + return "Error: Can't add key while wallet is locked".to_string(); + } + + if self.encrypted { + return "Error: Can't add key while wallet is encrypted".to_string(); + } + + let pos = self.tkeys.read().unwrap().len() as u32; + let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&self.seed, Language::English).unwrap(), ""); + + let sk = LightWallet::get_taddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos); + let address = self.address_from_sk(&sk); + + self.tkeys.write().unwrap().push(WalletTKey::new_hdkey(sk, address.clone())); + + address + } + + pub fn import_taddr(&mut self, sk: String, birthday: u64) -> String { + if !self.unlocked { + return "Error: Can't add key while wallet is locked".to_string(); + } + + //// Decode Wif to base58 to hex + let sk_to_bs58 = bs58::decode(sk).into_vec().unwrap(); + + let bs58_to_hex = hex::encode(sk_to_bs58); + + //// Manipulate string, to exclude last 4 bytes (checksum bytes), first 2 bytes (secretkey prefix) and the compressed flag (works only for compressed Wifs!) + + let slice_sk = &bs58_to_hex[2..66]; + + //// Get the SecretKey from slice + let secret_key = SecretKey::from_slice(&hex::decode(slice_sk).unwrap()); + + let sk_raw = secret_key.unwrap(); + + //// Make sure the key doesn't already exist + if self.tkeys.read().unwrap().iter().find(|&wk| wk.tkey.is_some() && wk.tkey.as_ref().unwrap() == &sk_raw.clone()).is_some() { + return "Error: Key already exists".to_string(); + } + //// Get the taddr from key + let address = self.address_from_sk(&sk_raw); + + //// Add to tkeys + self.tkeys.write().unwrap().push(WalletTKey::import_hdkey(sk_raw , address.clone())); + + // Adjust wallet birthday + if birthday < self.birthday { + self.birthday = if birthday < self.config.sapling_activation_height {self.config.sapling_activation_height} else {birthday}; + } + + address + } + + // Add a new imported spending key to the wallet + /// NOTE: This will not rescan the wallet + pub fn add_imported_sk(&mut self, sk: String, birthday: u64) -> String { + if !self.unlocked { + return "Error: Can't add key while wallet is locked".to_string(); + } + + // First, try to interpret the key + let extsk = match decode_extended_spending_key(self.config.hrp_sapling_private_key(), &sk) { + Ok(Some(k)) => k, + Ok(None) => return format!("Error: Couldn't decode spending key"), + Err(e) => return format!("Error importing spending key: {}", e) + }; + + // Make sure the key doesn't already exist + if self.zkeys.read().unwrap().iter().find(|&wk| wk.extsk.is_some() && wk.extsk.as_ref().unwrap() == &extsk.clone()).is_some() { + return "Error: Key already exists".to_string(); + } + + let extfvk = ExtendedFullViewingKey::from(&extsk); + let zaddress = { + let mut zkeys = self.zkeys.write().unwrap(); + let maybe_existing_zkey = zkeys.iter_mut().find(|wk| wk.extfvk == extfvk); + + // If the viewing key exists, and is now being upgraded to the spending key, replace it in-place + if maybe_existing_zkey.is_some() { + let mut existing_zkey = maybe_existing_zkey.unwrap(); + existing_zkey.extsk = Some(extsk); + existing_zkey.keytype = WalletZKeyType::ImportedSpendingKey; + existing_zkey.zaddress.clone() + } else { + let newkey = WalletZKey::new_imported_sk(extsk); + zkeys.push(newkey.clone()); + newkey.zaddress + } + }; + + // Adjust wallet birthday + if birthday < self.birthday { + self.birthday = if birthday < self.config.sapling_activation_height {self.config.sapling_activation_height} else {birthday}; + } + + encode_payment_address(self.config.hrp_sapling_address(), &zaddress) + } + + // Add a new imported viewing key to the wallet + /// NOTE: This will not rescan the wallet + pub fn add_imported_vk(&mut self, vk: String, birthday: u64) -> String { + if !self.unlocked { + return "Error: Can't add key while wallet is locked".to_string(); + } + + // First, try to interpret the key + let extfvk = match decode_extended_full_viewing_key(self.config.hrp_sapling_viewing_key(), &vk) { + Ok(Some(k)) => k, + Ok(None) => return format!("Error: Couldn't decode viewing key"), + Err(e) => return format!("Error importing viewing key: {}", e) + }; + + // Make sure the key doesn't already exist + if self.zkeys.read().unwrap().iter().find(|wk| wk.extfvk == extfvk.clone()).is_some() { + return "Error: Key already exists".to_string(); + } + + let newkey = WalletZKey::new_imported_viewkey(extfvk); + self.zkeys.write().unwrap().push(newkey.clone()); + + // Adjust wallet birthday + if birthday < self.birthday { + self.birthday = if birthday < self.config.sapling_activation_height {self.config.sapling_activation_height} else {birthday}; + } + + encode_payment_address(self.config.hrp_sapling_address(), &newkey.zaddress) + } + + /// Clears all the downloaded blocks and resets the state back to the initial block. + /// After this, the wallet's initial state will need to be set + /// and the wallet will need to be rescanned + pub fn clear_blocks(&self) { + match self.blocks.write() { + Ok(mut b) => b.clear(), + Err(p) => p.into_inner().clear(), + }; + match self.txs.write() { + Ok(mut t) => t.clear(), + Err(p) => p.into_inner().clear(), + }; + match self.mempool_txs.write() { + Ok(mut m) => m.clear(), + Err(p) => p.into_inner().clear(), + }; + match self.incoming_mempool_txs.write() { + Ok(mut m) => m.clear(), + Err(p) => p.into_inner().clear(), + }; + } + + pub fn set_initial_block(&self, height: i32, hash: &str, sapling_tree: &str) -> bool { + let mut blocks = match self.blocks.write() { + Ok(b) => b, + Err(p) => p.into_inner(), + }; + if !blocks.is_empty() { + return false; + } + + let hash = match hex::decode(hash) { + Ok(hash) => { + let mut r = hash; + r.reverse(); + BlockHash::from_slice(&r) + }, + Err(e) => { + eprintln!("{}", e); + return false; + } + }; + + let sapling_tree = match hex::decode(sapling_tree) { + Ok(tree) => tree, + Err(e) => { + eprintln!("{}", e); + return false; + } + }; + + if let Ok(tree) = CommitmentTree::read(&sapling_tree[..]) { + blocks.push(BlockData { height, hash, tree }); + true + } else { + false + } + } + + // Get the latest sapling commitment tree. It will return the height and the hex-encoded sapling commitment tree at that height + pub fn get_sapling_tree(&self) -> Result<(i32, String, String), String> { + let blocks = self.blocks.read().unwrap(); + + let block = match blocks.last() { + Some(block) => block, + None => return Err("Couldn't get a block height!".to_string()) + }; + + let mut write_buf = vec![]; + block.tree.write(&mut write_buf).map_err(|e| format!("Error writing commitment tree {}", e))?; + + let mut blockhash = vec![]; + blockhash.extend_from_slice(&block.hash.0); + blockhash.reverse(); + + Ok((block.height, hex::encode(blockhash), hex::encode(write_buf))) + } + + pub fn last_scanned_height(&self) -> i32 { + self.blocks.read().unwrap() + .last() + .map(|block| block.height) + .unwrap_or(self.config.sapling_activation_height as i32 - 1) + } + + /// Determines the target height for a transaction, and the offset from which to + /// select anchors, based on the current synchronised block chain. + fn get_target_height_and_anchor_offset(&self) -> Option<(u32, usize)> { + match { + let blocks = self.blocks.read().unwrap(); + ( + blocks.first().map(|block| block.height as u32), + blocks.last().map(|block| block.height as u32), + ) + } { + (Some(min_height), Some(max_height)) => { + let target_height = max_height; + + // Select an anchor ANCHOR_OFFSET back from the target block, + // unless that would be before the earliest block we have. + let anchor_height = + cmp::max(target_height.saturating_sub(self.config.anchor_offset), min_height); + + Some((target_height, (target_height - anchor_height) as usize)) + } + _ => None, + } + } + + /// Get the height of the anchor block + pub fn get_anchor_height(&self) -> u32 { + match self.get_target_height_and_anchor_offset() { + Some((height, anchor_offset)) => height - anchor_offset as u32 - 1, + None => return 0, + } + } + + pub fn get_all_taddresses(&self) -> Vec { + self.tkeys.read().unwrap() + .iter() + .map(|wtx| wtx.address.clone()).collect() + } + + pub fn get_all_zaddresses(&self) -> Vec { + self.zkeys.read().unwrap().iter().map( |zk| { + encode_payment_address(self.config.hrp_sapling_address(), &zk.zaddress) + }).collect() + } + + pub fn memo_str(memo: &Option) -> Option { + match memo { + Some(memo) => { + match memo.to_utf8() { + Some(Ok(memo_str)) => Some(memo_str), + _ => None + } + } + _ => None + } + } + + pub fn address_from_prefix_sk(prefix: &[u8; 1], sk: &secp256k1::SecretKey) -> String { + let secp = secp256k1::Secp256k1::new(); + let pk = secp256k1::PublicKey::from_secret_key(&secp, &sk); + + // Encode into t address + let mut hash160 = ripemd160::Ripemd160::new(); + hash160.input(Sha256::digest(&pk.serialize()[..].to_vec())); + + hash160.result().to_base58check(prefix, &[]) + } + + pub fn address_from_sk(&self, sk: &secp256k1::SecretKey) -> String { + LightWallet::address_from_prefix_sk(&self.config.base58_pubkey_address(), sk) + } + + pub fn address_from_pubkeyhash(&self, ta: Option) -> Option { + match ta { + Some(TransparentAddress::PublicKey(hash)) => { + Some(hash.to_base58check(&self.config.base58_pubkey_address(), &[])) + }, + Some(TransparentAddress::Script(hash)) => { + Some(hash.to_base58check(&self.config.base58_script_address(), &[])) + }, + _ => None + } + } + + pub fn get_seed_phrase(&self) -> String { + if !self.unlocked { + return "".to_string(); + } + + Mnemonic::from_entropy(&self.seed, + Language::English, + ).unwrap().phrase().to_string() + } + + pub fn encrypt(&mut self, passwd: String) -> io::Result<()> { + + + if self.encrypted { + return Err(io::Error::new(ErrorKind::AlreadyExists, "Wallet is already encrypted")); + } + + // Get the doublesha256 of the password, which is the right length + let key = secretbox::Key::from_slice(&double_sha256(passwd.as_bytes())).unwrap(); + let nonce = secretbox::gen_nonce(); + + let cipher = secretbox::seal(&self.seed, &nonce, &key); + + self.enc_seed.copy_from_slice(&cipher); + self.nonce = nonce.as_ref().to_vec(); + + // Encrypt the individual keys + + self.tkeys.write().unwrap().iter_mut() + .map(|k| k.encrypt(&key)) + .collect::>>()?; + + self.zkeys.write().unwrap().iter_mut() + .map(|k| k.encrypt(&key)) + .collect::>>()?; + + self.encrypted = true; + self.lock()?; + + Ok(()) + } + + pub fn lock(&mut self) -> io::Result<()> { + if !self.encrypted { + return Err(io::Error::new(ErrorKind::AlreadyExists, "Wallet is not encrypted")); + } + + if !self.unlocked { + return Err(io::Error::new(ErrorKind::AlreadyExists, "Wallet is already locked")); + } + + // Empty the seed and the secret keys + self.seed.copy_from_slice(&[0u8; 32]); + + // Remove all the private key from the tkeys + self.tkeys.write().unwrap().iter_mut().map(|tk| { + tk.lock() + }).collect::>>()?; + + // Remove all the private key from the zkeys + self.zkeys.write().unwrap().iter_mut().map(|zk| { + zk.lock() + }).collect::>>()?; + + self.unlocked = false; + + Ok(()) + } + + pub fn unlock(&mut self, passwd: String) -> io::Result<()> { + + + if !self.encrypted { + return Err(Error::new(ErrorKind::AlreadyExists, "Wallet is not encrypted")); + } + + if self.encrypted && self.unlocked { + return Err(Error::new(ErrorKind::AlreadyExists, "Wallet is already unlocked")); + } + + // Get the doublesha256 of the password, which is the right length + let key = secretbox::Key::from_slice(&double_sha256(passwd.as_bytes())).unwrap(); + let nonce = secretbox::Nonce::from_slice(&self.nonce).unwrap(); + + let seed = match secretbox::open(&self.enc_seed, &nonce, &key) { + Ok(s) => s, + Err(_) => {return Err(io::Error::new(ErrorKind::InvalidData, "Decryption failed. Is your password correct?"));} + }; + + // Now that we have the seed, we'll generate the extsks and tkeys, and verify the fvks and addresses + // respectively match + + // The seed bytes is the raw entropy. To pass it to HD wallet generation, + // we need to get the 64 byte bip39 entropy + let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&seed, Language::English).unwrap(), ""); + + // Go over the tkeys, and add the keys again + self.tkeys.write().unwrap().iter_mut().map(|tk| { + tk.unlock(&key) + }).collect::>>()?; + + // Go over the zkeys, and add the spending keys again + self.zkeys.write().unwrap().iter_mut().map(|zk| { + zk.unlock(&self.config, bip39_seed.as_bytes(), &key) + }).collect::>>()?; + + // Everything checks out, so we'll update our wallet with the decrypted values + self.seed.copy_from_slice(&seed); + + self.encrypted = true; + self.unlocked = true; + + Ok(()) + } + + // Removing encryption means unlocking it and setting the self.encrypted = false, + // permanantly removing the encryption + pub fn remove_encryption(&mut self, passwd: String) -> io::Result<()> { + if !self.encrypted { + return Err(Error::new(ErrorKind::AlreadyExists, "Wallet is not encrypted")); + } + + // Unlock the wallet if it's locked + if !self.unlocked { + self.unlock(passwd)?; + } + + // Remove encryption from individual tkeys + self.tkeys.write().unwrap().iter_mut().map(|tk| { + tk.remove_encryption() + }).collect::>>()?; + + // Remove encryption from individual zkeys + self.zkeys.write().unwrap().iter_mut().map(|zk| { + zk.remove_encryption() + }).collect::>>()?; + + // Permanantly remove the encryption + self.encrypted = false; + self.nonce = vec![]; + self.enc_seed.copy_from_slice(&[0u8; 48]); + + Ok(()) + } + + pub fn is_encrypted(&self) -> bool { + return self.encrypted; + } + + pub fn is_unlocked_for_spending(&self) -> bool { + return self.unlocked; + } + + pub fn zbalance(&self, addr: Option) -> u64 { + let unconfirmed_balance = self.unconfirmed_zbalance(addr.clone()); + + let confirmed_balance = self.txs.read().unwrap() + .values() + .map(|tx| { + tx.notes.iter() + .filter(|nd| { + match addr.as_ref() { + Some(a) => *a == encode_payment_address( + self.config.hrp_sapling_address(), + &nd.extfvk.fvk.vk + .into_payment_address(nd.diversifier, &JUBJUB).unwrap() + ), + None => true + } + }) + .map(|nd| { + if nd.spent.is_none() && nd.unconfirmed_spent.is_none() { + nd.note.value + } else { + 0 + } + }) + .sum::() + }) + .sum::(); + + confirmed_balance + unconfirmed_balance + } + + // Get all (unspent) utxos. Unconfirmed spent utxos are included + pub fn get_utxos(&self) -> Vec { + let txs = self.txs.read().unwrap(); + + txs.values() + .flat_map(|tx| { + tx.utxos.iter().filter(|utxo| utxo.spent.is_none()) + }) + .map(|utxo| utxo.clone()) + .collect::>() + } + + pub fn tbalance(&self, addr: Option) -> u64 { + self.get_utxos().iter() + .filter(|utxo| { + match addr.clone() { + Some(a) => utxo.address == a, + None => true, + } + }) + .map(|utxo| utxo.value ) + .sum::() as u64 + } + + pub fn verified_zbalance(&self, addr: Option) -> u64 { + let anchor_height = match self.get_target_height_and_anchor_offset() { + Some((height, anchor_offset)) => height - anchor_offset as u32 , + None => return 0, + }; + + self.txs + .read() + .unwrap() + .values() + .map(|tx| { + if tx.block as u32 <= anchor_height { + tx.notes + .iter() + .filter(|nd| nd.spent.is_none() && nd.unconfirmed_spent.is_none()) + .filter(|nd| { // TODO, this whole section is shared with verified_balance. Refactor it. + match addr.as_ref() { + Some(a) => *a == encode_payment_address( + self.config.hrp_sapling_address(), + &nd.extfvk.fvk.vk + .into_payment_address(nd.diversifier, &JUBJUB).unwrap() + ), + None => true + } + }) + .map(|nd| nd.note.value) + .sum::() + } else { + 0 + } + }) + .sum::() + } + + pub fn spendable_zbalance(&self, addr: Option) -> u64 { + let anchor_height = self.get_anchor_height(); + + self.txs + .read() + .unwrap() + .values() + .map(|tx| { + if tx.block as u32 <= anchor_height { + tx.notes + .iter() + .filter(|nd| nd.spent.is_none() && nd.unconfirmed_spent.is_none()) + .filter(|nd| { + // Check to see if we have this note's spending key. + self.have_spendingkey_for_extfvk(&nd.extfvk) + }) + .filter(|nd| { // TODO, this whole section is shared with verified_balance. Refactor it. + match addr.as_ref() { + Some(a) => *a == encode_payment_address( + self.config.hrp_sapling_address(), + &nd.extfvk.fvk.vk + .into_payment_address(nd.diversifier, &JUBJUB).unwrap() + ), + None => true + } + }) + .map(|nd| nd.note.value) + .sum::() + } else { + 0 + } + }) + .sum::() as u64 + } + + pub fn unconfirmed_zbalance(&self, addr: Option) -> u64 { + self.incoming_mempool_txs.read().unwrap() + .values() + .flat_map(|txs| txs.iter()) + .map(|tx| { + tx.incoming_metadata.iter() + .filter(|meta| { + match addr.as_ref() { + Some(a) => { + + a == &meta.address + }, + None => true + } + }) + .map(|meta| { + + meta.value + }) + .sum::() + }) + .sum::() + } + + pub fn have_spendingkey_for_extfvk(&self, extfvk: &ExtendedFullViewingKey) -> bool { + match self.zkeys.read().unwrap().iter().find(|zk| zk.extfvk == *extfvk) { + None => false, + Some(zk) => zk.have_spending_key() + } + } + + pub fn have_spending_key_for_zaddress(&self, address: &String) -> bool { + match self.zkeys.read().unwrap().iter() + .find(|zk| encode_payment_address(self.config.hrp_sapling_address(), &zk.zaddress) == *address) + { + None => false, + Some(zk) => zk.have_spending_key() + } + } + + + + fn add_toutput_to_wtx(&self, height: i32, timestamp: u64, txid: &TxId, vout: &TxOut, n: u64) { + let mut txs = self.txs.write().unwrap(); + + // Find the existing transaction entry, or create a new one. + if !txs.contains_key(&txid) { + let tx_entry = WalletTx::new(height, timestamp, &txid); + txs.insert(txid.clone(), tx_entry); + } + let tx_entry = txs.get_mut(&txid).unwrap(); + + // Make sure the vout isn't already there. + match tx_entry.utxos.iter().find(|utxo| { + utxo.txid == *txid && utxo.output_index == n && Amount::from_u64(utxo.value).unwrap() == vout.value + }) { + Some(utxo) => { + info!("Already have {}:{}", utxo.txid, utxo.output_index); + } + None => { + let address = self.address_from_pubkeyhash(vout.script_pubkey.address()); + if address.is_none() { + error!("Couldn't determine address for output!"); + } else { + info!("Added to wallet {}:{}", txid, n); + // Add the utxo + tx_entry.utxos.push(Utxo { + address: address.unwrap(), + txid: txid.clone(), + output_index: n, + script: vout.script_pubkey.0.clone(), + value: vout.value.into(), + height, + spent: None, + unconfirmed_spent: None, + }); + } + } + } + } + + // If one of the last 'n' taddress was used, ensure we add the next HD taddress to the wallet. + pub fn ensure_hd_taddresses(&self, address: &String) { + let last_addresses = { + self.tkeys.read().unwrap() + .iter() + .map(|t| t.address.clone()) + .rev().take(GAP_RULE_UNUSED_ADDRESSES).map(|s| + s.clone()) + .collect::>() + }; + + match last_addresses.iter().position(|s| *s == *address) { + None => { + return; + }, + Some(pos) => { + info!("Adding {} new zaddrs", (GAP_RULE_UNUSED_ADDRESSES - pos)); + // If it in the last unused, addresses, create that many more + for _ in 0..(GAP_RULE_UNUSED_ADDRESSES - pos) { + // If the wallet is locked, this is a no-op. That is fine, since we really + // need to only add new addresses when restoring a new wallet, when it will not be locked. + // Also, if it is locked, the user can't create new addresses anyway. + self.add_taddr(); + } + } + } + } + + // If one of the last 'n' zaddress was used, ensure we add the next HD zaddress to the wallet + pub fn ensure_hd_zaddresses(&self, address: &String) { + let last_addresses = { + self.zkeys.read().unwrap().iter() + .filter(|zk| zk.keytype == WalletZKeyType::HdKey) + .rev() + .take(GAP_RULE_UNUSED_ADDRESSES) + .map(|s| encode_payment_address(self.config.hrp_sapling_address(), &s.zaddress)) + .collect::>() + }; + + match last_addresses.iter().position(|s| *s == *address) { + None => { + return; + }, + Some(pos) => { + info!("Adding {} new zaddrs", (GAP_RULE_UNUSED_ADDRESSES - pos)); + // If it in the last unused, addresses, create that many more + for _ in 0..(GAP_RULE_UNUSED_ADDRESSES - pos) { + // If the wallet is locked, this is a no-op. That is fine, since we really + // need to only add new addresses when restoring a new wallet, when it will not be locked. + // Also, if it is locked, the user can't create new addresses anyway. + self.add_zaddr(); + } + } + } + } + +// Scan the full Tx and update memos for incoming shielded transactions. +pub fn scan_full_tx(&self, tx: &Transaction, height: i32, datetime: u64) { + let mut total_transparent_spend: u64 = 0; + // Scan all the inputs to see if we spent any transparent funds in this tx + for vin in tx.vin.iter() { + // Find the txid in the list of utxos that we have. + let txid = TxId {0: vin.prevout.hash}; + match self.txs.write().unwrap().get_mut(&txid) { + Some(wtx) => { + //println!("Looking for {}, {}", txid, vin.prevout.n); + // One of the tx outputs is a match + let spent_utxo = wtx.utxos.iter_mut() + .find(|u| u.txid == txid && u.output_index == (vin.prevout.n as u64)); + match spent_utxo { + Some(su) => { + info!("Spent utxo from {} was spent in {}", txid, tx.txid()); + su.spent = Some(tx.txid().clone()); + su.unconfirmed_spent = None; + total_transparent_spend += su.value; + }, + _ => {} + } + }, + _ => {} + }; + } + if total_transparent_spend > 0 { + // Update the WalletTx. Do it in a short scope because of the write lock. + let mut txs = self.txs.write().unwrap(); + if !txs.contains_key(&tx.txid()) { + let tx_entry = WalletTx::new(height, datetime, &tx.txid()); + txs.insert(tx.txid().clone(), tx_entry); + } + + txs.get_mut(&tx.txid()).unwrap() + .total_transparent_value_spent = total_transparent_spend; + } + // Scan for t outputs + let all_taddresses = self.tkeys.read().unwrap().iter() + .map(|wtx| wtx.address.clone()) + .map(|a| a.clone()) + .collect::>(); + for address in all_taddresses { + for (n, vout) in tx.vout.iter().enumerate() { + match vout.script_pubkey.address() { + Some(TransparentAddress::PublicKey(hash)) => { + if address == hash.to_base58check(&self.config.base58_pubkey_address(), &[]) { + // This is our address. Add this as an output to the txid + self.add_toutput_to_wtx(height, datetime, &tx.txid(), &vout, n as u64); + // Ensure that we add any new HD addresses + self.ensure_hd_taddresses(&address); + } + }, + _ => {} + } + } + } + { + let total_shielded_value_spent = self.txs.read().unwrap().get(&tx.txid()).map_or(0, |wtx| wtx.total_shielded_value_spent); + if total_transparent_spend + total_shielded_value_spent > 0 { + // We spent money in this Tx, so grab all the transparent outputs (except ours) and add them to the + // outgoing metadata + // Collect our t-addresses + let wallet_taddrs = self.tkeys.read().unwrap().iter() + .map(|wtx| wtx.address.clone()) + .map(|a| a.clone()) + .collect::>(); + for vout in tx.vout.iter() { + let taddr = self.address_from_pubkeyhash(vout.script_pubkey.address()); + if taddr.is_some() && !wallet_taddrs.contains(&taddr.clone().unwrap()) { + let taddr = taddr.unwrap(); + // Add it to outgoing metadata + let mut txs = self.txs.write().unwrap(); + if txs.get(&tx.txid()).unwrap().outgoing_metadata.iter() + .find(|om| + om.address == taddr && Amount::from_u64(om.value).unwrap() == vout.value) + .is_some() { + warn!("Duplicate outgoing metadata"); + continue; + } + // Write the outgoing metadata + txs.get_mut(&tx.txid()).unwrap() + .outgoing_metadata + .push(OutgoingTxMetadata{ + address: taddr, + value: vout.value.into(), + memo: Memo::default(), + }); + } + } + } + } + // Scan shielded sapling outputs to see if anyone of them is us, and if it is, extract the memo + for output in tx.shielded_outputs.iter() { + let ivks: Vec<_> = self.zkeys.read().unwrap().iter() + .map(|zk| zk.extfvk.fvk.vk.ivk() + ).collect(); + let cmu = output.cmu; + let ct = output.enc_ciphertext; + // Search all of our keys + for ivk in ivks { + let epk_prime = output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(); + let (note, _to, memo) = match try_sapling_note_decryption(&ivk, &epk_prime, &cmu, &ct) { + Some(ret) => ret, + None => continue, + }; + if memo.to_utf8().is_some() { + // info!("A sapling note was sent to wallet in {} that had a memo", tx.txid()); + // Do it in a short scope because of the write lock. + let mut txs = self.txs.write().unwrap(); + // Update memo if we have this Tx. + match txs.get_mut(&tx.txid()) + .and_then(|t| { + t.notes.iter_mut().find(|nd| nd.note == note) + }) { + None => { + info!("No txid matched for incoming sapling funds while updating memo"); + () + }, + Some(nd) => { + nd.memo = Some(memo) + } + } + } + } + // Also scan the output to see if it can be decoded with our OutgoingViewKey + // If it can, then we sent this transaction, so we should be able to get + // the memo and value for our records + + // Collect IVKs to detect change outputs to diversified addresses + let ivks_for_change: Vec<_> = self.zkeys.read().unwrap().iter() + .map(|zk| zk.extfvk.fvk.vk.ivk()) + .collect(); + + // Search all ovks that we have + let ovks: Vec<_> = self.zkeys.read().unwrap().iter() + .map(|zk| zk.extfvk.fvk.ovk.clone()) + .collect(); + for ovk in ovks { + match try_sapling_output_recovery( + &ovk, + &output.cv, + &output.cmu, + &output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(), + &output.enc_ciphertext, + &output.out_ciphertext) { + Some((note, payment_address, memo)) => { + let address = encode_payment_address(self.config.hrp_sapling_address(), + &payment_address); + + // Check if this output belongs to our wallet using IVK decryption. + // This correctly detects change sent to diversified addresses, + // not just the wallet's default z-address. + let epk_prime = output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(); + let is_to_self = ivks_for_change.iter().any(|ivk| { + try_sapling_note_decryption(ivk, &epk_prime, &output.cmu, &output.enc_ciphertext).is_some() + }); + + // If this is change (funds to ourself) without a meaningful memo, skip it. + // memo.to_utf8() returns Some(Ok("")) for empty memos (all-zero bytes), + // so we must also check for empty strings, not just None. + if is_to_self { + let has_memo = match memo.to_utf8() { + Some(Ok(ref s)) if !s.is_empty() => true, + _ => false, + }; + if !has_memo { + continue; + } + } + // Update the WalletTx + // Do it in a short scope because of the write lock. + { + info!("A sapling output was sent in {}", tx.txid()); + match self.txs.write() { + Ok(mut txs) => { + match txs.get(&tx.txid()) { + Some(wtx) => { + if wtx.outgoing_metadata.iter() + .any(|om| om.address == address && om.value == note.value && om.memo == memo) + { + warn!("Duplicate outgoing metadata"); + continue; + } + + // Write the outgoing metadata + txs.get_mut(&tx.txid()).unwrap() + .outgoing_metadata + .push(OutgoingTxMetadata { + address, + value: note.value, + memo, + }); + }, + None => { + error!("Can not find any entry for txid : {}", tx.txid()); + continue; + } + } + }, + Err(poisoned) => { + error!("Lock is poisoned: {}", poisoned); + return; + } + } + } + }, + None => {} + }; + } + } + // Mark this Tx as scanned + { + let mut txs = self.txs.write().unwrap(); + match txs.get_mut(&tx.txid()) { + Some(wtx) => wtx.full_tx_scanned = true, + None => {}, + }; + } +} + +pub fn scan_full_mempool_tx(&self, tx: &Transaction, height: i32, _datetime: u64, mempool_transaction: bool) { + if tx.shielded_outputs.is_empty() { + error!("Something went wrong, there are no shielded outputs"); + return; + } + + for output in tx.shielded_outputs.iter() { + let ivks: Vec<_> = self.zkeys.read().unwrap().iter() + .map(|zk| zk.extfvk.fvk.vk.ivk()) + .collect(); + + let cmu = output.cmu; + let ct = output.enc_ciphertext; + + // Search all of our keys + for ivk in ivks { + let epk_prime = output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(); + let (note, _to, memo) = match try_sapling_note_decryption(&ivk, &epk_prime, &cmu, &ct) { + Some(ret) => ret, + None => continue, + }; + + if mempool_transaction { + let mut incoming_mempool_txs = match self.incoming_mempool_txs.write() { + Ok(txs) => txs, + Err(e) => { + error!("Error acquiring write lock: {}", e); + return; + } + }; + + let addr = encode_payment_address(self.config.hrp_sapling_address(), &_to); + let amt = note.value; + let mut wtx = WalletTx::new(height, now() as u64, &tx.txid()); + let formatted_memo = LightWallet::memo_str(&Some(memo.clone())); + let existing_txs = incoming_mempool_txs.entry(tx.txid()) + .or_insert_with(Vec::new); + + if formatted_memo.as_ref().map_or(false, |m| !m.is_empty()) { + // Check if a transaction with the exact same memo already exists + if existing_txs.iter().any(|tx| tx.incoming_metadata.iter().any(|meta| LightWallet::memo_str(&Some(meta.memo.clone())) == formatted_memo.as_ref().cloned())) { + // Transaction with this memo already exists, do nothing + return; + } + + let incoming_metadata = IncomingTxMetadata { + address: addr.clone(), + value: amt, + memo: memo.clone(), + incoming_mempool: true, + }; + + wtx.incoming_metadata.push(incoming_metadata); + existing_txs.push(wtx); + + let mut txs = match self.txs.write() { + Ok(t) => t, + Err(e) => { + error!("Error acquiring write lock: {}", e); + return; + } + }; + + if let Some(wtx) = txs.get_mut(&tx.txid()) { + wtx.incoming_metadata.push(IncomingTxMetadata { + address: addr.clone(), + value: amt, + memo: memo.clone(), + incoming_mempool: true, + }); + } else { + let mut new_wtx = WalletTx::new(height, now() as u64, &tx.txid()); + new_wtx.incoming_metadata.push(IncomingTxMetadata { + address: addr.clone(), + value: amt, + memo: memo.clone(), + incoming_mempool: true, + }); + txs.insert(tx.txid(), new_wtx); + } + + info!("Successfully added txid with memo"); + } else { + + // Check if txid already exists in the hashmap + let txid_exists = match self.txs.read() { + Ok(t) => t.contains_key(&tx.txid()), + Err(e) => { + error!("Error acquiring read lock: {}", e); + return; + } + }; + + if txid_exists { + // If txid already exists, do not process further + info!("Txid already exists, not adding"); + return; + } + + let incoming_metadata = IncomingTxMetadata { + address: addr.clone(), + value: amt, + memo: memo.clone(), + incoming_mempool: true, + }; + + wtx.incoming_metadata.push(incoming_metadata); + existing_txs.push(wtx); + + let mut txs = match self.txs.write() { + Ok(t) => t, + Err(e) => { + error!("Error acquiring write lock: {}", e); + return; + } + }; + + if let Some(wtx) = txs.get_mut(&tx.txid()) { + wtx.incoming_metadata.push(IncomingTxMetadata { + address: addr.clone(), + value: amt, + memo: memo.clone(), + incoming_mempool: true, + }); + } else { + let mut new_wtx = WalletTx::new(height, now() as u64, &tx.txid()); + new_wtx.incoming_metadata.push(IncomingTxMetadata { + address: addr.clone(), + value: amt, + memo: memo.clone(), + incoming_mempool: true, + }); + txs.insert(tx.txid(), new_wtx); + } + + info!("Successfully added txid"); + } + } else { + info!("Not a mempool transaction"); + } + + // Mark this Tx as scanned + { + let mut txs = self.txs.write().unwrap(); + match txs.get_mut(&tx.txid()) { + Some(wtx) => wtx.full_tx_scanned = true, + None => {}, + }; + } + } + } +} + + // Invalidate all blocks including and after "at_height". + // Returns the number of blocks invalidated + pub fn invalidate_block(&self, at_height: i32) -> u64 { + let mut num_invalidated = 0; + + // First remove the blocks + { + let mut blks = match self.blocks.write() { + Ok(b) => b, + Err(p) => p.into_inner(), + }; + + while let Some(last) = blks.last() { + if last.height < at_height { + break; + } + blks.pop(); + num_invalidated += 1; + } + } + + // Next, remove entire transactions + { + let mut txs = match self.txs.write() { + Ok(t) => t, + Err(p) => p.into_inner(), + }; + let txids_to_remove = txs.values() + .filter_map(|wtx| if wtx.block >= at_height {Some(wtx.txid.clone())} else {None}) + .collect::>(); + + for txid in &txids_to_remove { + txs.remove(&txid); + } + + // We also need to update any sapling note data and utxos in existing transactions that + // were spent in any of the txids that were removed + txs.values_mut() + .for_each(|wtx| { + wtx.notes.iter_mut() + .for_each(|nd| { + if nd.spent.is_some() && txids_to_remove.contains(&nd.spent.unwrap()) { + nd.spent = None; + } + + if nd.unconfirmed_spent.is_some() && txids_to_remove.contains(&nd.spent.unwrap()) { + nd.unconfirmed_spent = None; + } + }) + }) + } + + // Of the notes that still remain, unroll the witness. + // Remove `num_invalidated` items from the witness + { + let mut txs = match self.txs.write() { + Ok(t) => t, + Err(p) => p.into_inner(), + }; + + // Trim all witnesses for the invalidated blocks + for tx in txs.values_mut() { + for nd in tx.notes.iter_mut() { + let _discard = nd.witnesses.split_off(nd.witnesses.len().saturating_sub(num_invalidated)); + } + } + } + + num_invalidated as u64 + } + + /// 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_internal( + &self, + (index, output): (usize, CompactOutput), + ivks: &[Fs], + tree: &mut CommitmentTree, + existing_witnesses: &mut [&mut IncrementalWitness], + block_witnesses: &mut [&mut IncrementalWitness], + new_witnesses: &mut [&mut IncrementalWitness], + pool: &ThreadPool + ) -> Option { + // A cmu we cannot parse means we cannot append the correct leaf to the commitment tree, + // so EVERY later anchor/witness silently diverges from consensus — this is the exact class + // of bug behind "missing sapling anchor" after a long rescan. We can't reconstruct the leaf + // here, but log it LOUDLY (was a silent `cmu().ok()?` early-return) so the divergence is + // diagnosable; the practical cure is to reseed from a newer checkpoint past the bad block. + let cmu = match output.cmu() { + Ok(cmu) => cmu, + Err(_) => { + error!("Failed to parse cmu for output index {} — commitment tree will DIVERGE from consensus from this block onward; reseed from a newer checkpoint", index); + return None; + } + }; + // The note commitment (cmu) MUST be appended to the commitment tree for EVERY output, + // regardless of whether we can decrypt it — the tree must match consensus or every later + // anchor/witness diverges (silently). epk is needed only for trial-decryption, so an epk + // parse failure must NOT skip the tree append (the old `epk().ok()?` early-return did, and + // silently). On epk failure: log it, still commit cmu to the tree, then skip decryption. + let epk = match output.epk() { + Ok(epk) => epk, + Err(_) => { + error!("Failed to parse epk for output index {} — committing cmu to tree, skipping decryption", index); + let node = Node::new(cmu.into()); + 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(); + return None; + } + }; + let ct = output.ciphertext; + + let (tx, rx) = channel(); + ivks.iter().enumerate().for_each(|(account, ivk)| { + // Clone all values for passing to the closure + let ivk = ivk.clone(); + let epk = epk.clone(); + let ct = ct.clone(); + let tx = tx.clone(); + + pool.execute(move || { + let m = try_sapling_compact_note_decryption(&ivk, &epk, &cmu, &ct); + let r = match m { + Some((note, to)) => { + tx.send(Some(Some((note, to, account)))) + }, + None => { + tx.send(Some(None)) + } + }; + + match r { + Ok(_) => {}, + Err(e) => println!("Send error {:?}", e) + } + }); + }); + + // Increment tree and witnesses + let node = Node::new(cmu.into()); + 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(); + + // Collect all the RXs and fine if there was a valid result somewhere + let mut wsos = vec![]; + for _i in 0..ivks.len() { + let n = rx.recv().unwrap(); + let epk = epk.clone(); + + let wso = match n { + None => panic!("Got a none!"), + Some(None) => None, + Some(Some((note, to, account))) => { + // 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); + + Some(WalletShieldedOutput { + index, cmu, epk, account, note, to, is_change: false, + witness: IncrementalWitness::from_tree(tree), + }) + } + }; + wsos.push(wso); + } + + match wsos.into_iter().find(|wso| wso.is_some()) { + Some(Some(wso)) => Some(wso), + _ => 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_internal( + &self, + block: CompactBlock, + extfvks: &[ExtendedFullViewingKey], + nullifiers: Vec<(Vec, usize)>, + tree: &mut CommitmentTree, + existing_witnesses: &mut [&mut IncrementalWitness], + pool: &ThreadPool + ) -> Vec { + let mut wtxs: Vec = vec![]; + let ivks = 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(); + + let (ctx, crx) = channel(); + { + let nullifiers = nullifiers.clone(); + let tx = tx.clone(); + pool.execute(move || { + // 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(); + + ctx.send((shielded_spends, spent_from_accounts)).unwrap(); + + drop(ctx); + }); + } + + + // Check for incoming notes while incrementing tree and witnesses + let mut shielded_outputs: Vec = 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) = self.scan_output_internal( + to_scan, + &ivks, + tree, + existing_witnesses, + &mut block_witnesses, + &mut new_witnesses, + pool + ) { + shielded_outputs.push(output); + } + } + } + + let (shielded_spends, spent_from_accounts) = crx.recv().unwrap(); + + // Identify change outputs + shielded_outputs.iter_mut().for_each(|output| { + if spent_from_accounts.contains(&output.account) { + output.is_change = true; + } + }); + + // Update wallet tx + if !(shielded_spends.is_empty() && shielded_outputs.is_empty()) { + let mut txid = TxId([0u8; 32]); + txid.0.copy_from_slice(&tx.hash); + wtxs.push(zcash_client_backend::wallet::WalletTx { + txid, + index: tx.index as usize, + num_spends, + num_outputs, + shielded_spends, + shielded_outputs, + }); + } + } + + wtxs + } + pub fn scan_block(&self, block_bytes: &[u8]) -> Result, i32> { + self.scan_block_with_pool(&block_bytes, &ThreadPool::new(1)) + } + + // Scan a block. Will return an error with the block height that failed to scan + pub fn scan_block_with_pool(&self, block_bytes: &[u8], pool: &ThreadPool) -> Result, i32> { + let block: CompactBlock = match parse_from_bytes(block_bytes) { + Ok(block) => block, + Err(e) => { + error!("Could not parse CompactBlock from bytes: {}", e); + return Err(-1); + } + }; + + // Scanned blocks MUST be height-sequential. + let height = block.get_height() as i32; + if height == self.last_scanned_height() { + // If the last scanned block is rescanned, check it still matches. + if let Some(hash) = self.blocks.read().unwrap().last().map(|block| block.hash) { + if block.hash() != hash { + warn!("Likely reorg. Block hash does not match for block {}. {} vs {}", height, block.hash(), hash); + return Err(height); + } + } + return Ok(vec![]); + } else if height != (self.last_scanned_height() + 1) { + error!( + "Block is not height-sequential (expected {}, found {})", + self.last_scanned_height() + 1, + height + ); + return Err(self.last_scanned_height()); + } + + // Check to see that the previous block hash matches + if let Some(hash) = self.blocks.read().unwrap().last().map(|block| block.hash) { + if block.prev_hash() != hash { + warn!("Likely reorg. Prev block hash does not match for block {}. {} vs {}", height, block.prev_hash(), hash); + return Err(height-1); + } + } + + // Get the most recent scanned data. + let mut block_data = BlockData { + height, + hash: block.hash(), + tree: self + .blocks + .read() + .unwrap() + .last() + .map(|block| block.tree.clone()) + .unwrap_or(CommitmentTree::new()), + }; + + // These are filled in inside the block + let new_txs; + let nfs: Vec<_>; + { + // Create a write lock + let mut txs = self.txs.write().unwrap(); + + // Create a Vec containing all unspent nullifiers. + // Include only the confirmed spent nullifiers, since unconfirmed ones still need to be included + // during scan_block below. + nfs = txs + .iter() + .map(|(txid, tx)| { + let txid = *txid; + tx.notes.iter().filter_map(move |nd| { + if nd.spent.is_none() { + Some((nd.nullifier, nd.account, txid)) + } else { + None + } + }) + }) + .flatten() + .collect(); + + // Prepare the note witnesses for updating + for tx in txs.values_mut() { + for nd in tx.notes.iter_mut() { + // Duplicate the most recent witness + if let Some(witness) = nd.witnesses.last() { + let clone = witness.clone(); + nd.witnesses.push(clone); + } + // Trim the oldest witnesses + nd.witnesses = nd + .witnesses + .split_off(nd.witnesses.len().saturating_sub(100)); + } + } + + new_txs = { + let nf_refs = nfs.iter().map(|(nf, account, _)| (nf.to_vec(), *account)).collect::>(); + let extfvks: Vec = self.zkeys.read().unwrap().iter().map(|zk| zk.extfvk.clone()).collect(); + + // Create a single mutable slice of all the newly-added witnesses. + let mut witness_refs: Vec<_> = txs + .values_mut() + .map(|tx| tx.notes.iter_mut().filter_map( + |nd| if nd.spent.is_none() && nd.unconfirmed_spent.is_none() { nd.witnesses.last_mut() } else { None })) + .flatten() + .collect(); + + self.scan_block_internal( + block.clone(), + &extfvks, + nf_refs, + &mut block_data.tree, + &mut witness_refs[..], + pool, + ) + }; + } + + + // If this block had any new Txs, return the list of ALL txids in this block, + // so the wallet can fetch them all as a decoy. + let all_txs = if !new_txs.is_empty() { + block.vtx.iter().map(|vtx| { + let mut t = [0u8; 32]; + t.copy_from_slice(&vtx.hash[..]); + TxId{0: t} + }).collect::>() + } else { + vec![] + }; + + for tx in new_txs { + // Create a write lock + let mut txs = self.txs.write().unwrap(); + + // Mark notes as spent. + let mut total_shielded_value_spent: u64 = 0; + + //info!("Txid {} belongs to wallet", tx.txid); + + for spend in &tx.shielded_spends { + let txid = nfs + .iter() + .find(|(nf, _, _)| &nf[..] == &spend.nf[..]) + .unwrap() + .2; + let mut spent_note = txs + .get_mut(&txid) + .unwrap() + .notes + .iter_mut() + .find(|nd| &nd.nullifier[..] == &spend.nf[..]) + .unwrap(); + + // Mark the note as spent, and remove the unconfirmed part of it + info!("Marked a note as spent"); + spent_note.spent = Some(tx.txid); + spent_note.unconfirmed_spent = None::; + + total_shielded_value_spent += spent_note.note.value; + } + + // Find the existing transaction entry, or create a new one. + if !txs.contains_key(&tx.txid) { + let tx_entry = WalletTx::new(block_data.height as i32, block.time as u64, &tx.txid); + txs.insert(tx.txid, tx_entry); + } + let tx_entry = txs.get_mut(&tx.txid).unwrap(); + tx_entry.total_shielded_value_spent = total_shielded_value_spent; + + // Save notes. + for output in tx.shielded_outputs + { + let new_note = SaplingNoteData::new(&self.zkeys.read().unwrap()[output.account].extfvk, output); + match LightWallet::note_address(self.config.hrp_sapling_address(), &new_note) { + Some(a) => { + // info!("Received sapling output to {}", a); + self.ensure_hd_zaddresses(&a); + }, + None => {} + } + + match tx_entry.notes.iter().find(|nd| nd.nullifier == new_note.nullifier) { + None => tx_entry.notes.push(new_note), + Some(_) => warn!("Tried to insert duplicate note for Tx {}", tx.txid) + }; + } + } + + { + let mut blks = self.blocks.write().unwrap(); + + // Store scanned data for this block. + blks.push(block_data); + + // Trim the old blocks, keeping only as many as needed for a worst-case reorg (i.e. 101 blocks) + let len = blks.len(); + if len > MAX_REORG + 1 { + let drain_first = len - (MAX_REORG+1); + blks.drain(..drain_first); + } + } + + { + // Cleanup mempool tx after adding a block, to remove all txns that got mined + self.cleanup_mempool(); + self.cleanup_incoming_mempool(); + } + + // Print info about the block every 10,000 blocks + if height % 10_000 == 0 { + match self.get_sapling_tree() { + Ok((h, hash, stree)) => info!("Sapling tree at height\n({}, \"{}\",\"{}\"),", h, hash, stree), + Err(e) => error!("Couldn't determine sapling tree: {}", e) + } + } + + Ok(all_txs) + } + + pub fn send_to_address ( + &self, + consensus_branch_id: u32, + spend_params: &[u8], + output_params: &[u8], + _transparent_only: bool, + tos: Vec<(&str, u64, Option)>, + fee: &u64, + broadcast_fn: F + ) -> Result<(String, Vec), String> + where F: Fn(Box<[u8]>) -> Result + { + if !self.unlocked { + return Err("Cannot spend while wallet is locked".to_string()); + } + + let start_time = now(); + if tos.len() == 0 { + return Err("Need at least one destination address".to_string()); + } + + // Check for duplicates in the to list - We need that for HushChat + // if tos.len() > 1 { + // let mut to_addresses = tos.iter().map(|t| t.0.to_string()).collect::>(); + // to_addresses.sort(); + // for i in 0..to_addresses.len()-1 { + // if to_addresses[i] == to_addresses[i+1] { + // return Err(format!("To address {} is duplicated", to_addresses[i])); + // } + // } + // } + + let total_value = tos.iter().map(|to| to.1).sum::() as u64; + println!( + "0: Creating transaction sending {} puposhis to {} addresses", + total_value, tos.len() + ); + + // Convert address (str) to RecipientAddress and value to Amount + + let recepients: Result)>, String> = tos.iter().map(|to| { + // Convert string to RecipientAddress + let ra = match address::RecipientAddress::from_str( + to.0, + self.config.hrp_sapling_address(), + self.config.base58_pubkey_address(), + self.config.base58_script_address() + ) { + Some(addr) => addr, + None => { + let e = format!("Invalid recipient address: '{}'", to.0); + error!("{}", e); + return Err(e); + } + }; + + // Convert the second tuple element to Amount + let value = Amount::from_u64(to.1).expect("Invalid amount value"); + + Ok((ra, value, to.2.clone())) + }).collect(); + + let recepients = recepients?; + + // Target the next block, assuming we are up-to-date. + let (height, anchor_offset) = match self.get_target_height_and_anchor_offset() { + Some(res) => res, + None => { + let e = format!("Cannot send funds before scanning any blocks"); + error!("{}", e); + return Err(e); + } + }; + + // Select notes to cover the target value + // Select notes to cover the target value + println!("{}: Selecting notes", now() - start_time); + let target_value = Amount::from_u64(total_value).unwrap() + Amount::from_u64(*fee).unwrap(); + // Select the candidate notes that are eligible to be spent + let notes: Vec<_> = self.txs.read().unwrap().iter() + .map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note))) + .flatten() + .filter_map(|(txid, note)| { + // Filter out notes that are already spent + if note.spent.is_some() || note.unconfirmed_spent.is_some() { + None + } else { + // Get the spending key for the selected fvk, if we have it + let extsk = self.zkeys.read().unwrap().iter() + .find(|zk| zk.extfvk == note.extfvk) + .and_then(|zk| zk.extsk.clone()); + SpendableNote::from(txid, note, anchor_offset, &extsk) + } + }) + .scan(0, |running_total, spendable| { + + let value = spendable.note.value; + let ret = if *running_total < u64::from(target_value) { + Some(spendable) + } else { + None + }; + *running_total = *running_total + value; + ret + }) + .collect(); + + let mut builder = Builder::new(height); + + // A note on t addresses + // Funds received by t-addresses can't be explicitly spent in silentdragonxlite. + // silentdragonxlite will lazily consolidate all t address funds into your shielded addresses. + // Specifically, if you send an outgoing transaction that is sent to a shielded address, + // silentdragonxlite will add all your t-address funds into that transaction, and send them to your shielded + // address as change. + let tinputs: Vec<_> = self.get_utxos().iter() + .filter(|utxo| utxo.unconfirmed_spent.is_none()) // Remove any unconfirmed spends + .map(|utxo| utxo.clone()) + .collect(); + + // Create a map from address -> sk for all taddrs, so we can spend from the + // right address + let address_to_sk = self.tkeys.read().unwrap().iter() + .filter(|wtk| wtk.tkey.is_some()) + .map(|wtk| (wtk.address.clone(), wtk.tkey.unwrap().clone())) + .collect::>(); + + // Add all tinputs + tinputs.iter() + .map(|utxo| { + let outpoint: OutPoint = utxo.to_outpoint(); + + let coin = TxOut { + value: Amount::from_u64(utxo.value).unwrap(), + script_pubkey: Script { 0: utxo.script.clone() }, + }; + + if let Some(sk) = address_to_sk.get(&utxo.address) { + return builder.add_transparent_input(*sk, outpoint.clone(), coin.clone()) + } else { + info!("Not adding a UTXO because secret key is absent."); + return Ok(()) + } + + }) + .collect::, _>>() + .map_err(|e| format!("{}", e))?; + + + // Confirm we were able to select sufficient value + let selected_value = notes.iter().map(|selected| selected.note.value).sum::() + + tinputs.iter().map::(|utxo| utxo.value.into()).sum::(); + + if selected_value < u64::from(target_value) { + let e = format!( + "Insufficient verified funds (have {}, need {:?}). NOTE: funds need {} confirmations before they can be spent.", + selected_value, target_value, self.config.anchor_offset + ); + error!("{}", e); + return Err(e); + } + + let fee_amount = Amount::from_u64(*fee).expect("Invalid fee amount"); + builder.set_fee(fee_amount); + + // Create the transaction + println!("{}: Adding {} notes and {} utxos and fee {:?}", now() - start_time, notes.len(), tinputs.len(), fee_amount); + + for selected in notes.iter() { + if let Err(e) = builder.add_sapling_spend( + selected.extsk.clone(), + selected.diversifier, + selected.note.clone(), + selected.witness.clone(), + ) { + let e = format!("Error adding note: {:?}", e); + error!("{}", e); + return Err(e); + } + } + + // If no Sapling notes were added, add the change address manually. That is, + // send the change to our sapling address manually. Note that if a sapling note was spent, + // the builder will automatically send change to that address + if notes.len() == 0 { + builder.send_change_to( + self.zkeys.read().unwrap()[0].extfvk.fvk.ovk, + self.zkeys.read().unwrap()[0].zaddress.clone()); + } + + // TODO: We're using the first ovk to encrypt outgoing Txns. Is that Ok? + let ovk = self.zkeys.read().unwrap()[0].extfvk.fvk.ovk; + + for (to, value, memo) in recepients { + // Compute memo if it exists + let encoded_memo = match memo { + None => None, + Some(s) => match Memo::from_str(&s) { + None => { + let e = format!("Error creating output. Memo {:?} is too long", s); + error!("{}", e); + return Err(e); + }, + Some(m) => Some(m) + } + }; + + println!("{}: Adding output", now() - start_time); + + if let Err(e) = match to { + address::RecipientAddress::Shielded(to) => { + builder.add_sapling_output(ovk, to.clone(), value, encoded_memo) + } + address::RecipientAddress::Transparent(to) => { + builder.add_transparent_output(&to, value) + } + } { + let e = format!("Error adding output: {:?}", e); + error!("{}", e); + return Err(e); + } + } + + println!("{}: Building transaction", now() - start_time); + let (tx, _) = match builder.build( + consensus_branch_id, + prover::InMemTxProver::new(spend_params, output_params), + ) { + Ok(res) => res, + Err(e) => { + let e = format!("Error creating transaction: {:?}", e); + error!("{}", e); + return Err(e); + } + }; + println!("{}: Transaction created", now() - start_time); + println!("Transaction ID: {}", tx.txid()); + + // Create the TX bytes + let mut raw_tx = vec![]; + tx.write(&mut raw_tx).unwrap(); + + let txid = broadcast_fn(raw_tx.clone().into_boxed_slice())?; + + // Mark notes as spent. + { + // Mark sapling notes as unconfirmed spent + let mut txs = self.txs.write().unwrap(); + for selected in notes { + let mut spent_note = txs.get_mut(&selected.txid).unwrap() + .notes.iter_mut() + .find(|nd| &nd.nullifier[..] == &selected.nullifier[..]) + .unwrap(); + spent_note.unconfirmed_spent = Some(tx.txid()); + } + + // Mark this utxo as unconfirmed spent + for utxo in tinputs { + let mut spent_utxo = txs.get_mut(&utxo.txid).unwrap().utxos.iter_mut() + .find(|u| utxo.txid == u.txid && utxo.output_index == u.output_index) + .unwrap(); + spent_utxo.unconfirmed_spent = Some(tx.txid()); + } + } + + // Add this Tx to the mempool structure + { + let mut mempool_txs = self.mempool_txs.write().unwrap(); + + match mempool_txs.get_mut(&tx.txid()) { + None => { + // Collect the outgoing metadata + let outgoing_metadata = tos.iter().map(|(addr, amt, maybe_memo)| { + OutgoingTxMetadata { + address: addr.to_string(), + value: *amt, + memo: match maybe_memo { + None => Memo::default(), + Some(s) => { + // If the address is not a z-address, then drop the memo + if LightWallet::is_shielded_address(&addr.to_string(), &self.config) { + Memo::from_str(s).unwrap() + } else { + Memo::default() + } + } + }, + } + }).collect::>(); + + // Create a new WalletTx + let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid()); + wtx.outgoing_metadata = outgoing_metadata; + + // Add it into the mempool + mempool_txs.insert(tx.txid(), wtx); + }, + Some(_) => { + warn!("A newly created Tx was already in the mempool! How's that possible? Txid: {}", tx.txid()); + } + } + } + + Ok((txid, raw_tx)) + } + + // After some blocks have been mined, we need to remove the Txns from the mempool_tx structure + // if they : + // 1. Have expired + // 2. The Tx has been added to the wallet via a mined block + pub fn cleanup_mempool(&self) { + const DEFAULT_TX_EXPIRY_DELTA: i32 = 20; + + let current_height = self.blocks.read().unwrap().last().map(|b| b.height).unwrap_or(0); + + { + // Remove all expired Txns + self.mempool_txs.write().unwrap().retain( | _, wtx| { + current_height < (wtx.block + DEFAULT_TX_EXPIRY_DELTA) + }); + } + + { + // Remove all txns where the txid is added to the wallet directly + self.mempool_txs.write().unwrap().retain ( |txid, _| { + self.txs.read().unwrap().get(txid).is_none() + }); + } + } + + pub fn cleanup_incoming_mempool(&self) { + const DEFAULT_TX_EXPIRY_DELTA: i32 = 20; + let current_height = self.blocks.read().unwrap().last().map(|b| b.height).unwrap_or(0); + + { + // Remove all expired Txns + self.incoming_mempool_txs.write().unwrap().retain(|_, wtxs| { + wtxs.retain(|wtx| current_height < (wtx.block + DEFAULT_TX_EXPIRY_DELTA)); + !wtxs.is_empty() // Behalte den Eintrag nur, wenn nicht alle Transaktionen abgelaufen sind + }); + } + + { + // Remove all txns where the txid is added to the wallet directly + self.incoming_mempool_txs.write().unwrap().retain(|txid, _| { + self.txs.read().unwrap().get(txid).is_none() + }); + } + } + +} + +#[cfg(test)] +pub mod tests; diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/address.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/address.rs new file mode 100644 index 0000000..b1080be --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/address.rs @@ -0,0 +1,46 @@ +//! Structs for handling supported address types. + +use pairing::bls12_381::Bls12; +use zcash_primitives::primitives::PaymentAddress; +use zcash_client_backend::encoding::{decode_payment_address, decode_transparent_address}; +use zcash_primitives::legacy::TransparentAddress; + +/// An address that funds can be sent to. +pub enum RecipientAddress { + Shielded(PaymentAddress), + Transparent(TransparentAddress), +} + +impl From> for RecipientAddress { + fn from(addr: PaymentAddress) -> Self { + RecipientAddress::Shielded(addr) + } +} + +impl From 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 { + // 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 + } + } +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/data.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/data.rs new file mode 100644 index 0000000..5c64aac --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/data.rs @@ -0,0 +1,558 @@ +use std::io::{self, Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use pairing::bls12_381::{Bls12}; +use ff::{PrimeField, PrimeFieldRepr}; + +use zcash_primitives::{ + block::BlockHash, + merkle_tree::{CommitmentTree, IncrementalWitness}, + sapling::Node, + serialize::{Vector, Optional}, + transaction::{ + components::{OutPoint}, + TxId, + }, + note_encryption::{Memo,}, + zip32::{ExtendedFullViewingKey,}, + JUBJUB, + primitives::{Diversifier, Note,}, + jubjub::{ + JubjubEngine, + fs::{Fs, FsRepr}, + } +}; +use zcash_primitives::zip32::ExtendedSpendingKey; + + +pub struct BlockData { + pub height: i32, + pub hash: BlockHash, + pub tree: CommitmentTree, +} + +impl BlockData { + pub fn read(mut reader: R) -> io::Result { + let height = reader.read_i32::()?; + + let mut hash_bytes = [0; 32]; + reader.read_exact(&mut hash_bytes)?; + + let tree = CommitmentTree::::read(&mut reader)?; + + let endtag = reader.read_u64::()?; + if endtag != 11 { + println!("End tag for blockdata {}", endtag); + } + + + Ok(BlockData{ + height, + hash: BlockHash{ 0: hash_bytes }, + tree + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_i32::(self.height)?; + writer.write_all(&self.hash.0)?; + self.tree.write(&mut writer)?; + writer.write_u64::(11) + } +} + +pub struct SaplingNoteData { + pub(super) account: usize, + pub(super) extfvk: ExtendedFullViewingKey, // Technically, this should be recoverable from the account number, but we're going to refactor this in the future, so I'll write it again here. + pub diversifier: Diversifier, + pub note: Note, + pub(super) witnesses: Vec>, + pub(super) nullifier: [u8; 32], + pub spent: Option, // If this note was confirmed spent + pub unconfirmed_spent: Option, // If this note was spent in a send, but has not yet been confirmed. + pub memo: Option, + pub is_change: bool, + // TODO: We need to remove the unconfirmed_spent (i.e., set it to None) if the Tx has expired +} + + +/// Reads an FsRepr from [u8] of length 32 +/// This will panic (abort) if length provided is +/// not correct +/// TODO: This is duplicate from rustzcash.rs +fn read_fs(from: &[u8]) -> FsRepr { + assert_eq!(from.len(), 32); + + let mut f = <::Fs as PrimeField>::Repr::default(); + f.read_le(from).expect("length is 32 bytes"); + + f +} + +// Reading a note also needs the corresponding address to read from. +pub fn read_note(mut reader: R) -> io::Result<(u64, Fs)> { + let value = reader.read_u64::()?; + + let mut r_bytes: [u8; 32] = [0; 32]; + reader.read_exact(&mut r_bytes)?; + + let r = match Fs::from_repr(read_fs(&r_bytes)) { + Ok(r) => r, + Err(_) => return Err(io::Error::new( + io::ErrorKind::InvalidInput, "Couldn't parse randomness")) + }; + + Ok((value, r)) +} + +impl SaplingNoteData { + fn serialized_version() -> u64 { + 1 + } + + pub fn new( + extfvk: &ExtendedFullViewingKey, + output: zcash_client_backend::wallet::WalletShieldedOutput + ) -> Self { + let witness = output.witness; + let nf = { + let mut nf = [0; 32]; + nf.copy_from_slice( + &output + .note + .nf(&extfvk.fvk.vk, witness.position() as u64, &JUBJUB), + ); + nf + }; + + SaplingNoteData { + account: output.account, + extfvk: extfvk.clone(), + diversifier: output.to.diversifier, + note: output.note, + witnesses: vec![witness], + nullifier: nf, + spent: None, + unconfirmed_spent: None, + memo: None, + is_change: output.is_change, + } + } + + // Reading a note also needs the corresponding address to read from. + pub fn read(mut reader: R) -> io::Result { + let _version = reader.read_u64::()?; + + let account = reader.read_u64::()? as usize; + + let extfvk = ExtendedFullViewingKey::read(&mut reader)?; + + let mut diversifier_bytes = [0u8; 11]; + reader.read_exact(&mut diversifier_bytes)?; + let diversifier = Diversifier{0: diversifier_bytes}; + + // To recover the note, read the value and r, and then use the payment address + // to recreate the note + let (value, r) = read_note(&mut reader)?; // TODO: This method is in a different package, because of some fields that are private + + let maybe_note = extfvk.fvk.vk.into_payment_address(diversifier, &JUBJUB).unwrap().create_note(value, r, &JUBJUB); + + let note = match maybe_note { + Some(n) => Ok(n), + None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the note for the address")) + }?; + + let witnesses = Vector::read(&mut reader, |r| IncrementalWitness::::read(r))?; + + let mut nullifier = [0u8; 32]; + reader.read_exact(&mut nullifier)?; + + // Note that this is only the spent field, we ignore the unconfirmed_spent field. + // The reason is that unconfirmed spents are only in memory, and we need to get the actual value of spent + // from the blockchain anyway. + let spent = Optional::read(&mut reader, |r| { + let mut txid_bytes = [0u8; 32]; + r.read_exact(&mut txid_bytes)?; + Ok(TxId{0: txid_bytes}) + })?; + + let memo = Optional::read(&mut reader, |r| { + let mut memo_bytes = [0u8; 512]; + r.read_exact(&mut memo_bytes)?; + match Memo::from_bytes(&memo_bytes) { + Some(m) => Ok(m), + None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the memo")) + } + })?; + + let is_change: bool = reader.read_u8()? > 0; + + Ok(SaplingNoteData { + account, + extfvk, + diversifier, + note, + witnesses, + nullifier, + spent, + unconfirmed_spent: None, + memo, + is_change, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + // Write a version number first, so we can later upgrade this if needed. + writer.write_u64::(SaplingNoteData::serialized_version())?; + + writer.write_u64::(self.account as u64)?; + + self.extfvk.write(&mut writer)?; + + writer.write_all(&self.diversifier.0)?; + + // Writing the note means writing the note.value and note.r. The Note is recoverable + // from these 2 values and the Payment address. + writer.write_u64::(self.note.value)?; + + let mut rcm = [0; 32]; + self.note.r.into_repr().write_le(&mut rcm[..])?; + writer.write_all(&rcm)?; + + Vector::write(&mut writer, &self.witnesses, |wr, wi| wi.write(wr) )?; + + writer.write_all(&self.nullifier)?; + Optional::write(&mut writer, &self.spent, |w, t| w.write_all(&t.0))?; + + Optional::write(&mut writer, &self.memo, |w, m| w.write_all(m.as_bytes()))?; + + writer.write_u8(if self.is_change {1} else {0})?; + + // Note that we don't write the unconfirmed_spent field, because if the wallet is restarted, + // we don't want to be beholden to any expired txns + + Ok(()) + } + +} + +#[derive(Clone, Debug)] +pub struct Utxo { + pub address: String, + pub txid: TxId, + pub output_index: u64, + pub script: Vec, + pub value: u64, + pub height: i32, + + pub spent: Option, // If this utxo was confirmed spent + pub unconfirmed_spent: Option, // If this utxo was spent in a send, but has not yet been confirmed. +} + +impl Utxo { + pub fn serialized_version() -> u64 { + return 1; + } + + pub fn to_outpoint(&self) -> OutPoint { + OutPoint { hash: self.txid.0, n: self.output_index as u32 } + } + + pub fn read(mut reader: R) -> io::Result { + let version = reader.read_u64::()?; + assert_eq!(version, Utxo::serialized_version()); + + let address_len = reader.read_i32::()?; + let mut address_bytes = vec![0; address_len as usize]; + reader.read_exact(&mut address_bytes)?; + let address = String::from_utf8(address_bytes).unwrap(); + assert_eq!(address.chars().take(1).collect::>()[0], 'R'); + + let mut txid_bytes = [0; 32]; + reader.read_exact(&mut txid_bytes)?; + let txid = TxId { 0: txid_bytes }; + + let output_index = reader.read_u64::()?; + let value = reader.read_u64::()?; + let height = reader.read_i32::()?; + + let script = Vector::read(&mut reader, |r| { + let mut byte = [0; 1]; + r.read_exact(&mut byte)?; + Ok(byte[0]) + })?; + + let spent = Optional::read(&mut reader, |r| { + let mut txbytes = [0u8; 32]; + r.read_exact(&mut txbytes)?; + Ok(TxId{0: txbytes}) + })?; + + // Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff. + + Ok(Utxo { + address, + txid, + output_index, + script, + value, + height, + spent, + unconfirmed_spent: None::, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_u64::(Utxo::serialized_version())?; + + writer.write_u32::(self.address.as_bytes().len() as u32)?; + writer.write_all(self.address.as_bytes())?; + + writer.write_all(&self.txid.0)?; + + writer.write_u64::(self.output_index)?; + writer.write_u64::(self.value)?; + writer.write_i32::(self.height)?; + + Vector::write(&mut writer, &self.script, |w, b| w.write_all(&[*b]))?; + + Optional::write(&mut writer, &self.spent, |w, txid| w.write_all(&txid.0))?; + + // Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff. + + Ok(()) + } +} + +pub struct OutgoingTxMetadata { + pub address: String, + pub value : u64, + pub memo : Memo, +} + +impl OutgoingTxMetadata { + pub fn read(mut reader: R) -> io::Result { + let address_len = reader.read_u64::()?; + let mut address_bytes = vec![0; address_len as usize]; + reader.read_exact(&mut address_bytes)?; + let address = String::from_utf8(address_bytes).unwrap(); + + let value = reader.read_u64::()?; + + let mut memo_bytes = [0u8; 512]; + reader.read_exact(&mut memo_bytes)?; + let memo = Memo::from_bytes(&memo_bytes).unwrap(); + + Ok(OutgoingTxMetadata{ + address, + value, + memo, + }) + } + + + pub fn write(&self, mut writer: W) -> io::Result<()> { + // Strings are written as len + utf8 + writer.write_u64::(self.address.as_bytes().len() as u64)?; + writer.write_all(self.address.as_bytes())?; + + writer.write_u64::(self.value)?; + writer.write_all(self.memo.as_bytes()) + } +} +#[derive(Debug)] +pub struct IncomingTxMetadata { + pub address: String, + pub value : u64, + pub memo : Memo, + pub incoming_mempool: bool, +} + +impl IncomingTxMetadata { + pub fn read(mut reader: R) -> io::Result { + let address_len = reader.read_u64::()?; + let mut address_bytes = vec![0; address_len as usize]; + reader.read_exact(&mut address_bytes)?; + let address = String::from_utf8(address_bytes).unwrap(); + + let value = reader.read_u64::()?; + let incoming_mempool = true; + // let position = 0; + + let mut memo_bytes = [0u8; 512]; + reader.read_exact(&mut memo_bytes)?; + let memo = Memo::from_bytes(&memo_bytes).unwrap(); + + Ok(IncomingTxMetadata{ + address, + value, + memo, + incoming_mempool, + // position, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + // Strings are written as len + utf8 + writer.write_u64::(self.address.as_bytes().len() as u64)?; + writer.write_all(self.address.as_bytes())?; + + writer.write_u64::(self.value)?; + writer.write_all(self.memo.as_bytes()) + } +} + +pub struct WalletTx { + // Block in which this tx was included + pub block: i32, + + // Timestamp of Tx. Added in v4 + pub datetime: u64, + + // Txid of this transaction. It's duplicated here (It is also the Key in the HashMap that points to this + // WalletTx in LightWallet::txs) + pub txid: TxId, + + // List of all notes received in this tx. Some of these might be change notes. + pub notes: Vec, + + // List of all Utxos received in this Tx. Some of these might be change notes + pub utxos: Vec, + + // Total shielded value spent in this Tx. Note that this is the value of the wallet's notes spent. + // Some change may be returned in one of the notes above. Subtract the two to get the actual value spent. + // Also note that even after subtraction, you might need to account for transparent inputs and outputs + // to make sure the value is accurate. + pub total_shielded_value_spent: u64, + + // Total amount of transparent funds that belong to us that were spent in this Tx. + pub total_transparent_value_spent : u64, + + // All outgoing sapling sends to addresses outside this wallet + pub outgoing_metadata: Vec, + + pub incoming_metadata: Vec, + + // Whether this TxID was downloaded from the server and scanned for Memos + pub full_tx_scanned: bool, +} + +impl WalletTx { + pub fn serialized_version() -> u64 { + return 5; + } + + pub fn new(height: i32, datetime: u64, txid: &TxId) -> Self { + WalletTx { + block: height, + datetime, + txid: txid.clone(), + notes: vec![], + utxos: vec![], + total_shielded_value_spent: 0, + total_transparent_value_spent: 0, + outgoing_metadata: vec![], + incoming_metadata: vec![], + full_tx_scanned: false, + } + } + + pub fn read(mut reader: R) -> io::Result { + let version = reader.read_u64::()?; + assert!(version <= WalletTx::serialized_version(), "Version mismatch. Please restore with your Seed"); + + let block = reader.read_i32::()?; + let datetime = if version >= 4 { + reader.read_u64::()? + } else { + 0 + }; + + let mut txid_bytes = [0u8; 32]; + reader.read_exact(&mut txid_bytes)?; + let txid = TxId{0: txid_bytes}; + + let notes = Vector::read(&mut reader, |r| SaplingNoteData::read(r))?; + let utxos = Vector::read(&mut reader, |r| Utxo::read(r))?; + let total_shielded_value_spent = reader.read_u64::()?; + let total_transparent_value_spent = reader.read_u64::()?; + let outgoing_metadata = Vector::read(&mut reader, |r| OutgoingTxMetadata::read(r))?; + + // Read incoming_metadata only if version is 5 or higher + let incoming_metadata = if version >= 5 { + Vector::read(&mut reader, |r| IncomingTxMetadata::read(r))? + } else { + vec![] + }; + + let full_tx_scanned = reader.read_u8()? > 0; + + Ok(WalletTx { + block, + datetime, + txid, + notes, + utxos, + total_shielded_value_spent, + total_transparent_value_spent, + outgoing_metadata, + incoming_metadata, + full_tx_scanned + }) + } + + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_u64::(WalletTx::serialized_version())?; + + writer.write_i32::(self.block)?; + + writer.write_u64::(self.datetime)?; + + writer.write_all(&self.txid.0)?; + + Vector::write(&mut writer, &self.notes, |w, nd| nd.write(w))?; + Vector::write(&mut writer, &self.utxos, |w, u| u.write(w))?; + + writer.write_u64::(self.total_shielded_value_spent)?; + writer.write_u64::(self.total_transparent_value_spent)?; + + // Write the outgoing metadata + Vector::write(&mut writer, &self.outgoing_metadata, |w, om| om.write(w))?; + Vector::write(&mut writer, &self.incoming_metadata, |w, om| om.write(w))?; + + writer.write_u8(if self.full_tx_scanned {1} else {0})?; + + Ok(()) + } +} + +pub struct SpendableNote { + pub txid: TxId, + pub nullifier: [u8; 32], + pub diversifier: Diversifier, + pub note: Note, + pub witness: IncrementalWitness, + pub extsk: ExtendedSpendingKey, +} + +impl SpendableNote { + pub fn from(txid: TxId, nd: &SaplingNoteData, anchor_offset: usize, extsk: &Option) -> Option { + // Include only notes that haven't been spent, or haven't been included in an unconfirmed spend yet. + if nd.spent.is_none() && nd.unconfirmed_spent.is_none() && extsk.is_some() && + nd.witnesses.len() >= (anchor_offset + 1) { + let witness = nd.witnesses.get(nd.witnesses.len() - anchor_offset - 1); + + witness.map(|w| SpendableNote { + txid, + nullifier: nd.nullifier, + diversifier: nd.diversifier, + note: nd.note.clone(), + witness: w.clone(), + extsk: extsk.clone().unwrap(), + }) + } else { + None + } + } +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/extended_key.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/extended_key.rs new file mode 100644 index 0000000..2758155 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/extended_key.rs @@ -0,0 +1,126 @@ +use ring::{ + hmac::{self, Context, Key}, +}; +use lazy_static::lazy_static; +use secp256k1::{PublicKey, Secp256k1, SecretKey, SignOnly, VerifyOnly, Error}; + +lazy_static! { + static ref SECP256K1_SIGN_ONLY: Secp256k1 = Secp256k1::signing_only(); + static ref SECP256K1_VERIFY_ONLY: Secp256k1 = Secp256k1::verification_only(); +} +/// Random entropy, part of extended key. +type ChainCode = Vec; + + +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 { + 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 { + if i < HARDENED_KEY_START_INDEX { + Ok(KeyIndex::Normal(i)) + } else { + Ok(KeyIndex::Hardened(i)) + } + } +} + +impl From 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 { + 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 { + 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(), + }) + } +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/prover.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/prover.rs new file mode 100644 index 0000000..85121a3 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/prover.rs @@ -0,0 +1,123 @@ +//! Abstractions over the proving system and parameters for ease of use. + +use bellman::groth16::{prepare_verifying_key, Parameters, PreparedVerifyingKey}; +use pairing::bls12_381::{Bls12, Fr}; +use zcash_primitives::{ + jubjub::{edwards, fs::Fs, Unknown}, + primitives::{Diversifier, PaymentAddress, ProofGenerationKey}, + redjubjub::{PublicKey, Signature}, + transaction::components::Amount +}; +use zcash_primitives::{ + merkle_tree::CommitmentTreeWitness, prover::TxProver, sapling::Node, + transaction::components::GROTH_PROOF_SIZE, JUBJUB, +}; +use zcash_proofs::sapling::SaplingProvingContext; + +/// An implementation of [`TxProver`] using Sapling Spend and Output parameters provided +/// in-memory. +pub struct InMemTxProver { + spend_params: Parameters, + spend_vk: PreparedVerifyingKey, + output_params: Parameters, +} + +impl InMemTxProver { + pub fn new(spend_params: &[u8], output_params: &[u8]) -> Self { + // Deserialize params + let spend_params = Parameters::::read(spend_params, false) + .expect("couldn't deserialize Sapling spend parameters file"); + let output_params = Parameters::::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, + diversifier: Diversifier, + rcm: Fs, + ar: Fs, + value: u64, + anchor: Fr, + witness: CommitmentTreeWitness, + ) -> Result< + ( + [u8; GROTH_PROOF_SIZE], + edwards::Point, + PublicKey, + ), + (), + > { + 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, + rcm: Fs, + value: u64, + ) -> ([u8; GROTH_PROOF_SIZE], edwards::Point) { + 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 { + ctx.binding_sig(value_balance, sighash, &JUBJUB) + } +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/tests.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/tests.rs new file mode 100644 index 0000000..fb43756 --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/tests.rs @@ -0,0 +1,2339 @@ +use std::convert::TryInto; +use std::io::{Error}; +use rand::{RngCore, rngs::OsRng}; + +use ff::{Field, PrimeField, PrimeFieldRepr}; +use pairing::bls12_381::Bls12; +use protobuf::{Message, UnknownFields, CachedSize, RepeatedField}; +use zcash_client_backend::{encoding::encode_payment_address, + proto::compact_formats::{ + CompactBlock, CompactOutput, CompactSpend, CompactTx, + } +}; +use zcash_primitives::{ + block::BlockHash, + jubjub::fs::Fs, + note_encryption::{Memo, SaplingNoteEncryption}, + primitives::{Note, PaymentAddress}, + legacy::{Script, TransparentAddress,}, + transaction::{ + TxId, Transaction, TransactionData, + components::{TxOut, TxIn, OutPoint, Amount,}, + components::amount::DEFAULT_FEE, + }, + zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, + JUBJUB, +}; + +use sha2::{Sha256, Digest}; + +use super::LightWallet; +use super::LightClientConfig; +use secp256k1::{Secp256k1, key::PublicKey, key::SecretKey}; +use crate::SaplingParams; + +fn get_sapling_params() -> Result<(Vec, Vec), Error> { + // Read Sapling Params + let mut sapling_output = vec![]; + sapling_output.extend_from_slice(SaplingParams::get("sapling-output.params").unwrap().as_ref()); + println!("Read output {}", sapling_output.len()); + + let mut sapling_spend = vec![]; + sapling_spend.extend_from_slice(SaplingParams::get("sapling-spend.params").unwrap().as_ref()); + println!("Read output {}", sapling_spend.len()); + + Ok((sapling_spend, sapling_output)) +} + +struct FakeCompactBlock { + block: CompactBlock, +} + +impl FakeCompactBlock { + fn new(height: i32, prev_hash: BlockHash) -> Self { + // Create a fake Note for the account + let mut rng = OsRng; + + let mut cb = CompactBlock::new(); + + cb.set_height(height as u64); + cb.hash.resize(32, 0); + rng.fill_bytes(&mut cb.hash); + + cb.prevHash.extend_from_slice(&prev_hash.0); + + FakeCompactBlock { block: cb } + } + + fn as_bytes(&self) -> Vec { + self.block.write_to_bytes().unwrap() + } + + fn hash(&self) -> BlockHash { + BlockHash(self.block.hash[..].try_into().unwrap()) + } + + fn tx_to_compact_tx(tx: &Transaction, index: u64) -> CompactTx { + let spends = tx.shielded_spends.iter().map(|s| { + let mut c_spend = CompactSpend::default(); + c_spend.set_nf(s.nullifier.to_vec()); + + c_spend + }).collect::>(); + + let outputs = tx.shielded_outputs.iter().map(|o| { + let mut c_out = CompactOutput::default(); + + let mut cmu_bytes = vec![]; + o.cmu.into_repr().write_le(&mut cmu_bytes).unwrap(); + + let mut epk_bytes = vec![]; + o.ephemeral_key.write(&mut epk_bytes).unwrap(); + + c_out.set_cmu(cmu_bytes); + c_out.set_epk(epk_bytes); + c_out.set_ciphertext(o.enc_ciphertext[0..52].to_vec()); + + c_out + }).collect::>(); + + CompactTx { + index, + hash: tx.txid().0.to_vec(), + fee: 0, // TODO: Get Fee + spends: RepeatedField::from_vec(spends), + outputs: RepeatedField::from_vec(outputs), + unknown_fields: UnknownFields::default(), + cached_size: CachedSize::default(), + } + } + + // Convert the transaction into a CompactTx and add it to this block + fn add_tx(&mut self, tx: &Transaction) { + let ctx = FakeCompactBlock::tx_to_compact_tx(&tx, self.block.vtx.len() as u64); + self.block.vtx.push(ctx); + } + + // Add a new tx into the block, paying the given address the amount. + // Returns the nullifier of the new note. + fn add_tx_paying(&mut self, extfvk: ExtendedFullViewingKey, value: u64) + -> (Vec, TxId) { + let to = extfvk.default_address().unwrap().1; + let value = Amount::from_u64(value).unwrap(); + + // Create a fake Note for the account + let mut rng = OsRng; + let note = Note { + g_d: to.diversifier.g_d::(&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 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.clone()); + ctx.outputs.push(cout); + + self.block.vtx.push(ctx); + (note.nf(&extfvk.fvk.vk, 0, &JUBJUB), TxId(txid[..].try_into().unwrap())) + } + + fn add_tx_spending(&mut self, + (nf, in_value): (Vec, u64), + extfvk: ExtendedFullViewingKey, + to: PaymentAddress, + value: u64) -> TxId { + let mut rng = OsRng; + + let in_value = Amount::from_u64(in_value).unwrap(); + let value = Amount::from_u64(value).unwrap(); + + // Create a fake CompactBlock containing the note + let mut cspend = CompactSpend::new(); + cspend.set_nf(nf); + let mut ctx = CompactTx::new(); + let mut txid = vec![0; 32]; + rng.fill_bytes(&mut txid); + ctx.set_hash(txid.clone()); + ctx.spends.push(cspend); + + // Create a fake Note for the payment + ctx.outputs.push({ + let note = Note { + g_d: to.diversifier.g_d::(&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, + 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(); + + let mut cout = CompactOutput::new(); + cout.set_cmu(cmu); + cout.set_epk(epk); + cout.set_ciphertext(enc_ciphertext[..52].to_vec()); + cout + }); + + // Create a fake Note for the change + ctx.outputs.push({ + let change_addr = extfvk.default_address().unwrap().1; + let note = Note { + g_d: change_addr.diversifier.g_d::(&JUBJUB).unwrap(), + pk_d: change_addr.pk_d.clone(), + value: (in_value - value).into(), + r: Fs::random(&mut rng), + }; + let encryptor = SaplingNoteEncryption::new( + extfvk.fvk.ovk, + note.clone(), + change_addr, + 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(); + + let mut cout = CompactOutput::new(); + cout.set_cmu(cmu); + cout.set_epk(epk); + cout.set_ciphertext(enc_ciphertext[..52].to_vec()); + cout + }); + + self.block.vtx.push(ctx); + + TxId(txid[..].try_into().unwrap()) + } +} + +struct FakeTransaction { + tx: Transaction, +} + +impl FakeTransaction { + // New FakeTransaction with random txid + fn new(rng: &mut R) -> Self { + let mut txid = [0u8; 32]; + rng.fill_bytes(&mut txid); + FakeTransaction::new_with_txid(TxId(txid)) + } + + fn new_with_txid(txid: TxId) -> Self { + FakeTransaction { + tx: Transaction { + txid, + data: TransactionData::new() + } + } + } + + fn get_tx(&self) -> &Transaction { + &self.tx + } + + fn add_t_output(&mut self, pk: &PublicKey, value: u64) { + let mut hash160 = ripemd160::Ripemd160::new(); + hash160.input(Sha256::digest(&pk.serialize()[..].to_vec())); + + let taddr_bytes = hash160.result(); + + self.tx.data.vout.push(TxOut { + value: Amount::from_u64(value).unwrap(), + script_pubkey: TransparentAddress::PublicKey(taddr_bytes.try_into().unwrap()).script(), + }); + } + + fn add_t_input(&mut self, txid: TxId, n: u32) { + self.tx.data.vin.push(TxIn { + prevout: OutPoint{ + hash: txid.0, + n + }, + script_sig: Script{0: vec![]}, + sequence: 0, + }); + } +} + +#[test] +fn test_z_balances() { + let wallet = LightWallet::new(None, &get_test_config(), 0).unwrap(); + + const AMOUNT1:u64 = 5; + // Address is encoded in bech32 + let address = Some(encode_payment_address(wallet.config.hrp_sapling_address(), + &wallet.extfvks.read().unwrap()[0].default_address().unwrap().1)); + + let mut cb1 = FakeCompactBlock::new(0, BlockHash([0; 32])); + cb1.add_tx_paying(wallet.extfvks.read().unwrap()[0].clone(), AMOUNT1); + + // Make sure that the intial state is empty + assert_eq!(wallet.txs.read().unwrap().len(), 0); + assert_eq!(wallet.blocks.read().unwrap().len(), 0); + assert_eq!(wallet.zbalance(None), 0); + assert_eq!(wallet.zbalance(address.clone()), 0); + + wallet.scan_block(&cb1.as_bytes()).unwrap(); + + assert_eq!(wallet.txs.read().unwrap().len(), 1); + assert_eq!(wallet.blocks.read().unwrap().len(), 1); + assert_eq!(wallet.zbalance(None), AMOUNT1); + assert_eq!(wallet.zbalance(address.clone()), AMOUNT1); + + const AMOUNT2:u64 = 10; + + // Add a second block + let mut cb2 = FakeCompactBlock::new(1, cb1.hash()); + cb2.add_tx_paying(wallet.extfvks.read().unwrap()[0].clone(), AMOUNT2); + + wallet.scan_block(&cb2.as_bytes()).unwrap(); + + assert_eq!(wallet.txs.read().unwrap().len(), 2); + assert_eq!(wallet.blocks.read().unwrap().len(), 2); + assert_eq!(wallet.zbalance(None), AMOUNT1 + AMOUNT2); + assert_eq!(wallet.zbalance(address.clone()), AMOUNT1 + AMOUNT2); +} + +#[test] +fn test_z_change_balances() { + let wallet = LightWallet::new(None, &get_test_config(), 0).unwrap(); + + // First, add an incoming transaction + const AMOUNT1:u64 = 5; + + let mut cb1 = FakeCompactBlock::new(0, BlockHash([0; 32])); + let (nf1, txid1) = cb1.add_tx_paying(wallet.extfvks.read().unwrap()[0].clone(), AMOUNT1); + + wallet.scan_block(&cb1.as_bytes()).unwrap(); + + assert_eq!(wallet.txs.read().unwrap().len(), 1); + assert_eq!(wallet.blocks.read().unwrap().len(), 1); + assert_eq!(wallet.zbalance(None), AMOUNT1); + + const AMOUNT2:u64 = 2; + + // Add a second block, spending the first note + let addr2 = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[0u8; 32])) + .default_address().unwrap().1; + let mut cb2 = FakeCompactBlock::new(1, cb1.hash()); + let txid2 = cb2.add_tx_spending((nf1, AMOUNT1), wallet.extfvks.read().unwrap()[0].clone(), addr2, AMOUNT2); + wallet.scan_block(&cb2.as_bytes()).unwrap(); + + // Now, the original note should be spent and there should be a change + assert_eq!(wallet.zbalance(None), AMOUNT1 - AMOUNT2); + + let txs = wallet.txs.read().unwrap(); + + // Old note was spent + assert_eq!(txs[&txid1].txid, txid1); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].spent.unwrap(), txid2); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].is_change, false); + + // new note is not spent + assert_eq!(txs[&txid2].txid, txid2); + assert_eq!(txs[&txid2].notes.len(), 1); + assert_eq!(txs[&txid2].notes[0].spent, None); + assert_eq!(txs[&txid2].notes[0].note.value, AMOUNT1 - AMOUNT2); + assert_eq!(txs[&txid2].notes[0].is_change, true); + assert_eq!(txs[&txid2].total_shielded_value_spent, AMOUNT1); +} + +#[test] +fn test_t_receive_spend() { + let mut rng = OsRng; + let secp = Secp256k1::new(); + + let wallet = LightWallet::new(None, &get_test_config(), 0).unwrap(); + + let pk = PublicKey::from_secret_key(&secp, &wallet.tkeys.read().unwrap()[0]); + let taddr = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); + + const AMOUNT1: u64 = 20; + + let mut tx = FakeTransaction::new(&mut rng); + tx.add_t_output(&pk, AMOUNT1); + let txid1 = tx.get_tx().txid(); + + wallet.scan_full_tx(&tx.get_tx(), 100, 0); // Pretend it is at height 100 + + { + let txs = wallet.txs.read().unwrap(); + + // Now make sure the t addr was recieved + assert_eq!(txs.len(), 1); + assert_eq!(txs[&txid1].utxos.len(), 1); + assert_eq!(txs[&txid1].utxos[0].address, taddr); + assert_eq!(txs[&txid1].utxos[0].txid, txid1); + assert_eq!(txs[&txid1].utxos[0].output_index, 0); + assert_eq!(txs[&txid1].utxos[0].value, AMOUNT1); + assert_eq!(txs[&txid1].utxos[0].height, 100); + assert_eq!(txs[&txid1].utxos[0].spent, None); + assert_eq!(txs[&txid1].utxos[0].unconfirmed_spent, None); + + assert_eq!(wallet.tbalance(None), AMOUNT1); + assert_eq!(wallet.tbalance(Some(taddr)), AMOUNT1); + } + + // Create a new Tx, spending this taddr + let mut tx = FakeTransaction::new(&mut rng); + tx.add_t_input(txid1, 0); + let txid2 = tx.get_tx().txid(); + + wallet.scan_full_tx(&tx.get_tx(), 101, 0); // Pretent it is at height 101 + + { + // Make sure the txid was spent + let txs = wallet.txs.read().unwrap(); + + // Old utxo, that should be spent now + assert_eq!(txs.len(), 2); + assert_eq!(txs[&txid1].utxos.len(), 1); + assert_eq!(txs[&txid1].utxos[0].value, AMOUNT1); + assert_eq!(txs[&txid1].utxos[0].spent, Some(txid2)); + assert_eq!(txs[&txid1].utxos[0].unconfirmed_spent, None); + + assert_eq!(txs[&txid2].block, 101); // The second TxId is at block 101 + assert_eq!(txs[&txid2].utxos.len(), 0); // The second TxId has no UTXOs + assert_eq!(txs[&txid2].total_transparent_value_spent, AMOUNT1); + + // Make sure there is no t-ZEC left + assert_eq!(wallet.tbalance(None), 0); + } +} + + +#[test] +/// This test spends and receives t addresses among non-wallet t addresses to make sure that +/// we're detecting and spending only our t addrs. +fn test_t_receive_spend_among_tadds() { + let mut rng = OsRng; + let secp = Secp256k1::new(); + + let wallet = LightWallet::new(None, &get_test_config(), 0).unwrap(); + + let pk = PublicKey::from_secret_key(&secp, &wallet.tkeys.read().unwrap()[0]); + let taddr = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); + + let non_wallet_sk = &SecretKey::from_slice(&[1u8; 32]).unwrap(); + let non_wallet_pk = PublicKey::from_secret_key(&secp, &non_wallet_sk); + + const AMOUNT1: u64 = 30; + + let mut tx = FakeTransaction::new(&mut rng); + // Add a non-wallet output + tx.add_t_output(&non_wallet_pk, 20); + tx.add_t_output(&pk, AMOUNT1); // Our wallet t output + tx.add_t_output(&non_wallet_pk, 25); + let txid1 = tx.get_tx().txid(); + + wallet.scan_full_tx(&tx.get_tx(), 100, 0); // Pretend it is at height 100 + + { + let txs = wallet.txs.read().unwrap(); + + // Now make sure the t addr was received + assert_eq!(txs.len(), 1); + assert_eq!(txs[&txid1].utxos.len(), 1); + assert_eq!(txs[&txid1].utxos[0].address, taddr); + assert_eq!(txs[&txid1].utxos[0].txid, txid1); + assert_eq!(txs[&txid1].utxos[0].output_index, 1); + assert_eq!(txs[&txid1].utxos[0].value, AMOUNT1); + assert_eq!(txs[&txid1].utxos[0].height, 100); + assert_eq!(txs[&txid1].utxos[0].spent, None); + assert_eq!(txs[&txid1].utxos[0].unconfirmed_spent, None); + + assert_eq!(wallet.tbalance(None), AMOUNT1); + assert_eq!(wallet.tbalance(Some(taddr)), AMOUNT1); + } + + // Create a new Tx, spending this taddr + let mut tx = FakeTransaction::new(&mut rng); + tx.add_t_input(txid1, 1); // Ours was at position 1 in the input tx + let txid2 = tx.get_tx().txid(); + + wallet.scan_full_tx(&tx.get_tx(), 101, 0); // Pretent it is at height 101 + + { + // Make sure the txid was spent + let txs = wallet.txs.read().unwrap(); + + // Old utxo, that should be spent now + assert_eq!(txs.len(), 2); + assert_eq!(txs[&txid1].utxos.len(), 1); + assert_eq!(txs[&txid1].utxos[0].value, AMOUNT1); + assert_eq!(txs[&txid1].utxos[0].spent, Some(txid2)); + assert_eq!(txs[&txid1].utxos[0].unconfirmed_spent, None); + + assert_eq!(txs[&txid2].block, 101); // The second TxId is at block 101 + assert_eq!(txs[&txid2].utxos.len(), 0); // The second TxId has no UTXOs + assert_eq!(txs[&txid2].total_transparent_value_spent, AMOUNT1); + + // Make sure there is no t-ZEC left + assert_eq!(wallet.tbalance(None), 0); + } +} + +#[test] +fn test_serialization() { + let secp = Secp256k1::new(); + let config = get_test_config(); + + let wallet = LightWallet::new(None, &config, 0).unwrap(); + + // First, add an incoming transaction + const AMOUNT1:u64 = 5; + + let mut cb1 = FakeCompactBlock::new(0, BlockHash([0; 32])); + let (nf1, txid1) = cb1.add_tx_paying(wallet.extfvks.read().unwrap()[0].clone(), AMOUNT1); + + wallet.scan_block(&cb1.as_bytes()).unwrap(); + + assert_eq!(wallet.txs.read().unwrap().len(), 1); + assert_eq!(wallet.blocks.read().unwrap().len(), 1); + assert_eq!(wallet.zbalance(None), AMOUNT1); + + // Add a t input at the Tx + let pk = PublicKey::from_secret_key(&secp, &wallet.tkeys.read().unwrap()[0]); + let taddr = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); + + const TAMOUNT1: u64 = 20; + + let mut tx = FakeTransaction::new_with_txid(txid1); + tx.add_t_output(&pk, TAMOUNT1); + wallet.scan_full_tx(&tx.get_tx(), 0, 0); // Height 0 + + const AMOUNT2:u64 = 2; + + // Add a second block, spending the first note + let addr2 = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[0u8; 32])) + .default_address().unwrap().1; + let mut cb2 = FakeCompactBlock::new(1, cb1.hash()); + let txid2 = cb2.add_tx_spending((nf1, AMOUNT1), wallet.extfvks.read().unwrap()[0].clone(), addr2, AMOUNT2); + wallet.scan_block(&cb2.as_bytes()).unwrap(); + + let mut tx = FakeTransaction::new_with_txid(txid2); + tx.add_t_input(txid1, 0); + wallet.scan_full_tx(&tx.get_tx(), 1, 0); // Height 1 + + // Now, the original note should be spent and there should be a change + assert_eq!(wallet.zbalance(None), AMOUNT1 - AMOUNT2 ); // The t addr amount is received + spent, so it cancels out + + // Now, serialize the wallet and read it back again + let mut serialized_data = vec![]; + wallet.write(&mut serialized_data).expect("Serialize wallet"); + let wallet2 = LightWallet::read(&serialized_data[..], &config).unwrap(); + + assert_eq!(wallet2.zbalance(None), AMOUNT1 - AMOUNT2); + + // Test the keys were serialized correctly + { + assert_eq!(wallet.seed, wallet2.seed); + + assert_eq!(wallet.extsks.read().unwrap().len(), wallet2.extsks.read().unwrap().len()); + assert_eq!(wallet.extsks.read().unwrap()[0], wallet2.extsks.read().unwrap()[0]); + assert_eq!(wallet.extfvks.read().unwrap()[0], wallet2.extfvks.read().unwrap()[0]); + assert_eq!(wallet.zaddress.read().unwrap()[0], wallet2.zaddress.read().unwrap()[0]); + + assert_eq!(wallet.tkeys.read().unwrap().len(), wallet2.tkeys.read().unwrap().len()); + assert_eq!(wallet.tkeys.read().unwrap()[0], wallet2.tkeys.read().unwrap()[0]); + } + + // Test blocks were serialized properly + { + let blks = wallet2.blocks.read().unwrap(); + + assert_eq!(blks.len(), 2); + assert_eq!(blks[0].height, 0); + assert_eq!(blks[1].height, 1); + } + + // Test txns were serialized properly. + { + let txs = wallet2.txs.read().unwrap(); + + // Old note was spent + assert_eq!(txs[&txid1].txid, txid1); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].spent.unwrap(), txid2); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].is_change, false); + + // Old UTXO was spent + assert_eq!(txs[&txid1].utxos.len(), 1); + assert_eq!(txs[&txid1].utxos[0].address, taddr); + assert_eq!(txs[&txid1].utxos[0].txid, txid1); + assert_eq!(txs[&txid1].utxos[0].output_index, 0); + assert_eq!(txs[&txid1].utxos[0].value, TAMOUNT1); + assert_eq!(txs[&txid1].utxos[0].height, 0); + assert_eq!(txs[&txid1].utxos[0].spent, Some(txid2)); + assert_eq!(txs[&txid1].utxos[0].unconfirmed_spent, None); + + // new note is not spent + assert_eq!(txs[&txid2].txid, txid2); + assert_eq!(txs[&txid2].notes.len(), 1); + assert_eq!(txs[&txid2].notes[0].spent, None); + assert_eq!(txs[&txid2].notes[0].note.value, AMOUNT1 - AMOUNT2); + assert_eq!(txs[&txid2].notes[0].is_change, true); + assert_eq!(txs[&txid2].total_shielded_value_spent, AMOUNT1); + + // The UTXO was spent in txid2 + assert_eq!(txs[&txid2].utxos.len(), 0); // The second TxId has no UTXOs + assert_eq!(txs[&txid2].total_transparent_value_spent, TAMOUNT1); + } +} + +#[test] +fn test_multi_serialization() { + let config = get_test_config(); + + let wallet = LightWallet::new(None, &config, 0).unwrap(); + + let taddr1 = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); + let taddr2 = wallet.add_taddr(); + + let (zaddr1, zpk1) = &wallet.get_z_private_keys()[0]; + let zaddr2 = wallet.add_zaddr(); + + let mut serialized_data = vec![]; + wallet.write(&mut serialized_data).expect("Serialize wallet"); + let wallet2 = LightWallet::read(&serialized_data[..], &config).unwrap(); + + assert_eq!(wallet2.tkeys.read().unwrap().len(), 2); + assert_eq!(wallet2.extsks.read().unwrap().len(), 2); + assert_eq!(wallet2.extfvks.read().unwrap().len(), 2); + assert_eq!(wallet2.zaddress.read().unwrap().len(), 2); + + assert_eq!(taddr1, wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0])); + assert_eq!(taddr2, wallet.address_from_sk(&wallet.tkeys.read().unwrap()[1])); + + let (w2_zaddr1, w2_zpk1) = &wallet.get_z_private_keys()[0]; + let (w2_zaddr2, _) = &wallet.get_z_private_keys()[1]; + assert_eq!(zaddr1, w2_zaddr1); + assert_eq!(zpk1, w2_zpk1); + assert_eq!(zaddr2, *w2_zaddr2); + +} + +fn get_test_config() -> LightClientConfig { + LightClientConfig { + server: "0.0.0.0:0".parse().unwrap(), + chain_name: "test".to_string(), + sapling_activation_height: 0, + consensus_branch_id: "000000".to_string(), + anchor_offset: 0, + no_cert_verification: false, + data_dir: None, + } +} + +// Get a test wallet already setup with a single note +fn get_test_wallet(amount: u64) -> (LightWallet, TxId, BlockHash) { + let config = get_test_config(); + + let wallet = LightWallet::new(None, &config, 0).unwrap(); + + let mut cb1 = FakeCompactBlock::new(0, BlockHash([0; 32])); + let (_, txid1) = cb1.add_tx_paying(wallet.extfvks.read().unwrap()[0].clone(), amount); + wallet.scan_block(&cb1.as_bytes()).unwrap(); + + // We have one note + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, amount); + assert_eq!(txs[&txid1].notes[0].spent, None); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + } + + assert_eq!(wallet.verified_zbalance(None), amount); + + // Create a new block so that the note is now verified to be spent + let cb2 = FakeCompactBlock::new(1, cb1.hash()); + wallet.scan_block(&cb2.as_bytes()).unwrap(); + + (wallet, txid1, cb2.hash()) +} + +#[test] +fn test_z_spend_to_z() { + const AMOUNT1: u64 = 50000; + let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT1); + + let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32])); + let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &fvk.default_address().unwrap().1); + + const AMOUNT_SENT: u64 = 20; + + let outgoing_memo = "Outgoing Memo".to_string(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + // Make sure that the balance exists + { + assert_eq!(wallet.zbalance(None), AMOUNT1); + assert_eq!(wallet.verified_zbalance(None), AMOUNT1); + } + + // Create a tx and send to address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, None)], |_| Ok(' '.to_string())).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + // Now, the note should be unconfirmed spent + { + let txs = wallet.txs.read().unwrap(); + + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].is_change, false); + assert_eq!(txs[&txid1].notes[0].spent, None); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, Some(sent_txid)); + } + + // It should also be in the mempool structure + { + let mem = wallet.mempool_txs.read().unwrap(); + + assert_eq!(mem[&sent_txid].block, 2); // block number is next block + assert! (mem[&sent_txid].datetime > 0); + assert_eq!(mem[&sent_txid].txid, sent_txid); + assert_eq!(mem[&sent_txid].outgoing_metadata.len(), 1); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].address, ext_address); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } + + { + // The wallet should deduct this from the verified balance. The zbalance still includes it + assert_eq!(wallet.zbalance(None), AMOUNT1); + assert_eq!(wallet.verified_zbalance(None), 0); + } + + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + + // Now this new Spent tx should be in, so the note should be marked confirmed spent + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid)); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + + // The sent tx should generate change + assert_eq!(txs[&sent_txid].notes.len(), 1); + assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT1 - AMOUNT_SENT - fee); + assert_eq!(wallet.zbalance(None), AMOUNT1 - AMOUNT_SENT - fee); + assert_eq!(txs[&sent_txid].notes[0].is_change, true); + assert_eq!(txs[&sent_txid].notes[0].spent, None); + assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); + } + + { + // And the mempool tx should disappear + let mem = wallet.mempool_txs.read().unwrap(); + assert!(mem.get(&sent_txid).is_none()); + } + + // Now, full scan the Tx, which should populate the Outgoing Meta data + wallet.scan_full_tx(&sent_tx, 2, 0); + + // Check Outgoing Metadata + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT1); + + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 1); + + assert_eq!(txs[&sent_txid].outgoing_metadata[0].address, ext_address); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } +} + +#[test] +fn test_self_txns_ttoz_withmemo() { + let mut rng = OsRng; + let secp = Secp256k1::new(); + + let (wallet, _txid1, block_hash) = get_test_wallet(0); + + let pk = PublicKey::from_secret_key(&secp, &wallet.tkeys.read().unwrap()[0]); + let taddr = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); + + const TAMOUNT1: u64 = 50000; + + let mut tx = FakeTransaction::new(&mut rng); + tx.add_t_output(&pk, TAMOUNT1); + let txid1 = tx.get_tx().txid(); + + wallet.scan_full_tx(&tx.get_tx(), 1, 0); + + { + let txs = wallet.txs.read().unwrap(); + + // Now make sure the t addr was recieved + assert_eq!(txs[&txid1].utxos.len(), 1); + assert_eq!(txs[&txid1].utxos[0].address, taddr); + assert_eq!(txs[&txid1].utxos[0].value, TAMOUNT1); + } + + // Create a new Tx, spending this taddr + const AMOUNT_SENT: u64 = 20; + + let outgoing_memo = "Outgoing Memo".to_string(); + let zaddr = wallet.add_zaddr(); + + let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); + let (ss, so) =get_sapling_params().unwrap(); + + // Create a tx and send to address. This should consume both the UTXO and the note + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + + // Scan the compact block and the full Tx + wallet.scan_block(&cb3.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 2, 0); + + { + let txs = wallet.txs.read().unwrap(); + + // Includes Outgoing meta data, since this is a wallet -> wallet tx with a memo + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } +} + +#[test] +fn test_self_txns_ttoz_nomemo() { + let mut rng = OsRng; + let secp = Secp256k1::new(); + + let (wallet, _txid1, block_hash) = get_test_wallet(0); + + let pk = PublicKey::from_secret_key(&secp, &wallet.tkeys.read().unwrap()[0]); + let taddr = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); + + const TAMOUNT1: u64 = 50000; + + let mut tx = FakeTransaction::new(&mut rng); + tx.add_t_output(&pk, TAMOUNT1); + let txid1 = tx.get_tx().txid(); + + wallet.scan_full_tx(&tx.get_tx(), 1, 0); + + { + let txs = wallet.txs.read().unwrap(); + + // Now make sure the t addr was recieved + assert_eq!(txs[&txid1].utxos.len(), 1); + assert_eq!(txs[&txid1].utxos[0].address, taddr); + assert_eq!(txs[&txid1].utxos[0].value, TAMOUNT1); + } + + // Create a new Tx, spending this taddr + const AMOUNT_SENT: u64 = 20; + + let zaddr = wallet.add_zaddr(); + + let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); + let (ss, so) =get_sapling_params().unwrap(); + + // Create a tx and send to address. This should consume both the UTXO and the note + let raw_tx = wallet.send_to_address(branch_id, &ss, &so, + vec![(&zaddr, AMOUNT_SENT, None)]).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + + // Scan the compact block and the full Tx + wallet.scan_block(&cb3.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 2, 0); + + { + let txs = wallet.txs.read().unwrap(); + + // No Outgoing meta data, since this is a wallet -> wallet tx without a memo + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 0); + } +} + +#[test] +fn test_self_txns_ztoz() { + const AMOUNT1: u64 = 50000; + let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT1); + + let zaddr2 = wallet.add_zaddr(); // This is acually address #6, since there are 5 initial addresses in the wallet + + const AMOUNT_SENT: u64 = 20; + + let outgoing_memo = "Outgoing Memo".to_string(); + + let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); + let (ss, so) =get_sapling_params().unwrap(); + + // Create a tx and send to address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&zaddr2, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 2, 0); + + { + let txs = wallet.txs.read().unwrap(); + + // Includes Outgoing meta data, since this is a wallet -> wallet tx with a memo + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } + + // Another self tx, this time without a memo + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&zaddr2, AMOUNT_SENT, None)], |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + let mut cb4 = FakeCompactBlock::new(3, cb3.hash()); + cb4.add_tx(&sent_tx); + wallet.scan_block(&cb4.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 3, 0); + + { + let txs = wallet.txs.read().unwrap(); + + // No Outgoing meta data, since this is a wallet -> wallet tx without a memo + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 0); + } +} + + + +#[test] +fn test_multi_z() { + const AMOUNT1: u64 = 50000; + let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT1); + + let zaddr2 = wallet.add_zaddr(); // This is acually address #6, since there are 5 initial addresses in the wallet + + const AMOUNT_SENT: u64 = 20; + + let outgoing_memo = "Outgoing Memo".to_string(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) =get_sapling_params().unwrap(); + + // Create a tx and send to address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&zaddr2, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 2, 0); + + // Because the builder will randomize notes outputted, we need to find + // which note number is the change and which is the output note (Because this tx + // had both outputs in the same Tx) + let (change_note_number, ext_note_number) = { + let txs = wallet.txs.read().unwrap(); + if txs[&sent_txid].notes[0].is_change { (0,1) } else { (1,0) } + }; + + // Now this new Spent tx should be in, so the note should be marked confirmed spent + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid)); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + + // The sent tx should generate change + the new incoming note + assert_eq!(txs[&sent_txid].notes.len(), 2); + + assert_eq!(txs[&sent_txid].notes[change_note_number].note.value, AMOUNT1 - AMOUNT_SENT - fee); + assert_eq!(txs[&sent_txid].notes[change_note_number].account, 0); + assert_eq!(txs[&sent_txid].notes[change_note_number].is_change, true); + assert_eq!(txs[&sent_txid].notes[change_note_number].spent, None); + assert_eq!(txs[&sent_txid].notes[change_note_number].unconfirmed_spent, None); + assert_eq!(LightWallet::memo_str(&txs[&sent_txid].notes[change_note_number].memo), None); + + assert_eq!(txs[&sent_txid].notes[ext_note_number].note.value, AMOUNT_SENT); + assert_eq!(txs[&sent_txid].notes[ext_note_number].account, 6); // The new addr is added after the change addresses + assert_eq!(txs[&sent_txid].notes[ext_note_number].is_change, false); + assert_eq!(txs[&sent_txid].notes[ext_note_number].spent, None); + assert_eq!(txs[&sent_txid].notes[ext_note_number].unconfirmed_spent, None); + assert_eq!(LightWallet::memo_str(&txs[&sent_txid].notes[ext_note_number].memo), Some(outgoing_memo.clone())); + + assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT1); + + // Includes Outgoing meta data, since this is a wallet -> wallet tx with a memo + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } + + // Now spend the money, which should pick notes from both addresses + let amount_all:u64 = (AMOUNT1 - AMOUNT_SENT - fee) + (AMOUNT_SENT) - fee; + let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); + + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&taddr, amount_all, None)], |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_ext_txid = sent_tx.txid(); + + let mut cb4 = FakeCompactBlock::new(3, cb3.hash()); + cb4.add_tx(&sent_tx); + wallet.scan_block(&cb4.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 3, 0); + + { + // Both notes should be spent now. + let txs = wallet.txs.read().unwrap(); + + assert_eq!(txs[&sent_txid].notes[change_note_number].is_change, true); + assert_eq!(txs[&sent_txid].notes[change_note_number].spent, Some(sent_ext_txid)); + assert_eq!(txs[&sent_txid].notes[change_note_number].unconfirmed_spent, None); + + assert_eq!(txs[&sent_txid].notes[ext_note_number].is_change, false); + assert_eq!(txs[&sent_txid].notes[ext_note_number].spent, Some(sent_ext_txid)); + assert_eq!(txs[&sent_txid].notes[ext_note_number].unconfirmed_spent, None); + + // Check outgoing metadata for the external sent tx + assert_eq!(txs[&sent_ext_txid].notes.len(), 0); // No change was generated + assert_eq!(txs[&sent_ext_txid].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_ext_txid].outgoing_metadata[0].address, taddr); + assert_eq!(txs[&sent_ext_txid].outgoing_metadata[0].value, amount_all); + } +} + +#[test] +fn test_z_spend_to_taddr() { + const AMOUNT1: u64 = 50000; + let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT1); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); + const AMOUNT_SENT: u64 = 30; + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&taddr, AMOUNT_SENT, None)], |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + // Now, the note should be unconfirmed spent + { + let txs = wallet.txs.read().unwrap(); + + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].is_change, false); + assert_eq!(txs[&txid1].notes[0].spent, None); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, Some(sent_txid)); + } + + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + + // Now this new Spent tx should be in, so the note should be marked confirmed spent + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid)); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + + // The sent tx should generate change + assert_eq!(txs[&sent_txid].notes.len(), 1); + assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT1 - AMOUNT_SENT - fee); + assert_eq!(txs[&sent_txid].notes[0].is_change, true); + assert_eq!(txs[&sent_txid].notes[0].spent, None); + assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); + } + + // Now, full scan the Tx, which should populate the Outgoing Meta data + wallet.scan_full_tx(&sent_tx, 2, 0); + + // Check Outgoing Metadata for t address + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].address, taddr); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT1); + } + + // Create a new Tx, but this time with a memo. + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&taddr, AMOUNT_SENT, Some("T address memo".to_string()))], |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid2 = sent_tx.txid(); + + // There should be a mempool Tx, but the memo should be dropped, because it was sent to a + // t address + { + let txs = wallet.mempool_txs.read().unwrap(); + + assert_eq!(txs[&sent_txid2].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_txid2].outgoing_metadata[0].address, taddr); + assert_eq!(txs[&sent_txid2].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(LightWallet::memo_str(&Some(txs[&sent_txid2].outgoing_metadata[0].memo.clone())), None); + } + + // Now add the block + let mut cb4 = FakeCompactBlock::new(3, cb3.hash()); + cb4.add_tx(&sent_tx); + wallet.scan_block(&cb4.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 3, 0); + + // Check Outgoing Metadata for t address, but once again there should be no memo + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&sent_txid2].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_txid2].outgoing_metadata[0].address, taddr); + assert_eq!(txs[&sent_txid2].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(LightWallet::memo_str(&Some(txs[&sent_txid2].outgoing_metadata[0].memo.clone())), None); + } +} + +#[test] +fn test_t_spend_to_z() { + let mut rng = OsRng; + let secp = Secp256k1::new(); + + const AMOUNT_Z: u64 = 50000; + const AMOUNT_T: u64 = 40000; + let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT_Z); + + let pk = PublicKey::from_secret_key(&secp, &wallet.tkeys.read().unwrap()[0]); + let taddr = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); + + let mut tx = FakeTransaction::new(&mut rng); + tx.add_t_output(&pk, AMOUNT_T); + let txid_t = tx.get_tx().txid(); + + wallet.scan_full_tx(&tx.get_tx(), 1, 0); // Pretend it is at height 1 + + { + let txs = wallet.txs.read().unwrap(); + + // Now make sure the t addr was recieved + assert_eq!(txs[&txid_t].utxos.len(), 1); + assert_eq!(txs[&txid_t].utxos[0].address, taddr); + assert_eq!(txs[&txid_t].utxos[0].spent, None); + assert_eq!(txs[&txid_t].utxos[0].unconfirmed_spent, None); + + assert_eq!(wallet.tbalance(None), AMOUNT_T); + } + + + let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32])); + let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &fvk.default_address().unwrap().1); + const AMOUNT_SENT: u64 = 20; + + let outgoing_memo = "Outgoing Memo".to_string(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) =get_sapling_params().unwrap(); + + // Create a tx and send to address. This should consume both the UTXO and the note + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + // Verify the sent_tx for sanity + { + // The tx has 1 note spent, 1 utxo spent, and (1 note out, 1 note change) + assert_eq!(sent_tx.shielded_spends.len(), 1); + assert_eq!(sent_tx.vin.len(), 1); + assert_eq!(sent_tx.shielded_outputs.len(), 2); + } + + // Now, the note and utxo should be unconfirmed spent + { + let txs = wallet.txs.read().unwrap(); + + // UTXO + assert_eq!(txs[&txid_t].utxos.len(), 1); + assert_eq!(txs[&txid_t].utxos[0].address, taddr); + assert_eq!(txs[&txid_t].utxos[0].spent, None); + assert_eq!(txs[&txid_t].utxos[0].unconfirmed_spent, Some(sent_txid)); + + // Note + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT_Z); + assert_eq!(txs[&txid1].notes[0].spent, None); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, Some(sent_txid)); + } + + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + + // Scan the compact block and the full Tx + wallet.scan_block(&cb3.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 2, 0); + + // Now this new Spent tx should be in, so the note should be marked confirmed spent + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT_Z); + assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid)); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + + // The UTXO should also be spent + assert_eq!(txs[&txid_t].utxos[0].address, taddr); + assert_eq!(txs[&txid_t].utxos[0].spent, Some(sent_txid)); + assert_eq!(txs[&txid_t].utxos[0].unconfirmed_spent, None); + + // The sent tx should generate change + assert_eq!(txs[&sent_txid].notes.len(), 1); + assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT_Z + AMOUNT_T - AMOUNT_SENT - fee); + assert_eq!(txs[&sent_txid].notes[0].is_change, true); + assert_eq!(txs[&sent_txid].notes[0].spent, None); + assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); + } +} + +#[test] +fn test_z_incoming_memo() { + const AMOUNT1: u64 = 50000; + let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT1); + + let my_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &wallet.extfvks.read().unwrap()[0].default_address().unwrap().1); + + let memo = "Incoming Memo".to_string(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + // Create a tx and send to address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + // Add it to a block + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + + // And scan the Full Tx to get the memo + wallet.scan_full_tx(&sent_tx, 2, 0); + + { + let txs = wallet.txs.read().unwrap(); + + assert_eq!(txs[&sent_txid].notes.len(), 1); + + assert_eq!(txs[&sent_txid].notes[0].extfvk, wallet.extfvks.read().unwrap()[0]); + assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT1 - fee); + assert_eq!(LightWallet::note_address(wallet.config.hrp_sapling_address(), &txs[&sent_txid].notes[0]), Some(my_address)); + assert_eq!(LightWallet::memo_str(&txs[&sent_txid].notes[0].memo), Some(memo)); + } +} + +#[test] +fn test_add_new_zt_hd_after_incoming() { + // When an address recieves funds, a new, unused address should automatically get added + const AMOUNT1: u64 = 50000; + let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT1); + + // Get the last address + let my_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &wallet.extfvks.read().unwrap().last().unwrap().default_address().unwrap().1); + + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + assert_eq!(wallet.zaddress.read().unwrap().len(), 6); // Starts with 1+5 addresses + + // Create a tx and send to the last address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + + // Add it to a block + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + + // NOw, 5 new addresses should be created + assert_eq!(wallet.zaddress.read().unwrap().len(), 6+5); + + let mut rng = OsRng; + let secp = Secp256k1::new(); + // Send a fake transaction to the last taddr + let pk = PublicKey::from_secret_key(&secp, &wallet.tkeys.read().unwrap().last().unwrap()); + + // Start with 1 taddr + assert_eq!(wallet.taddresses.read().unwrap().len(), 1); + + // Send a Tx to the last address + let mut tx = FakeTransaction::new(&mut rng); + tx.add_t_output(&pk, AMOUNT1); + wallet.scan_full_tx(&tx.get_tx(), 3, 0); + + // Now, 5 new addresses should be created. + assert_eq!(wallet.taddresses.read().unwrap().len(), 1+5); +} + +#[test] +fn test_z_to_t_withinwallet() { + const AMOUNT: u64 = 500000; + const AMOUNT_SENT: u64 = 20000; + let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT); + + let taddr = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); + + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + // Create a tx and send to address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + // Add it to a block + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + + // And scan the Full Tx to get the memo + wallet.scan_full_tx(&sent_tx, 2, 0); + + { + let txs = wallet.txs.read().unwrap(); + + // We have the original note + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT); + + // We have the spent tx + assert_eq!(txs[&sent_txid].notes.len(), 1); + assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT - AMOUNT_SENT - fee); + assert_eq!(txs[&sent_txid].notes[0].is_change, true); + assert_eq!(txs[&sent_txid].notes[0].spent, None); + assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); + + // Since we sent the Tx to ourself, there should be no outgoing + // metadata + assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT); + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 0); + + + // We have the taddr utxo in the same Tx + assert_eq!(txs[&sent_txid].utxos.len(), 1); + assert_eq!(txs[&sent_txid].utxos[0].address, taddr); + assert_eq!(txs[&sent_txid].utxos[0].value, AMOUNT_SENT); + assert_eq!(txs[&sent_txid].utxos[0].spent, None); + assert_eq!(txs[&sent_txid].utxos[0].unconfirmed_spent, None); + + } +} + +#[test] +fn test_multi_t() { + const AMOUNT: u64 = 5000000; + const AMOUNT_SENT1: u64 = 20000; + const AMOUNT_SENT2: u64 = 10000; + + let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT); + + // Add a new taddr + let taddr2 = wallet.add_taddr(); + + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + // Create a Tx and send to the second t address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid1 = sent_tx.txid(); + + // Add it to a block + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 2, 0); + + // Check that the send to the second taddr worked + { + let txs = wallet.txs.read().unwrap(); + + // We have the original note + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT); + + // We have the spent tx + assert_eq!(txs[&sent_txid1].notes.len(), 1); + assert_eq!(txs[&sent_txid1].notes[0].note.value, AMOUNT - AMOUNT_SENT1 - fee); + assert_eq!(txs[&sent_txid1].notes[0].is_change, true); + assert_eq!(txs[&sent_txid1].notes[0].spent, None); + assert_eq!(txs[&sent_txid1].notes[0].unconfirmed_spent, None); + + // Since we sent the Tx to ourself, there should be no outgoing + // metadata + assert_eq!(txs[&sent_txid1].total_shielded_value_spent, AMOUNT); + assert_eq!(txs[&sent_txid1].outgoing_metadata.len(), 0); + + + // We have the taddr utxo in the same Tx + assert_eq!(txs[&sent_txid1].utxos.len(), 1); + assert_eq!(txs[&sent_txid1].utxos[0].address, taddr2); + assert_eq!(txs[&sent_txid1].utxos[0].value, AMOUNT_SENT1); + assert_eq!(txs[&sent_txid1].utxos[0].spent, None); + assert_eq!(txs[&sent_txid1].utxos[0].unconfirmed_spent, None); + } + + // Send some money to the 3rd t addr + let taddr3 = wallet.add_taddr(); + + // Create a Tx and send to the second t address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid2 = sent_tx.txid(); + + // Add it to a block + let mut cb4 = FakeCompactBlock::new(3, cb3.hash()); + cb4.add_tx(&sent_tx); + wallet.scan_block(&cb4.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 3, 0); + + // Quickly check we have it + { + let txs = wallet.txs.read().unwrap(); + + // We have the taddr utxo in the same Tx + assert_eq!(txs[&sent_txid2].utxos.len(), 1); + assert_eq!(txs[&sent_txid2].utxos[0].address, taddr3); + assert_eq!(txs[&sent_txid2].utxos[0].value, AMOUNT_SENT2); + + // Old UTXO was spent here + assert_eq!(txs[&sent_txid1].utxos.len(), 1); + assert_eq!(txs[&sent_txid1].utxos[0].value, AMOUNT_SENT1); + assert_eq!(txs[&sent_txid1].utxos[0].address, taddr2); + assert_eq!(txs[&sent_txid1].utxos[0].spent, Some(sent_txid2)); + assert_eq!(txs[&sent_txid1].utxos[0].unconfirmed_spent, None); + } + + // Now, spend to an external z address, which will select all the utxos + let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32])); + let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &fvk.default_address().unwrap().1); + + const AMOUNT_SENT_EXT: u64 = 45; + let outgoing_memo = "Outgoing Memo".to_string(); + + // Create a tx and send to address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid3 = sent_tx.txid(); + + let mut cb5 = FakeCompactBlock::new(4, cb4.hash()); + cb5.add_tx(&sent_tx); + wallet.scan_block(&cb5.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 4, 0); + + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&sent_txid3].outgoing_metadata.len(), 1); + + assert_eq!(txs[&sent_txid3].outgoing_metadata[0].address, ext_address); + assert_eq!(txs[&sent_txid3].outgoing_metadata[0].value, AMOUNT_SENT_EXT); + assert_eq!(txs[&sent_txid3].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + + // Test to see that the UTXOs were spent. + + // UTXO2 + assert_eq!(txs[&sent_txid2].utxos[0].value, AMOUNT_SENT2); + assert_eq!(txs[&sent_txid2].utxos[0].address, taddr3); + assert_eq!(txs[&sent_txid2].utxos[0].spent, Some(sent_txid3)); + assert_eq!(txs[&sent_txid2].utxos[0].unconfirmed_spent, None); + } + +} + +#[test] +fn test_multi_spends() { + const AMOUNT1: u64 = 50000; + let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT1); + + let zaddr2 = wallet.add_zaddr(); // Address number 6 + const ZAMOUNT2:u64 = 30; + let outgoing_memo2 = "Outgoing Memo2".to_string(); + + let zaddr3 = wallet.add_zaddr(); // Address number 7 + const ZAMOUNT3:u64 = 40; + let outgoing_memo3 = "Outgoing Memo3".to_string(); + + let taddr2 = wallet.add_taddr(); + const TAMOUNT2:u64 = 50; + let taddr3 = wallet.add_taddr(); + const TAMOUNT3:u64 = 60; + + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + let tos = vec![ (zaddr2.as_str(), ZAMOUNT2, Some(outgoing_memo2.clone())), + (zaddr3.as_str(), ZAMOUNT3, Some(outgoing_memo3.clone())), + (taddr2.as_str(), TAMOUNT2, None), + (taddr3.as_str(), TAMOUNT3, None) ]; + + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, tos, |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 2, 0); + + // Make sure all the outputs are there! + { + let txs = wallet.txs.read().unwrap(); + + // The single note was spent + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid)); + + // The outputs are all sent to the wallet, so they should + // correspond to notes & utxos. + // 2 notes + 1 change + assert_eq!(txs[&sent_txid].notes.len(), 3); + + // Find the change note + let change_note = txs[&sent_txid].notes.iter().find(|n| n.is_change).unwrap(); + assert_eq!(change_note.note.value, AMOUNT1 - (ZAMOUNT2+ZAMOUNT3+TAMOUNT2+TAMOUNT3+fee)); + assert_eq!(change_note.spent, None); + assert_eq!(change_note.unconfirmed_spent, None); + + // Find zaddr2 + let zaddr2_note = txs[&sent_txid].notes.iter().find(|n| n.note.value == ZAMOUNT2).unwrap(); + assert_eq!(zaddr2_note.account, 6); + assert_eq!(zaddr2_note.is_change, false); + assert_eq!(zaddr2_note.spent, None); + assert_eq!(zaddr2_note.unconfirmed_spent, None); + assert_eq!(LightWallet::memo_str(&zaddr2_note.memo), Some(outgoing_memo2)); + + // Find zaddr3 + let zaddr3_note = txs[&sent_txid].notes.iter().find(|n| n.note.value == ZAMOUNT3).unwrap(); + assert_eq!(zaddr3_note.account, 7); + assert_eq!(zaddr3_note.is_change, false); + assert_eq!(zaddr3_note.spent, None); + assert_eq!(zaddr3_note.unconfirmed_spent, None); + assert_eq!(LightWallet::memo_str(&zaddr3_note.memo), Some(outgoing_memo3)); + + // Find taddr2 + let utxo2 = txs[&sent_txid].utxos.iter().find(|u| u.value == TAMOUNT2).unwrap(); + assert_eq!(utxo2.address, taddr2); + assert_eq!(utxo2.txid, sent_txid); + assert_eq!(utxo2.spent, None); + assert_eq!(utxo2.unconfirmed_spent, None); + + // Find taddr3 + let utxo3 = txs[&sent_txid].utxos.iter().find(|u| u.value == TAMOUNT3).unwrap(); + assert_eq!(utxo3.address, taddr3); + assert_eq!(utxo3.txid, sent_txid); + assert_eq!(utxo3.spent, None); + assert_eq!(utxo3.unconfirmed_spent, None); + } + + // Now send an outgoing tx to one ext taddr and one ext zaddr + let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32])); + let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &fvk.default_address().unwrap().1); + let ext_memo = "External memo".to_string(); + let ext_taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); + + const EXT_ZADDR_AMOUNT: u64 = 3000; + let ext_taddr_amount = AMOUNT1 - fee - EXT_ZADDR_AMOUNT - fee; // Spend everything + println!("taddr amount {}", ext_taddr_amount); + + let tos = vec![ (ext_address.as_str(), EXT_ZADDR_AMOUNT, Some(ext_memo.clone())), + (ext_taddr.as_str(), ext_taddr_amount, None)]; + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, tos, |_| Ok(' '.to_string())).unwrap(); + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid2 = sent_tx.txid(); + + let mut cb4 = FakeCompactBlock::new(3, cb3.hash()); + cb4.add_tx(&sent_tx); + wallet.scan_block(&cb4.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 3, 0); + + // Make sure all the outputs are there! + { + let txs = wallet.txs.read().unwrap(); + + // All notes were spent + assert_eq!(txs[&sent_txid].notes[0].spent, Some(sent_txid2)); + assert_eq!(txs[&sent_txid].notes[1].spent, Some(sent_txid2)); + assert_eq!(txs[&sent_txid].notes[2].spent, Some(sent_txid2)); + + // All utxos were spent + assert_eq!(txs[&sent_txid].utxos[0].spent, Some(sent_txid2)); + assert_eq!(txs[&sent_txid].utxos[1].spent, Some(sent_txid2)); + + // The new tx has no change + assert_eq!(txs[&sent_txid2].notes.len(), 0); + assert_eq!(txs[&sent_txid2].utxos.len(), 0); + + // Test the outgoing metadata + // Find the znote + let zoutgoing = txs[&sent_txid2].outgoing_metadata.iter().find(|o| o.address == ext_address).unwrap(); + assert_eq!(zoutgoing.value, EXT_ZADDR_AMOUNT); + assert_eq!(LightWallet::memo_str(&Some(zoutgoing.memo.clone())), Some(ext_memo)); + + // Find the taddr + let toutgoing = txs[&sent_txid2].outgoing_metadata.iter().find(|o| o.address == ext_taddr).unwrap(); + assert_eq!(toutgoing.value, ext_taddr_amount); + assert_eq!(LightWallet::memo_str(&Some(toutgoing.memo.clone())), None); + } +} + +#[test] +fn test_bad_send() { + // Test all the ways in which a send should fail + const AMOUNT1: u64 = 50000; + let _fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let (wallet, _txid1, _block_hash) = get_test_wallet(AMOUNT1); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + let ext_taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); + + // Bad address + let raw_tx = wallet.send_to_address(branch_id, &ss, &so, + vec![(&"badaddress", 10, None)], |_| Ok(' '.to_string())); + assert!(raw_tx.err().unwrap().contains("Invalid recipient address")); + + // Insufficient funds + let raw_tx = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_taddr, AMOUNT1 + 10, None)], |_| Ok(' '.to_string())); + assert!(raw_tx.err().unwrap().contains("Insufficient verified funds")); + + // No addresses + let raw_tx = wallet.send_to_address(branch_id, &ss, &so, vec![], |_| Ok(' '.to_string())); + assert!(raw_tx.err().unwrap().contains("at least one")); + +} + +#[test] +fn test_duplicate_outputs() { + // Test all the ways in which a send should fail + const AMOUNT1: u64 = 50000; + let _fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let (wallet, _txid1, _block_hash) = get_test_wallet(AMOUNT1); + + let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + let ext_taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); + + // Duplicated addresses with memos are fine too + let raw_tx = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_taddr, 100, Some("First memo".to_string())), + (&ext_taddr, 0, Some("Second memo".to_string())), + (&ext_taddr, 0, Some("Third memo".to_string()))], |_| Ok(' '.to_string())); + assert!(raw_tx.is_ok()); +} + +#[test] +#[should_panic] +fn test_bad_params() { + let (wallet, _, _) = get_test_wallet(100000); + let ext_taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + // Bad params + let _ = wallet.send_to_address(branch_id, &[], &[], + vec![(&ext_taddr, 10, None)], |_| Ok(' '.to_string())); +} + +/// Test helper to add blocks +fn add_blocks(wallet: &LightWallet, start: i32, num: i32, mut prev_hash: BlockHash) -> Result{ + // Add it to a block + let mut new_blk = FakeCompactBlock::new(start, prev_hash); + for i in 0..num { + new_blk = FakeCompactBlock::new(start+i, prev_hash); + prev_hash = new_blk.hash(); + match wallet.scan_block(&new_blk.as_bytes()) { + Ok(_) => {}, // continue + Err(e) => return Err(e) + }; + } + + Ok(new_blk.hash()) +} + +#[test] +fn test_z_mempool_expiry() { + const AMOUNT1: u64 = 50000; + let (wallet, _, block_hash) = get_test_wallet(AMOUNT1); + + let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32])); + let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &fvk.default_address().unwrap().1); + + const AMOUNT_SENT: u64 = 20; + + let outgoing_memo = "Outgoing Memo".to_string(); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + // Create a tx and send to address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + // It should also be in the mempool structure + { + let mem = wallet.mempool_txs.read().unwrap(); + + assert_eq!(mem[&sent_txid].block, 2); // block number is next block + assert! (mem[&sent_txid].datetime > 0); + assert_eq!(mem[&sent_txid].txid, sent_txid); + assert_eq!(mem[&sent_txid].outgoing_metadata.len(), 1); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].address, ext_address); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } + + // Don't mine the Tx, but just add several blocks + add_blocks(&wallet, 2, 21, block_hash).unwrap(); + + // After 21 blocks, it should disappear (expiry is 20 blocks) since it was not mined + { + let mem = wallet.mempool_txs.read().unwrap(); + + assert!(mem.get(&sent_txid).is_none()); + } +} + +#[test] +fn test_block_limit() { + const AMOUNT: u64 = 500000; + let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT); + + let prev_hash = add_blocks(&wallet, 2, 1, block_hash).unwrap(); + assert_eq!(wallet.blocks.read().unwrap().len(), 3); + + let prev_hash = add_blocks(&wallet, 3, 47, prev_hash).unwrap(); + assert_eq!(wallet.blocks.read().unwrap().len(), 50); + + let prev_hash = add_blocks(&wallet, 50, 51, prev_hash).unwrap(); + assert_eq!(wallet.blocks.read().unwrap().len(), 101); + + // Subsequent blocks should start to trim + let prev_hash = add_blocks(&wallet, 101, 1, prev_hash).unwrap(); + assert_eq!(wallet.blocks.read().unwrap().len(), 101); + + // Add lots + let _ = add_blocks(&wallet, 102, 10, prev_hash).unwrap(); + assert_eq!(wallet.blocks.read().unwrap().len(), 101); + + // Now clear the blocks + wallet.clear_blocks(); + assert_eq!(wallet.blocks.read().unwrap().len(), 0); + + let prev_hash = add_blocks(&wallet, 0, 1, BlockHash([0;32])).unwrap(); + assert_eq!(wallet.blocks.read().unwrap().len(), 1); + + let _ = add_blocks(&wallet, 1, 10, prev_hash).unwrap(); + assert_eq!(wallet.blocks.read().unwrap().len(), 11); +} + +#[test] +fn test_rollback() { + const AMOUNT: u64 = 500000; + + let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT); + + add_blocks(&wallet, 2, 5, block_hash).unwrap(); + + // Make sure the note exists with the witnesses + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes[0].witnesses.len(), 7); + } + + // Invalidate 2 blocks + assert_eq!(wallet.last_scanned_height(), 6); + assert_eq!(wallet.invalidate_block(5), 2); + + // THe witnesses should be rolledback + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes[0].witnesses.len(), 5); + } + + let blk3_hash; + let blk4_hash; + { + let blks = wallet.blocks.read().unwrap(); + blk3_hash = blks[3].hash.clone(); + blk4_hash = blks[4].hash.clone(); + } + + // This should result in an exception, because the "prevhash" is wrong + assert!(add_blocks(&wallet, 5, 2, blk3_hash).is_err(), + "Shouldn't be able to add because of invalid prev hash"); + + // Add with the proper prev hash + add_blocks(&wallet, 5, 2, blk4_hash).unwrap(); + + let blk6_hash; + { + let blks = wallet.blocks.read().unwrap(); + blk6_hash = blks[6].hash.clone(); + } + + // Now do a Tx + let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + // Create a tx and send to address + const AMOUNT_SENT: u64 = 30000; + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&taddr, AMOUNT_SENT, None)], |_| Ok(' '.to_string())).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + let mut cb3 = FakeCompactBlock::new(7, blk6_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 7, 0); + + // Make sure the Tx is in. + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT); + assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid)); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + + // The sent tx should generate change + assert_eq!(txs[&sent_txid].notes.len(), 1); + assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT - AMOUNT_SENT - fee); + assert_eq!(txs[&sent_txid].notes[0].is_change, true); + assert_eq!(txs[&sent_txid].notes[0].spent, None); + assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); + assert_eq!(txs[&sent_txid].notes[0].witnesses.len(), 1); + } + + // Invalidate 3 blocks + assert_eq!(wallet.last_scanned_height(), 7); + assert_eq!(wallet.invalidate_block(5), 3); + assert_eq!(wallet.last_scanned_height(), 4); + + // Make sure the orig Tx is there, but new Tx has disappeared + { + let txs = wallet.txs.read().unwrap(); + + // Orig Tx is still there, since this is in block 0 + // But now the spent tx is gone + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT); + assert_eq!(txs[&txid1].notes[0].spent, None); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + + // The sent tx is missing + assert!(txs.get(&sent_txid).is_none()); + } +} + +#[test] +fn test_t_derivation() { + let lc = LightClientConfig { + server: "0.0.0.0:0".parse().unwrap(), + chain_name: "main".to_string(), + sapling_activation_height: 0, + consensus_branch_id: "000000".to_string(), + anchor_offset: 1, + no_cert_verification: false, + data_dir: None, + }; + + let seed_phrase = Some("chimney better bulb horror rebuild whisper improve intact letter giraffe brave rib appear bulk aim burst snap salt hill sad merge tennis phrase raise".to_string()); + + let wallet = LightWallet::new(seed_phrase.clone(), &lc, 0).unwrap(); + + // Test the addresses against https://iancoleman.io/bip39/ + let (taddr, pk) = &wallet.get_t_secret_keys()[0]; + assert_eq!(taddr, "RXBxhYDg8vSsHVmAGadniQKh3NvzAtzjRe"); + assert_eq!(pk, "UvGDnY7bpsyz9GnRPvMRQTMKHPyCK5k6c2FBiGQcgRSe9xVNuGGs"); + + // Test a couple more + wallet.add_taddr(); + let (taddr, pk) = &wallet.get_t_secret_keys()[1]; + assert_eq!(taddr, "RKDGjDoFHf9BfTFuL27voFdqdV7LhWw9rG"); + assert_eq!(pk, "UqGi6D8rFfdoEtbqhjz1utu1hKLmagY5TBVHWau54pput9HRfYbG"); + + let (zaddr, sk) = &wallet.get_z_private_keys()[0]; + assert_eq!(zaddr, "zs1wp96063hjs496d28e05uz5gavg7mwshhsgglchtan57el88uwa5jfdgk4nd68kda62vgwucfe6z"); + assert_eq!(sk, "secret-extended-key-main1qvkk0dn2qqqqpqrk6fl6c6fzzrmkhlj59d2c6kkz37hmal6d4dm69ne75xf0exuvnyk6qrjgp6crvdtaehkda2edg0llv488u25vjh5jtldnp53nrphqeexel57a0dn4t2kkcr6uj6y832yg8wsx3wx6t6rk470dynzdx3cp37xdwl9mpe59vj6yqh67x09vea9khzk5wdqkt65c6x9qkuht7nxyetthu0pr7jrwpthzq2ncgm0dvczadqxuhhk5ekua5v5zzw2kydcudu965"); + + assert_eq!(seed_phrase, Some(wallet.get_seed_phrase())); +} + +#[test] +fn test_lock_unlock() { + const AMOUNT: u64 = 500000; + + let (mut wallet, _, _) = get_test_wallet(AMOUNT); + let config = wallet.config.clone(); + + // Add some addresses + let zaddr0 = encode_payment_address(config.hrp_sapling_address(), + &wallet.extfvks.read().unwrap()[0].default_address().unwrap().1); + let zaddr1 = wallet.add_zaddr(); // This is actually address at index 6 + let zaddr2 = wallet.add_zaddr(); // This is actually address at index 7 + + let taddr0 = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]); + let taddr1 = wallet.add_taddr(); + let taddr2 = wallet.add_taddr(); + + let seed = wallet.seed; + + // Trying to lock a wallet that's not encrpyted is an error + assert!(wallet.lock().is_err()); + + // Encrypt the wallet + wallet.encrypt("somepassword".to_string()).unwrap(); + + // Encrypting an already encrypted wallet should fail + assert!(wallet.encrypt("somepassword".to_string()).is_err()); + + // Serialize a locked wallet + let mut serialized_data = vec![]; + wallet.write(&mut serialized_data).expect("Serialize wallet"); + + // Should fail when there's a wrong password + assert!(wallet.unlock("differentpassword".to_string()).is_err()); + + // Properly unlock + wallet.unlock("somepassword".to_string()).unwrap(); + + assert_eq!(seed, wallet.seed); + { + let extsks = wallet.extsks.read().unwrap(); + let tkeys = wallet.tkeys.read().unwrap(); + assert_eq!(extsks.len(), 8); // 3 zaddrs + 1 original + 4 extra HD addreses + assert_eq!(tkeys.len(), 3); + + assert_eq!(zaddr0, encode_payment_address(config.hrp_sapling_address(), + &ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1)); + assert_eq!(zaddr1, encode_payment_address(config.hrp_sapling_address(), + &ExtendedFullViewingKey::from(&extsks[6]).default_address().unwrap().1)); + assert_eq!(zaddr2, encode_payment_address(config.hrp_sapling_address(), + &ExtendedFullViewingKey::from(&extsks[7]).default_address().unwrap().1)); + + assert_eq!(taddr0, wallet.address_from_sk(&tkeys[0])); + assert_eq!(taddr1, wallet.address_from_sk(&tkeys[1])); + assert_eq!(taddr2, wallet.address_from_sk(&tkeys[2])); + } + + // Unlocking an already unlocked wallet should fail + assert!(wallet.unlock("somepassword".to_string()).is_err()); + + // Trying to serialize a encrypted but unlocked wallet should fail + assert!(wallet.write(&mut vec![]).is_err()); + + // ...but if we lock it again, it should serialize + wallet.lock().unwrap(); + wallet.write(&mut vec![]).expect("Serialize wallet"); + + // Locking an already locked wallet is an error + assert!(wallet.lock().is_err()); + + // Try from a deserialized, locked wallet + let mut wallet2 = LightWallet::read(&serialized_data[..], &config).unwrap(); + wallet2.unlock("somepassword".to_string()).unwrap(); + + assert_eq!(seed, wallet2.seed); + { + let extsks = wallet2.extsks.read().unwrap(); + let tkeys = wallet2.tkeys.read().unwrap(); + assert_eq!(extsks.len(), 8); + assert_eq!(tkeys.len(), 3); + + assert_eq!(zaddr0, encode_payment_address(wallet2.config.hrp_sapling_address(), + &ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1)); + assert_eq!(zaddr1, encode_payment_address(wallet2.config.hrp_sapling_address(), + &ExtendedFullViewingKey::from(&extsks[6]).default_address().unwrap().1)); + assert_eq!(zaddr2, encode_payment_address(wallet2.config.hrp_sapling_address(), + &ExtendedFullViewingKey::from(&extsks[7]).default_address().unwrap().1)); + + assert_eq!(taddr0, wallet2.address_from_sk(&tkeys[0])); + assert_eq!(taddr1, wallet2.address_from_sk(&tkeys[1])); + assert_eq!(taddr2, wallet2.address_from_sk(&tkeys[2])); + } + + // Remove encryption from a unlocked wallet should succeed + wallet2.remove_encryption("somepassword".to_string()).unwrap(); + assert_eq!(seed, wallet2.seed); + + // Now encrypt with a different password + wallet2.encrypt("newpassword".to_string()).unwrap(); + assert_eq!([0u8; 32], wallet2.seed); // Seed is cleared out + + // Locking should fail because it is already locked + assert!(wallet2.lock().is_err()); + + // The old password shouldn't work + assert!(wallet2.remove_encryption("somepassword".to_string()).is_err()); + + // Remove encryption with the right password + wallet2.remove_encryption("newpassword".to_string()).unwrap(); + assert_eq!(seed, wallet2.seed); + + // Unlocking a wallet without encryption is an error + assert!(wallet2.remove_encryption("newpassword".to_string()).is_err()); + // Can't lock/unlock a wallet that's not encrypted + assert!(wallet2.lock().is_err()); + assert!(wallet2.unlock("newpassword".to_string()).is_err()); +} + +#[test] +#[should_panic] +fn test_invalid_bip39_t() { + // Passing a 32-byte seed to bip32 should fail. + let config = get_test_config(); + LightWallet::get_taddr_from_bip39seed(&config, &[0u8; 32], 0); +} + +#[test] +#[should_panic] +fn test_invalid_bip39_z() { + // Passing a 32-byte seed to bip32 should fail. + let config = get_test_config(); + LightWallet::get_zaddr_from_bip39seed(&config, &[0u8; 32], 0); +} + +#[test] +fn test_invalid_scan_blocks() { + const AMOUNT: u64 = 500000; + let (wallet, _txid1, block_hash) = get_test_wallet(AMOUNT); + + let prev_hash = add_blocks(&wallet, 2, 1, block_hash).unwrap(); + assert_eq!(wallet.blocks.read().unwrap().len(), 3); + + // Block fails to scan for bad encoding + assert_eq!(wallet.scan_block(&[0; 32]), Err(-1)); + + // Block is invalid height + let new_blk = FakeCompactBlock::new(4, prev_hash); + assert_eq!(wallet.scan_block(&new_blk.as_bytes()), Err(2)); + + // Block is right height, but invalid prev height (for reorgs) + let new_blk = FakeCompactBlock::new(2, BlockHash([0; 32])); + assert_eq!(wallet.scan_block(&new_blk.as_bytes()), Err(2)); + + // Block is right height, but invalid prev height (for reorgs) + let new_blk = FakeCompactBlock::new(3, BlockHash([0; 32])); + assert_eq!(wallet.scan_block(&new_blk.as_bytes()), Err(2)); + + // Then the rest add properly + let _ = add_blocks(&wallet, 3, 2, prev_hash).unwrap(); + assert_eq!(wallet.blocks.read().unwrap().len(), 5); +} + +#[test] +fn test_encrypted_zreceive() { + const AMOUNT1: u64 = 50000; + let password: String = "password".to_string(); + + let (mut wallet, txid1, block_hash) = get_test_wallet(AMOUNT1); + + let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32])); + let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &fvk.default_address().unwrap().1); + + const AMOUNT_SENT: u64 = 20; + + let outgoing_memo = "Outgoing Memo".to_string(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + // Create a tx and send to address + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))], |_| Ok(' '.to_string())).unwrap(); + + // Now that we have the transaction, we'll encrypt the wallet + wallet.encrypt(password.clone()).unwrap(); + + // Scan the tx and make sure it gets added + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + + // Now, full scan the Tx, which should populate the Outgoing Meta data + wallet.scan_full_tx(&sent_tx, 2, 0); + + // Now this new Spent tx should be in, so the note should be marked confirmed spent + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid)); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + + // The sent tx should generate change + assert_eq!(txs[&sent_txid].notes.len(), 1); + assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT1 - AMOUNT_SENT - fee); + assert_eq!(txs[&sent_txid].notes[0].is_change, true); + assert_eq!(txs[&sent_txid].notes[0].spent, None); + assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); + + // Outgoing Metadata + assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT1); + + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 1); + + assert_eq!(txs[&sent_txid].outgoing_metadata[0].address, ext_address); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } + + // Trying to spend from a locked wallet is an error + assert!(wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, None)], |_| Ok(' '.to_string())).is_err()); + + // unlock the wallet so we can spend to the second z address + wallet.unlock(password.clone()).unwrap(); + + // Second z address + let zaddr2 = wallet.add_zaddr(); // This is address number 6 + const ZAMOUNT2:u64 = 30; + let outgoing_memo2 = "Outgoing Memo2".to_string(); + + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&zaddr2, ZAMOUNT2, Some(outgoing_memo2.clone()))], |_| Ok(' '.to_string())).unwrap(); + + // Now lock the wallet again + wallet.lock().unwrap(); + + let sent_tx2 = Transaction::read(&raw_tx[..]).unwrap(); + let txid2 = sent_tx2.txid(); + + let mut cb4 = FakeCompactBlock::new(3, cb3.hash()); + cb4.add_tx(&sent_tx2); + wallet.scan_block(&cb4.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx2, 3, 0); + + { + let txs = wallet.txs.read().unwrap(); + let prev_change_value = AMOUNT1 - AMOUNT_SENT - fee; + + // Change note from prev transaction is spent + assert_eq!(txs[&sent_txid].notes[0].note.value, prev_change_value); + assert_eq!(txs[&sent_txid].notes[0].is_change, true); + assert_eq!(txs[&sent_txid].notes[0].spent, Some(txid2)); + + // New change note. So find it. + let change_note = txs[&txid2].notes.iter().find(|n| n.is_change).unwrap(); + + // New incoming tx is present + assert_eq!(change_note.note.value, prev_change_value - (ZAMOUNT2+fee)); + assert_eq!(change_note.spent, None); + assert_eq!(change_note.unconfirmed_spent, None); + + // Find zaddr2 + let zaddr2_note = txs[&txid2].notes.iter().find(|n| n.note.value == ZAMOUNT2).unwrap(); + assert_eq!(zaddr2_note.account, 6); + assert_eq!(zaddr2_note.is_change, false); + assert_eq!(zaddr2_note.spent, None); + assert_eq!(zaddr2_note.unconfirmed_spent, None); + assert_eq!(LightWallet::memo_str(&zaddr2_note.memo), Some(outgoing_memo2)); + } +} + + +#[test] +fn test_encrypted_treceive() { + const AMOUNT1: u64 = 50000; + let password: String = "password".to_string(); + let (mut wallet, txid1, block_hash) = get_test_wallet(AMOUNT1); + + let branch_id = u32::from_str_radix("76b809bb", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); + const AMOUNT_SENT: u64 = 30; + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); + + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&taddr, AMOUNT_SENT, None)], |_| Ok(' '.to_string())).unwrap(); + + // Now that we have the transaction, we'll encrypt the wallet + wallet.encrypt(password.clone()).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + let mut cb3 = FakeCompactBlock::new(2, block_hash); + cb3.add_tx(&sent_tx); + wallet.scan_block(&cb3.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx, 2, 0); + + // Now this new Spent tx should be in, so the note should be marked confirmed spent + { + let txs = wallet.txs.read().unwrap(); + assert_eq!(txs[&txid1].notes.len(), 1); + assert_eq!(txs[&txid1].notes[0].note.value, AMOUNT1); + assert_eq!(txs[&txid1].notes[0].spent, Some(sent_txid)); + assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, None); + + // The sent tx should generate change + assert_eq!(txs[&sent_txid].notes.len(), 1); + assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT1 - AMOUNT_SENT - fee); + assert_eq!(txs[&sent_txid].notes[0].is_change, true); + assert_eq!(txs[&sent_txid].notes[0].spent, None); + assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); + + // Outgoing Metadata + assert_eq!(txs[&sent_txid].outgoing_metadata.len(), 1); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].address, taddr); + assert_eq!(txs[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(txs[&sent_txid].total_shielded_value_spent, AMOUNT1); + } + + // Trying to spend from a locked wallet is an error + assert!(wallet.send_to_address(branch_id, &ss, &so, + vec![(&taddr, AMOUNT_SENT, None)], |_| Ok(' '.to_string())).is_err()); + + // unlock the wallet so we can spend to the second z address + wallet.unlock(password.clone()).unwrap(); + + // Second z address + let taddr2 = wallet.add_taddr(); + const TAMOUNT2:u64 = 50; + + let (_, raw_tx) = wallet.send_to_address(branch_id, &ss, &so, + vec![(&taddr2, TAMOUNT2, None)], |_| Ok(' '.to_string())).unwrap(); + + // Now lock the wallet again + wallet.lock().unwrap(); + + let sent_tx2 = Transaction::read(&raw_tx[..]).unwrap(); + let txid2 = sent_tx2.txid(); + + let mut cb4 = FakeCompactBlock::new(3, cb3.hash()); + cb4.add_tx(&sent_tx2); + wallet.scan_block(&cb4.as_bytes()).unwrap(); + wallet.scan_full_tx(&sent_tx2, 3, 0); + + { + let txs = wallet.txs.read().unwrap(); + let prev_change_value = AMOUNT1 - AMOUNT_SENT - fee; + + // Change note from prev transaction is spent + assert_eq!(txs[&sent_txid].notes[0].note.value, prev_change_value); + assert_eq!(txs[&sent_txid].notes[0].is_change, true); + assert_eq!(txs[&sent_txid].notes[0].spent, Some(txid2)); + + // New change note. So find it. + let change_note = txs[&txid2].notes.iter().find(|n| n.is_change).unwrap(); + + // New incoming tx is present + assert_eq!(change_note.note.value, prev_change_value - (TAMOUNT2+fee)); + assert_eq!(change_note.spent, None); + assert_eq!(change_note.unconfirmed_spent, None); + + // Find taddr2 + let utxo2 = txs[&txid2].utxos.iter().find(|u| u.value == TAMOUNT2).unwrap(); + assert_eq!(txs[&txid2].utxos.len(), 1); + assert_eq!(utxo2.address, taddr2); + assert_eq!(utxo2.txid, txid2); + assert_eq!(utxo2.spent, None); + assert_eq!(utxo2.unconfirmed_spent, None); + } +} diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/utils.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/utils.rs new file mode 100644 index 0000000..5b16b8f --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/utils.rs @@ -0,0 +1,21 @@ +use std::io::{self, Read, Write}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + +pub fn read_string(mut reader: R) -> io::Result { + // Strings are written as len + bytes + let str_len = reader.read_u64::()?; + 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(mut writer: W, s: &String) -> io::Result<()> { + // Strings are written as len + utf8 + writer.write_u64::(s.as_bytes().len() as u64)?; + writer.write_all(s.as_bytes()) +} \ No newline at end of file diff --git a/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/walletzkey.rs b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/walletzkey.rs new file mode 100644 index 0000000..554fb1c --- /dev/null +++ b/third_party/silentdragonxlite/silentdragonxlite-cli/lib/src/lightwallet/walletzkey.rs @@ -0,0 +1,585 @@ +use std::io::{self, Read, Write}; +use std::io::{Error, ErrorKind}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use pairing::bls12_381::{Bls12}; + +use sodiumoxide::crypto::secretbox; + +use zcash_primitives::{ + serialize::{Vector, Optional}, + zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, + primitives::{PaymentAddress}, +}; + +use crate::lightclient::{LightClientConfig}; +use crate::lightwallet::{LightWallet, utils}; + +#[derive(PartialEq, Debug, Clone)] +pub enum WalletTKeyType { + HdKey = 0, + ImportedKey = 1, +} + + +// A struct that holds z-address private keys or view keys +#[derive(Clone, Debug, PartialEq)] +pub struct WalletTKey { + pub(super) keytype: WalletTKeyType, + locked: bool, + pub(super) address: String, + pub(super) tkey: Option, + + // If locked, the encrypted key is here + enc_key: Option>, + nonce: Option>, +} + +impl WalletTKey { + pub fn new_hdkey(key: secp256k1::SecretKey, address: String) -> Self { + WalletTKey { + keytype: WalletTKeyType::HdKey, + locked: false, + address, + tkey: Some(key), + + enc_key: None, + nonce: None, + } + } + + pub fn import_hdkey(key: secp256k1::SecretKey, address: String) -> Self { + WalletTKey { + keytype: WalletTKeyType::ImportedKey, + locked: false, + address, + tkey: Some(key), + + enc_key: None, + nonce: None, + } + } + + fn serialized_version() -> u8 { + return 1; + } + + pub fn read(mut inp: R) -> io::Result { + let version = inp.read_u8()?; + assert!(version <= Self::serialized_version()); + + let keytype: WalletTKeyType = match inp.read_u32::()? { + 0 => Ok(WalletTKeyType::HdKey), + 1 => Ok(WalletTKeyType::ImportedKey), + n => Err(io::Error::new(ErrorKind::InvalidInput, format!("Unknown tkey type {}", n))) + }?; + + let locked = inp.read_u8()? > 0; + + let address = utils::read_string(&mut inp)?; + let tkey = Optional::read(&mut inp, |r| { + let mut tpk_bytes = [0u8; 32]; + r.read_exact(&mut tpk_bytes)?; + secp256k1::SecretKey::from_slice(&tpk_bytes).map_err(|e| io::Error::new(ErrorKind::InvalidData, e)) + })?; + + let enc_key = Optional::read(&mut inp, |r| + Vector::read(r, |r| r.read_u8()))?; + let nonce = Optional::read(&mut inp, |r| + Vector::read(r, |r| r.read_u8()))?; + + Ok(WalletTKey { + keytype, + locked, + address, + tkey, + enc_key, + nonce, + }) + } + + pub fn write(&self, mut out: W) -> io::Result<()> { + out.write_u8(Self::serialized_version())?; + + out.write_u32::(self.keytype.clone() as u32)?; + + out.write_u8(self.locked as u8)?; + + utils::write_string(&mut out, &self.address)?; + Optional::write(&mut out, &self.tkey, |w, pk| + w.write_all(&pk[..]) + )?; + + // Write enc_key + Optional::write(&mut out, &self.enc_key, |o, v| + Vector::write(o, v, |o,n| o.write_u8(*n)))?; + + // Write nonce + Optional::write(&mut out, &self.nonce, |o, v| + Vector::write(o, v, |o,n| o.write_u8(*n))) + } + + + pub fn lock(&mut self) -> io::Result<()> { + // For keys, encrypt the key into enckey + // assert that we have the encrypted key. + if self.enc_key.is_none() { + return Err(Error::new(ErrorKind::InvalidInput, "Can't lock when t-addr private key is not encrypted")); + } + self.tkey = None; + self.locked = true; + + + Ok(()) + } + + pub fn unlock(&mut self, key: &secretbox::Key) -> io::Result<()> { + // For imported keys, we need to decrypt from the encrypted key + let nonce = secretbox::Nonce::from_slice(&self.nonce.as_ref().unwrap()).unwrap(); + let sk_bytes = match secretbox::open(&self.enc_key.as_ref().unwrap(), &nonce, &key) { + Ok(s) => s, + Err(_) => {return Err(io::Error::new(ErrorKind::InvalidData, "Decryption failed. Is your password correct?"));} + }; + + self.tkey = Some(secp256k1::SecretKey::from_slice(&sk_bytes[..]).map_err(|e| + io::Error::new(ErrorKind::InvalidData, format!("{}", e)) + )?); + + self.locked = false; + Ok(()) + } + + pub fn encrypt(&mut self, key: &secretbox::Key) -> io::Result<()> { + // For keys, encrypt the key into enckey + let nonce = secretbox::gen_nonce(); + + let sk_bytes = &self.tkey.unwrap()[..]; + + self.enc_key = Some(secretbox::seal(&sk_bytes, &nonce, &key)); + self.nonce = Some(nonce.as_ref().to_vec()); + + self.tkey = None; + + // Also lock after encrypt + self.lock() + } + + pub fn remove_encryption(&mut self) -> io::Result<()> { + if self.locked { + return Err(Error::new(ErrorKind::InvalidInput, "Can't remove encryption while locked")); + } + + self.enc_key = None; + self.nonce = None; + Ok(()) + } +} + +#[derive(PartialEq, Debug, Clone)] +pub enum WalletZKeyType { + HdKey = 0, + ImportedSpendingKey = 1, + ImportedViewKey = 2 +} + +// A struct that holds z-address private keys or view keys +#[derive(Clone, Debug, PartialEq)] +pub struct WalletZKey { + pub(super) keytype: WalletZKeyType, + locked: bool, + pub(super) extsk: Option, + pub(super) extfvk: ExtendedFullViewingKey, + pub(super) zaddress: PaymentAddress, + + // If this is a HD key, what is the key number + pub(super) hdkey_num: Option, + + // If locked, the encrypted private key is stored here + enc_key: Option>, + nonce: Option>, +} + +impl WalletZKey { + pub fn new_hdkey(hdkey_num: u32, extsk: ExtendedSpendingKey) -> Self { + let extfvk = ExtendedFullViewingKey::from(&extsk); + let zaddress = extfvk.default_address().unwrap().1; + + WalletZKey { + keytype: WalletZKeyType::HdKey, + locked: false, + extsk: Some(extsk), + extfvk, + zaddress, + hdkey_num: Some(hdkey_num), + enc_key: None, + nonce: None, + } + } + + pub fn new_locked_hdkey(hdkey_num: u32, extfvk: ExtendedFullViewingKey) -> Self { + let zaddress = extfvk.default_address().unwrap().1; + + WalletZKey { + keytype: WalletZKeyType::HdKey, + locked: true, + extsk: None, + extfvk, + zaddress, + hdkey_num: Some(hdkey_num), + enc_key: None, + nonce: None + } + } + + pub fn new_imported_sk(extsk: ExtendedSpendingKey) -> Self { + let extfvk = ExtendedFullViewingKey::from(&extsk); + let zaddress = extfvk.default_address().unwrap().1; + + WalletZKey { + keytype: WalletZKeyType::ImportedSpendingKey, + locked: false, + extsk: Some(extsk), + extfvk, + zaddress, + hdkey_num: None, + enc_key: None, + nonce: None, + } + } + + pub fn new_imported_viewkey(extfvk: ExtendedFullViewingKey) -> Self { + let zaddress = extfvk.default_address().unwrap().1; + + WalletZKey { + keytype: WalletZKeyType::ImportedViewKey, + locked: false, + extsk: None, + extfvk, + zaddress, + hdkey_num: None, + enc_key: None, + nonce: None, + } + } + + fn serialized_version() -> u8 { + return 1; + } + + pub fn have_spending_key(&self) -> bool { + self.extsk.is_some() || self.enc_key.is_some() || self.hdkey_num.is_some() + } + + pub fn read(mut inp: R) -> io::Result { + let version = inp.read_u8()?; + assert!(version <= Self::serialized_version()); + + let keytype: WalletZKeyType = match inp.read_u32::()? { + 0 => Ok(WalletZKeyType::HdKey), + 1 => Ok(WalletZKeyType::ImportedSpendingKey), + 2 => Ok(WalletZKeyType::ImportedViewKey), + n => Err(io::Error::new(ErrorKind::InvalidInput, format!("Unknown zkey type {}", n))) + }?; + + let locked = inp.read_u8()? > 0; + + let extsk = Optional::read(&mut inp, |r| ExtendedSpendingKey::read(r))?; + let extfvk = ExtendedFullViewingKey::read(&mut inp)?; + let zaddress = extfvk.default_address().unwrap().1; + + let hdkey_num = Optional::read(&mut inp, |r| r.read_u32::())?; + + let enc_key = Optional::read(&mut inp, |r| + Vector::read(r, |r| r.read_u8()))?; + let nonce = Optional::read(&mut inp, |r| + Vector::read(r, |r| r.read_u8()))?; + + Ok(WalletZKey { + keytype, + locked, + extsk, + extfvk, + zaddress, + hdkey_num, + enc_key, + nonce, + }) + } + + pub fn write(&self, mut out: W) -> io::Result<()> { + out.write_u8(Self::serialized_version())?; + + out.write_u32::(self.keytype.clone() as u32)?; + + out.write_u8(self.locked as u8)?; + + Optional::write(&mut out, &self.extsk, |w, sk| ExtendedSpendingKey::write(sk, w))?; + + ExtendedFullViewingKey::write(&self.extfvk, &mut out)?; + + Optional::write(&mut out, &self.hdkey_num, |o, n| o.write_u32::(*n))?; + + // Write enc_key + Optional::write(&mut out, &self.enc_key, |o, v| + Vector::write(o, v, |o,n| o.write_u8(*n)))?; + + // Write nonce + Optional::write(&mut out, &self.nonce, |o, v| + Vector::write(o, v, |o,n| o.write_u8(*n))) + } + + pub fn lock(&mut self) -> io::Result<()> { + match self.keytype { + WalletZKeyType::HdKey => { + // For HD keys, just empty out the keys, since they will be reconstructed from the hdkey_num + self.extsk = None; + self.locked = true; + }, + WalletZKeyType::ImportedSpendingKey => { + // For imported keys, encrypt the key into enckey + // assert that we have the encrypted key. + if self.enc_key.is_none() { + return Err(Error::new(ErrorKind::InvalidInput, "Can't lock when imported key is not encrypted")); + } + self.extsk = None; + self.locked = true; + }, + WalletZKeyType::ImportedViewKey => { + // For viewing keys, there is nothing to lock, so just return true + self.locked = true; + } + } + + Ok(()) + } + + pub fn unlock(&mut self, config: &LightClientConfig, bip39_seed: &[u8], key: &secretbox::Key) -> io::Result<()> { + match self.keytype { + WalletZKeyType::HdKey => { + let (extsk, extfvk, address) = + LightWallet::get_zaddr_from_bip39seed(&config, &bip39_seed, self.hdkey_num.unwrap()); + + if address != self.zaddress { + return Err(io::Error::new(ErrorKind::InvalidData, + format!("zaddress mismatch at {}. {:?} vs {:?}", self.hdkey_num.unwrap(), address, self.zaddress))); + } + + if extfvk != self.extfvk { + return Err(io::Error::new(ErrorKind::InvalidData, + format!("fvk mismatch at {}. {:?} vs {:?}", self.hdkey_num.unwrap(), extfvk, self.extfvk))); + } + + self.extsk = Some(extsk); + }, + WalletZKeyType::ImportedSpendingKey => { + // For imported keys, we need to decrypt from the encrypted key + let nonce = secretbox::Nonce::from_slice(&self.nonce.as_ref().unwrap()).unwrap(); + let extsk_bytes = match secretbox::open(&self.enc_key.as_ref().unwrap(), &nonce, &key) { + Ok(s) => s, + Err(_) => {return Err(io::Error::new(ErrorKind::InvalidData, "Decryption failed. Is your password correct?"));} + }; + + self.extsk = Some(ExtendedSpendingKey::read(&extsk_bytes[..])?); + }, + WalletZKeyType::ImportedViewKey => { + // Viewing key unlocking is basically a no op + } + }; + + self.locked = false; + Ok(()) + } + + pub fn encrypt(&mut self, key: &secretbox::Key) -> io::Result<()> { + match self.keytype { + WalletZKeyType::HdKey => { + // For HD keys, we don't need to do anything, since the hdnum has all the info to recreate this key + }, + WalletZKeyType::ImportedSpendingKey => { + // For imported keys, encrypt the key into enckey + let nonce = secretbox::gen_nonce(); + + let mut sk_bytes = vec![]; + self.extsk.as_ref().unwrap().write(&mut sk_bytes)?; + + self.enc_key = Some(secretbox::seal(&sk_bytes, &nonce, &key)); + self.nonce = Some(nonce.as_ref().to_vec()); + }, + WalletZKeyType::ImportedViewKey => { + // Encrypting a viewing key is a no-op + } + } + + // Also lock after encrypt + self.lock() + } + + pub fn remove_encryption(&mut self) -> io::Result<()> { + if self.locked { + return Err(Error::new(ErrorKind::InvalidInput, "Can't remove encryption while locked")); + } + + match self.keytype { + WalletZKeyType::HdKey => { + // For HD keys, we don't need to do anything, since the hdnum has all the info to recreate this key + Ok(()) + }, + WalletZKeyType::ImportedSpendingKey => { + self.enc_key = None; + self.nonce = None; + Ok(()) + }, + WalletZKeyType::ImportedViewKey => { + // Removing encryption is a no-op for viewing keys + Ok(()) + } + } + } +} + +#[cfg(test)] +pub mod tests { + use zcash_client_backend::{ + encoding::{encode_payment_address, decode_extended_spending_key, decode_extended_full_viewing_key} + }; + use sodiumoxide::crypto::secretbox; + + use crate::lightclient::LightClientConfig; + use super::WalletZKey; + + fn get_config() -> LightClientConfig { + LightClientConfig { + server: "0.0.0.0:0".parse().unwrap(), + chain_name: "main".to_string(), + sapling_activation_height: 0, + consensus_branch_id: "000000".to_string(), + anchor_offset: 0, + data_dir: None, + } + } + + #[test] + fn test_serialize() { + let config = get_config(); + + // Priv Key's address is "zs1fxgluwznkzm52ux7jkf4st5znwzqay8zyz4cydnyegt2rh9uhr9458z0nk62fdsssx0cqhy6lyv" + let privkey = "secret-extended-key-main1q0p44m9zqqqqpqyxfvy5w2vq6ahvxyrwsk2w4h2zleun4cft4llmnsjlv77lhuuknv6x9jgu5g2clf3xq0wz9axxxq8klvv462r5pa32gjuj5uhxnvps6wsrdg6xll05unwks8qpgp4psmvy5e428uxaggn4l29duk82k3sv3njktaaj453fdmfmj2fup8rls4egqxqtj2p5a3yt4070khn99vzxj5ag5qjngc4v2kq0ctl9q2rpc2phu4p3e26egu9w88mchjf83sqgh3cev"; + + let esk = decode_extended_spending_key(config.hrp_sapling_private_key(), privkey).unwrap().unwrap(); + let wzk = WalletZKey::new_imported_sk(esk); + assert_eq!(encode_payment_address(config.hrp_sapling_address(), &wzk.zaddress), "zs1fxgluwznkzm52ux7jkf4st5znwzqay8zyz4cydnyegt2rh9uhr9458z0nk62fdsssx0cqhy6lyv".to_string()); + + let mut v: Vec = vec![]; + // Serialize + wzk.write(&mut v).unwrap(); + // Read it right back + let wzk2 = WalletZKey::read(&v[..]).unwrap(); + + { + assert_eq!(wzk, wzk2); + assert_eq!(wzk.extsk, wzk2.extsk); + assert_eq!(wzk.extfvk, wzk2.extfvk); + assert_eq!(wzk.zaddress, wzk2.zaddress); + } + } + + #[test] + fn test_encrypt_decrypt_sk() { + let config = get_config(); + + // Priv Key's address is "zs1fxgluwznkzm52ux7jkf4st5znwzqay8zyz4cydnyegt2rh9uhr9458z0nk62fdsssx0cqhy6lyv" + let privkey = "secret-extended-key-main1q0p44m9zqqqqpqyxfvy5w2vq6ahvxyrwsk2w4h2zleun4cft4llmnsjlv77lhuuknv6x9jgu5g2clf3xq0wz9axxxq8klvv462r5pa32gjuj5uhxnvps6wsrdg6xll05unwks8qpgp4psmvy5e428uxaggn4l29duk82k3sv3njktaaj453fdmfmj2fup8rls4egqxqtj2p5a3yt4070khn99vzxj5ag5qjngc4v2kq0ctl9q2rpc2phu4p3e26egu9w88mchjf83sqgh3cev"; + + let esk = decode_extended_spending_key(config.hrp_sapling_private_key(), privkey).unwrap().unwrap(); + let mut wzk = WalletZKey::new_imported_sk(esk); + assert_eq!(encode_payment_address(config.hrp_sapling_address(), &wzk.zaddress), "zs1fxgluwznkzm52ux7jkf4st5znwzqay8zyz4cydnyegt2rh9uhr9458z0nk62fdsssx0cqhy6lyv".to_string()); + + // Can't lock without encryption + assert!(wzk.lock().is_err()); + + // Encryption key + let key = secretbox::Key::from_slice(&[0; 32]).unwrap(); + + // Encrypt, but save the extsk first + let orig_extsk = wzk.extsk.clone().unwrap(); + wzk.encrypt(&key).unwrap(); + { + assert!(wzk.enc_key.is_some()); + assert!(wzk.nonce.is_some()); + } + + // Now lock + assert!(wzk.lock().is_ok()); + { + assert!(wzk.extsk.is_none()); + assert_eq!(wzk.locked, true); + assert_eq!(wzk.zaddress, wzk.extfvk.default_address().unwrap().1); + } + + // Can't remove encryption without unlocking + assert!(wzk.remove_encryption().is_err()); + + // Unlock + assert!(wzk.unlock(&config, &[], &key).is_ok()); + { + assert_eq!(wzk.extsk, Some(orig_extsk)); + } + + // Remove encryption + assert!(wzk.remove_encryption().is_ok()); + { + assert_eq!(wzk.enc_key, None); + assert_eq!(wzk.nonce, None); + } + } + + + #[test] + fn test_encrypt_decrypt_vk() { + let config = get_config(); + + // Priv Key's address is "zs1va5902apnzlhdu0pw9r9q7ca8s4vnsrp2alr6xndt69jnepn2v2qrj9vg3wfcnjyks5pg65g9dc" + let viewkey = "zxviews1qvvx7cqdqyqqpqqte7292el2875kw2fgvnkmlmrufyszlcy8xgstwarnumqye3tr3d9rr3ydjm9zl9464majh4pa3ejkfy779dm38sfnkar67et7ykxkk0z9rfsmf9jclfj2k85xt2exkg4pu5xqyzyxzlqa6x3p9wrd7pwdq2uvyg0sal6zenqgfepsdp8shestvkzxuhm846r2h3m4jvsrpmxl8pfczxq87886k0wdasppffjnd2eh47nlmkdvrk6rgyyl0ekh3ycqtvvje"; + + let extfvk = decode_extended_full_viewing_key(config.hrp_sapling_viewing_key(), viewkey).unwrap().unwrap(); + let mut wzk = WalletZKey::new_imported_viewkey(extfvk); + + assert_eq!(encode_payment_address(config.hrp_sapling_address(), &wzk.zaddress), "zs1va5902apnzlhdu0pw9r9q7ca8s4vnsrp2alr6xndt69jnepn2v2qrj9vg3wfcnjyks5pg65g9dc".to_string()); + + // Encryption key + let key = secretbox::Key::from_slice(&[0; 32]).unwrap(); + + // Encrypt + wzk.encrypt(&key).unwrap(); + { + assert!(wzk.enc_key.is_none()); + assert!(wzk.nonce.is_none()); + } + + // Now lock + assert!(wzk.lock().is_ok()); + { + assert!(wzk.extsk.is_none()); + assert_eq!(wzk.locked, true); + assert_eq!(wzk.zaddress, wzk.extfvk.default_address().unwrap().1); + } + + // Can't remove encryption without unlocking + assert!(wzk.remove_encryption().is_err()); + + // Unlock + assert!(wzk.unlock(&config, &[], &key).is_ok()); + { + assert_eq!(wzk.extsk, None); + } + + // Remove encryption + assert!(wzk.remove_encryption().is_ok()); + { + assert_eq!(wzk.enc_key, None); + assert_eq!(wzk.nonce, None); + } + } + + +} \ No newline at end of file