From a6a80dc224bb3f8e7a08253226495f2fa1207992 Mon Sep 17 00:00:00 2001 From: DanS Date: Sat, 21 Mar 2026 03:55:18 -0500 Subject: [PATCH] Crash fixes, reorg handling, and sync performance improvements - Fix FFI panics with catch_unwind and safe CString construction - Handle poisoned mutex/RwLock after prior panics instead of crashing - Fix empty block list panics in clear_blocks and invalidate_block - Reuse Tokio runtime across block fetch batches to reduce overhead - Add fetch_blocks_with_runtime for caller-managed runtime lifecycle - Update branding, dependencies, and checkpoints for DragonX --- .github/workflows/rust.yml | 176 +- .github/workflows/rust_BACKUP_5752.yml | 208 +- .github/workflows/rust_BASE_5752.yml | 160 +- .github/workflows/rust_LOCAL_5752.yml | 186 +- .github/workflows/rust_REMOTE_5752.yml | 176 +- .gitignore | 14 +- Cargo.lock | 5766 ++++++++++++------------ Cargo.toml | 14 +- Makefile | 46 +- README.md | 200 +- cli/Cargo.toml | 36 +- cli/src/lib.rs | 524 +-- cli/src/main.rs | 186 +- cli/src/version.rs | 2 +- docker/Dockerfile | 86 +- lib/Cargo.toml | 160 +- lib/build.rs | 26 +- lib/proto/compact_formats.proto | 130 +- lib/proto/service.proto | 338 +- lib/src/commands.rs | 1950 ++++---- lib/src/grpcconnector.rs | 729 ++- lib/src/lib.rs | 50 +- lib/src/lightclient.rs | 3600 +++++++-------- lib/src/lightclient/checkpoints.rs | 1232 ++--- lib/src/lightwallet.rs | 5081 ++++++++++----------- lib/src/lightwallet/address.rs | 92 +- lib/src/lightwallet/data.rs | 1116 ++--- lib/src/lightwallet/extended_key.rs | 252 +- lib/src/lightwallet/prover.rs | 246 +- lib/src/lightwallet/tests.rs | 4678 +++++++++---------- lib/src/lightwallet/utils.rs | 40 +- lib/src/lightwallet/walletzkey.rs | 1168 ++--- mkrelease.sh | 222 +- util/build.sh | 100 +- 34 files changed, 14509 insertions(+), 14481 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fd6c66f..ee7c039 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,88 +1,88 @@ -name: Rust - -on: [push, pull_request] - -jobs: - build: - name: Build on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-16.04, windows-latest, macOS-latest] - - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.38.0 - override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --verbose --release --all - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all - - name: Upload ubuntu/macos - uses: actions/upload-artifact@v1 - if: contains(matrix.os, 'macos') || contains(matrix.os, 'ubuntu') - with: - name: ${{ matrix.os }}-silentdragonlite-cli - path: target/release/silentdragonlite-cli - - name: Upload windows - uses: actions/upload-artifact@v1 - if: contains(matrix.os, 'windows') - with: - name: ${{ matrix.os }}-silentdragonlite-cli.exe - path: target/release/silentdragonlite-cli.exe - - - linux_arm7: - name: Linux ARMv7 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: armv7-unknown-linux-gnueabihf - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target armv7-unknown-linux-gnueabihf - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_armv7-silentdragonlite-cli - path: target/armv7-unknown-linux-gnueabihf/release/silentdragonlite-cli - - linux_aarch64: - name: Linux ARM64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: aarch64-unknown-linux-gnu - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target aarch64-unknown-linux-gnu - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_aarch64-silentdragonlite-cli - path: target/aarch64-unknown-linux-gnu/release/silentdragonlite-cli - +name: Rust + +on: [push, pull_request] + +jobs: + build: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-16.04, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.38.0 + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --release --all + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all + - name: Upload ubuntu/macos + uses: actions/upload-artifact@v1 + if: contains(matrix.os, 'macos') || contains(matrix.os, 'ubuntu') + with: + name: ${{ matrix.os }}-silentdragonlite-cli + path: target/release/silentdragonlite-cli + - name: Upload windows + uses: actions/upload-artifact@v1 + if: contains(matrix.os, 'windows') + with: + name: ${{ matrix.os }}-silentdragonlite-cli.exe + path: target/release/silentdragonlite-cli.exe + + + linux_arm7: + name: Linux ARMv7 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: armv7-unknown-linux-gnueabihf + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target armv7-unknown-linux-gnueabihf + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_armv7-silentdragonlite-cli + path: target/armv7-unknown-linux-gnueabihf/release/silentdragonlite-cli + + linux_aarch64: + name: Linux ARM64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: aarch64-unknown-linux-gnu + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target aarch64-unknown-linux-gnu + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_aarch64-silentdragonlite-cli + path: target/aarch64-unknown-linux-gnu/release/silentdragonlite-cli + diff --git a/.github/workflows/rust_BACKUP_5752.yml b/.github/workflows/rust_BACKUP_5752.yml index 796878c..3b669e6 100644 --- a/.github/workflows/rust_BACKUP_5752.yml +++ b/.github/workflows/rust_BACKUP_5752.yml @@ -1,104 +1,104 @@ -name: Rust - -on: [push, pull_request] - -jobs: - build: - name: Build on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.38.0 - override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --verbose --release --all - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all - - name: Upload ubuntu/macos - uses: actions/upload-artifact@v1 - if: contains(matrix.os, 'macos') || contains(matrix.os, 'ubuntu') - with: -<<<<<<< HEAD -<<<<<<< HEAD - name: ${{ matrix.os }}-silentdragonlite-cli - path: target/release/silentdragonlite-cli -======= - name: ${{ matrix.os }}-silentdragonlite-cli - path: target/release/silentdragonlite-cli -======= - name: ${{ matrix.os }}-zecwallet-cli - path: target/release/zecwallet-cli ->>>>>>> 959755d705b18d13a8dbdf0502c47818fe7a4e94 - - name: Upload windows - uses: actions/upload-artifact@v1 - if: contains(matrix.os, 'windows') - with: -<<<<<<< HEAD - name: ${{ matrix.os }}-silentdragonlite-cli.exe - path: target/release/silentdragonlite-cli.exe - ->>>>>>> 959755d705b18d13a8dbdf0502c47818fe7a4e94 -======= - name: ${{ matrix.os }}-zecwallet-cli.exe - path: target/release/zecwallet-cli.exe - ->>>>>>> 959755d705b18d13a8dbdf0502c47818fe7a4e94 - - linux_arm7: - name: Linux ARMv7 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: armv7-unknown-linux-gnueabihf - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target armv7-unknown-linux-gnueabihf - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_armv7-silentdragonlite-cli - path: target/armv7-unknown-linux-gnueabihf/release/silentdragonlite-cli - - linux_aarch64: - name: Linux ARM64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: aarch64-unknown-linux-gnu - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target aarch64-unknown-linux-gnu - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_aarch64-silentdragonlite-cli - path: target/aarch64-unknown-linux-gnu/release/silentdragonlite-cli - +name: Rust + +on: [push, pull_request] + +jobs: + build: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.38.0 + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --release --all + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all + - name: Upload ubuntu/macos + uses: actions/upload-artifact@v1 + if: contains(matrix.os, 'macos') || contains(matrix.os, 'ubuntu') + with: +<<<<<<< HEAD +<<<<<<< HEAD + name: ${{ matrix.os }}-silentdragonlite-cli + path: target/release/silentdragonlite-cli +======= + name: ${{ matrix.os }}-silentdragonlite-cli + path: target/release/silentdragonlite-cli +======= + name: ${{ matrix.os }}-zecwallet-cli + path: target/release/zecwallet-cli +>>>>>>> 959755d705b18d13a8dbdf0502c47818fe7a4e94 + - name: Upload windows + uses: actions/upload-artifact@v1 + if: contains(matrix.os, 'windows') + with: +<<<<<<< HEAD + name: ${{ matrix.os }}-silentdragonlite-cli.exe + path: target/release/silentdragonlite-cli.exe + +>>>>>>> 959755d705b18d13a8dbdf0502c47818fe7a4e94 +======= + name: ${{ matrix.os }}-zecwallet-cli.exe + path: target/release/zecwallet-cli.exe + +>>>>>>> 959755d705b18d13a8dbdf0502c47818fe7a4e94 + + linux_arm7: + name: Linux ARMv7 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: armv7-unknown-linux-gnueabihf + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target armv7-unknown-linux-gnueabihf + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_armv7-silentdragonlite-cli + path: target/armv7-unknown-linux-gnueabihf/release/silentdragonlite-cli + + linux_aarch64: + name: Linux ARM64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: aarch64-unknown-linux-gnu + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target aarch64-unknown-linux-gnu + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_aarch64-silentdragonlite-cli + path: target/aarch64-unknown-linux-gnu/release/silentdragonlite-cli + diff --git a/.github/workflows/rust_BASE_5752.yml b/.github/workflows/rust_BASE_5752.yml index 422cf60..9eb67c3 100644 --- a/.github/workflows/rust_BASE_5752.yml +++ b/.github/workflows/rust_BASE_5752.yml @@ -1,80 +1,80 @@ -name: Rust - -on: [push, pull_request] - -jobs: - build: - name: Build on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.38.0 - override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --verbose --release --all - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: ${{ matrix.os }}-zecwallet-cli - path: target/release/zecwallet-cli - - linux_arm7: - name: Linux ARMv7 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: armv7-unknown-linux-gnueabihf - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target armv7-unknown-linux-gnueabihf - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_armv7-zecwallet-cli - path: target/armv7-unknown-linux-gnueabihf/release/zecwallet-cli - - linux_aarch64: - name: Linux ARM64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: aarch64-unknown-linux-gnu - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target aarch64-unknown-linux-gnu - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_aarch64-zecwallet-cli - path: target/aarch64-unknown-linux-gnu/release/zecwallet-cli - +name: Rust + +on: [push, pull_request] + +jobs: + build: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.38.0 + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --release --all + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: ${{ matrix.os }}-zecwallet-cli + path: target/release/zecwallet-cli + + linux_arm7: + name: Linux ARMv7 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: armv7-unknown-linux-gnueabihf + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target armv7-unknown-linux-gnueabihf + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_armv7-zecwallet-cli + path: target/armv7-unknown-linux-gnueabihf/release/zecwallet-cli + + linux_aarch64: + name: Linux ARM64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: aarch64-unknown-linux-gnu + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target aarch64-unknown-linux-gnu + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_aarch64-zecwallet-cli + path: target/aarch64-unknown-linux-gnu/release/zecwallet-cli + diff --git a/.github/workflows/rust_LOCAL_5752.yml b/.github/workflows/rust_LOCAL_5752.yml index f61dc33..0d81054 100644 --- a/.github/workflows/rust_LOCAL_5752.yml +++ b/.github/workflows/rust_LOCAL_5752.yml @@ -1,93 +1,93 @@ -name: Rust - -on: [push, pull_request] - -jobs: - build: - name: Build on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.38.0 - override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --verbose --release --all - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all - - name: Upload ubuntu/macos - uses: actions/upload-artifact@v1 - if: contains(matrix.os, 'macos') || contains(matrix.os, 'ubuntu') - with: -<<<<<<< HEAD - name: ${{ matrix.os }}-silentdragonlite-cli - path: target/release/silentdragonlite-cli -======= - name: ${{ matrix.os }}-silentdragonlite-cli - path: target/release/silentdragonlite-cli - - name: Upload windows - uses: actions/upload-artifact@v1 - if: contains(matrix.os, 'windows') - with: - name: ${{ matrix.os }}-silentdragonlite-cli.exe - path: target/release/silentdragonlite-cli.exe - ->>>>>>> 959755d705b18d13a8dbdf0502c47818fe7a4e94 - - linux_arm7: - name: Linux ARMv7 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: armv7-unknown-linux-gnueabihf - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target armv7-unknown-linux-gnueabihf - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_armv7-silentdragonlite-cli - path: target/armv7-unknown-linux-gnueabihf/release/silentdragonlite-cli - - linux_aarch64: - name: Linux ARM64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: aarch64-unknown-linux-gnu - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target aarch64-unknown-linux-gnu - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_aarch64-silentdragonlite-cli - path: target/aarch64-unknown-linux-gnu/release/silentdragonlite-cli - +name: Rust + +on: [push, pull_request] + +jobs: + build: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.38.0 + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --release --all + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all + - name: Upload ubuntu/macos + uses: actions/upload-artifact@v1 + if: contains(matrix.os, 'macos') || contains(matrix.os, 'ubuntu') + with: +<<<<<<< HEAD + name: ${{ matrix.os }}-silentdragonlite-cli + path: target/release/silentdragonlite-cli +======= + name: ${{ matrix.os }}-silentdragonlite-cli + path: target/release/silentdragonlite-cli + - name: Upload windows + uses: actions/upload-artifact@v1 + if: contains(matrix.os, 'windows') + with: + name: ${{ matrix.os }}-silentdragonlite-cli.exe + path: target/release/silentdragonlite-cli.exe + +>>>>>>> 959755d705b18d13a8dbdf0502c47818fe7a4e94 + + linux_arm7: + name: Linux ARMv7 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: armv7-unknown-linux-gnueabihf + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target armv7-unknown-linux-gnueabihf + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_armv7-silentdragonlite-cli + path: target/armv7-unknown-linux-gnueabihf/release/silentdragonlite-cli + + linux_aarch64: + name: Linux ARM64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: aarch64-unknown-linux-gnu + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target aarch64-unknown-linux-gnu + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_aarch64-silentdragonlite-cli + path: target/aarch64-unknown-linux-gnu/release/silentdragonlite-cli + diff --git a/.github/workflows/rust_REMOTE_5752.yml b/.github/workflows/rust_REMOTE_5752.yml index 3f634ac..f724e4a 100644 --- a/.github/workflows/rust_REMOTE_5752.yml +++ b/.github/workflows/rust_REMOTE_5752.yml @@ -1,88 +1,88 @@ -name: Rust - -on: [push, pull_request] - -jobs: - build: - name: Build on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.38.0 - override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --verbose --release --all - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all - - name: Upload ubuntu/macos - uses: actions/upload-artifact@v1 - if: contains(matrix.os, 'macos') || contains(matrix.os, 'ubuntu') - with: - name: ${{ matrix.os }}-zecwallet-cli - path: target/release/zecwallet-cli - - name: Upload windows - uses: actions/upload-artifact@v1 - if: contains(matrix.os, 'windows') - with: - name: ${{ matrix.os }}-zecwallet-cli.exe - path: target/release/zecwallet-cli.exe - - - linux_arm7: - name: Linux ARMv7 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: armv7-unknown-linux-gnueabihf - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target armv7-unknown-linux-gnueabihf - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_armv7-zecwallet-cli - path: target/armv7-unknown-linux-gnueabihf/release/zecwallet-cli - - linux_aarch64: - name: Linux ARM64 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: aarch64-unknown-linux-gnu - override: true - - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target aarch64-unknown-linux-gnu - - name: Upload - uses: actions/upload-artifact@v1 - with: - name: linux_aarch64-zecwallet-cli - path: target/aarch64-unknown-linux-gnu/release/zecwallet-cli - +name: Rust + +on: [push, pull_request] + +jobs: + build: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.38.0 + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --release --all + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all + - name: Upload ubuntu/macos + uses: actions/upload-artifact@v1 + if: contains(matrix.os, 'macos') || contains(matrix.os, 'ubuntu') + with: + name: ${{ matrix.os }}-zecwallet-cli + path: target/release/zecwallet-cli + - name: Upload windows + uses: actions/upload-artifact@v1 + if: contains(matrix.os, 'windows') + with: + name: ${{ matrix.os }}-zecwallet-cli.exe + path: target/release/zecwallet-cli.exe + + + linux_arm7: + name: Linux ARMv7 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: armv7-unknown-linux-gnueabihf + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target armv7-unknown-linux-gnueabihf + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_armv7-zecwallet-cli + path: target/armv7-unknown-linux-gnueabihf/release/zecwallet-cli + + linux_aarch64: + name: Linux ARM64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: aarch64-unknown-linux-gnu + override: true + - uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target aarch64-unknown-linux-gnu + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: linux_aarch64-zecwallet-cli + path: target/aarch64-unknown-linux-gnu/release/zecwallet-cli + diff --git a/.gitignore b/.gitignore index 990f1e8..caf33c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -target/ -.vscode/ -history.txt -/.idea/ -tarpaulin-report.html -/log* -silentdragonlite-cli +target/ +.vscode/ +history.txt +/.idea/ +tarpaulin-report.html +/log* +silentdragonlite-cli diff --git a/Cargo.lock b/Cargo.lock index 6c2a48d..eae11bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,2883 +1,2883 @@ -# 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 = "aho-corasick" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.8", -] - -[[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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" - -[[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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58982858be7540a465c790b95aaea6710e5139bf8956b1d1344d014fa40100b0" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "393356ed99aa7bff0ac486dde592633b83ab02bd254d8c209d5b9f1d0f533480" -dependencies = [ - "proc-macro2 1.0.6", - "quote 1.0.2", - "syn 1.0.11", -] - -[[package]] -name = "async-trait" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8df72488e87761e772f14ae0c2480396810e51b2c2ade912f97f0f7e5b95e3c" -dependencies = [ - "proc-macro2 1.0.6", - "quote 1.0.2", - "syn 1.0.11", -] - -[[package]] -name = "atty" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -dependencies = [ - "libc", - "winapi 0.3.8", -] - -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - -[[package]] -name = "backtrace" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" -dependencies = [ - "backtrace-sys", - "cfg-if", - "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 = "bech32" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0089c35ab7c6f2bc55ab23f769913f0ac65b1023e7e74638a1f43128dd5df2" - -[[package]] -name = "bellman" -version = "0.1.0" -source = "git+https://git.hush.is/hush/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.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b83b7baab1e671718d78204225800d6b170e648188ac7dc992e9d6bddf87d0c0" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] - -[[package]] -name = "blake2s_simd" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "050efd7a5bdb220988d4c5204f84ab796e778612af94275f1d39479798b39cf9" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] - -[[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 = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" - -[[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38" - -[[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.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[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 = "clap" -version = "2.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim", - "textwrap", - "unicode-width", - "vec_map", -] - -[[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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" - -[[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", -] - -[[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.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -dependencies = [ - "cfg-if", - "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", - "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", - "libc", - "redox_users", - "winapi 0.3.8", -] - -[[package]] -name = "dtoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" - -[[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.6", - "quote 1.0.2", - "syn 1.0.11", - "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.hush.is/hush/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.hush.is/hush/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", - "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", - "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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" - -[[package]] -name = "futures-task" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" - -[[package]] -name = "futures-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" -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.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "group" -version = "0.1.0" -source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" -dependencies = [ - "ff", - "rand 0.7.2", - "rand_xorshift 0.2.0", -] - -[[package]] -name = "h2" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" -dependencies = [ - "bytes 0.5.3", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "log", - "slab", - "tokio", - "tokio-util", -] - -[[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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7" -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.3", - "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.3", - "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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf49cfb32edee45d890537d9057d1b02ed55f53b7b6a30bae83a38c9231749e" -dependencies = [ - "bytes 0.5.3", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "log", - "net2", - "pin-project", - "time", - "tokio", - "tower-service", - "want", -] - -[[package]] -name = "indexmap" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" -dependencies = [ - "autocfg", -] - -[[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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" - -[[package]] -name = "js-sys" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "367647c532db6f1555d7151e619540ec5f713328235b8c062c6b4f63e84adfe3" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "json" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ca41abbeb7615d56322a984e63be5e5d0a117dfaca86c14393e32a762ccac1" - -[[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.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" - -[[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", - "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.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" - -[[package]] -name = "miniz_oxide" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625" -dependencies = [ - "adler32", -] - -[[package]] -name = "mio" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" -dependencies = [ - "cfg-if", - "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", - "libc", - "winapi 0.3.8", -] - -[[package]] -name = "nix" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "void", -] - -[[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c3f34cdd24f334cb265d9bf8bfa8a241920d026916785747a92f0e55541a1a" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" -dependencies = [ - "autocfg", -] - -[[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.hush.is/hush/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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355" -dependencies = [ - "proc-macro2 1.0.6", - "quote 1.0.2", - "syn 1.0.11", -] - -[[package]] -name = "pin-project-lite" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0af6cbca0e6e3ce8692ee19fb8d734b641899e07b68eb73e9bbbd32f1703991" - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" -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.3", - "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.3", - "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.6", - "quote 1.0.2", - "syn 1.0.11", -] - -[[package]] -name = "prost-types" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1834f67c0697c001304b75be76f67add9c89742eda3a085ad8ee0bb38c3417aa" -dependencies = [ - "bytes 0.5.3", - "prost", -] - -[[package]] -name = "protobuf" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20" - -[[package]] -name = "protobuf-codegen" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a" -dependencies = [ - "protobuf", -] - -[[package]] -name = "protobuf-codegen-pure" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1646acda5319f5b28b0bff4a484324df43ddae2c0f5a3f3e63c0b26095cd600" -dependencies = [ - "protobuf", - "protobuf-codegen", -] - -[[package]] -name = "quick-error" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" - -[[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.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi 0.3.8", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg", - "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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" -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", - "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", - "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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" -dependencies = [ - "failure", - "rand_os 0.1.3", - "redox_syscall", - "rust-argon2", -] - -[[package]] -name = "regex" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", - "thread_local", -] - -[[package]] -name = "regex-syntax" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" - -[[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.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6747f8da1f2b1fabbee1aaa4eb8a11abf9adef0bf58a41cee45db5d59cecdfac" -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.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" -dependencies = [ - "base64", - "blake2b_simd", - "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.11", - "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", - "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 = "rustyline" -version = "5.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae156e2a68be20a3ae95574089b65c188d86291cd5421cc647222b8d1864ffb" -dependencies = [ - "cfg-if", - "dirs", - "libc", - "log", - "memchr", - "nix", - "unicode-segmentation", - "unicode-width", - "utf8parse", - "winapi 0.3.8", -] - -[[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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" -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.6", - "quote 1.0.2", - "syn 1.0.11", -] - -[[package]] -name = "serde_json" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" -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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" -dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", -] - -[[package]] -name = "shellwords" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685f0e9b0efe23d26e60a780d8dcd3ac95e90975814de9bc6f48e5d609b5d0f5" -dependencies = [ - "lazy_static", - "regex", -] - -[[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 = "silentdragonxlite-cli" -version = "1.0.0" -dependencies = [ - "byteorder", - "clap", - "http", - "json", - "log", - "log4rs", - "rustyline", - "shellwords", - "silentdragonxlitelib", - "tiny-bip39", -] - -[[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.2", - "ring", - "ripemd160", - "rust-embed", - "secp256k1", - "sha2", - "sodiumoxide", - "subtle 2.2.2", - "tempdir", - "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.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "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 = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[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.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" -dependencies = [ - "proc-macro2 1.0.6", - "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.6", - "quote 1.0.2", - "syn 1.0.11", - "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 = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -dependencies = [ - "rand 0.4.6", - "remove_dir_all", -] - -[[package]] -name = "tempfile" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -dependencies = [ - "cfg-if", - "libc", - "rand 0.7.2", - "redox_syscall", - "remove_dir_all", - "winapi 0.3.8", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[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 = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "threadpool" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dae184447c15d5a6916d973c642aec485105a13cd238192a6927ae3e077d66" -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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1bef565a52394086ecac0a6fa3b8ace4cb3a138ee1d96bd2b93283b56824e3" -dependencies = [ - "bytes 0.5.3", - "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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de6c21a09bab0ce34614bb1071403ad9996db62715eb61e63be5d82f91342bc" -dependencies = [ - "quote 1.0.2", - "syn 1.0.11", -] - -[[package]] -name = "tokio-rustls" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2ce8e6bc7ce619b4fc05761a550c8a750f6bf551f2e9b1efaec93124e13d51" -dependencies = [ - "bytes 0.5.3", - "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.3", - "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", - "bytes 0.5.3", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "percent-encoding", - "pin-project", - "prost", - "prost-derive", - "rustls-native-certs", - "tokio", - "tokio-rustls", - "tokio-util", - "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.6", - "prost-build", - "quote 1.0.2", - "syn 1.0.11", -] - -[[package]] -name = "tower" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b299df54795e6f72bca45063b5803d1f9a1ba9b11a3c7c64d0b84519b451fdd" -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.2", - "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.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6de6a8590a29d3f401eab60470c699efa0adf7b4f0352055bf24df2b69849b40" -dependencies = [ - "cfg-if", - "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.11", -] - -[[package]] -name = "tracing-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fb511ac6ca1d031c5cfc26d8c38da9d88e91d2bd5b38b60cf8dc1b8b5c211f" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "tracing-futures" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107ae59580d2a1d994b6b965b16fe94c969fe86d3f7fd2572a1ee243bcaf7f09" -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-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" - -[[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 = "utf8parse" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" - -[[package]] -name = "vcpkg" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" - -[[package]] -name = "vec_map" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "walkdir" -version = "2.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" -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.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" - -[[package]] -name = "wasm-bindgen" -version = "0.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99de4b68939a880d530aed51289a7c7baee154e3ea8ac234b542c49da7134aaf" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58e66a093a7b7571cb76409763c495b8741ac4319ac20acc2b798f6766d92ee" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2 1.0.6", - "quote 1.0.2", - "syn 1.0.11", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a80f89daea7b0a67b11f6e9f911422ed039de9963dce00048a653b63d51194bf" -dependencies = [ - "quote 1.0.2", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9dbc3734ad6cff6b76b75b7df98c06982becd0055f651465a08f769bca5c61" -dependencies = [ - "proc-macro2 1.0.6", - "quote 1.0.2", - "syn 1.0.11", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d907984f8506b3554eab48b8efff723e764ddbf76d4cd4a3fe4196bc00c49a70" - -[[package]] -name = "wasm-bindgen-webidl" -version = "0.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f85a3825a459cf6a929d03bacb54dca37a614d43032ad1343ef2d4822972947d" -dependencies = [ - "anyhow", - "heck", - "log", - "proc-macro2 1.0.6", - "quote 1.0.2", - "syn 1.0.11", - "wasm-bindgen-backend", - "weedle", -] - -[[package]] -name = "web-sys" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb60433d0dc12c803b9b017b3902d80c9451bab78d27bc3210bf2a7b96593f1" -dependencies = [ - "anyhow", - "js-sys", - "sourcefile", - "wasm-bindgen", - "wasm-bindgen-webidl", -] - -[[package]] -name = "webpki" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e664e770ac0110e2384769bcc59ed19e329d81f555916a6e072714957b81b4" -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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" -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.hush.is/hush/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.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" -dependencies = [ - "aes", - "blake2b_simd", - "blake2s_simd", - "byteorder", - "crypto_api_chachapoly", - "ff", - "fpe", - "hex", - "lazy_static", - "pairing", - "rand 0.7.2", - "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.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" -dependencies = [ - "bellman", - "blake2b_simd", - "byteorder", - "ff", - "pairing", - "rand_os 0.2.2", - "zcash_primitives", -] +# 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 = "aho-corasick" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.8", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58982858be7540a465c790b95aaea6710e5139bf8956b1d1344d014fa40100b0" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "393356ed99aa7bff0ac486dde592633b83ab02bd254d8c209d5b9f1d0f533480" +dependencies = [ + "proc-macro2 1.0.6", + "quote 1.0.2", + "syn 1.0.11", +] + +[[package]] +name = "async-trait" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8df72488e87761e772f14ae0c2480396810e51b2c2ade912f97f0f7e5b95e3c" +dependencies = [ + "proc-macro2 1.0.6", + "quote 1.0.2", + "syn 1.0.11", +] + +[[package]] +name = "atty" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" +dependencies = [ + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "backtrace" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" +dependencies = [ + "backtrace-sys", + "cfg-if", + "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 = "bech32" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e0089c35ab7c6f2bc55ab23f769913f0ac65b1023e7e74638a1f43128dd5df2" + +[[package]] +name = "bellman" +version = "0.1.0" +source = "git+https://git.hush.is/hush/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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b83b7baab1e671718d78204225800d6b170e648188ac7dc992e9d6bddf87d0c0" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050efd7a5bdb220988d4c5204f84ab796e778612af94275f1d39479798b39cf9" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[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 = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38" + +[[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.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[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 = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" + +[[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", +] + +[[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.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +dependencies = [ + "cfg-if", + "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", + "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", + "libc", + "redox_users", + "winapi 0.3.8", +] + +[[package]] +name = "dtoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" + +[[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.6", + "quote 1.0.2", + "syn 1.0.11", + "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.hush.is/hush/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.hush.is/hush/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", + "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", + "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" + +[[package]] +name = "futures-task" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" + +[[package]] +name = "futures-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" +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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.1.0" +source = "git+https://git.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "ff", + "rand 0.7.2", + "rand_xorshift 0.2.0", +] + +[[package]] +name = "h2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" +dependencies = [ + "bytes 0.5.3", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "log", + "slab", + "tokio", + "tokio-util", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7" +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.3", + "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.3", + "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf49cfb32edee45d890537d9057d1b02ed55f53b7b6a30bae83a38c9231749e" +dependencies = [ + "bytes 0.5.3", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "log", + "net2", + "pin-project", + "time", + "tokio", + "tower-service", + "want", +] + +[[package]] +name = "indexmap" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" +dependencies = [ + "autocfg", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" + +[[package]] +name = "js-sys" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367647c532db6f1555d7151e619540ec5f713328235b8c062c6b4f63e84adfe3" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3ca41abbeb7615d56322a984e63be5e5d0a117dfaca86c14393e32a762ccac1" + +[[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.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" + +[[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", + "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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" + +[[package]] +name = "miniz_oxide" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625" +dependencies = [ + "adler32", +] + +[[package]] +name = "mio" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +dependencies = [ + "cfg-if", + "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", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "nix" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c3f34cdd24f334cb265d9bf8bfa8a241920d026916785747a92f0e55541a1a" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" +dependencies = [ + "autocfg", +] + +[[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.hush.is/hush/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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355" +dependencies = [ + "proc-macro2 1.0.6", + "quote 1.0.2", + "syn 1.0.11", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0af6cbca0e6e3ce8692ee19fb8d734b641899e07b68eb73e9bbbd32f1703991" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" +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.3", + "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.3", + "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.6", + "quote 1.0.2", + "syn 1.0.11", +] + +[[package]] +name = "prost-types" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1834f67c0697c001304b75be76f67add9c89742eda3a085ad8ee0bb38c3417aa" +dependencies = [ + "bytes 0.5.3", + "prost", +] + +[[package]] +name = "protobuf" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20" + +[[package]] +name = "protobuf-codegen" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a" +dependencies = [ + "protobuf", +] + +[[package]] +name = "protobuf-codegen-pure" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1646acda5319f5b28b0bff4a484324df43ddae2c0f5a3f3e63c0b26095cd600" +dependencies = [ + "protobuf", + "protobuf-codegen", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" + +[[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.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.8", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg", + "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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +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", + "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", + "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" +dependencies = [ + "failure", + "rand_os 0.1.3", + "redox_syscall", + "rust-argon2", +] + +[[package]] +name = "regex" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6747f8da1f2b1fabbee1aaa4eb8a11abf9adef0bf58a41cee45db5d59cecdfac" +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.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" +dependencies = [ + "base64", + "blake2b_simd", + "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.11", + "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", + "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 = "rustyline" +version = "5.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae156e2a68be20a3ae95574089b65c188d86291cd5421cc647222b8d1864ffb" +dependencies = [ + "cfg-if", + "dirs", + "libc", + "log", + "memchr", + "nix", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi 0.3.8", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" +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.6", + "quote 1.0.2", + "syn 1.0.11", +] + +[[package]] +name = "serde_json" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "shellwords" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685f0e9b0efe23d26e60a780d8dcd3ac95e90975814de9bc6f48e5d609b5d0f5" +dependencies = [ + "lazy_static", + "regex", +] + +[[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 = "silentdragonxlite-cli" +version = "1.0.0" +dependencies = [ + "byteorder", + "clap", + "http", + "json", + "log", + "log4rs", + "rustyline", + "shellwords", + "silentdragonxlitelib", + "tiny-bip39", +] + +[[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.2", + "ring", + "ripemd160", + "rust-embed", + "secp256k1", + "sha2", + "sodiumoxide", + "subtle 2.2.2", + "tempdir", + "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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "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 = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" +dependencies = [ + "proc-macro2 1.0.6", + "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.6", + "quote 1.0.2", + "syn 1.0.11", + "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 = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if", + "libc", + "rand 0.7.2", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.8", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[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 = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "threadpool" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8dae184447c15d5a6916d973c642aec485105a13cd238192a6927ae3e077d66" +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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1bef565a52394086ecac0a6fa3b8ace4cb3a138ee1d96bd2b93283b56824e3" +dependencies = [ + "bytes 0.5.3", + "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de6c21a09bab0ce34614bb1071403ad9996db62715eb61e63be5d82f91342bc" +dependencies = [ + "quote 1.0.2", + "syn 1.0.11", +] + +[[package]] +name = "tokio-rustls" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2ce8e6bc7ce619b4fc05761a550c8a750f6bf551f2e9b1efaec93124e13d51" +dependencies = [ + "bytes 0.5.3", + "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.3", + "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", + "bytes 0.5.3", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tokio-util", + "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.6", + "prost-build", + "quote 1.0.2", + "syn 1.0.11", +] + +[[package]] +name = "tower" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b299df54795e6f72bca45063b5803d1f9a1ba9b11a3c7c64d0b84519b451fdd" +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.2", + "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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6de6a8590a29d3f401eab60470c699efa0adf7b4f0352055bf24df2b69849b40" +dependencies = [ + "cfg-if", + "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.11", +] + +[[package]] +name = "tracing-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fb511ac6ca1d031c5cfc26d8c38da9d88e91d2bd5b38b60cf8dc1b8b5c211f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107ae59580d2a1d994b6b965b16fe94c969fe86d3f7fd2572a1ee243bcaf7f09" +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-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[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 = "utf8parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" + +[[package]] +name = "vcpkg" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "walkdir" +version = "2.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" +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.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" + +[[package]] +name = "wasm-bindgen" +version = "0.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99de4b68939a880d530aed51289a7c7baee154e3ea8ac234b542c49da7134aaf" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58e66a093a7b7571cb76409763c495b8741ac4319ac20acc2b798f6766d92ee" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2 1.0.6", + "quote 1.0.2", + "syn 1.0.11", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a80f89daea7b0a67b11f6e9f911422ed039de9963dce00048a653b63d51194bf" +dependencies = [ + "quote 1.0.2", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9dbc3734ad6cff6b76b75b7df98c06982becd0055f651465a08f769bca5c61" +dependencies = [ + "proc-macro2 1.0.6", + "quote 1.0.2", + "syn 1.0.11", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d907984f8506b3554eab48b8efff723e764ddbf76d4cd4a3fe4196bc00c49a70" + +[[package]] +name = "wasm-bindgen-webidl" +version = "0.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85a3825a459cf6a929d03bacb54dca37a614d43032ad1343ef2d4822972947d" +dependencies = [ + "anyhow", + "heck", + "log", + "proc-macro2 1.0.6", + "quote 1.0.2", + "syn 1.0.11", + "wasm-bindgen-backend", + "weedle", +] + +[[package]] +name = "web-sys" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb60433d0dc12c803b9b017b3902d80c9451bab78d27bc3210bf2a7b96593f1" +dependencies = [ + "anyhow", + "js-sys", + "sourcefile", + "wasm-bindgen", + "wasm-bindgen-webidl", +] + +[[package]] +name = "webpki" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7e664e770ac0110e2384769bcc59ed19e329d81f555916a6e072714957b81b4" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +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.hush.is/hush/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.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "aes", + "blake2b_simd", + "blake2s_simd", + "byteorder", + "crypto_api_chachapoly", + "ff", + "fpe", + "hex", + "lazy_static", + "pairing", + "rand 0.7.2", + "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.hush.is/hush/librustzcash.git?rev=acff1444ec373e9c3e37b47ca95bfd358e45255b#acff1444ec373e9c3e37b47ca95bfd358e45255b" +dependencies = [ + "bellman", + "blake2b_simd", + "byteorder", + "ff", + "pairing", + "rand_os 0.2.2", + "zcash_primitives", +] diff --git a/Cargo.toml b/Cargo.toml index 1e7b3f0..76a4b7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ -[workspace] -members = [ - "lib", - "cli", -] - -[profile.release] +[workspace] +members = [ + "lib", + "cli", +] + +[profile.release] debug = false \ No newline at end of file diff --git a/Makefile b/Makefile index b98e849..b7f0776 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,23 @@ -.PHONY: format help -# Help system from https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html -.DEFAULT_GOAL := help -# Copyright (c) 2019-2024 Jahway603 & The Hush Developers -# Released under the GPLv3 -# -# DragonX Silentdragonlite-cli Makefile -PROJECT_NAME := "silentdragonxlite-cli" - -help: - @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -about: ## Display release info - printf "DragonX Silentdragonlite-cli Makefile by jahway603\n" - -build: ## Build the release - ./util/build.sh - cp `pwd`/target/release/$(PROJECT_NAME) . - printf "DragonX silentdragonxlite-cli is ready for you\n" - -clean: ## Clean the repo - cargo clean - rm $(PROJECT_NAME) +.PHONY: format help +# Help system from https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +.DEFAULT_GOAL := help +# Copyright (c) 2019-2024 Jahway603 & The Hush Developers +# Released under the GPLv3 +# +# DragonX Silentdragonlite-cli Makefile +PROJECT_NAME := "silentdragonxlite-cli" + +help: + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +about: ## Display release info + printf "DragonX Silentdragonlite-cli Makefile by jahway603\n" + +build: ## Build the release + ./util/build.sh + cp `pwd`/target/release/$(PROJECT_NAME) . + printf "DragonX silentdragonxlite-cli is ready for you\n" + +clean: ## Clean the repo + cargo clean + rm $(PROJECT_NAME) diff --git a/README.md b/README.md index 7c060bf..3644263 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,100 @@ -# SilentDragonXLite CLI - -`silentdragonxlite-cli` is a command line SilentDragonXLite light client. To use it, download the latest binary from the releases page and run `./silentdragonxlite-cli` or compile it yourself as documented below. - -This will launch the interactive prompt. Type `help` to get a list of commands - -## Running in non-interactive mode: -You can also run `silentdragonxlite-cli` in non-interactive mode by passing the command you want to run as an argument. For example, `silentdragonxlite-cli addresses` will list all wallet addresses and exit. -Run `silentdragonxlite-cli help` to see a list of all commands. - -## Privacy -* While all the keys and transaction detection happens on the client, the server can learn what blocks contain your shielded transactions. -* The server also learns other metadata about you like your ip address etc... -* Also remember that t-addresses don't provide any privacy protection. - -## Notes: -* If you want to run your own server, please see [SilentDragonXLite-cli lightwalletd](https://git.hush.is/hush/lightwalletd), and then run `./silentdragonxlite-cli --server http://127.0.0.1:9067`. You might also need to pass `--dangerous` if you are using a self-signed TLS certificate. - -* The log file is in `~/.silentdragonxlite/silentdragonxlite-cli.debug.log`. Wallet is stored in `~/.silentdragonxlite/silentdragonxlite-cli.dat` - -### Note Management -silentdragonxlite does automatic note and utxo management, which means it doesn't allow you to manually select which address to send outgoing transactions from. It follows these principles: -* Defaults to sending shielded transactions, even if you're sending to a transparent address -* Sapling funds need at least 2 confirmations before they can be spent -* Can select funds from multiple shielded addresses in the same transaction -* Will automatically shield your transparent funds at the first opportunity - * When sending an outgoing transaction to a shielded address, silentdragonxlite can decide to use the transaction to additionally shield your transparent funds (i.e., send your transparent funds to your own shielded address in the same transaction) - -## Compiling from source - -#### Pre-requisites - - - -* You need Rust and how you install it will depend on your version of Linux. Below are well known rust versions tested on common Linux distributions. - -| Linux Version | Rust Version Tested | Command to install | -|---------------|--------|---------------------------| -| Ubuntu 18.04 | 1.47.0 | [USE RUSTUP](https://www.rust-lang.org/tools/install) | -| Ubuntu 20.04 | 1.57.0 | sudo apt install rust-all | -| Debian 11 | 1.50.0 | [USE RUSTUP](https://www.rust-lang.org/tools/install) | -| Arch Linux | 1.56.0 | pacman -S rustc cargo | - -* Debian 11 comes with a much older rust compiler (1.48.0) and so you want to use rustup with Debian and install at least 1.50.0. -* If you're using another Linux distro, then consult their package manager for rustc and cargo, but if it's tool old then you want to [use Rustup](https://www.rust-lang.org/tools/install) to install at least 1.50.0. -* The build will fail if you do not have `rustfmt` binary, which is included when you use `rustup` but may not be included in via operating system packages. Using `rustup` is recommended - -To securely install rustup by compiling it yourself: - -``` -git clone https://github.com/rust-lang/rustup -cd rustup -cargo run --release -``` - -The above avoids piping the output of curl to bash (bad idea) and avoids using binaries. It will take a few minutes longer but is the better solution. - - -#### The compilation - -Run the following commands to compile on your computer. - -```shell script -git clone https://git.hush.is/dragonx/silentdragonxlite-cli -cd silentdragonxlite-cli -cargo build --release -./target/release/silentdragonxlite-cli -``` - -#### Or build with make - -Alternatively, you can use the new makefile to build - -```shell script -make help -make build -``` - -## Options -Here are some CLI arguments you can pass to `silentdragonxlite-cli`. Please run `silentdragonxlite-cli --help` for the full list. - -* `--server`: Connect to a custom SilentDragonXLite lightwalletd server. - * Example: `./silentdragonxlite-cli --server 127.0.0.1:9067` - * Example: `./silentdragonxlite-cli --server lite.dragonx.is` -* `--seed`: Restore a wallet from a seed phrase. **Note** that this will fail if there is an existing wallet. Delete (or move) any existing wallet to restore from the 24-word seed phrase - * Example: `./silentdragonxlite-cli --seed "twenty four words seed phrase"` - * `--recover`: Attempt to recover the seed phrase from a corrupted wallet -* `-n, --nosync`: By default, silentdragonxlite-cli will sync the wallet at startup, so use this option to prevent the automatic sync at startup - -### Support - -For support or other questions, join us on [Telegram](https://dragonx.is/tg) or [file an issue](https://git.hush.is/dragonx/silentdragonxlite-cli/issues). - -## Copyright - -Copyright The Hush Developers 2019-2024 - -## License - -GPLv3 or later +# SilentDragonXLite CLI + +`silentdragonxlite-cli` is a command line SilentDragonXLite light client. To use it, download the latest binary from the releases page and run `./silentdragonxlite-cli` or compile it yourself as documented below. + +This will launch the interactive prompt. Type `help` to get a list of commands + +## Running in non-interactive mode: +You can also run `silentdragonxlite-cli` in non-interactive mode by passing the command you want to run as an argument. For example, `silentdragonxlite-cli addresses` will list all wallet addresses and exit. +Run `silentdragonxlite-cli help` to see a list of all commands. + +## Privacy +* While all the keys and transaction detection happens on the client, the server can learn what blocks contain your shielded transactions. +* The server also learns other metadata about you like your ip address etc... +* Also remember that t-addresses don't provide any privacy protection. + +## Notes: +* If you want to run your own server, please see [SilentDragonXLite-cli lightwalletd](https://git.hush.is/hush/lightwalletd), and then run `./silentdragonxlite-cli --server http://127.0.0.1:9067`. You might also need to pass `--dangerous` if you are using a self-signed TLS certificate. + +* The log file is in `~/.silentdragonxlite/silentdragonxlite-cli.debug.log`. Wallet is stored in `~/.silentdragonxlite/silentdragonxlite-cli.dat` + +### Note Management +silentdragonxlite does automatic note and utxo management, which means it doesn't allow you to manually select which address to send outgoing transactions from. It follows these principles: +* Defaults to sending shielded transactions, even if you're sending to a transparent address +* Sapling funds need at least 2 confirmations before they can be spent +* Can select funds from multiple shielded addresses in the same transaction +* Will automatically shield your transparent funds at the first opportunity + * When sending an outgoing transaction to a shielded address, silentdragonxlite can decide to use the transaction to additionally shield your transparent funds (i.e., send your transparent funds to your own shielded address in the same transaction) + +## Compiling from source + +#### Pre-requisites + + + +* You need Rust and how you install it will depend on your version of Linux. Below are well known rust versions tested on common Linux distributions. + +| Linux Version | Rust Version Tested | Command to install | +|---------------|--------|---------------------------| +| Ubuntu 18.04 | 1.47.0 | [USE RUSTUP](https://www.rust-lang.org/tools/install) | +| Ubuntu 20.04 | 1.57.0 | sudo apt install rust-all | +| Debian 11 | 1.50.0 | [USE RUSTUP](https://www.rust-lang.org/tools/install) | +| Arch Linux | 1.56.0 | pacman -S rustc cargo | + +* Debian 11 comes with a much older rust compiler (1.48.0) and so you want to use rustup with Debian and install at least 1.50.0. +* If you're using another Linux distro, then consult their package manager for rustc and cargo, but if it's tool old then you want to [use Rustup](https://www.rust-lang.org/tools/install) to install at least 1.50.0. +* The build will fail if you do not have `rustfmt` binary, which is included when you use `rustup` but may not be included in via operating system packages. Using `rustup` is recommended + +To securely install rustup by compiling it yourself: + +``` +git clone https://github.com/rust-lang/rustup +cd rustup +cargo run --release +``` + +The above avoids piping the output of curl to bash (bad idea) and avoids using binaries. It will take a few minutes longer but is the better solution. + + +#### The compilation + +Run the following commands to compile on your computer. + +```shell script +git clone https://git.hush.is/dragonx/silentdragonxlite-cli +cd silentdragonxlite-cli +cargo build --release +./target/release/silentdragonxlite-cli +``` + +#### Or build with make + +Alternatively, you can use the new makefile to build + +```shell script +make help +make build +``` + +## Options +Here are some CLI arguments you can pass to `silentdragonxlite-cli`. Please run `silentdragonxlite-cli --help` for the full list. + +* `--server`: Connect to a custom SilentDragonXLite lightwalletd server. + * Example: `./silentdragonxlite-cli --server 127.0.0.1:9067` + * Example: `./silentdragonxlite-cli --server lite.dragonx.is` +* `--seed`: Restore a wallet from a seed phrase. **Note** that this will fail if there is an existing wallet. Delete (or move) any existing wallet to restore from the 24-word seed phrase + * Example: `./silentdragonxlite-cli --seed "twenty four words seed phrase"` + * `--recover`: Attempt to recover the seed phrase from a corrupted wallet +* `-n, --nosync`: By default, silentdragonxlite-cli will sync the wallet at startup, so use this option to prevent the automatic sync at startup + +### Support + +For support or other questions, join us on [Telegram](https://dragonx.is/tg) or [file an issue](https://git.hush.is/dragonx/silentdragonxlite-cli/issues). + +## Copyright + +Copyright The Hush Developers 2019-2024 + +## License + +GPLv3 or later diff --git a/cli/Cargo.toml b/cli/Cargo.toml index f5949d5..0082689 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,18 +1,18 @@ -[package] -name = "silentdragonxlite-cli" -version = "1.0.0" -edition = "2018" - -[dependencies] -rustyline = "5.0.2" -clap = "2.33" -log = "0.4" -log4rs = "0.8.3" -shellwords = "1.0.0" -json = "0.12.0" -http = "0.2" -byteorder = "1" -tiny-bip39 = "0.6.2" - -silentdragonxlitelib = { path = "../lib/" } - +[package] +name = "silentdragonxlite-cli" +version = "1.0.0" +edition = "2018" + +[dependencies] +rustyline = "5.0.2" +clap = "2.33" +log = "0.4" +log4rs = "0.8.3" +shellwords = "1.0.0" +json = "0.12.0" +http = "0.2" +byteorder = "1" +tiny-bip39 = "0.6.2" + +silentdragonxlitelib = { path = "../lib/" } + diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 3d73075..86c584f 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,262 +1,262 @@ -use std::io::{self}; -use std::sync::Arc; -use std::sync::mpsc::{channel, Sender, Receiver}; - -use log::{info, error}; - -use silentdragonxlitelib::{commands, - lightclient::{LightClient, LightClientConfig}, -}; - -#[macro_export] -macro_rules! configure_clapapp { - ( $freshapp: expr ) => { - $freshapp.version("1.0.0") - .arg(Arg::with_name("dangerous") - .long("dangerous") - .help("Disable server TLS certificate verification. Use this if you're running a local lightwalletd with a self-signed certificate. WARNING: This is dangerous, don't use it with a server that is not your own.") - .takes_value(false)) - .arg(Arg::with_name("nosync") - .help("By default, Silentdragonlite-cli will sync the wallet at startup. Pass --nosync to prevent the automatic sync at startup.") - .long("nosync") - .short("n") - .takes_value(false)) - .arg(Arg::with_name("recover") - .long("recover") - .help("Attempt to recover the seed from the wallet") - .takes_value(false)) - .arg(Arg::with_name("password") - .long("password") - .help("When recovering seed, specify a password for the encrypted wallet") - .takes_value(true)) - .arg(Arg::with_name("seed") - .short("s") - .long("seed") - .value_name("seed_phrase") - .help("Create a new wallet with the given 24-word seed phrase. Will fail if wallet already exists") - .takes_value(true)) - .arg(Arg::with_name("birthday") - .long("birthday") - .value_name("birthday") - .help("Specify wallet birthday when restoring from seed. This is the earlist block height where the wallet has a transaction.") - .takes_value(true)) - .arg(Arg::with_name("server") - .long("server") - .value_name("server") - .help("Lightwalletd server to connect to.") - .takes_value(true) - .default_value(lightclient::DEFAULT_SERVER)) - .arg(Arg::with_name("COMMAND") - .help("Command to execute. If a command is not specified, Silentdragonlite-cli will start in interactive mode.") - .required(false) - .index(1)) - .arg(Arg::with_name("PARAMS") - .help("Params to execute command with. Run the 'help' command to get usage help.") - .required(false) - .multiple(true)) - }; -} - -/// This function is only tested against Linux. -pub fn report_permission_error() { - let user = std::env::var("USER").expect( - "Unexpected error reading value of $USER!"); - let home = std::env::var("HOME").expect( - "Unexpected error reading value of $HOME!"); - let current_executable = std::env::current_exe() - .expect("Unexpected error reporting executable path!"); - eprintln!("USER: {}", user); - eprintln!("HOME: {}", home); - eprintln!("Executable: {}", current_executable.display()); - if home == "/" { - eprintln!("User {} must have permission to write to '{}.silentdragonxlite/' .", - user, - home); - } else { - eprintln!("User {} must have permission to write to '{}/.silentdragonxlite/' .", - user, - home); - } -} - -pub fn startup(server: http::Uri, dangerous: bool, seed: Option, birthday: u64, first_sync: bool, print_updates: bool) - -> io::Result<(Sender<(String, Vec)>, Receiver)> { - // Try to get the configuration - let (config, latest_block_height) = LightClientConfig::create(server.clone(), dangerous)?; - - let lightclient = match seed { - Some(phrase) => Arc::new(LightClient::new_from_phrase(phrase, &config, birthday,0, false)?), - None => { - if config.wallet_exists() { - Arc::new(LightClient::read_from_disk(&config)?) - } else { - println!("Creating a new wallet"); - Arc::new(LightClient::new(&config, latest_block_height)?) - } - } - }; - - // Initialize logging - lightclient.init_logging()?; - - // Print startup Messages - info!(""); // Blank line - info!("Starting Silentdragonlite-CLI"); - info!("Light Client config {:?}", config); - - if print_updates { - println!("Lightclient connecting to {}", config.server); - } - - // At startup, run a sync. - if first_sync { - let update = lightclient.do_sync(true); - if print_updates { - match update { - Ok(j) => { - println!("{}", j.pretty(2)); - }, - Err(e) => println!("{}", e) - } - } - } - - // Start the command loop - let (command_tx, resp_rx) = command_loop(lightclient.clone()); - - Ok((command_tx, resp_rx)) -} - -pub fn start_interactive(command_tx: Sender<(String, Vec)>, resp_rx: Receiver) { - // `()` can be used when no completer is required - let mut rl = rustyline::Editor::<()>::new(); - - println!("Ready!"); - - let send_command = |cmd: String, args: Vec| -> String { - command_tx.send((cmd.clone(), args)).unwrap(); - match resp_rx.recv() { - Ok(s) => s, - Err(e) => { - let e = format!("Error executing command {}: {}", cmd, e); - eprintln!("{}", e); - error!("{}", e); - return "".to_string() - } - } - }; - - let info = send_command("info".to_string(), vec![]); - let chain_name = json::parse(&info).unwrap()["chain_name"].as_str().unwrap().to_string(); - - loop { - // Read the height first - let height = json::parse(&send_command("height".to_string(), vec!["false".to_string()])).unwrap()["height"].as_i64().unwrap(); - - let readline = rl.readline(&format!("({}) Block:{} (type 'help') >> ", - chain_name, height)); - match readline { - Ok(line) => { - rl.add_history_entry(line.as_str()); - // Parse command line arguments - let mut cmd_args = match shellwords::split(&line) { - Ok(args) => args, - Err(_) => { - println!("Mismatched Quotes"); - continue; - } - }; - - if cmd_args.is_empty() { - continue; - } - - let cmd = cmd_args.remove(0); - let args: Vec = cmd_args; - - println!("{}", send_command(cmd, args)); - - // Special check for Quit command. - if line == "quit" { - break; - } - }, - Err(rustyline::error::ReadlineError::Interrupted) => { - println!("CTRL-C"); - info!("CTRL-C"); - println!("{}", send_command("save".to_string(), vec![])); - break - }, - Err(rustyline::error::ReadlineError::Eof) => { - println!("CTRL-D"); - info!("CTRL-D"); - println!("{}", send_command("save".to_string(), vec![])); - break - }, - Err(err) => { - println!("Error: {:?}", err); - break - } - } - } -} - -pub fn command_loop(lightclient: Arc) -> (Sender<(String, Vec)>, Receiver) { - let (command_tx, command_rx) = channel::<(String, Vec)>(); - let (resp_tx, resp_rx) = channel::(); - - let lc = lightclient.clone(); - std::thread::spawn(move || { - //start mempool_monitor - match LightClient::start_mempool_monitor(lc.clone()) { - Ok(_) => {}, - Err(e) => { - - error!("Error starting mempool: {:?}", e); - } - } - loop { - match command_rx.recv_timeout(std::time::Duration::from_secs(5 * 60)) { - Ok((cmd, args)) => { - let args = args.iter().map(|s| s.as_ref()).collect(); - - let cmd_response = commands::do_user_command(&cmd, &args, lc.as_ref()); - resp_tx.send(cmd_response).unwrap(); - - if cmd == "quit" { - info!("Quit"); - break; - } - }, - Err(_) => { - // Timeout. Do a sync to keep the wallet up-to-date. False to whether to print updates on the console - info!("Timeout, doing a sync"); - match lc.do_sync(false) { - Ok(_) => {}, - Err(e) => {error!("{}", e)} - } - } - } - } - }); - - (command_tx, resp_rx) -} - -pub fn attempt_recover_seed(password: Option) { - // Create a Light Client Config in an attempt to recover the file. - let config = 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, - no_cert_verification: false, - data_dir: None, - }; - - match LightClient::attempt_recover_seed(&config, password) { - Ok(_seed) => println!("Recovered seed "), - Err(e) => eprintln!("Failed to recover seed. Error: {}", e) - }; -} +use std::io::{self}; +use std::sync::Arc; +use std::sync::mpsc::{channel, Sender, Receiver}; + +use log::{info, error}; + +use silentdragonxlitelib::{commands, + lightclient::{LightClient, LightClientConfig}, +}; + +#[macro_export] +macro_rules! configure_clapapp { + ( $freshapp: expr ) => { + $freshapp.version("1.0.0") + .arg(Arg::with_name("dangerous") + .long("dangerous") + .help("Disable server TLS certificate verification. Use this if you're running a local lightwalletd with a self-signed certificate. WARNING: This is dangerous, don't use it with a server that is not your own.") + .takes_value(false)) + .arg(Arg::with_name("nosync") + .help("By default, Silentdragonlite-cli will sync the wallet at startup. Pass --nosync to prevent the automatic sync at startup.") + .long("nosync") + .short("n") + .takes_value(false)) + .arg(Arg::with_name("recover") + .long("recover") + .help("Attempt to recover the seed from the wallet") + .takes_value(false)) + .arg(Arg::with_name("password") + .long("password") + .help("When recovering seed, specify a password for the encrypted wallet") + .takes_value(true)) + .arg(Arg::with_name("seed") + .short("s") + .long("seed") + .value_name("seed_phrase") + .help("Create a new wallet with the given 24-word seed phrase. Will fail if wallet already exists") + .takes_value(true)) + .arg(Arg::with_name("birthday") + .long("birthday") + .value_name("birthday") + .help("Specify wallet birthday when restoring from seed. This is the earlist block height where the wallet has a transaction.") + .takes_value(true)) + .arg(Arg::with_name("server") + .long("server") + .value_name("server") + .help("Lightwalletd server to connect to.") + .takes_value(true) + .default_value(lightclient::DEFAULT_SERVER)) + .arg(Arg::with_name("COMMAND") + .help("Command to execute. If a command is not specified, Silentdragonlite-cli will start in interactive mode.") + .required(false) + .index(1)) + .arg(Arg::with_name("PARAMS") + .help("Params to execute command with. Run the 'help' command to get usage help.") + .required(false) + .multiple(true)) + }; +} + +/// This function is only tested against Linux. +pub fn report_permission_error() { + let user = std::env::var("USER").expect( + "Unexpected error reading value of $USER!"); + let home = std::env::var("HOME").expect( + "Unexpected error reading value of $HOME!"); + let current_executable = std::env::current_exe() + .expect("Unexpected error reporting executable path!"); + eprintln!("USER: {}", user); + eprintln!("HOME: {}", home); + eprintln!("Executable: {}", current_executable.display()); + if home == "/" { + eprintln!("User {} must have permission to write to '{}.silentdragonxlite/' .", + user, + home); + } else { + eprintln!("User {} must have permission to write to '{}/.silentdragonxlite/' .", + user, + home); + } +} + +pub fn startup(server: http::Uri, dangerous: bool, seed: Option, birthday: u64, first_sync: bool, print_updates: bool) + -> io::Result<(Sender<(String, Vec)>, Receiver)> { + // Try to get the configuration + let (config, latest_block_height) = LightClientConfig::create(server.clone(), dangerous)?; + + let lightclient = match seed { + Some(phrase) => Arc::new(LightClient::new_from_phrase(phrase, &config, birthday,0, false)?), + None => { + if config.wallet_exists() { + Arc::new(LightClient::read_from_disk(&config)?) + } else { + println!("Creating a new wallet"); + Arc::new(LightClient::new(&config, latest_block_height)?) + } + } + }; + + // Initialize logging + lightclient.init_logging()?; + + // Print startup Messages + info!(""); // Blank line + info!("Starting Silentdragonlite-CLI"); + info!("Light Client config {:?}", config); + + if print_updates { + println!("Lightclient connecting to {}", config.server); + } + + // At startup, run a sync. + if first_sync { + let update = lightclient.do_sync(true); + if print_updates { + match update { + Ok(j) => { + println!("{}", j.pretty(2)); + }, + Err(e) => println!("{}", e) + } + } + } + + // Start the command loop + let (command_tx, resp_rx) = command_loop(lightclient.clone()); + + Ok((command_tx, resp_rx)) +} + +pub fn start_interactive(command_tx: Sender<(String, Vec)>, resp_rx: Receiver) { + // `()` can be used when no completer is required + let mut rl = rustyline::Editor::<()>::new(); + + println!("Ready!"); + + let send_command = |cmd: String, args: Vec| -> String { + command_tx.send((cmd.clone(), args)).unwrap(); + match resp_rx.recv() { + Ok(s) => s, + Err(e) => { + let e = format!("Error executing command {}: {}", cmd, e); + eprintln!("{}", e); + error!("{}", e); + return "".to_string() + } + } + }; + + let info = send_command("info".to_string(), vec![]); + let chain_name = json::parse(&info).unwrap()["chain_name"].as_str().unwrap().to_string(); + + loop { + // Read the height first + let height = json::parse(&send_command("height".to_string(), vec!["false".to_string()])).unwrap()["height"].as_i64().unwrap(); + + let readline = rl.readline(&format!("({}) Block:{} (type 'help') >> ", + chain_name, height)); + match readline { + Ok(line) => { + rl.add_history_entry(line.as_str()); + // Parse command line arguments + let mut cmd_args = match shellwords::split(&line) { + Ok(args) => args, + Err(_) => { + println!("Mismatched Quotes"); + continue; + } + }; + + if cmd_args.is_empty() { + continue; + } + + let cmd = cmd_args.remove(0); + let args: Vec = cmd_args; + + println!("{}", send_command(cmd, args)); + + // Special check for Quit command. + if line == "quit" { + break; + } + }, + Err(rustyline::error::ReadlineError::Interrupted) => { + println!("CTRL-C"); + info!("CTRL-C"); + println!("{}", send_command("save".to_string(), vec![])); + break + }, + Err(rustyline::error::ReadlineError::Eof) => { + println!("CTRL-D"); + info!("CTRL-D"); + println!("{}", send_command("save".to_string(), vec![])); + break + }, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +pub fn command_loop(lightclient: Arc) -> (Sender<(String, Vec)>, Receiver) { + let (command_tx, command_rx) = channel::<(String, Vec)>(); + let (resp_tx, resp_rx) = channel::(); + + let lc = lightclient.clone(); + std::thread::spawn(move || { + //start mempool_monitor + match LightClient::start_mempool_monitor(lc.clone()) { + Ok(_) => {}, + Err(e) => { + + error!("Error starting mempool: {:?}", e); + } + } + loop { + match command_rx.recv_timeout(std::time::Duration::from_secs(5 * 60)) { + Ok((cmd, args)) => { + let args = args.iter().map(|s| s.as_ref()).collect(); + + let cmd_response = commands::do_user_command(&cmd, &args, lc.as_ref()); + resp_tx.send(cmd_response).unwrap(); + + if cmd == "quit" { + info!("Quit"); + break; + } + }, + Err(_) => { + // Timeout. Do a sync to keep the wallet up-to-date. False to whether to print updates on the console + info!("Timeout, doing a sync"); + match lc.do_sync(false) { + Ok(_) => {}, + Err(e) => {error!("{}", e)} + } + } + } + } + }); + + (command_tx, resp_rx) +} + +pub fn attempt_recover_seed(password: Option) { + // Create a Light Client Config in an attempt to recover the file. + let config = 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, + no_cert_verification: false, + data_dir: None, + }; + + match LightClient::attempt_recover_seed(&config, password) { + Ok(_seed) => println!("Recovered seed "), + Err(e) => eprintln!("Failed to recover seed. Error: {}", e) + }; +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 6be6a0c..b1283da 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,93 +1,93 @@ -use silentdragonxlitelib::lightclient::{self, LightClientConfig}; -use silentdragonxlite_cli::{configure_clapapp, - report_permission_error, - startup, - start_interactive, - attempt_recover_seed}; - //version::VERSION -use log::error; - -pub fn main() { - // Get command line arguments - use clap::{App, Arg}; - let fresh_app = App::new("SilentDragonXLite CLI"); - let configured_app = configure_clapapp!(fresh_app); - let matches = configured_app.get_matches(); - - if matches.is_present("recover") { - // Create a Light Client Config in an attempt to recover the file. - attempt_recover_seed(matches.value_of("password").map(|s| s.to_string())); - return; - } - - let command = matches.value_of("COMMAND"); - let params = matches.values_of("PARAMS").map(|v| v.collect()).or(Some(vec![])).unwrap(); - - let maybe_server = matches.value_of("server").map(|s| s.to_string()); - - let seed = matches.value_of("seed").map(|s| s.to_string()); - let maybe_birthday = matches.value_of("birthday"); - - if seed.is_some() && maybe_birthday.is_none() { - eprintln!("ERROR!"); - eprintln!("Please specify the wallet birthday (eg. '--birthday 600000') to restore from seed."); - eprintln!("This should be the block height where the wallet was created. If you don't remember the block height, you can pass '--birthday 0' to scan from the start of the blockchain."); - return; - } - - let birthday = match maybe_birthday.unwrap_or("0").parse::() { - Ok(b) => b, - Err(e) => { - eprintln!("Couldn't parse birthday. This should be a block number. Error={}", e); - return; - } - }; - - let server = LightClientConfig::get_server_or_default(maybe_server); - - // Test to make sure the server has all of scheme, host and port - if server.scheme_str().is_none() || server.host().is_none() || server.port().is_none() { - eprintln!("Please provide the --server parameter as [scheme]://[host]:[port].\nYou provided: {}", server); - return; - } - - let dangerous = matches.is_present("dangerous"); - let nosync = matches.is_present("nosync"); - let (command_tx, resp_rx) = match startup(server, dangerous, seed, birthday, !nosync, command.is_none()) { - Ok(c) => c, - Err(e) => { - let emsg = format!("Error during startup:{}\nIf you repeatedly run into this issue, you might have to restore your wallet from your seed phrase.", e); - eprintln!("{}", emsg); - error!("{}", emsg); - if cfg!(target_os = "unix" ) { - match e.raw_os_error() { - Some(13) => report_permission_error(), - _ => {}, - } - }; - return; - } - }; - - if command.is_none() { - start_interactive(command_tx, resp_rx); - } else { - command_tx.send( - (command.unwrap().to_string(), - params.iter().map(|s| s.to_string()).collect::>())) - .unwrap(); - - match resp_rx.recv() { - Ok(s) => println!("{}", s), - Err(e) => { - let e = format!("Error executing command {}: {}", command.unwrap(), e); - eprintln!("{}", e); - error!("{}", e); - } - } - - // Save before exit - command_tx.send(("save".to_string(), vec![])).unwrap(); - resp_rx.recv().unwrap(); - } -} +use silentdragonxlitelib::lightclient::{self, LightClientConfig}; +use silentdragonxlite_cli::{configure_clapapp, + report_permission_error, + startup, + start_interactive, + attempt_recover_seed}; + //version::VERSION +use log::error; + +pub fn main() { + // Get command line arguments + use clap::{App, Arg}; + let fresh_app = App::new("SilentDragonXLite CLI"); + let configured_app = configure_clapapp!(fresh_app); + let matches = configured_app.get_matches(); + + if matches.is_present("recover") { + // Create a Light Client Config in an attempt to recover the file. + attempt_recover_seed(matches.value_of("password").map(|s| s.to_string())); + return; + } + + let command = matches.value_of("COMMAND"); + let params = matches.values_of("PARAMS").map(|v| v.collect()).or(Some(vec![])).unwrap(); + + let maybe_server = matches.value_of("server").map(|s| s.to_string()); + + let seed = matches.value_of("seed").map(|s| s.to_string()); + let maybe_birthday = matches.value_of("birthday"); + + if seed.is_some() && maybe_birthday.is_none() { + eprintln!("ERROR!"); + eprintln!("Please specify the wallet birthday (eg. '--birthday 600000') to restore from seed."); + eprintln!("This should be the block height where the wallet was created. If you don't remember the block height, you can pass '--birthday 0' to scan from the start of the blockchain."); + return; + } + + let birthday = match maybe_birthday.unwrap_or("0").parse::() { + Ok(b) => b, + Err(e) => { + eprintln!("Couldn't parse birthday. This should be a block number. Error={}", e); + return; + } + }; + + let server = LightClientConfig::get_server_or_default(maybe_server); + + // Test to make sure the server has all of scheme, host and port + if server.scheme_str().is_none() || server.host().is_none() || server.port().is_none() { + eprintln!("Please provide the --server parameter as [scheme]://[host]:[port].\nYou provided: {}", server); + return; + } + + let dangerous = matches.is_present("dangerous"); + let nosync = matches.is_present("nosync"); + let (command_tx, resp_rx) = match startup(server, dangerous, seed, birthday, !nosync, command.is_none()) { + Ok(c) => c, + Err(e) => { + let emsg = format!("Error during startup:{}\nIf you repeatedly run into this issue, you might have to restore your wallet from your seed phrase.", e); + eprintln!("{}", emsg); + error!("{}", emsg); + if cfg!(target_os = "unix" ) { + match e.raw_os_error() { + Some(13) => report_permission_error(), + _ => {}, + } + }; + return; + } + }; + + if command.is_none() { + start_interactive(command_tx, resp_rx); + } else { + command_tx.send( + (command.unwrap().to_string(), + params.iter().map(|s| s.to_string()).collect::>())) + .unwrap(); + + match resp_rx.recv() { + Ok(s) => println!("{}", s), + Err(e) => { + let e = format!("Error executing command {}: {}", command.unwrap(), e); + eprintln!("{}", e); + error!("{}", e); + } + } + + // Save before exit + command_tx.send(("save".to_string(), vec![])).unwrap(); + resp_rx.recv().unwrap(); + } +} diff --git a/cli/src/version.rs b/cli/src/version.rs index e64c7ba..cbce1dd 100644 --- a/cli/src/version.rs +++ b/cli/src/version.rs @@ -1 +1 @@ -pub const VERSION:&str = "1.1.2"; +pub const VERSION:&str = "1.1.2"; diff --git a/docker/Dockerfile b/docker/Dockerfile index e782017..7523579 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,43 +1,43 @@ -FROM ubuntu:16.04 -LABEL Description="Rust compile env for Linux + Windows (cross)" - -RUN apt update -RUN apt install -y build-essential mingw-w64 gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf curl vim wget - -# Get Rust -RUN curl https://sh.rustup.rs -sSf | bash -s -- -y -ENV PATH="/root/.cargo/bin:${PATH}" - -RUN rustup target add x86_64-pc-windows-gnu -RUN rustup target add aarch64-unknown-linux-gnu -RUN rustup target add armv7-unknown-linux-gnueabihf - -# Append the linker to the cargo config for Windows cross compile -RUN echo "[target.x86_64-pc-windows-gnu]" >> /root/.cargo/config && \ - echo "linker = '/usr/bin/x86_64-w64-mingw32-gcc'" >> /root/.cargo/config - -RUN echo "[target.aarch64-unknown-linux-gnu]" >> /root/.cargo/config && \ - echo "linker = '/usr/bin/aarch64-linux-gnu-gcc'" >> /root/.cargo/config - -RUN echo "[target.armv7-unknown-linux-gnueabihf]" >> /root/.cargo/config && \ - echo "linker = '/usr/bin/arm-linux-gnueabihf-gcc'" >> /root/.cargo/config - -ENV CC_x86_64_unknown_linux_musl="gcc" -ENV CC_aarch64_unknown_linux_gnu="aarch64-linux-gnu-gcc" -ENV CC_armv7_unknown_linux_gnueabhihf="arm-linux-gnueabihf-gcc" - -# This is a bug fix for the windows cross compiler for Rust. -RUN cp /usr/x86_64-w64-mingw32/lib/crt2.o /root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o - -# For windows cross compilation, use a pre-build binary. Remember to set the -# SODIUM_LIB_DIR for windows cross compilation -RUN cd /opt && wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.17-mingw.tar.gz && \ - tar xvf libsodium-1.0.17-mingw.tar.gz - -RUN apt install -y git - -# Cargo fetch the dependencies so we don't download them over and over again -RUN cd /tmp && git clone https://github.com/adityapk00/silentdragonlite-light-cli.git && \ - cd silentdragonlite-light-cli && \ - cargo fetch && \ - cd /tmp && rm -rf silentdragonlite-light-cli +FROM ubuntu:16.04 +LABEL Description="Rust compile env for Linux + Windows (cross)" + +RUN apt update +RUN apt install -y build-essential mingw-w64 gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf curl vim wget + +# Get Rust +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN rustup target add x86_64-pc-windows-gnu +RUN rustup target add aarch64-unknown-linux-gnu +RUN rustup target add armv7-unknown-linux-gnueabihf + +# Append the linker to the cargo config for Windows cross compile +RUN echo "[target.x86_64-pc-windows-gnu]" >> /root/.cargo/config && \ + echo "linker = '/usr/bin/x86_64-w64-mingw32-gcc'" >> /root/.cargo/config + +RUN echo "[target.aarch64-unknown-linux-gnu]" >> /root/.cargo/config && \ + echo "linker = '/usr/bin/aarch64-linux-gnu-gcc'" >> /root/.cargo/config + +RUN echo "[target.armv7-unknown-linux-gnueabihf]" >> /root/.cargo/config && \ + echo "linker = '/usr/bin/arm-linux-gnueabihf-gcc'" >> /root/.cargo/config + +ENV CC_x86_64_unknown_linux_musl="gcc" +ENV CC_aarch64_unknown_linux_gnu="aarch64-linux-gnu-gcc" +ENV CC_armv7_unknown_linux_gnueabhihf="arm-linux-gnueabihf-gcc" + +# This is a bug fix for the windows cross compiler for Rust. +RUN cp /usr/x86_64-w64-mingw32/lib/crt2.o /root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o + +# For windows cross compilation, use a pre-build binary. Remember to set the +# SODIUM_LIB_DIR for windows cross compilation +RUN cd /opt && wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.17-mingw.tar.gz && \ + tar xvf libsodium-1.0.17-mingw.tar.gz + +RUN apt install -y git + +# Cargo fetch the dependencies so we don't download them over and over again +RUN cd /tmp && git clone https://github.com/adityapk00/silentdragonlite-light-cli.git && \ + cd silentdragonlite-light-cli && \ + cargo fetch && \ + cd /tmp && rm -rf silentdragonlite-light-cli diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 2d35112..1e20dab 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,80 +1,80 @@ -[package] -name = "silentdragonxlitelib" -version = "0.1.0" -edition = "2018" - -[features] -default = ["embed_params"] -embed_params = [] - -[dependencies] -base58 = "0.1.0" -bs58 = { version = "0.2", features = ["check"] } -log = "0.4" -log4rs = "0.8.3" -dirs = "2.0.2" -http = "0.2" -hex = "0.3" -protobuf = "2" -byteorder = "1" -json = "0.12.0" -tiny-bip39 = "0.6.2" -secp256k1 = "=0.15.0" -sha2 = "0.8.0" -ripemd160 = "0.8.0" -lazy_static = "1.2.0" -rust-embed = { version = "5.1.0", features = ["debug-embed"] } -rand = "0.7.2" -sodiumoxide = "0.2.5" -ring = "0.16.9" -libflate = "0.1" -subtle = "2" -threadpool = "1.8.0" -num_cpus = "1.13.0" - -tonic = { version = "0.1.1", features = ["tls", "tls-roots"] } -bytes = "0.4" -prost = "0.6" -prost-types = "0.6" -tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds", "full"] } -tokio-rustls = { version = "0.12.1", features = ["dangerous_configuration"] } -webpki = "0.21.0" -webpki-roots = "0.18.0" - -[dependencies.bellman] -git = "https://git.hush.is/hush/librustzcash.git" -rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" -default-features = false -features = ["groth16"] - -[dependencies.pairing] -git = "https://git.hush.is/hush/librustzcash.git" -rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" - -[dependencies.zcash_client_backend] -git = "https://git.hush.is/hush/librustzcash.git" -rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" - -default-features = false - -[dependencies.zcash_primitives] -git = "https://git.hush.is/hush/librustzcash.git" -rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" -default-features = false -features = ["transparent-inputs"] - -[dependencies.zcash_proofs] -git = "https://git.hush.is/hush/librustzcash.git" -rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" -default-features = false - -[dependencies.ff] -git = "https://git.hush.is/hush/librustzcash.git" -rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" -features = ["ff_derive"] - -[build-dependencies] -tonic-build = "0.1.1" - -[dev-dependencies] -tempdir = "0.3.7" +[package] +name = "silentdragonxlitelib" +version = "0.1.0" +edition = "2018" + +[features] +default = ["embed_params"] +embed_params = [] + +[dependencies] +base58 = "0.1.0" +bs58 = { version = "0.2", features = ["check"] } +log = "0.4" +log4rs = "0.8.3" +dirs = "2.0.2" +http = "0.2" +hex = "0.3" +protobuf = "2" +byteorder = "1" +json = "0.12.0" +tiny-bip39 = "0.6.2" +secp256k1 = "=0.15.0" +sha2 = "0.8.0" +ripemd160 = "0.8.0" +lazy_static = "1.2.0" +rust-embed = { version = "5.1.0", features = ["debug-embed"] } +rand = "0.7.2" +sodiumoxide = "0.2.5" +ring = "0.16.9" +libflate = "0.1" +subtle = "2" +threadpool = "1.8.0" +num_cpus = "1.13.0" + +tonic = { version = "0.1.1", features = ["tls", "tls-roots"] } +bytes = "0.4" +prost = "0.6" +prost-types = "0.6" +tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds", "full"] } +tokio-rustls = { version = "0.12.1", features = ["dangerous_configuration"] } +webpki = "0.21.0" +webpki-roots = "0.18.0" + +[dependencies.bellman] +git = "https://git.hush.is/hush/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" +default-features = false +features = ["groth16"] + +[dependencies.pairing] +git = "https://git.hush.is/hush/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" + +[dependencies.zcash_client_backend] +git = "https://git.hush.is/hush/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" + +default-features = false + +[dependencies.zcash_primitives] +git = "https://git.hush.is/hush/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" +default-features = false +features = ["transparent-inputs"] + +[dependencies.zcash_proofs] +git = "https://git.hush.is/hush/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" +default-features = false + +[dependencies.ff] +git = "https://git.hush.is/hush/librustzcash.git" +rev= "acff1444ec373e9c3e37b47ca95bfd358e45255b" +features = ["ff_derive"] + +[build-dependencies] +tonic-build = "0.1.1" + +[dev-dependencies] +tempdir = "0.3.7" diff --git a/lib/build.rs b/lib/build.rs index 7ebc37e..241f937 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -1,13 +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(()) - } - +// 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/lib/proto/compact_formats.proto b/lib/proto/compact_formats.proto index cecce25..4471ae3 100644 --- a/lib/proto/compact_formats.proto +++ b/lib/proto/compact_formats.proto @@ -1,65 +1,65 @@ -syntax = "proto3"; -package cash.z.wallet.sdk.rpc; -option go_package = "lightwalletd/walletrpc"; -option swift_prefix = ""; - -// Remember that proto3 fields are all optional. A field that is not present will be set to its zero value. -// bytes fields of hashes are in canonical little-endian format. - -// CompactBlock is a packaging of ONLY the data from a block that's needed to: -// 1. Detect a payment to your shielded Sapling address -// 2. Detect a spend of your shielded Sapling notes -// 3. Update your witnesses to generate new Sapling spend proofs. -message CompactBlock { - uint32 protoVersion = 1; // the version of this wire format, for storage - uint64 height = 2; // the height of this block - bytes hash = 3; // the ID (hash) of this block, same as in block explorers - bytes prevHash = 4; // the ID (hash) of this block's predecessor - uint32 time = 5; // Unix epoch time when the block was mined - bytes header = 6; // (hash, prevHash, and time) OR (full header) - repeated CompactTx vtx = 7; // compact transactions from this block -} - -message CompactTx { - // Index and hash will allow the receiver to call out to chain - // explorers or other data structures to retrieve more information - // about this transaction. - uint64 index = 1; // the index within the full block - bytes hash = 2; // the ID (hash) of this transaction, same as in block explorers - - // The transaction fee: present if server can provide. In the case of a - // stateless server and a transaction with transparent inputs, this will be - // unset because the calculation requires reference to prior transactions. - // in a pure-Sapling context, the fee will be calculable as: - // valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut)) - uint32 fee = 3; - - repeated CompactSaplingSpend spends = 4; - repeated CompactSaplingOutput outputs = 5; -} - -// CompactSaplingSpend is a Sapling Spend Description as described in 7.3 of the Zcash -// protocol specification. -message CompactSaplingSpend { - bytes nf = 1; // nullifier (see the Zcash protocol specification) -} - -// output is a Sapling Output Description as described in section 7.4 of the -// Zcash protocol spec. Total size is 948. -message CompactSaplingOutput { - bytes cmu = 1; // note commitment u-coordinate - bytes epk = 2; // ephemeral public key - bytes ciphertext = 3; // first 52 bytes of ciphertext -} - -/* -message CompactSpend { - bytes nf = 1; // nullifier (see the Zcash protocol specification) -} - -message CompactOutput { - bytes cmu = 1; // note commitment u-coordinate - bytes epk = 2; // ephemeral public key - bytes ciphertext = 3; // first 52 bytes of ciphertext -} -*/ +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "lightwalletd/walletrpc"; +option swift_prefix = ""; + +// Remember that proto3 fields are all optional. A field that is not present will be set to its zero value. +// bytes fields of hashes are in canonical little-endian format. + +// CompactBlock is a packaging of ONLY the data from a block that's needed to: +// 1. Detect a payment to your shielded Sapling address +// 2. Detect a spend of your shielded Sapling notes +// 3. Update your witnesses to generate new Sapling spend proofs. +message CompactBlock { + uint32 protoVersion = 1; // the version of this wire format, for storage + uint64 height = 2; // the height of this block + bytes hash = 3; // the ID (hash) of this block, same as in block explorers + bytes prevHash = 4; // the ID (hash) of this block's predecessor + uint32 time = 5; // Unix epoch time when the block was mined + bytes header = 6; // (hash, prevHash, and time) OR (full header) + repeated CompactTx vtx = 7; // compact transactions from this block +} + +message CompactTx { + // Index and hash will allow the receiver to call out to chain + // explorers or other data structures to retrieve more information + // about this transaction. + uint64 index = 1; // the index within the full block + bytes hash = 2; // the ID (hash) of this transaction, same as in block explorers + + // The transaction fee: present if server can provide. In the case of a + // stateless server and a transaction with transparent inputs, this will be + // unset because the calculation requires reference to prior transactions. + // in a pure-Sapling context, the fee will be calculable as: + // valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut)) + uint32 fee = 3; + + repeated CompactSaplingSpend spends = 4; + repeated CompactSaplingOutput outputs = 5; +} + +// CompactSaplingSpend is a Sapling Spend Description as described in 7.3 of the Zcash +// protocol specification. +message CompactSaplingSpend { + bytes nf = 1; // nullifier (see the Zcash protocol specification) +} + +// output is a Sapling Output Description as described in section 7.4 of the +// Zcash protocol spec. Total size is 948. +message CompactSaplingOutput { + bytes cmu = 1; // note commitment u-coordinate + bytes epk = 2; // ephemeral public key + bytes ciphertext = 3; // first 52 bytes of ciphertext +} + +/* +message CompactSpend { + bytes nf = 1; // nullifier (see the Zcash protocol specification) +} + +message CompactOutput { + bytes cmu = 1; // note commitment u-coordinate + bytes epk = 2; // ephemeral public key + bytes ciphertext = 3; // first 52 bytes of ciphertext +} +*/ diff --git a/lib/proto/service.proto b/lib/proto/service.proto index aa52e39..0080a37 100644 --- a/lib/proto/service.proto +++ b/lib/proto/service.proto @@ -1,170 +1,170 @@ -syntax = "proto3"; -package cash.z.wallet.sdk.rpc; -option go_package = "lightwalletd/walletrpc"; -option swift_prefix = ""; -import "compact_formats.proto"; - -// A BlockID message contains identifiers to select a block: a height or a -// hash. If the hash is present it takes precedence. -message BlockID { - uint64 height = 1; - bytes hash = 2; -} - -// BlockRange technically allows ranging from hash to hash etc but this is not -// currently intended for support, though there is no reason you couldn't do -// it. Further permutations are left as an exercise. -message BlockRange { - BlockID start = 1; - BlockID end = 2; -} - -// A TxFilter contains the information needed to identify a particular -// transaction: either a block and an index, or a direct transaction hash. -message TxFilter { - BlockID block = 1; - uint64 index = 2; - bytes hash = 3; -} - -// RawTransaction contains the complete transaction data. It also optionally includes -// the block height in which the transaction was included -message RawTransaction { - bytes data = 1; - uint64 height = 2; -} - -message SendResponse { - int32 errorCode = 1; - string errorMessage = 2; -} - -// Empty placeholder. Someday we may want to specify e.g. a particular chain fork. -message ChainSpec {} - -message Empty {} - -message LightdInfo { - string version = 1; - string vendor = 2; - bool taddrSupport = 3; - string chainName = 4; - uint64 saplingActivationHeight = 5; - string consensusBranchId = 6; // This should really be u32 or []byte, but string for readability - uint64 blockHeight = 7; - uint64 difficulty = 8; - uint64 longestchain = 9; - uint64 notarized = 10; -} -message Coinsupply { - string result = 1; - string coin = 2; - uint64 height = 3; - uint64 supply = 4; - uint64 zfunds = 5; - uint64 total = 6; -} - -message TransparentAddress { - string address = 1; -} - -message TransparentAddressBlockFilter { - string address = 1; - BlockRange range = 2; -} - -message Address { - string address = 1; -} -message AddressList { - repeated string addresses = 1; -} -message Balance { - int64 valueZat = 1; -} - -message Exclude { - repeated bytes txid = 1; -} - -// The TreeState is derived from the Hush getblockmerkletree rpc. -// https://faq.hush.is/rpc/getblockmerkletree.html -message TreeState { - string network = 1; // "main" or "test" - uint64 height = 2; // block height - string hash = 3; // block id - uint32 time = 4; // Unix epoch time when the block was mined - string saplingTree = 5; // sapling commitment tree state -} - -// Results are sorted by height, which makes it easy to issue another -// request that picks up from where the previous left off. -message GetAddressUtxosArg { - repeated string addresses = 1; - uint64 startHeight = 2; - uint32 maxEntries = 3; // zero means unlimited -} -message GetAddressUtxosReply { - string address = 6; - bytes txid = 1; - int32 index = 2; - bytes script = 3; - int64 valueZat = 4; - uint64 height = 5; -} -message GetAddressUtxosReplyList { - repeated GetAddressUtxosReply addressUtxos = 1; -} - -service CompactTxStreamer { - // Return the height of the tip of the best chain - rpc GetLatestBlock(ChainSpec) returns (BlockID) {} - // Return the compact block corresponding to the given block identifier - rpc GetBlock(BlockID) returns (CompactBlock) {} - // Return a list of consecutive compact blocks - rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {} - - // Return the requested full (not compact) transaction (as from zcashd) - rpc GetTransaction(TxFilter) returns (RawTransaction) {} - // Submit the given transaction to the Zcash network - rpc SendTransaction(RawTransaction) returns (SendResponse) {} - - // Return the txids corresponding to the given t-address within the given block range - rpc GetTaddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} - // wrapper for GetTaddressTxids - rpc GetAddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} - rpc GetTaddressBalance(AddressList) returns (Balance) {} - rpc GetTaddressBalanceStream(stream Address) returns (Balance) {} - - // Return the compact transactions currently in the mempool; the results - // can be a few seconds out of date. If the Exclude list is empty, return - // all transactions; otherwise return all *except* those in the Exclude list - // (if any); this allows the client to avoid receiving transactions that it - // already has (from an earlier call to this rpc). The transaction IDs in the - // Exclude list can be shortened to any number of bytes to make the request - // more bandwidth-efficient; if two or more transactions in the mempool - // match a shortened txid, they are all sent (none is excluded). Transactions - // in the exclude list that don't exist in the mempool are ignored. - rpc GetMempoolTx(Exclude) returns (stream CompactTx) {} - - // Return a stream of current Mempool transactions. This will keep the output stream open while - // there are mempool transactions. It will close the returned stream when a new block is mined. - rpc GetMempoolStream(Empty) returns (stream RawTransaction) {} - - // GetTreeState returns the note commitment tree state corresponding to the given block. - // See section 3.7 of the Zcash protocol specification. It returns several other useful - // values also (even though they can be obtained using GetBlock). - // The block can be specified by either height or hash. - rpc GetTreeState(BlockID) returns (TreeState) {} - rpc GetLatestTreeState(Empty) returns (TreeState) {} - - rpc GetAddressUtxos(GetAddressUtxosArg) returns (GetAddressUtxosReplyList) {} - rpc GetAddressUtxosStream(GetAddressUtxosArg) returns (stream GetAddressUtxosReply) {} - - // Return information about this lightwalletd instance and the blockchain - rpc GetLightdInfo(Empty) returns (LightdInfo) {} - // Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production) - // rpc Ping(Duration) returns (PingResponse) {} - rpc GetCoinsupply(Empty) returns (Coinsupply) {} +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "lightwalletd/walletrpc"; +option swift_prefix = ""; +import "compact_formats.proto"; + +// A BlockID message contains identifiers to select a block: a height or a +// hash. If the hash is present it takes precedence. +message BlockID { + uint64 height = 1; + bytes hash = 2; +} + +// BlockRange technically allows ranging from hash to hash etc but this is not +// currently intended for support, though there is no reason you couldn't do +// it. Further permutations are left as an exercise. +message BlockRange { + BlockID start = 1; + BlockID end = 2; +} + +// A TxFilter contains the information needed to identify a particular +// transaction: either a block and an index, or a direct transaction hash. +message TxFilter { + BlockID block = 1; + uint64 index = 2; + bytes hash = 3; +} + +// RawTransaction contains the complete transaction data. It also optionally includes +// the block height in which the transaction was included +message RawTransaction { + bytes data = 1; + uint64 height = 2; +} + +message SendResponse { + int32 errorCode = 1; + string errorMessage = 2; +} + +// Empty placeholder. Someday we may want to specify e.g. a particular chain fork. +message ChainSpec {} + +message Empty {} + +message LightdInfo { + string version = 1; + string vendor = 2; + bool taddrSupport = 3; + string chainName = 4; + uint64 saplingActivationHeight = 5; + string consensusBranchId = 6; // This should really be u32 or []byte, but string for readability + uint64 blockHeight = 7; + uint64 difficulty = 8; + uint64 longestchain = 9; + uint64 notarized = 10; +} +message Coinsupply { + string result = 1; + string coin = 2; + uint64 height = 3; + uint64 supply = 4; + uint64 zfunds = 5; + uint64 total = 6; +} + +message TransparentAddress { + string address = 1; +} + +message TransparentAddressBlockFilter { + string address = 1; + BlockRange range = 2; +} + +message Address { + string address = 1; +} +message AddressList { + repeated string addresses = 1; +} +message Balance { + int64 valueZat = 1; +} + +message Exclude { + repeated bytes txid = 1; +} + +// The TreeState is derived from the Hush getblockmerkletree rpc. +// https://faq.hush.is/rpc/getblockmerkletree.html +message TreeState { + string network = 1; // "main" or "test" + uint64 height = 2; // block height + string hash = 3; // block id + uint32 time = 4; // Unix epoch time when the block was mined + string saplingTree = 5; // sapling commitment tree state +} + +// Results are sorted by height, which makes it easy to issue another +// request that picks up from where the previous left off. +message GetAddressUtxosArg { + repeated string addresses = 1; + uint64 startHeight = 2; + uint32 maxEntries = 3; // zero means unlimited +} +message GetAddressUtxosReply { + string address = 6; + bytes txid = 1; + int32 index = 2; + bytes script = 3; + int64 valueZat = 4; + uint64 height = 5; +} +message GetAddressUtxosReplyList { + repeated GetAddressUtxosReply addressUtxos = 1; +} + +service CompactTxStreamer { + // Return the height of the tip of the best chain + rpc GetLatestBlock(ChainSpec) returns (BlockID) {} + // Return the compact block corresponding to the given block identifier + rpc GetBlock(BlockID) returns (CompactBlock) {} + // Return a list of consecutive compact blocks + rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {} + + // Return the requested full (not compact) transaction (as from zcashd) + rpc GetTransaction(TxFilter) returns (RawTransaction) {} + // Submit the given transaction to the Zcash network + rpc SendTransaction(RawTransaction) returns (SendResponse) {} + + // Return the txids corresponding to the given t-address within the given block range + rpc GetTaddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} + // wrapper for GetTaddressTxids + rpc GetAddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} + rpc GetTaddressBalance(AddressList) returns (Balance) {} + rpc GetTaddressBalanceStream(stream Address) returns (Balance) {} + + // Return the compact transactions currently in the mempool; the results + // can be a few seconds out of date. If the Exclude list is empty, return + // all transactions; otherwise return all *except* those in the Exclude list + // (if any); this allows the client to avoid receiving transactions that it + // already has (from an earlier call to this rpc). The transaction IDs in the + // Exclude list can be shortened to any number of bytes to make the request + // more bandwidth-efficient; if two or more transactions in the mempool + // match a shortened txid, they are all sent (none is excluded). Transactions + // in the exclude list that don't exist in the mempool are ignored. + rpc GetMempoolTx(Exclude) returns (stream CompactTx) {} + + // Return a stream of current Mempool transactions. This will keep the output stream open while + // there are mempool transactions. It will close the returned stream when a new block is mined. + rpc GetMempoolStream(Empty) returns (stream RawTransaction) {} + + // GetTreeState returns the note commitment tree state corresponding to the given block. + // See section 3.7 of the Zcash protocol specification. It returns several other useful + // values also (even though they can be obtained using GetBlock). + // The block can be specified by either height or hash. + rpc GetTreeState(BlockID) returns (TreeState) {} + rpc GetLatestTreeState(Empty) returns (TreeState) {} + + rpc GetAddressUtxos(GetAddressUtxosArg) returns (GetAddressUtxosReplyList) {} + rpc GetAddressUtxosStream(GetAddressUtxosArg) returns (stream GetAddressUtxosReply) {} + + // Return information about this lightwalletd instance and the blockchain + rpc GetLightdInfo(Empty) returns (LightdInfo) {} + // Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production) + // rpc Ping(Duration) returns (PingResponse) {} + rpc GetCoinsupply(Empty) returns (Coinsupply) {} } \ No newline at end of file diff --git a/lib/src/commands.rs b/lib/src/commands.rs index 66f43be..6972e72 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -1,975 +1,975 @@ -// 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 { - 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 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("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 - } -} +// 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 { + 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 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("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/lib/src/grpcconnector.rs b/lib/src/grpcconnector.rs index 8ade8f0..ba08292 100644 --- a/lib/src/grpcconnector.rs +++ b/lib/src/grpcconnector.rs @@ -1,366 +1,363 @@ -// Copyright The Hush Developers 2019-2022 -// Released under the GPLv3 -use log::{info,error}; -use std::sync::Arc; -use zcash_primitives::transaction::{TxId}; - -use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, CompactBlock, - TransparentAddressBlockFilter, TxFilter, Empty, LightdInfo, Coinsupply}; -use tonic::transport::{Channel, ClientTlsConfig}; -use tokio_rustls::{rustls::ClientConfig}; -use tonic::{Request}; - -use threadpool::ThreadPool; -use std::sync::mpsc::channel; - -use crate::PubCertificate; -use crate::grpc_client::compact_tx_streamer_client::CompactTxStreamerClient; - -mod danger { - use tokio_rustls::rustls; - use webpki; - - pub struct NoCertificateVerification {} - - impl rustls::ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert(&self, - _roots: &rustls::RootCertStore, - _presented_certs: &[rustls::Certificate], - _dns_name: webpki::DNSNameRef<'_>, - _ocsp: &[u8]) -> Result { - 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 = match tokio::runtime::Runtime::new() { - Ok(r) => r, - Err(e) => { - let es = format!("Error creating runtime {:?}", e); - error!("{}", es); - return Err(es); - } - }; - - match rt.block_on(get_block_range(uri, start_height, end_height, no_cert, pool, c)) { - Ok(o) => Ok(o), - Err(e) => { - let e = format!("Error fetching blocks {:?}", e); - error!("{}", e); - Err(e) - } - } -} - -// get_address_txids GRPC call -async fn get_address_txids( - 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 - }) -} +// 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/lib/src/lib.rs b/lib/src/lib.rs index 96b76ea..197a4ac 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,25 +1,25 @@ -// Copyright The Hush Developers 2019-2022 -// Released under the GPLv3 -#[macro_use] -extern crate rust_embed; - -pub mod lightclient; -pub mod grpcconnector; -pub mod lightwallet; -pub mod commands; - -#[cfg(feature = "embed_params")] -#[derive(RustEmbed)] -#[folder = "zcash-params/"] -pub struct SaplingParams; - -#[derive(RustEmbed)] -#[folder = "res/"] -pub struct PubCertificate; - - -pub const ANCHOR_OFFSET: u32 = 0; - -pub mod grpc_client { - tonic::include_proto!("cash.z.wallet.sdk.rpc"); -} +// Copyright The Hush Developers 2019-2022 +// Released under the GPLv3 +#[macro_use] +extern crate rust_embed; + +pub mod lightclient; +pub mod grpcconnector; +pub mod lightwallet; +pub mod commands; + +#[cfg(feature = "embed_params")] +#[derive(RustEmbed)] +#[folder = "zcash-params/"] +pub struct SaplingParams; + +#[derive(RustEmbed)] +#[folder = "res/"] +pub struct PubCertificate; + + +pub const ANCHOR_OFFSET: u32 = 0; + +pub mod grpc_client { + tonic::include_proto!("cash.z.wallet.sdk.rpc"); +} diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index c05d8cd..ca754a9 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -1,1798 +1,1802 @@ -// 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::{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. -} - -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())), - }; - - 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())), - }; - - 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())), - }; - - // 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() { - return Err(Error::new(ErrorKind::AlreadyExists, - format!("Cannot read wallet. No file at {}", config.get_wallet_path().display()))); - } - - let mut file_buffer = BufReader::new(File::open(config.get_wallet_path())?); - - let wallet = LightWallet::read(&mut file_buffer, config)?; - 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())), - }; - - #[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 = self.wallet.write().unwrap(); - let mut file_buffer = BufWriter::with_capacity( - 1_000_000, // 1 MB write buffer - File::create(self.config.get_wallet_path()).unwrap()); - - r = match wallet.write(&mut file_buffer) { - Ok(_) => Ok(()), - Err(e) => { - let err = format!("ERR: {}", e); - error!("{}", err); - Err(e.to_string()) - } - }; - - file_buffer.flush().map_err(|e| format!("{}", e))?; - } - - r - } - - pub fn get_server_uri(&self) -> http::Uri { - self.config.server.clone() - } - - 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 (incoming_mempool_tx, incoming_mempool_rx) = std::sync::mpsc::channel::(); - - // Thread for reveive transactions - std::thread::spawn(move || { - while let Ok(rtx) = incoming_mempool_rx.recv() { - 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 { - 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) => warn!("Mempool monitor returned {:?}, will restart listening", e), - } - - std::thread::sleep(Duration::from_secs(10)); - } - }); - }); - - Ok(()) -} - /// 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) { - // First, clear the state from the wallet - self.wallet.read().unwrap().clear_blocks(); - - // Then set the initial block - self.set_wallet_initial_state(self.wallet.read().unwrap().get_birthday()); - info!("Cleared wallet state"); - } - - 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. - let scan_batch_size = 1000; - 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()))); - - // 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(&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 + 1000, 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 + 1000; - - 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())), - }; - { - 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()); - } - } - -} +// 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::{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. +} + +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())), + }; + + 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())), + }; + + 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())), + }; + + // 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() { + return Err(Error::new(ErrorKind::AlreadyExists, + format!("Cannot read wallet. No file at {}", config.get_wallet_path().display()))); + } + + let mut file_buffer = BufReader::new(File::open(config.get_wallet_path())?); + + let wallet = LightWallet::read(&mut file_buffer, config)?; + 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())), + }; + + #[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 = self.wallet.write().unwrap(); + let mut file_buffer = BufWriter::with_capacity( + 1_000_000, // 1 MB write buffer + File::create(self.config.get_wallet_path()).unwrap()); + + r = match wallet.write(&mut file_buffer) { + Ok(_) => Ok(()), + Err(e) => { + let err = format!("ERR: {}", e); + error!("{}", err); + Err(e.to_string()) + } + }; + + file_buffer.flush().map_err(|e| format!("{}", e))?; + } + + r + } + + pub fn get_server_uri(&self) -> http::Uri { + self.config.server.clone() + } + + 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 (incoming_mempool_tx, incoming_mempool_rx) = std::sync::mpsc::channel::(); + + // Thread for reveive transactions + std::thread::spawn(move || { + while let Ok(rtx) = incoming_mempool_rx.recv() { + 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 { + 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) => warn!("Mempool monitor returned {:?}, will restart listening", e), + } + + std::thread::sleep(Duration::from_secs(10)); + } + }); + }); + + Ok(()) +} + /// 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) { + // First, clear the state from the wallet + self.wallet.read().unwrap().clear_blocks(); + + // Then set the initial block + self.set_wallet_initial_state(self.wallet.read().unwrap().get_birthday()); + info!("Cleared wallet state"); + } + + 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. + let scan_batch_size = 1000; + 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 + 1000, 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 + 1000; + + 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())), + }; + { + 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/lib/src/lightclient/checkpoints.rs b/lib/src/lightclient/checkpoints.rs index 8190be8..4f9b69e 100644 --- a/lib/src/lightclient/checkpoints.rs +++ b/lib/src/lightclient/checkpoints.rs @@ -1,616 +1,616 @@ - -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" - ), -]; - - 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); - } - -} + +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" + ), +]; + + 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/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index c264c66..2e41b08 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -1,2527 +1,2554 @@ -// 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) { - self.blocks.write().unwrap().clear(); - self.txs.write().unwrap().clear(); - self.mempool_txs.write().unwrap().clear(); - self.incoming_mempool_txs.write().unwrap().clear(); - } - - pub fn set_initial_block(&self, height: i32, hash: &str, sapling_tree: &str) -> bool { - let mut blocks = self.blocks.write().unwrap(); - 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 - // First, collect all our z addresses, to check for change - // Collect z addresses - let z_addresses = self.zkeys.read().unwrap().iter().map( |zk| { - encode_payment_address(self.config.hrp_sapling_address(), &zk.zaddress) - }).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 is change, and if it also doesn't have a memo, don't add - // to the outgoing metadata. - // If this is change (i.e., funds sent to ourself) AND has a memo, then - // presumably the users is writing a memo to themself, so we will add it to - // the outgoing metadata, even though it might be confusing in the UI, but hopefully - // the user can make sense of it. - if z_addresses.contains(&address) && memo.to_utf8().is_none() { - 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 = self.blocks.write().unwrap(); - - while blks.last().unwrap().height >= at_height { - blks.pop(); - num_invalidated += 1; - } - } - - // Next, remove entire transactions - { - let mut txs = self.txs.write().unwrap(); - 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 = self.txs.write().unwrap(); - - // 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 { - let cmu = output.cmu().ok()?; - let epk = output.epk().ok()?; - 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; +// 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 + // First, collect all our z addresses, to check for change + // Collect z addresses + let z_addresses = self.zkeys.read().unwrap().iter().map( |zk| { + encode_payment_address(self.config.hrp_sapling_address(), &zk.zaddress) + }).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 is change, and if it also doesn't have a memo, don't add + // to the outgoing metadata. + // If this is change (i.e., funds sent to ourself) AND has a memo, then + // presumably the users is writing a memo to themself, so we will add it to + // the outgoing metadata, even though it might be confusing in the UI, but hopefully + // the user can make sense of it. + if z_addresses.contains(&address) && memo.to_utf8().is_none() { + 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 { + let cmu = output.cmu().ok()?; + let epk = output.epk().ok()?; + 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/lib/src/lightwallet/address.rs b/lib/src/lightwallet/address.rs index 2d1ca0f..b1080be 100644 --- a/lib/src/lightwallet/address.rs +++ b/lib/src/lightwallet/address.rs @@ -1,46 +1,46 @@ -//! Structs for handling supported address types. - -use pairing::bls12_381::Bls12; -use zcash_primitives::primitives::PaymentAddress; -use zcash_client_backend::encoding::{decode_payment_address, decode_transparent_address}; -use zcash_primitives::legacy::TransparentAddress; - -/// An address that funds can be sent to. -pub enum RecipientAddress { - Shielded(PaymentAddress), - 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 - } - } -} +//! 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/lib/src/lightwallet/data.rs b/lib/src/lightwallet/data.rs index 5b0208e..5c64aac 100644 --- a/lib/src/lightwallet/data.rs +++ b/lib/src/lightwallet/data.rs @@ -1,558 +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 - } - } -} +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/lib/src/lightwallet/extended_key.rs b/lib/src/lightwallet/extended_key.rs index 942f6ba..2758155 100644 --- a/lib/src/lightwallet/extended_key.rs +++ b/lib/src/lightwallet/extended_key.rs @@ -1,126 +1,126 @@ -use ring::{ - hmac::{self, Context, Key}, -}; -use lazy_static::lazy_static; -use secp256k1::{PublicKey, Secp256k1, SecretKey, SignOnly, VerifyOnly, Error}; - -lazy_static! { - static ref SECP256K1_SIGN_ONLY: Secp256k1 = 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(), - }) - } -} +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/lib/src/lightwallet/prover.rs b/lib/src/lightwallet/prover.rs index ae98694..85121a3 100644 --- a/lib/src/lightwallet/prover.rs +++ b/lib/src/lightwallet/prover.rs @@ -1,123 +1,123 @@ -//! Abstractions over the proving system and parameters for ease of use. - -use bellman::groth16::{prepare_verifying_key, Parameters, PreparedVerifyingKey}; -use pairing::bls12_381::{Bls12, Fr}; -use zcash_primitives::{ - jubjub::{edwards, fs::Fs, Unknown}, - primitives::{Diversifier, PaymentAddress, ProofGenerationKey}, - redjubjub::{PublicKey, Signature}, - transaction::components::Amount -}; -use zcash_primitives::{ - merkle_tree::CommitmentTreeWitness, prover::TxProver, sapling::Node, - transaction::components::GROTH_PROOF_SIZE, JUBJUB, -}; -use zcash_proofs::sapling::SaplingProvingContext; - -/// An implementation of [`TxProver`] using Sapling Spend and Output parameters provided -/// in-memory. -pub struct InMemTxProver { - spend_params: Parameters, - 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) - } -} +//! 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/lib/src/lightwallet/tests.rs b/lib/src/lightwallet/tests.rs index e4853d2..fb43756 100644 --- a/lib/src/lightwallet/tests.rs +++ b/lib/src/lightwallet/tests.rs @@ -1,2339 +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); - } -} +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/lib/src/lightwallet/utils.rs b/lib/src/lightwallet/utils.rs index 1ece11e..5b16b8f 100644 --- a/lib/src/lightwallet/utils.rs +++ b/lib/src/lightwallet/utils.rs @@ -1,21 +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()) +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/lib/src/lightwallet/walletzkey.rs b/lib/src/lightwallet/walletzkey.rs index ee1dec7..554fb1c 100644 --- a/lib/src/lightwallet/walletzkey.rs +++ b/lib/src/lightwallet/walletzkey.rs @@ -1,585 +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); - } - } - - +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 diff --git a/mkrelease.sh b/mkrelease.sh index f321d73..0303c6e 100755 --- a/mkrelease.sh +++ b/mkrelease.sh @@ -1,111 +1,111 @@ -#!/bin/bash -# This script depends on a docker image already being built -# To build it, -# cd docker -# docker build --tag rustbuild:latest . - -POSITIONAL=() -while [[ $# -gt 0 ]] -do -key="$1" - -case $key in - -v|--version) - APP_VERSION="$2" - shift # past argument - shift # past value - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; -esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -if [ -z $APP_VERSION ]; then echo "APP_VERSION is not set"; exit 1; fi - -# Write the version file -echo "pub const VERSION:&str = \"$APP_VERSION\";" > /cli/src/version.rs - -# First, do the tests -cd lib && cargo test --release -retVal=$? -if [ $retVal -ne 0 ]; then - echo "Error" - exit $retVal -fi -cd .. - -# Compile for mac directly -cargo build --release - -#macOS -rm -rf target/macOS-silentdragonxlite-cli-v$APP_VERSION -mkdir -p target/macOS-silentdragonxlite-cli-v$APP_VERSION -cp target/release/silentdragonxlite-cli target/macOS-silentdragonxlite-cli-v$APP_VERSION/ - -# For Windows and Linux, build via docker -docker run --rm -v $(pwd)/:/opt/silentdragonxlite-cli rustbuild:latest bash -c "cd /opt/silentdragonxlite-cli && cargo build --release && cargo build --release --target armv7-unknown-linux-gnueabihf && cargo build --release --target aarch64-unknown-linux-gnu && SODIUM_LIB_DIR='/opt/libsodium-win64/lib/' cargo build --release --target x86_64-pc-windows-gnu" - -# Now sign and zip the binaries -# macOS -gpg --batch --output target/macOS-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/macOS-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli -cd target -cd macOS-silentdragonxlite-cli-v$APP_VERSION -gsha256sum silentdragonxlite-cli > sha256sum.txt -cd .. -zip -r macOS-silentdragonxlite-cli-v$APP_VERSION.zip macOS-silentdragonxlite-cli-v$APP_VERSION -cd .. - - -#Linux -rm -rf target/linux-silentdragonxlite-cli-v$APP_VERSION -mkdir -p target/linux-silentdragonxlite-cli-v$APP_VERSION -cp target/release/silentdragonxlite-cli target/linux-silentdragonxlite-cli-v$APP_VERSION/ -gpg --batch --output target/linux-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/linux-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli -cd target -cd linux-silentdragonxlite-cli-v$APP_VERSION -gsha256sum silentdragonxlite-cli > sha256sum.txt -cd .. -zip -r linux-silentdragonxlite-cli-v$APP_VERSION.zip linux-silentdragonxlite-cli-v$APP_VERSION -cd .. - - -#Windows -rm -rf target/Windows-silentdragonxlite-cli-v$APP_VERSION -mkdir -p target/Windows-silentdragonxlite-cli-v$APP_VERSION -cp target/x86_64-pc-windows-gnu/release/silentdragonxlite-cli.exe target/Windows-silentdragonxlite-cli-v$APP_VERSION/ -gpg --batch --output target/Windows-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/Windows-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.exe -cd target -cd Windows-silentdragonxlite-cli-v$APP_VERSION -gsha256sum silentdragonxlite-cli.exe > sha256sum.txt -cd .. -zip -r Windows-silentdragonxlite-cli-v$APP_VERSION.zip Windows-silentdragonxlite-cli-v$APP_VERSION -cd .. - - -#Armv7 -rm -rf target/Armv7-silentdragonxlite-cli-v$APP_VERSION -mkdir -p target/Armv7-silentdragonxlite-cli-v$APP_VERSION -cp target/armv7-unknown-linux-gnueabihf/release/silentdragonxlite-cli target/Armv7-silentdragonxlite-cli-v$APP_VERSION/ -gpg --batch --output target/Armv7-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/Armv7-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli -cd target -cd Armv7-silentdragonxlite-cli-v$APP_VERSION -gsha256sum silentdragonxlite-cli > sha256sum.txt -cd .. -zip -r Armv7-silentdragonxlite-cli-v$APP_VERSION.zip Armv7-silentdragonxlite-cli-v$APP_VERSION -cd .. - - -#AARCH64 -rm -rf target/aarch64-silentdragonxlite-cli-v$APP_VERSION -mkdir -p target/aarch64-silentdragonxlite-cli-v$APP_VERSION -cp target/aarch64-unknown-linux-gnu/release/silentdragonxlite-cli target/aarch64-silentdragonxlite-cli-v$APP_VERSION/ -gpg --batch --output target/aarch64-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/aarch64-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli -cd target -cd aarch64-silentdragonxlite-cli-v$APP_VERSION -gsha256sum silentdragonxlite-cli > sha256sum.txt -cd .. -zip -r aarch64-silentdragonxlite-cli-v$APP_VERSION.zip aarch64-silentdragonxlite-cli-v$APP_VERSION -cd .. +#!/bin/bash +# This script depends on a docker image already being built +# To build it, +# cd docker +# docker build --tag rustbuild:latest . + +POSITIONAL=() +while [[ $# -gt 0 ]] +do +key="$1" + +case $key in + -v|--version) + APP_VERSION="$2" + shift # past argument + shift # past value + ;; + *) # unknown option + POSITIONAL+=("$1") # save it in an array for later + shift # past argument + ;; +esac +done +set -- "${POSITIONAL[@]}" # restore positional parameters + +if [ -z $APP_VERSION ]; then echo "APP_VERSION is not set"; exit 1; fi + +# Write the version file +echo "pub const VERSION:&str = \"$APP_VERSION\";" > /cli/src/version.rs + +# First, do the tests +cd lib && cargo test --release +retVal=$? +if [ $retVal -ne 0 ]; then + echo "Error" + exit $retVal +fi +cd .. + +# Compile for mac directly +cargo build --release + +#macOS +rm -rf target/macOS-silentdragonxlite-cli-v$APP_VERSION +mkdir -p target/macOS-silentdragonxlite-cli-v$APP_VERSION +cp target/release/silentdragonxlite-cli target/macOS-silentdragonxlite-cli-v$APP_VERSION/ + +# For Windows and Linux, build via docker +docker run --rm -v $(pwd)/:/opt/silentdragonxlite-cli rustbuild:latest bash -c "cd /opt/silentdragonxlite-cli && cargo build --release && cargo build --release --target armv7-unknown-linux-gnueabihf && cargo build --release --target aarch64-unknown-linux-gnu && SODIUM_LIB_DIR='/opt/libsodium-win64/lib/' cargo build --release --target x86_64-pc-windows-gnu" + +# Now sign and zip the binaries +# macOS +gpg --batch --output target/macOS-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/macOS-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli +cd target +cd macOS-silentdragonxlite-cli-v$APP_VERSION +gsha256sum silentdragonxlite-cli > sha256sum.txt +cd .. +zip -r macOS-silentdragonxlite-cli-v$APP_VERSION.zip macOS-silentdragonxlite-cli-v$APP_VERSION +cd .. + + +#Linux +rm -rf target/linux-silentdragonxlite-cli-v$APP_VERSION +mkdir -p target/linux-silentdragonxlite-cli-v$APP_VERSION +cp target/release/silentdragonxlite-cli target/linux-silentdragonxlite-cli-v$APP_VERSION/ +gpg --batch --output target/linux-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/linux-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli +cd target +cd linux-silentdragonxlite-cli-v$APP_VERSION +gsha256sum silentdragonxlite-cli > sha256sum.txt +cd .. +zip -r linux-silentdragonxlite-cli-v$APP_VERSION.zip linux-silentdragonxlite-cli-v$APP_VERSION +cd .. + + +#Windows +rm -rf target/Windows-silentdragonxlite-cli-v$APP_VERSION +mkdir -p target/Windows-silentdragonxlite-cli-v$APP_VERSION +cp target/x86_64-pc-windows-gnu/release/silentdragonxlite-cli.exe target/Windows-silentdragonxlite-cli-v$APP_VERSION/ +gpg --batch --output target/Windows-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/Windows-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.exe +cd target +cd Windows-silentdragonxlite-cli-v$APP_VERSION +gsha256sum silentdragonxlite-cli.exe > sha256sum.txt +cd .. +zip -r Windows-silentdragonxlite-cli-v$APP_VERSION.zip Windows-silentdragonxlite-cli-v$APP_VERSION +cd .. + + +#Armv7 +rm -rf target/Armv7-silentdragonxlite-cli-v$APP_VERSION +mkdir -p target/Armv7-silentdragonxlite-cli-v$APP_VERSION +cp target/armv7-unknown-linux-gnueabihf/release/silentdragonxlite-cli target/Armv7-silentdragonxlite-cli-v$APP_VERSION/ +gpg --batch --output target/Armv7-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/Armv7-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli +cd target +cd Armv7-silentdragonxlite-cli-v$APP_VERSION +gsha256sum silentdragonxlite-cli > sha256sum.txt +cd .. +zip -r Armv7-silentdragonxlite-cli-v$APP_VERSION.zip Armv7-silentdragonxlite-cli-v$APP_VERSION +cd .. + + +#AARCH64 +rm -rf target/aarch64-silentdragonxlite-cli-v$APP_VERSION +mkdir -p target/aarch64-silentdragonxlite-cli-v$APP_VERSION +cp target/aarch64-unknown-linux-gnu/release/silentdragonxlite-cli target/aarch64-silentdragonxlite-cli-v$APP_VERSION/ +gpg --batch --output target/aarch64-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli.sig --detach-sig target/aarch64-silentdragonxlite-cli-v$APP_VERSION/silentdragonxlite-cli +cd target +cd aarch64-silentdragonxlite-cli-v$APP_VERSION +gsha256sum silentdragonxlite-cli > sha256sum.txt +cd .. +zip -r aarch64-silentdragonxlite-cli-v$APP_VERSION.zip aarch64-silentdragonxlite-cli-v$APP_VERSION +cd .. diff --git a/util/build.sh b/util/build.sh index 32fa997..dbc29cc 100755 --- a/util/build.sh +++ b/util/build.sh @@ -1,50 +1,50 @@ -#!/usr/bin/env bash -# Copyright 2021-2022 The Hush Developers -# Distributed under the GPLv3 software license, see the accompanying -# file LICENSE or https://www.gnu.org/licenses/gpl-3.0.en.html - -# Purpose: Script to build Hush silentdragonxlite on x86 64-bit arch -## Usage: ./util/build.sh - -# Check if rustc is installed on system and exits if it is not -if ! [ -x "$(command -v rustc)" ]; then - echo 'Error: rustc is not installed. Install it and try again.' >&2 - exit 1 -fi -# Check if cargo is installed on system and exits if it is not -if ! [ -x "$(command -v cargo)" ]; then - echo 'Error: cargo is not installed. Install it and try again.' >&2 - exit 1 -fi -# Check if rustfmt is installed on system and exits if it is not -if ! [ -x "$(command -v rustfmt)" ]; then - echo 'Error: rustfmt is not installed. Install it and try again.' >&2 - exit 1 -fi - -echo "" -echo "Welcome to the Hush magic folks..." -echo "" -echo " #### ##### # #### # # ##### # # # # ##### ##### # # # ###### " -echo "# # # # # # # # # # # # # # # # # # ## ## # " -echo " #### # # # ##### # # # ##### # # # # # # # # # ## # ##### " -echo " # # # # # # # # # # # # # # # # # # # # " -echo "# # # # # # # # # # # # # # # # # # # # # # " -echo " #### ##### ###### #### ###### # ##### #### # ###### ##### # # # # ###### " - -# now to compiling... -echo "" -echo "You have the requirements installed, so let's build!" - -cargo build --release - -# check if compile was success -if [ $? -ne 0 ]; then - echo "" - echo 'Error: Something went wrong and it did not build successfully... Please reach out if you need support.' >&2 - exit 1 -fi - -echo "" -echo "Hush silentdragonxlite-cli is now compiled for you. Enjoy and reach out if you need support." -echo "For options, run ./silentdragonxlite --help" +#!/usr/bin/env bash +# Copyright 2021-2022 The Hush Developers +# Distributed under the GPLv3 software license, see the accompanying +# file LICENSE or https://www.gnu.org/licenses/gpl-3.0.en.html + +# Purpose: Script to build Hush silentdragonxlite on x86 64-bit arch +## Usage: ./util/build.sh + +# Check if rustc is installed on system and exits if it is not +if ! [ -x "$(command -v rustc)" ]; then + echo 'Error: rustc is not installed. Install it and try again.' >&2 + exit 1 +fi +# Check if cargo is installed on system and exits if it is not +if ! [ -x "$(command -v cargo)" ]; then + echo 'Error: cargo is not installed. Install it and try again.' >&2 + exit 1 +fi +# Check if rustfmt is installed on system and exits if it is not +if ! [ -x "$(command -v rustfmt)" ]; then + echo 'Error: rustfmt is not installed. Install it and try again.' >&2 + exit 1 +fi + +echo "" +echo "Welcome to the Hush magic folks..." +echo "" +echo " #### ##### # #### # # ##### # # # # ##### ##### # # # ###### " +echo "# # # # # # # # # # # # # # # # # # ## ## # " +echo " #### # # # ##### # # # ##### # # # # # # # # # ## # ##### " +echo " # # # # # # # # # # # # # # # # # # # # " +echo "# # # # # # # # # # # # # # # # # # # # # # " +echo " #### ##### ###### #### ###### # ##### #### # ###### ##### # # # # ###### " + +# now to compiling... +echo "" +echo "You have the requirements installed, so let's build!" + +cargo build --release + +# check if compile was success +if [ $? -ne 0 ]; then + echo "" + echo 'Error: Something went wrong and it did not build successfully... Please reach out if you need support.' >&2 + exit 1 +fi + +echo "" +echo "Hush silentdragonxlite-cli is now compiled for you. Enjoy and reach out if you need support." +echo "For options, run ./silentdragonxlite --help"