Compare commits
288 Commits
3be05964d7
...
e5210d50c9
Author | SHA1 | Date |
---|---|---|
Jack Grigg | e5210d50c9 | |
Jack Grigg | d9c1b05a83 | |
Jack Grigg | 85b5595519 | |
Jack Grigg | 9e9f58b26f | |
str4d | bd1feab2db | |
Jack Grigg | 77a971fbc1 | |
sasha | 5cfaea5dd4 | |
sasha | c85fae833e | |
str4d | d38da460eb | |
Jack Grigg | 1f3c145e4a | |
Jack Grigg | 5ea2aaff0d | |
Jack Grigg | 96edba2e41 | |
str4d | 584e8e56a8 | |
Jack Grigg | e646905f1b | |
Jack Grigg | 479b10364b | |
Charlie O'Keefe | 7b7ac148ac | |
Jack Grigg | c21890af8d | |
Charlie O'Keefe | b62d4917be | |
Jack Grigg | d4e47e4720 | |
Jack Grigg | 24089109b2 | |
Jack Grigg | e576f6616f | |
Larry Ruane | e575e0f217 | |
Kris Nuttycombe | 17e078f068 | |
Kris Nuttycombe | 151074cbb1 | |
Kris Nuttycombe | a8bae004b1 | |
Kris Nuttycombe | 758852b281 | |
Kris Nuttycombe | 9220ebb0de | |
Charlie O'Keefe | 11bd9463c1 | |
Kris Nuttycombe | 87cc0d22db | |
Kris Nuttycombe | 15bb06f452 | |
Charlie O'Keefe | 8c0e76e12b | |
Charlie O'Keefe | 612f3e7e81 | |
Kris Nuttycombe | ba3594f9ee | |
Charlie O'Keefe | 9974be75fb | |
Kris Nuttycombe | b32c8d3f0f | |
Kris Nuttycombe | b73ca36c45 | |
Kris Nuttycombe | 4392375404 | |
Kris Nuttycombe | fabff30d88 | |
Kris Nuttycombe | d724c81289 | |
Jack Grigg | 78fea33cde | |
Jonas Schnelli | eaf8eb40f4 | |
Kris Nuttycombe | a1bd440f2b | |
Kris Nuttycombe | 4df6c028f4 | |
Kris Nuttycombe | 7c3afad111 | |
Kris Nuttycombe | 2d673ca704 | |
Kris Nuttycombe | 0c977076bc | |
Steven Smith | e1e480e9c4 | |
Steven Smith | 445f4de0f4 | |
Kris Nuttycombe | 28210791a1 | |
Kris Nuttycombe | 315392ce3b | |
Steven Smith | 9993d948ef | |
Daira Hopwood | 6f4b283a3d | |
Jack Grigg | 562f5add87 | |
Jack Grigg | 65cb79e023 | |
Jack Grigg | 1505121877 | |
Kris Nuttycombe | cf84e1e79d | |
Kris Nuttycombe | 80324bc653 | |
Kris Nuttycombe | 7cd4462b20 | |
Kris Nuttycombe | b708b8ef60 | |
Jack Grigg | 22c6bf88a3 | |
Kris Nuttycombe | a783de43e3 | |
Kris Nuttycombe | 9582a2c1f1 | |
Kris Nuttycombe | 9d8c20ed78 | |
str4d | 2e77745c4e | |
str4d | 12b37d5a11 | |
str4d | 454e7640f0 | |
Kris Nuttycombe | b9b471bfa6 | |
Kris Nuttycombe | 5c9b7ec4df | |
Kris Nuttycombe | 860fc8886a | |
Kris Nuttycombe | 482f0e7e44 | |
ying tong | 1cd92e579c | |
therealyingtong | c1cd8a7e2d | |
therealyingtong | 6caec7aef1 | |
Kris Nuttycombe | 235767ba6e | |
Kris Nuttycombe | 24ce54f3ed | |
Kris Nuttycombe | 5d65952f54 | |
Kris Nuttycombe | 1b1495b7d7 | |
sasha | 2dcaac5fcb | |
Jack Grigg | 381b626a78 | |
Jack Grigg | c2220f4eb9 | |
Kris Nuttycombe | 7ba239e605 | |
Jack Grigg | d49c8a2865 | |
Jack Grigg | d4eee99060 | |
Jack Grigg | f2ae807891 | |
Jack Grigg | 920de99c85 | |
Jack Grigg | 959b068468 | |
Jack Grigg | 63437e4c18 | |
Jack Grigg | f56a04ded2 | |
Kris Nuttycombe | c3c5236cee | |
sasha | 3a957618b6 | |
sasha | 7071b41c55 | |
Kris Nuttycombe | 5a98080677 | |
sasha | f37d186e7e | |
ying tong | 4d7892bdde | |
Kris Nuttycombe | e335ff2325 | |
Kris Nuttycombe | d420e2efab | |
Daira Hopwood | 4611493c9d | |
Daira Hopwood | 340840894a | |
Daira Hopwood | 6ee6692c83 | |
Daira Hopwood | 1d31d4bc7c | |
Daira Hopwood | 55d14faebf | |
Kris Nuttycombe | 19bce27321 | |
Kris Nuttycombe | c9938d0348 | |
Daira Hopwood | ccada66324 | |
Kris Nuttycombe | 4eda33f184 | |
Kris Nuttycombe | 30dd633de6 | |
Kris Nuttycombe | 5d17e53c5c | |
Kris Nuttycombe | f5b23b3336 | |
Kris Nuttycombe | 1e1521f101 | |
str4d | 0f59c5b926 | |
therealyingtong | 3c0e1d9aa8 | |
Kris Nuttycombe | 0c38ad9200 | |
Jack Grigg | 93797d185b | |
Daira Hopwood | 577f695af5 | |
Daira Hopwood | 3e36252d9a | |
Daira Hopwood | d5b6e226f0 | |
Daira Hopwood | 7a98644ff2 | |
Daira Hopwood | cb2c89eedf | |
Jack Grigg | 7921ef1c55 | |
Jack Grigg | 6a7213b5d5 | |
str4d | 8964763bef | |
Daira Hopwood | f6a8461047 | |
Daira Hopwood | 799b46b90a | |
Daira Hopwood | 999b395360 | |
Kris Nuttycombe | 880cbe0dc4 | |
Kris Nuttycombe | 8bf99eaacc | |
Kris Nuttycombe | b21ca34d44 | |
Kris Nuttycombe | f057ebb375 | |
Kris Nuttycombe | e63d52896a | |
Kris Nuttycombe | ca34a97a37 | |
Daira Hopwood | d4405feddf | |
Kris Nuttycombe | 98ee3bd733 | |
Kris Nuttycombe | dc1609094d | |
Kris Nuttycombe | 0149451217 | |
Kris Nuttycombe | ecd0d24baa | |
Kris Nuttycombe | 46c33c68e0 | |
therealyingtong | 26b8ec5562 | |
therealyingtong | 663f8d434a | |
Kris Nuttycombe | 38f4afa80c | |
Kris Nuttycombe | 3b5fc4e583 | |
sasha | ced825edec | |
Kris Nuttycombe | 19ce19155c | |
Kris Nuttycombe | 9284c5552b | |
str4d | 7129827509 | |
Kris Nuttycombe | 51cb37e541 | |
Charlie O'Keefe | 4f04c96f7b | |
Kris Nuttycombe | c3cf141936 | |
Kris Nuttycombe | c9ef5cd45a | |
Kris Nuttycombe | 97895a6f6d | |
Kris Nuttycombe | faffe2e084 | |
Kris Nuttycombe | 6171f27b02 | |
Kris Nuttycombe | 9336c5e7f0 | |
Kris Nuttycombe | 23a7314607 | |
Kris Nuttycombe | 6b8ecc8a31 | |
Kris Nuttycombe | b78c2732ab | |
Kris Nuttycombe | 5c049b6b13 | |
Kris Nuttycombe | 8bc4c2aad8 | |
Kris Nuttycombe | f7006e9a33 | |
Kris Nuttycombe | 237384cf2b | |
Kris Nuttycombe | 5a6ef3467e | |
Daira Hopwood | 9b20105413 | |
Daira Hopwood | 6fb943d0f6 | |
Daira Hopwood | e940829313 | |
Daira Hopwood | 6f5efcbb0f | |
Daira Hopwood | 25792cba93 | |
Daira Hopwood | 8ec0d854b9 | |
Daira Hopwood | de933c1cb5 | |
Daira Hopwood | a36fceca70 | |
Steven Smith | 7f7d9e6e1f | |
sasha | f1d652fc78 | |
sasha | 8f68166c24 | |
sasha | 0e6b004a64 | |
str4d | 13351ff31d | |
Jack Grigg | fdb5709e7e | |
Jack Grigg | 9d5d781a15 | |
str4d | 414e74f70a | |
Kris Nuttycombe | 6540694845 | |
Kris Nuttycombe | cf4d0f3d2d | |
Charlie O'Keefe | e33178615c | |
Charlie O'Keefe | c2b5bf0c42 | |
str4d | d4a019e89b | |
Jack Grigg | 8b90d32972 | |
Kris Nuttycombe | 8b44cc7072 | |
Kris Nuttycombe | ceff068e20 | |
Jack Grigg | 613534bbb3 | |
Jack Grigg | 3fa58149b0 | |
Jack Grigg | 1c31a1c7d8 | |
Kris Nuttycombe | 1732a50957 | |
Daira Hopwood | 5990853de3 | |
Kris Nuttycombe | fd2f5bdc28 | |
Kris Nuttycombe | 65b5b7509c | |
Jack Grigg | 35b8c8648f | |
Jack Grigg | 9a60cdeed9 | |
Jack Grigg | ae3d2a3525 | |
Jack Grigg | 2230ba912f | |
therealyingtong | dbb7e027c8 | |
Sean Bowe | bd137561f2 | |
Kris Nuttycombe | 681505e9d6 | |
sasha | 2e52c1623d | |
sasha | 902187368e | |
sasha | 0e13aba1b7 | |
Sean Bowe | c5b87ba163 | |
therealyingtong | cedf2b5ecb | |
therealyingtong | 454c1eed46 | |
therealyingtong | 98cd4bab0b | |
str4d | 1c38c9833c | |
Sean Bowe | 3665f69a48 | |
Sean Bowe | f7824e8cec | |
Sean Bowe | 49ce03ed79 | |
Kris Nuttycombe | 5141c2971c | |
Kris Nuttycombe | 68d6c3ddc3 | |
Kris Nuttycombe | 1389eea5c2 | |
Larry Ruane | e170c3abd6 | |
Pieter Wuille | c079a518c0 | |
Pieter Wuille | 4693f8165f | |
Kris Nuttycombe | 783e269a7b | |
Kris Nuttycombe | bd50fbbb94 | |
therealyingtong | 4a8bdabb2f | |
sasha | 0f06774e94 | |
sasha | 3a7b4eca87 | |
sasha | 489c025f81 | |
Jack Grigg | b5ce94d16c | |
Jack Grigg | 7b7dddba0c | |
Jack Grigg | 2f1fbcc81f | |
Jack Grigg | 21430b13ac | |
Jack Grigg | adb7d074d5 | |
Jack Grigg | 4c49af7750 | |
Jack Grigg | 9f5f36e94c | |
Kris Nuttycombe | 18f443db1c | |
Kris Nuttycombe | 399ffa7f5c | |
sasha | 077f5550d4 | |
Kris Nuttycombe | f9380572c1 | |
Kris Nuttycombe | 044d2de463 | |
Kris Nuttycombe | 41ed311afa | |
Kris Nuttycombe | d9cddac773 | |
Jack Grigg | 701a7875a7 | |
Jack Grigg | d4078c8d25 | |
Jack Grigg | 85a8784b38 | |
Jack Grigg | bb4e792c84 | |
Taylor Hornby | 7d9dda4b7e | |
str4d | 48bf0241e6 | |
Steven | 82c33596b9 | |
Jack Grigg | b0769e3f1d | |
Jack Grigg | 235fde5193 | |
Steven Smith | a0740650c3 | |
Kris Nuttycombe | 49065bee59 | |
John Newbery | 4942020624 | |
Jack Grigg | 3179e45467 | |
Jack Grigg | d099e469e1 | |
Jack Grigg | c2a47b8d97 | |
Kris Nuttycombe | 3c84ed604e | |
Kris Nuttycombe | 225c78f3ab | |
Kris Nuttycombe | 258f0fc72f | |
sasha | eab7efcae5 | |
Kris Nuttycombe | bb91c9fbc3 | |
sasha | 2d6dcd4750 | |
therealyingtong | 1dae16a41c | |
ying tong | 2943e13c8b | |
Kris Nuttycombe | 1a1522a4f1 | |
Kris Nuttycombe | 3e3ee1bf3b | |
Kris Nuttycombe | 98ab4bcadb | |
Kris Nuttycombe | 1969add228 | |
Kris Nuttycombe | 66890866d6 | |
therealyingtong | 108e9d4658 | |
therealyingtong | 99a69182cd | |
therealyingtong | 4c4ee558d6 | |
str4d | 6cd5b8792b | |
Kris Nuttycombe | d08c992b5e | |
Kris Nuttycombe | dcf7f46260 | |
Kris Nuttycombe | 4c53499f11 | |
Kris Nuttycombe | 6587b2ed86 | |
Larry Ruane | ba9e020fcd | |
Kris Nuttycombe | 73a75a37fe | |
Steven Smith | 59c6b028f7 | |
therealyingtong | 79c0514919 | |
Steven | ffdc11c378 | |
Daira Hopwood | 816649db2a | |
Steven Smith | 512c862a5f | |
str4d | d0d6d47aa7 | |
Kris Nuttycombe | f6a3c68b09 | |
Jack Grigg | da755c2ae8 | |
therealyingtong | 94ab8e4c77 | |
Jack Grigg | 2da0856e6f | |
Taylor Hornby | a2c647d4bf | |
Larry Ruane | 698f7ba770 | |
therealyingtong | c7346e802e | |
therealyingtong | 6436230562 | |
Marius Kjærstad | bf1cc206c0 |
|
@ -1,9 +1,17 @@
|
|||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
|
||||
[source.crates-io]
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."https://github.com/zcash/librustzcash.git"]
|
||||
git = "https://github.com/zcash/librustzcash.git"
|
||||
rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7"
|
||||
rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."https://github.com/nuttycom/hdwallet.git"]
|
||||
git = "https://github.com/nuttycom/hdwallet.git"
|
||||
rev = "576683b9f2865f1118c309017ff36e01f84420c9"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source.vendored-sources]
|
||||
|
|
|
@ -70,19 +70,19 @@ jobs:
|
|||
if: always()
|
||||
|
||||
rust-clippy:
|
||||
name: Clippy (1.54.0)
|
||||
name: Clippy (1.59.0)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.59.0
|
||||
components: clippy
|
||||
override: true
|
||||
- name: Run clippy
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
name: Clippy (1.54.0)
|
||||
name: Clippy (1.59.0)
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features --all-targets -- -D warnings
|
||||
|
||||
|
@ -93,7 +93,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.59.0
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*.exe
|
||||
src/bitcoin
|
||||
src/zcashd
|
||||
src/zcashd-wallet-tool
|
||||
src/zcash-cli
|
||||
src/zcash-gtest
|
||||
src/zcash-tx
|
||||
|
|
6
COPYING
6
COPYING
|
@ -1,6 +1,6 @@
|
|||
Copyright (c) 2016-2021 The Zcash developers
|
||||
Copyright (c) 2009-2021 The Bitcoin Core developers
|
||||
Copyright (c) 2009-2021 Bitcoin Developers
|
||||
Copyright (c) 2016-2022 The Zcash developers
|
||||
Copyright (c) 2009-2022 The Bitcoin Core developers
|
||||
Copyright (c) 2009-2022 Bitcoin Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
File diff suppressed because it is too large
Load Diff
34
Cargo.toml
34
Cargo.toml
|
@ -18,13 +18,17 @@ repository = "https://github.com/zcash/zcash"
|
|||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.59"
|
||||
|
||||
[lib]
|
||||
name = "rustzcash"
|
||||
path = "src/rust/src/rustzcash.rs"
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[[bin]]
|
||||
name = "zcashd-wallet-tool"
|
||||
path = "src/rust/bin/wallet_tool.rs"
|
||||
|
||||
[dependencies]
|
||||
bellman = "0.11"
|
||||
blake2b_simd = "1"
|
||||
|
@ -32,12 +36,12 @@ blake2s_simd = "1"
|
|||
bls12_381 = "0.6"
|
||||
byteorder = "1"
|
||||
group = "0.11"
|
||||
incrementalmerkletree = "0.2"
|
||||
incrementalmerkletree = "=0.3.0-beta.1"
|
||||
libc = "0.2"
|
||||
jubjub = "0.8"
|
||||
memuse = "0.2"
|
||||
nonempty = "0.7"
|
||||
orchard = "=0.1.0-beta.1"
|
||||
orchard = "=0.1.0-beta.2"
|
||||
secp256k1 = "0.20"
|
||||
subtle = "2.2"
|
||||
rand_core = "0.6"
|
||||
|
@ -61,10 +65,20 @@ metrics-exporter-prometheus = "0.6"
|
|||
thiserror = "1"
|
||||
tokio = { version = "1.0", features = ["rt", "net", "time", "macros"] }
|
||||
|
||||
# Wallet tool
|
||||
# (also depends on thiserror, tracing, and tracing-subscriber with "env-filter" and "fmt" features)
|
||||
anyhow = "1.0"
|
||||
backtrace = "0.3"
|
||||
clearscreen = "1.0"
|
||||
gumdrop = "0.8"
|
||||
rand = "0.8"
|
||||
secrecy = "0.8"
|
||||
time = { version = "0.3", features = ["formatting", "macros"] }
|
||||
|
||||
[dependencies.tracing-subscriber]
|
||||
version = "0.3"
|
||||
default-features = false
|
||||
features = ["ansi", "env-filter", "time"]
|
||||
features = ["ansi", "env-filter", "fmt", "time"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
@ -73,9 +87,9 @@ codegen-units = 1
|
|||
|
||||
[patch.crates-io]
|
||||
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
|
||||
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
||||
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
||||
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
||||
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
||||
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
||||
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||
|
|
|
@ -48,6 +48,12 @@ $(BITCOIND_BIN): FORCE
|
|||
$(BITCOIN_CLI_BIN): FORCE
|
||||
$(MAKE) -C src $(@F)
|
||||
|
||||
check-security: FORCE
|
||||
$(MAKE) -C src check-security
|
||||
|
||||
check-symbols: FORCE
|
||||
$(MAKE) -C src check-symbols
|
||||
|
||||
if USE_LCOV
|
||||
|
||||
baseline.info:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Zcash 4.6.0-1
|
||||
Zcash 4.7.0-rc1
|
||||
<img align="right" width="120" height="80" src="doc/imgs/logo.png">
|
||||
===========
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
|
||||
AC_PREREQ([2.60])
|
||||
define(_CLIENT_VERSION_MAJOR, 4)
|
||||
define(_CLIENT_VERSION_MINOR, 6)
|
||||
define(_CLIENT_VERSION_MINOR, 7)
|
||||
define(_CLIENT_VERSION_REVISION, 0)
|
||||
define(_CLIENT_VERSION_BUILD, 51)
|
||||
define(_CLIENT_VERSION_BUILD, 25)
|
||||
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
|
||||
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))
|
||||
define(_CLIENT_VERSION_IS_RELEASE, true)
|
||||
define(_COPYRIGHT_YEAR, 2021)
|
||||
define(_COPYRIGHT_YEAR, 2022)
|
||||
AC_INIT([Zcash],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_SUFFIX(_ZC_BUILD_VAL)],[https://github.com/zcash/zcash/issues],[zcash])
|
||||
AC_CONFIG_SRCDIR([src/main.cpp])
|
||||
AC_CONFIG_HEADERS([src/config/bitcoin-config.h])
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
zcash (4.7.0~rc1) stable; urgency=medium
|
||||
|
||||
* 4.7.0-rc1 release.
|
||||
|
||||
-- Electric Coin Company <team@electriccoin.co> Tue, 22 Mar 2022 21:14:02 +0000
|
||||
|
||||
zcash (4.6.0+1) stable; urgency=medium
|
||||
|
||||
* 4.6.0-1 release.
|
||||
|
|
|
@ -4,9 +4,9 @@ Upstream-Contact: Electric Coin Company <team@electriccoin.co>
|
|||
Source: https://github.com/zcash/zcash
|
||||
|
||||
Files: *
|
||||
Copyright: 2016-2021, The Zcash developers
|
||||
2009-2021, Bitcoin Core developers
|
||||
2009-2021, Bitcoin Developers
|
||||
Copyright: 2016-2022, The Zcash developers
|
||||
2009-2022, Bitcoin Core developers
|
||||
2009-2022, Bitcoin Developers
|
||||
License: Expat
|
||||
Comment: The Bitcoin Core developers encompasses the current developers listed on
|
||||
bitcoin.org, as well as the numerous contributors to the project.
|
||||
|
|
|
@ -189,8 +189,12 @@ def identify_executable(executable):
|
|||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = sys.argv[1:]
|
||||
allow_no_canary = "--allow-no-canary" in args
|
||||
files = [arg for arg in args if not arg.startswith("--")]
|
||||
|
||||
retval = 0
|
||||
for filename in sys.argv[1:]:
|
||||
for filename in files:
|
||||
try:
|
||||
etype = identify_executable(filename)
|
||||
if etype is None:
|
||||
|
@ -201,6 +205,8 @@ if __name__ == '__main__':
|
|||
failed = []
|
||||
warning = []
|
||||
for (name, func) in CHECKS[etype]:
|
||||
if name == "Canary" and allow_no_canary:
|
||||
continue
|
||||
if not func(filename):
|
||||
if name in NONFATAL:
|
||||
warning.append(name)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
name: "zcash-4.6.0-1"
|
||||
name: "zcash-4.7.0-rc1"
|
||||
enable_cache: true
|
||||
distro: "debian"
|
||||
suites:
|
||||
- "stretch"
|
||||
- "buster"
|
||||
- "bullseye"
|
||||
architectures:
|
||||
- "amd64"
|
||||
packages:
|
||||
|
|
|
@ -8,10 +8,10 @@ ifneq ($(host_os),mingw32)
|
|||
$(package)_download_path=$(native_clang_download_path)
|
||||
$(package)_download_file_aarch64_linux=clang+llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
||||
$(package)_file_name_aarch64_linux=clang-llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
||||
$(package)_sha256_hash_aarch64_linux=968d65d2593850ee9b37fcda074fb7641529bd45d2f976af6c8197de3c22612f
|
||||
$(package)_download_file_linux=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
||||
$(package)_file_name_linux=clang-llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
||||
$(package)_sha256_hash_linux=76d0bf002ede7a893f69d9ad2c4e101d15a8f4186fbfe24e74856c8449acd7c1
|
||||
$(package)_sha256_hash_aarch64_linux=15ff2db12683e69e552b6668f7ca49edaa01ce32cb1cbc8f8ed2e887ab291069
|
||||
$(package)_download_file_linux=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-18.04.tar.xz
|
||||
$(package)_file_name_linux=clang-llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-18.04.tar.xz
|
||||
$(package)_sha256_hash_linux=84a54c69781ad90615d1b0276a83ff87daaeded99fbc64457c350679df7b4ff0
|
||||
|
||||
define $(package)_stage_cmds
|
||||
mkdir -p $($(package)_staging_prefix_dir)/lib && \
|
||||
|
@ -21,6 +21,7 @@ endef
|
|||
|
||||
else
|
||||
# For Windows cross-compilation, use the MSYS2 binaries.
|
||||
# Using 13.0.0-3 because 13.0.1-1 has missing `new` and `delete` symbols.
|
||||
$(package)_download_path=https://repo.msys2.org/mingw/x86_64
|
||||
$(package)_download_file=mingw-w64-x86_64-libc++-13.0.0-3-any.pkg.tar.zst
|
||||
$(package)_file_name=mingw-w64-x86_64-libcxx-13.0.0-3-any.pkg.tar.zst
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
package=native_clang
|
||||
$(package)_major_version=13
|
||||
$(package)_version=13.0.0
|
||||
$(package)_version=13.0.1
|
||||
$(package)_download_path=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
||||
$(package)_download_path_linux=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
||||
$(package)_download_file_linux=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
||||
$(package)_file_name_linux=clang-llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
||||
$(package)_sha256_hash_linux=76d0bf002ede7a893f69d9ad2c4e101d15a8f4186fbfe24e74856c8449acd7c1
|
||||
$(package)_download_path_darwin=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_major_version).0.0
|
||||
$(package)_download_file_darwin=clang+llvm-$($(package)_major_version).0.0-x86_64-apple-darwin.tar.xz
|
||||
$(package)_file_name_darwin=clang-llvm-$($(package)_major_version).0.0-x86_64-apple-darwin.tar.xz
|
||||
$(package)_sha256_hash_darwin=d051234eca1db1f5e4bc08c64937c879c7098900f7a0370f3ceb7544816a8b09
|
||||
$(package)_download_file_linux=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-18.04.tar.xz
|
||||
$(package)_file_name_linux=clang-llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-18.04.tar.xz
|
||||
$(package)_sha256_hash_linux=84a54c69781ad90615d1b0276a83ff87daaeded99fbc64457c350679df7b4ff0
|
||||
$(package)_download_path_darwin=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
||||
$(package)_download_file_darwin=clang+llvm-$($(package)_version)-x86_64-apple-darwin.tar.xz
|
||||
$(package)_file_name_darwin=clang-llvm-$($(package)_version)-x86_64-apple-darwin.tar.xz
|
||||
$(package)_sha256_hash_darwin=dec02d17698514d0fc7ace8869c38937851c542b02adf102c4e898f027145a4d
|
||||
$(package)_download_path_freebsd=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
||||
$(package)_download_file_freebsd=clang+llvm-$($(package)_version)-amd64-unknown-freebsd12.tar.xz
|
||||
$(package)_file_name_freebsd=clang-llvm-$($(package)_version)-amd64-unknown-freebsd12.tar.xz
|
||||
$(package)_sha256_hash_freebsd=e579747a36ff78aa0a5533fe43bc1ed1f8ed449c9bfec43c358d953ffbbdcf76
|
||||
$(package)_sha256_hash_freebsd=8101c8d3a920bf930b33987ada5373f43537c5de8c194be0ea10530fd0ad5617
|
||||
$(package)_download_file_aarch64_linux=clang+llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
||||
$(package)_file_name_aarch64_linux=clang-llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
||||
$(package)_sha256_hash_aarch64_linux=968d65d2593850ee9b37fcda074fb7641529bd45d2f976af6c8197de3c22612f
|
||||
$(package)_sha256_hash_aarch64_linux=15ff2db12683e69e552b6668f7ca49edaa01ce32cb1cbc8f8ed2e887ab291069
|
||||
|
||||
ifneq (,$(wildcard /etc/arch-release))
|
||||
$(package)_dependencies=native_libtinfo
|
||||
endif
|
||||
|
||||
# Ensure we have clang native to the builder, not the target host
|
||||
ifneq ($(canonical_host),$(build))
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package=native_tinfo
|
||||
$(package)_version=5.6.0
|
||||
$(package)_download_path_linux=http://ftp.debian.org/debian/pool/main/n/ncurses/
|
||||
$(package)_download_file_linux=libtinfo5_6.0+20161126-1+deb9u2_amd64.deb
|
||||
$(package)_file_name_linux=libtinfo5_6.0+20161126-1+deb9u2_amd64.deb
|
||||
$(package)_sha256_hash_linux=1d249a3193568b5ef785ad8993b9ba6d6fdca0eb359204c2355532b82d25e9f5
|
||||
|
||||
define $(package)_extract_cmds
|
||||
mkdir -p $($(package)_extract_dir) && \
|
||||
echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \
|
||||
$(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \
|
||||
mkdir -p libtinfo5 && \
|
||||
ar x --output libtinfo5 $($(package)_source_dir)/$($(package)_file_name) && \
|
||||
cd libtinfo5 && \
|
||||
tar xf data.tar.xz
|
||||
endef
|
||||
|
||||
define $(package)_stage_cmds
|
||||
pwd && \
|
||||
mkdir -p $($(package)_staging_prefix_dir)/lib && \
|
||||
cp libtinfo5/lib/x86_64-linux-gnu/libtinfo.so.5.9 $($(package)_staging_prefix_dir)/lib/libtinfo.so.5
|
||||
endef
|
|
@ -1,14 +1,14 @@
|
|||
package=native_rust
|
||||
$(package)_version=1.57.0
|
||||
$(package)_version=1.59.0
|
||||
$(package)_download_path=https://static.rust-lang.org/dist
|
||||
$(package)_file_name_linux=rust-$($(package)_version)-x86_64-unknown-linux-gnu.tar.gz
|
||||
$(package)_sha256_hash_linux=ea0253784b2e5c22659ff148d492a68d2e11da734491714ebc61cc93896efcda
|
||||
$(package)_sha256_hash_linux=0c1c2da3fa26372e5178123aa5bb0fdcd4933fbad9bfb268ffbd71807182ecae
|
||||
$(package)_file_name_darwin=rust-$($(package)_version)-x86_64-apple-darwin.tar.gz
|
||||
$(package)_sha256_hash_darwin=15ceffc4743434c19d08f73fb4edd6642b7fd8162ed7101d3e6ca2c691fcb699
|
||||
$(package)_sha256_hash_darwin=d82204f536af0c7bfd2ea2213dc46b99911860cfc5517f7321244412ae96f159
|
||||
$(package)_file_name_freebsd=rust-$($(package)_version)-x86_64-unknown-freebsd.tar.gz
|
||||
$(package)_sha256_hash_freebsd=ebe96fa1f15e8d70c91e81aab7e0c341717b909225029f37d52fbdfa506e3fab
|
||||
$(package)_sha256_hash_freebsd=83f9c49b6e9025b712fc5d65e49f1b6ad959966534cd39c8dc2ce2c85a6ca484
|
||||
$(package)_file_name_aarch64_linux=rust-$($(package)_version)-aarch64-unknown-linux-gnu.tar.gz
|
||||
$(package)_sha256_hash_aarch64_linux=d66847f7cf7b548ecb328c400ac4f691ee2aea6ff5cd9286ad8733239569556c
|
||||
$(package)_sha256_hash_aarch64_linux=ab5da30a3de5433e26cbc74c56b9d97b569769fc2e456fc54378adc8baaee4f0
|
||||
|
||||
# Mapping from GCC canonical hosts to Rust targets
|
||||
# If a mapping is not present, we assume they are identical, unless $host_os is
|
||||
|
@ -17,10 +17,10 @@ $(package)_rust_target_x86_64-pc-linux-gnu=x86_64-unknown-linux-gnu
|
|||
$(package)_rust_target_x86_64-w64-mingw32=x86_64-pc-windows-gnu
|
||||
|
||||
# Mapping from Rust targets to SHA-256 hashes
|
||||
$(package)_rust_std_sha256_hash_aarch64-unknown-linux-gnu=4c70901d1cbddec9ea99fbd62b20f454d30e1ffbb48a21169ac823b3f02a1fbc
|
||||
$(package)_rust_std_sha256_hash_x86_64-apple-darwin=c1eb892ddb50ebeed288b7aa8171ad46d62362bb26b2d82d2b463dfd45606dc2
|
||||
$(package)_rust_std_sha256_hash_x86_64-pc-windows-gnu=75c910899ed36a90b155e3a01c21b863000675867efc56f2b68c44edd4b7e18c
|
||||
$(package)_rust_std_sha256_hash_x86_64-unknown-freebsd=1528a4bc7e3ba42da164bcc7b952dfa73048333c5b9254ce2d03db6bab6081e8
|
||||
$(package)_rust_std_sha256_hash_aarch64-unknown-linux-gnu=81dbd37919f631f962ac0798111803eb8f06ffde608f0e5dd3682d701cf5566d
|
||||
$(package)_rust_std_sha256_hash_x86_64-apple-darwin=959af8bafbc9f3916a1d1111d7378fdd7aa459410cdd2d3bbfc2d9d9a6db0683
|
||||
$(package)_rust_std_sha256_hash_x86_64-pc-windows-gnu=9a67ae84e9e75efb57eeeab617e32379a555de336a30bb74a476e575cd38f63a
|
||||
$(package)_rust_std_sha256_hash_x86_64-unknown-freebsd=cf5e4303dd7c3b70a738a2336097c9f2189c8b702a89a8c453d83ac0dee4602c
|
||||
|
||||
define rust_target
|
||||
$(if $($(1)_rust_target_$(2)),$($(1)_rust_target_$(2)),$(if $(findstring darwin,$(3)),x86_64-apple-darwin,$(if $(findstring freebsd,$(3)),x86_64-unknown-freebsd,$(2))))
|
||||
|
|
|
@ -2,6 +2,10 @@ zcash_packages := libsodium utfcpp
|
|||
packages := boost libevent zeromq $(zcash_packages) googletest
|
||||
native_packages := native_clang native_ccache native_rust
|
||||
|
||||
ifneq (,$(wildcard /etc/arch-release))
|
||||
native_packages += native_libtinfo
|
||||
endif
|
||||
|
||||
wallet_packages=bdb
|
||||
|
||||
$(host_arch)_$(host_os)_native_packages += native_b2
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||
.TH ZCASH-CLI "1" "January 2022" "zcash-cli v4.6.0-1" "User Commands"
|
||||
.TH ZCASH-CLI "1" "March 2022" "zcash-cli v4.7.0-rc1" "User Commands"
|
||||
.SH NAME
|
||||
zcash-cli \- manual page for zcash-cli v4.6.0-1
|
||||
zcash-cli \- manual page for zcash-cli v4.7.0-rc1
|
||||
.SH DESCRIPTION
|
||||
Zcash RPC client version v4.6.0\-1
|
||||
Zcash RPC client version v4.7.0\-rc1
|
||||
.PP
|
||||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||
please see <https://z.cash/support/security/>.
|
||||
|
@ -29,7 +29,7 @@ Specify configuration file (default: zcash.conf)
|
|||
.HP
|
||||
\fB\-datadir=\fR<dir>
|
||||
.IP
|
||||
Specify data directory
|
||||
Specify data directory (this path cannot use '~')
|
||||
.HP
|
||||
\fB\-stdin\fR
|
||||
.IP
|
||||
|
@ -79,8 +79,8 @@ Timeout in seconds during HTTP requests, or 0 for no timeout. (default:
|
|||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||
please see <https://z.cash/support/security/>.
|
||||
|
||||
Copyright (C) 2009-2021 The Bitcoin Core Developers
|
||||
Copyright (C) 2015-2021 The Zcash Developers
|
||||
Copyright (C) 2009-2022 The Bitcoin Core Developers
|
||||
Copyright (C) 2015-2022 The Zcash Developers
|
||||
|
||||
This is experimental software.
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||
.TH ZCASH-TX "1" "January 2022" "zcash-tx v4.6.0-1" "User Commands"
|
||||
.TH ZCASH-TX "1" "March 2022" "zcash-tx v4.7.0-rc1" "User Commands"
|
||||
.SH NAME
|
||||
zcash-tx \- manual page for zcash-tx v4.6.0-1
|
||||
zcash-tx \- manual page for zcash-tx v4.7.0-rc1
|
||||
.SH DESCRIPTION
|
||||
Zcash zcash\-tx utility version v4.6.0\-1
|
||||
Zcash zcash\-tx utility version v4.7.0\-rc1
|
||||
.SS "Usage:"
|
||||
.TP
|
||||
zcash\-tx [options] <hex\-tx> [commands]
|
||||
|
@ -91,8 +91,8 @@ Set register NAME to given JSON\-STRING
|
|||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||
please see <https://z.cash/support/security/>.
|
||||
|
||||
Copyright (C) 2009-2021 The Bitcoin Core Developers
|
||||
Copyright (C) 2015-2021 The Zcash Developers
|
||||
Copyright (C) 2009-2022 The Bitcoin Core Developers
|
||||
Copyright (C) 2015-2022 The Zcash Developers
|
||||
|
||||
This is experimental software.
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||
.TH ZCASHD "1" "January 2022" "zcashd v4.6.0-1" "User Commands"
|
||||
.TH ZCASHD "1" "March 2022" "zcashd v4.7.0-rc1" "User Commands"
|
||||
.SH NAME
|
||||
zcashd \- manual page for zcashd v4.6.0-1
|
||||
zcashd \- manual page for zcashd v4.7.0-rc1
|
||||
.SH DESCRIPTION
|
||||
Zcash Daemon version v4.6.0\-1
|
||||
Zcash Daemon version v4.7.0\-rc1
|
||||
.PP
|
||||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||
please see <https://z.cash/support/security/>.
|
||||
|
@ -49,7 +49,7 @@ Run in the background as a daemon and accept commands
|
|||
.HP
|
||||
\fB\-datadir=\fR<dir>
|
||||
.IP
|
||||
Specify data directory
|
||||
Specify data directory (this path cannot use '~')
|
||||
.HP
|
||||
\fB\-paramsdir=\fR<dir>
|
||||
.IP
|
||||
|
@ -352,6 +352,14 @@ by TxID)
|
|||
Delete all wallet transactions and only recover those parts of the
|
||||
blockchain through \fB\-rescan\fR on startup (1 = keep tx meta data e.g.
|
||||
account owner and payment request information, 2 = drop tx meta data)
|
||||
.HP
|
||||
\fB\-walletrequirebackup=\fR<bool>
|
||||
.IP
|
||||
By default, the wallet will not allow generation of new spending keys &
|
||||
addresses from the mnemonic seed until the backup of that seed has been
|
||||
confirmed with the `zcashd\-wallet\-tool` utility. A user may start zcashd
|
||||
with `\-walletrequirebackup=false` to allow generation of spending keys
|
||||
even if the backup has not yet been confirmed.
|
||||
.PP
|
||||
ZeroMQ notification options:
|
||||
.HP
|
||||
|
@ -558,8 +566,8 @@ console, 600 otherwise)
|
|||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||
please see <https://z.cash/support/security/>.
|
||||
|
||||
Copyright (C) 2009-2021 The Bitcoin Core Developers
|
||||
Copyright (C) 2015-2021 The Zcash Developers
|
||||
Copyright (C) 2009-2022 The Bitcoin Core Developers
|
||||
Copyright (C) 2015-2022 The Zcash Developers
|
||||
|
||||
This is experimental software.
|
||||
|
||||
|
|
|
@ -13,23 +13,23 @@ be generated on load of the wallet, or the first time the wallet is unlocked,
|
|||
and is available via the `z_exportwallet` RPC call. All new addresses produced
|
||||
by the wallet are now derived from this seed using the HD wallet functionality
|
||||
described in ZIP 32 and ZIP 316. For users upgrading an existing Zcashd wallet,
|
||||
it is recommended that the wallet be backed up prior to upgrading to the 4.5.2
|
||||
it is recommended that the wallet be backed up prior to upgrading to the 4.7.0
|
||||
Zcashd release.
|
||||
|
||||
Following the upgrade to 4.5.2, Zcashd will require that the user confirm that
|
||||
Following the upgrade to 4.7.0, Zcashd will require that the user confirm that
|
||||
they have backed up their new emergency recovery phrase, which may be obtained
|
||||
from the output of the `z_exportwallet` RPC call. This confirmation can be
|
||||
performed manually using the `zcashd-wallet-tool` utility that is supplied with
|
||||
this release. The wallet will not allow the generation of new addresses until
|
||||
this confirmation has been performed. It is recommended that after this
|
||||
upgrade, that funds tied to preexisting addresses be migrated to newly
|
||||
generated addresses so that all wallet funds are recoverable using the
|
||||
emergency recovery phrase going forward. If you choose not to migrate funds in
|
||||
this fashion, you will continue to need to securely back up the entire
|
||||
`wallet.dat` file to ensure that you do not lose access to existing funds;
|
||||
EXISTING FUNDS WILL NOT BE RECOVERABLE USING THE EMERGENCY RECOVERY PHRASE
|
||||
UNLESS THEY HAVE BEEN MOVED TO A NEWLY GENERATED ADDRESS FOLLOWING THE 4.5.2
|
||||
UPGRADE.
|
||||
performed manually using the `zcashd-wallet-tool` utility that is supplied
|
||||
with this release (built or installed in the same directory as `zcashd`).
|
||||
The wallet will not allow the generation of new addresses until this
|
||||
confirmation has been performed. It is recommended that after this upgrade,
|
||||
that funds tied to preexisting addresses be migrated to newly generated
|
||||
addresses so that all wallet funds are recoverable using the emergency
|
||||
recovery phrase going forward. If you choose not to migrate funds in this
|
||||
fashion, you will continue to need to securely back up the entire `wallet.dat`
|
||||
file to ensure that you do not lose access to existing funds; EXISTING FUNDS
|
||||
WILL NOT BE RECOVERABLE USING THE EMERGENCY RECOVERY PHRASE UNLESS THEY HAVE
|
||||
BEEN MOVED TO A NEWLY GENERATED ADDRESS FOLLOWING THE 4.7.0 UPGRADE.
|
||||
|
||||
New RPC Methods
|
||||
---------------
|
||||
|
@ -81,11 +81,15 @@ Wallet
|
|||
pool boundaries, they must be explicitly enabled via a parameter to
|
||||
the 'z_sendmany' call.
|
||||
|
||||
- A new boolean parameter, `allowRevealedAmounts`, has been added to the
|
||||
list of arguments accepted by 'z_sendmany'. This parameter defaults to
|
||||
`false` and is only required when the transaction being constructed
|
||||
would reveal transaction amounts as a consequence of ZEC value crossing
|
||||
shielded pool boundaries via the turnstile.
|
||||
- A new string parameter, `privacyPolicy`, has been added to the list of
|
||||
arguments accepted by `z_sendmany`. This parameter enables the caller to
|
||||
control what kind of information they permit `zcashd` to reveal when creating
|
||||
the transaction. If the transaction can only be created by revealing more
|
||||
information than the given strategy permits, `z_sendmany` will return an
|
||||
error. The parameter defaults to `LegacyCompat`, which applies the most
|
||||
restrictive strategy `FullPrivacy` when a Unified Address is present as the
|
||||
sender or a recipient, and otherwise preserves existing behaviour (which
|
||||
corresponds to the `AllowFullyTransparent` policy).
|
||||
|
||||
- Since Sprout outputs are no longer created (with the exception of change)
|
||||
'z_sendmany' no longer generates payment disclosures (which were only
|
||||
|
|
|
@ -0,0 +1,538 @@
|
|||
Notable changes
|
||||
===============
|
||||
|
||||
Mnemonic Recovery Phrases
|
||||
-------------------------
|
||||
|
||||
The zcashd wallet has been modified to support BIP 39, which describes how to
|
||||
derive the wallet's HD seed from a mnemonic phrase. The mnemonic phrase will
|
||||
be generated on load of the wallet, or the first time the wallet is unlocked,
|
||||
and is available via the `z_exportwallet` RPC call. All new addresses produced
|
||||
by the wallet are now derived from this seed using the HD wallet functionality
|
||||
described in ZIP 32 and ZIP 316. For users upgrading an existing Zcashd wallet,
|
||||
it is recommended that the wallet be backed up prior to upgrading to the 4.7.0
|
||||
Zcashd release.
|
||||
|
||||
Following the upgrade to 4.7.0, Zcashd will require that the user confirm that
|
||||
they have backed up their new emergency recovery phrase, which may be obtained
|
||||
from the output of the `z_exportwallet` RPC call. This confirmation can be
|
||||
performed manually using the `zcashd-wallet-tool` utility that is supplied
|
||||
with this release (built or installed in the same directory as `zcashd`).
|
||||
The wallet will not allow the generation of new addresses until this
|
||||
confirmation has been performed. It is recommended that after this upgrade,
|
||||
that funds tied to preexisting addresses be migrated to newly generated
|
||||
addresses so that all wallet funds are recoverable using the emergency
|
||||
recovery phrase going forward. If you choose not to migrate funds in this
|
||||
fashion, you will continue to need to securely back up the entire `wallet.dat`
|
||||
file to ensure that you do not lose access to existing funds; EXISTING FUNDS
|
||||
WILL NOT BE RECOVERABLE USING THE EMERGENCY RECOVERY PHRASE UNLESS THEY HAVE
|
||||
BEEN MOVED TO A NEWLY GENERATED ADDRESS FOLLOWING THE 4.7.0 UPGRADE.
|
||||
|
||||
New RPC Methods
|
||||
---------------
|
||||
|
||||
- 'walletconfirmbackup' This newly created API checks a provided emergency
|
||||
recovery phrase against the wallet's emergency recovery phrase; if the phrases
|
||||
match then it updates the wallet state to allow the generation of new addresses.
|
||||
This backup confirmation workflow can be disabled by starting zcashd with
|
||||
`-requirewalletbackup=false` but this is not recommended unless you know what
|
||||
you're doing (and have otherwise backed up the wallet's recovery phrase anyway).
|
||||
For security reasons, this RPC method is not intended for use via zcash-cli
|
||||
but is provided to enable `zcashd-wallet-tool` and other third-party wallet
|
||||
interfaces to satisfy the backup confirmation requirement. Use of the
|
||||
`walletconfirmbackup` API via zcash-cli would risk that the recovery phrase
|
||||
being confirmed might be leaked via the user's shell history or the system
|
||||
process table; `zcashd-wallet-tool` is specifically provided to avoid this
|
||||
problem.
|
||||
- 'z_getbalanceforviewingkey' This newly created API allows a user to obtain
|
||||
balance information for funds visible to a Sapling or Unified full
|
||||
viewing key; if a Sprout viewing key is provided, this method allows
|
||||
retrieval of the balance only in the case that the wallet controls the
|
||||
corresponding spending key.
|
||||
|
||||
RPC Changes
|
||||
-----------
|
||||
|
||||
- The results of the 'dumpwallet' and 'z_exportwallet' RPC methods have been modified
|
||||
to now include the wallet's newly generated emergency recovery phrase as part of the
|
||||
exported data.
|
||||
|
||||
- The results of the 'getwalletinfo' RPC have been modified to return two new fields:
|
||||
`mnemonic_seedfp` and `legacy_seedfp`, the latter of which replaces the field that
|
||||
was previously named `seedfp`.
|
||||
|
||||
Wallet
|
||||
------
|
||||
|
||||
'z_sendmany'
|
||||
------------
|
||||
|
||||
- The 'z_sendmany' RPC call no longer permits Sprout recipients in the
|
||||
list of recipient addresses. Transactions spending Sprout funds will
|
||||
still result in change being sent back into the Sprout pool, but no
|
||||
other `Sprout->Sprout` transactions will be constructed by the Zcashd
|
||||
wallet.
|
||||
|
||||
- The restriction that prohibited `Sprout->Sapling` transactions has been
|
||||
lifted; however, since such transactions reveal the amount crossing
|
||||
pool boundaries, they must be explicitly enabled via a parameter to
|
||||
the 'z_sendmany' call.
|
||||
|
||||
- A new string parameter, `privacyPolicy`, has been added to the list of
|
||||
arguments accepted by `z_sendmany`. This parameter enables the caller to
|
||||
control what kind of information they permit `zcashd` to reveal when creating
|
||||
the transaction. If the transaction can only be created by revealing more
|
||||
information than the given strategy permits, `z_sendmany` will return an
|
||||
error. The parameter defaults to `LegacyCompat`, which applies the most
|
||||
restrictive strategy `FullPrivacy` when a Unified Address is present as the
|
||||
sender or a recipient, and otherwise preserves existing behaviour (which
|
||||
corresponds to the `AllowFullyTransparent` policy).
|
||||
|
||||
- Since Sprout outputs are no longer created (with the exception of change)
|
||||
'z_sendmany' no longer generates payment disclosures (which were only
|
||||
available for Sprout outputs) when the `-paymentdisclosure` experimental
|
||||
feature flag is set.
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Charlie O'Keefe (2):
|
||||
Update base image used by Dockerfile from debian 10 to debian 11
|
||||
Remove stretch (debian 9), add bullseye (debian 11) in gitian descriptor
|
||||
|
||||
Conrado Gouvea (1):
|
||||
Add comment to zcash_script_new_precomputed_tx about references to input buffers
|
||||
|
||||
Daira Hopwood (25):
|
||||
Avoid a warning by explicitly calling drop.
|
||||
Replace call to drop with zeroization.
|
||||
contrib/devtools/rust-deps-graph.sh: allow overriding the arguments to `cargo deps`.
|
||||
Cosmetic whitespace change
|
||||
Make a zcashd-wallet-tool executable.
|
||||
Attempt to fix linking problem on ARM.
|
||||
Add some text about choosing location of the physical backup. Add TODO for better handling of file not found and permission errors.
|
||||
Move `wallet_tool.rs` from `src/rust/src` into `src/rust/bin`. Also add a brief description of `zcashd-wallet-tool` to `src/rust/README.md`.
|
||||
Improved error handling.
|
||||
The recovery phrase confirmation and `zcashd-wallet-tool` are being introduced in zcashd v4.7.0.
|
||||
Refactor use of `export_path` as suggested.
|
||||
Tweak the wording of the fallback messages when the terminal cannot be automatically cleared.
|
||||
Simplify extraction of recovery phrase.
|
||||
Improve memory hygiene, and use -stdin to pass the recovery phrase to the child zcash-cli process.
|
||||
Cleanups to error handling for the first invocation of zcash-cli.
|
||||
Use the tracing crate for debugging.
|
||||
Improve error message when the config file cannot be found, taking into account -conf and -datadir.
|
||||
Ensure the buffer used in `prompt` is zeroized even on error.
|
||||
Document that '~' cannot be used in `-datadir` (see #5661).
|
||||
Set `meta` for `-datadir` option.
|
||||
Simplify debug tracing.
|
||||
Correct the fallback instruction for how to clear the terminal on macOS: pressing Command + K also clears scrollback.
|
||||
Include $(bin_SCRIPTS) in `check-symbols`, `check-security`, and `clean` targets. Checking for stack canaries in `check-security` is disabled for Rust executables (Rust does support `-Z stack-protector=all` but only for the nightly compiler).
|
||||
qa/zcash/full_test_suite.py: enable `test_rpath_runpath` for Rust binaries, and reenable `test_fortify_source` for C++ binaries.
|
||||
Tweaks to message text.
|
||||
|
||||
Dimitris Apostolou (1):
|
||||
Fix typos
|
||||
|
||||
Jack Grigg (65):
|
||||
wallet: Implement `z_getnewaccount`
|
||||
wallet: Implement `z_getaddressforaccount`
|
||||
wallet: Implement `z_listunifiedreceivers`
|
||||
wallet: Show UAs owned by the wallet in `z_viewtransaction`
|
||||
wallet: Reverse order of arguments to z_getaddressforaccount
|
||||
rust: Add missing Orchard receiver check in UA parsing
|
||||
rust: Add missing checks for shielded UFVK items
|
||||
rust: Add missing checks for transparent UFVK items
|
||||
wallet: Implement `z_getbalanceforaccount`
|
||||
wallet: Fix account generation bug
|
||||
wallet: Implement `z_getbalanceforaddress`
|
||||
wallet: Don't show Sapling receivers from UAs in `z_listaddresses`
|
||||
wallet: Show UAs instead of Sapling receivers in `z_listunspent`
|
||||
wallet: Remove `CWallet::GetKeyFromPool`
|
||||
wallet: Store internal transparent keys in the keypool
|
||||
wallet: Separate counters for external and internal transparent keys
|
||||
Add Orchard recipient support to the transaction builder
|
||||
Make `TransactionBuilder::AddOrchardOutput` memo optional
|
||||
Return UFVK from `CWallet::GenerateNewUnifiedSpendingKey`
|
||||
Rename to `ZcashdUnifiedSpendingKey::GetSaplingKey` for consistency
|
||||
Trial-decrypt candidate Sapling receivers with all wallet UFVKs
|
||||
Add mappings from Orchard receivers to IVKs to the wallet
|
||||
Add Orchard to default UA receiver types
|
||||
Fix semantic merge conflicts
|
||||
Select Orchard receivers preferentially from UAs
|
||||
Regenerate `TxDigests` after rebuilding tx with Orchard bundle
|
||||
qa: Bump all postponed dependencies
|
||||
qa: Postpone recent CCache release
|
||||
depends: Update Rust to 1.59.0
|
||||
depends: Update Clang / libcxx to LLVM 13.0.1
|
||||
cargo update
|
||||
rust: Fix clippy lint
|
||||
Ensure the view's best Orchard anchor matches the previous block
|
||||
Add missing `view.PopAnchor(_, ORCHARD)` in `DisconnectBlock`
|
||||
Add RPC test for the Orchard commitment tree bug on first NU5 testnet
|
||||
Use `std::optional` in `CValidationInterface::GetAddressForMining`
|
||||
Select Orchard receivers from UA miner addresses once NU5 activates
|
||||
miner: Manually add dummy Orchard output with Orchard miner address
|
||||
rpc: Handle keypool exhaustion separately in `generate` RPC
|
||||
depends: Revert to `libc++ 13.0.0-3` for Windows cross-compile
|
||||
Add Orchard spend support to the transaction builder
|
||||
wallet: Alter `OrchardWallet::GetSpendInfo` to return the spending key
|
||||
Improve FFI interface documentation
|
||||
Refactor `CWallet::GenerateChangeAddressForAccount`
|
||||
Add unit tests for `SpendableInputs::LimitToAmount`
|
||||
Fix bug in `SpendableInputs::LimitToAmount`
|
||||
Select spendable inputs based on recipient and change pool types
|
||||
Implement opportunistic shielding in `SpendableInputs::LimitToAmount`
|
||||
Add Orchard cases to note selection logic
|
||||
Add Orchard to change address generation
|
||||
Add support for sending Orchard funds in `z_sendmany`
|
||||
Set default Orchard anchor confirmations to 1
|
||||
build: Fix `zcash/address/orchard.hpp` filename in `src/Makefile.am`
|
||||
z_sendmany: Replace `allowRevealedAmount` with `privacyStrategy`
|
||||
z_sendmany: Expand `privacyPolicy` cases
|
||||
build: Add missing `util/match.h` to `src/Makefile.am`
|
||||
wallet: Add seedfp to transparent keys in dumpwallet / z_exportwallet
|
||||
Fix bugs in wallet_addresses RPC test
|
||||
wallet: Fix bugs in `listaddresses`
|
||||
wallet: Fix Sapling address bug in `listaddresses`
|
||||
wallet: Fix expected `listaddresses` sources in `rpc_wallet_tests`
|
||||
qa: Exclude `native_libtinfo` from dependency update checks
|
||||
cargo update
|
||||
make-release.py: Versioning changes for 4.7.0-rc1.
|
||||
make-release.py: Updated manpages for 4.7.0-rc1.
|
||||
|
||||
John Newbery (1):
|
||||
Log calls to getblocktemplate
|
||||
|
||||
Jonas Schnelli (1):
|
||||
[Wallet] add HD xpriv to dumpwallet
|
||||
|
||||
Kris Nuttycombe (243):
|
||||
Derive random HD seeds from ZIP-339 seed phrases.
|
||||
Add support for externally searching for valid Sapling diversifiers.
|
||||
Adds basic unified spending key derivation.
|
||||
Add unified full viewing keys and Zcash-internal unified addresses.
|
||||
Use the default UA-based Sapling address for the saplingmigration tool.
|
||||
Fix tests for wallet operations on legacy Sapling keys.
|
||||
Remove unused forward declaration.
|
||||
Update librustzcash dependency version.
|
||||
Apply suggestions from code review
|
||||
Derive transparent keys from mnemonic seed.
|
||||
Generate legacy Sapling addresses from the mnemonic seed.
|
||||
Replace account ID with ZIP-0032 key path in listaddresses output.
|
||||
Use legacy address for Sapling migration until UA functionality is available to the RPC tests.
|
||||
Revert change to the type of GenerateNewKey
|
||||
Fix wallet import/export test
|
||||
Use 0x7FFFFFFF for the legacy account ID.
|
||||
Require backup of the emergency recovery phrase.
|
||||
Use hardened derivation for the legacy Sapling key at the address index level.
|
||||
Address comments from code review.
|
||||
Restore legacy HD seed storage & retrieval tests.
|
||||
Fix spurious test passage.
|
||||
Move CKeyMetadata back to wallet.h
|
||||
Clean up format of recovery information in the wallet dump.
|
||||
Use SecureString for mnemonic phrase.
|
||||
Apply suggestions from code review
|
||||
Fix diversifier_index_t less than operator.
|
||||
Restore FindAddress comment
|
||||
Fix transparent BIP-44 keypaths.
|
||||
Fix naming of unified spending & full viewing keys
|
||||
Fix max transparent diversifier index.
|
||||
Clean up handling of mnemonic seed metadata.
|
||||
Restore legacy HDSeed encryption tests.
|
||||
Style fix in BIP 32 path account parsing test.
|
||||
Do not strip quotes when verifying mnemonic seed.
|
||||
Apply suggestions from code review
|
||||
Fix name of menmonic entropy length constant.
|
||||
Fix polymorphism of string serialization.
|
||||
Document mnemonic-seed-related RPC method changes & update changelog.
|
||||
Minor cleanup of the code that searches for a valid transparent key.
|
||||
Generalize keypath parsing over account id.
|
||||
Update documentation for GenerateNewSeed.
|
||||
Use MnemonicSeed instead of HDSeed where appropriate.
|
||||
Add diversifier_index_t::ToTransparentChildIndex
|
||||
Only maintain CKeyID for the transparent part of ZcashdUnifiedAddress
|
||||
Minor naming fixes
|
||||
Parameterize HD keypath parsing by coin type.
|
||||
Fix error message text to refer to zcashd-wallet-tool rather than the RPC method.
|
||||
Apply suggestions from code review
|
||||
Minor textual fixes to release notes.
|
||||
Improve documentation of the `-walletrequirebackup` zcashd wallet configuration option.
|
||||
Add libzcash::AccountId type.
|
||||
Adds Orchard Address, IncomingViewingKey, FullViewingKey, and SpendingKey types.
|
||||
Apply suggestions from code review
|
||||
Update orchard & librustzcash dependency versions.
|
||||
Remove incorrect FFI method documentation.
|
||||
Remove Orchard spending key equality implementation.
|
||||
Refine structure of Zcash address generation.
|
||||
Use CKeyID and CScriptID instead of new P2PKH/P2SHAddress classes.
|
||||
Remove ZcashdUnifiedAddress in favor of UnifiedAddress
|
||||
Update to ufvk zcash_address build.
|
||||
Adds SaplingDiversifiableFullViewingKey
|
||||
Add Rust FFI components for unified full viewing keys.
|
||||
Add UnifiedFullViewingKey type.
|
||||
Apply suggestions from code review
|
||||
Add tests for ufvk roundtrip serialization.
|
||||
Apply suggestions from code review
|
||||
Apply suggestions from code review
|
||||
Apply suggestions from code review
|
||||
Add functions for generating BIP-44 and ZIP-32 keypaths
|
||||
Check the output of zip339_phrase_to_seed in MnemonicSeed initialization.
|
||||
Compute key id for UFVKs.
|
||||
Add ZcashdUnifiedKeyMetadata and libzcash::ReceiverType
|
||||
Add unified key components to the transparent & Sapling wallet parts.
|
||||
Store ufvks to the wallet database.
|
||||
Add unified address tracking to KeyStore
|
||||
Load unified full viewing keys from the walletdb.
|
||||
Add key metadata to walletdb.
|
||||
Add unified address generation.
|
||||
AddTransparentSecretKey does not need to take a CExtKey
|
||||
Add newly generated transparent UA receivers to the wallet.
|
||||
Add CWallet::GetUnifiedForReceiver
|
||||
Add tests for keystore storage and retrieval of UFVKs.
|
||||
Add test for wallet UA generation & detection.
|
||||
Add test for CKeyStore::AddUnifiedAddress
|
||||
Fix handling of unified full viewing key metadata.
|
||||
Apply suggestions from code review
|
||||
Only derive ZcashdUnifiedFullViewingKey from UnifiedFullViewingKey
|
||||
Rename `ZcashdUnifiedSpendingKeyMetadata` -> `ZcashdUnifiedAccount`
|
||||
Remove unused ufvkid argument from AddTransparentSecretKey
|
||||
Ensure that unified address metadata is always correctly populated.
|
||||
Apply suggestions from code review
|
||||
Make `FindAddress` correctly check for maximum transparent child index.
|
||||
Use Bip44TransparentAccountKeyPath() for Bip44AccountChains keypath construction.
|
||||
Improve documentation of UFVK/UA metadata storage semantics.
|
||||
Apply suggestions from code review
|
||||
Fix encoding order of unified addresses.
|
||||
Remove the `InvalidEncoding` type from key & address variants.
|
||||
Use CKeyID and CScriptID instead of new P2PKH/P2SHAddress classes.
|
||||
Remove spurious uses of HaveSpendingKeyForPaymentAddress
|
||||
Add raw transparent address types to PaymentAddress
|
||||
Remove spurious variant from asyncrpcoperation_sendmany
|
||||
Remove `RawAddress`
|
||||
Replace `DecodeDestination` in `GetMinerAddress` with `DecodePaymentAddress`
|
||||
Remove uses of KeyIO::DecodeDestination
|
||||
Remove a use of KeyIO::DecodeDestination in z_shieldcoinbase
|
||||
Use libzcash::PaymentAddress instead of std::string in mergetoaddress
|
||||
Improve error messages in the case of taddr decoding failure.
|
||||
Apply suggestions from code review
|
||||
Apply suggestions from code review
|
||||
Use transaction builder for asyncrpcoperation_sendmany.
|
||||
Transaction builder must not set consensus branch ID for V4 transactions.
|
||||
Fix conditions around dust thresholds.
|
||||
Require an explicit flag to allow cross-pool transfers in z_sendmany.
|
||||
Apply suggestions from code review
|
||||
Return z_sendmany errors synchronously when possible.
|
||||
Update release notes to reflect z_sendmany changes
|
||||
Move FindSpendableInputs to CWallet
|
||||
Replace the badly-named `PaymentSource` with `ZTXOSelector`
|
||||
Add support for unified addresses to CWallet::ToZTXOSelector
|
||||
Replace `HaveSpendingKeyForAddress` with checks at ZTXOSelector construction.
|
||||
Modify CWallet::FindSpendableInputs to use ZTXOSelector
|
||||
Add CWallet::FindAccountForSelector
|
||||
Add RecipientAddress type.
|
||||
Use libzcash::RecipientAddress for z_sendmany recipients.
|
||||
Apply suggestions from code review
|
||||
Rename ZTXOSelector::SpendingKeysAvailable -> RequireSpendingKeys
|
||||
Make `FindSpendableInputs` respect `ZTXOSelector::RequireSpendingKeys()`
|
||||
Clarify documentation of CWallet::FindAccountForSelector
|
||||
Use `ZTXOSelector::IncludesSapling` rather than selector internals.
|
||||
Add documentation for `UnifiedAddress::GetPreferredRecipientAddress`
|
||||
Add correct selection of change addresses to z_sendmany
|
||||
Add a first-class type for transparent full viewing keys.
|
||||
Implement OVK selection for z_sendmany.
|
||||
Implement derivation of the internal Sapling spending key.
|
||||
Use a BIP 44 change address for change when sending from legacy t-addrs.
|
||||
Add a check for internal vs. external outputs to wallet_listreceived test.
|
||||
Fix z_sendmany handling of transparent OVK derivation in the ANY_TADDR case.
|
||||
Simplify determination of valid change types.
|
||||
Add failing tests for z_sendmany ANY_TADDR -> UA and UA -> <legacy_taddr>
|
||||
Add gtest for change address derivation.
|
||||
Fix variable shadowing in change address derivation & add change IVK to the keystore.
|
||||
GenerateLegacySaplingZKey only needs to return an address, not an extfvk.
|
||||
Rename AddSaplingIncomingViewingKey -> AddSaplingPaymentAddress
|
||||
Do not add Sapling addresses to the wallet by default when adding extfvks.
|
||||
Fix a bug in the generation of addresses from UFVKs
|
||||
Add accessor method for Sapling IVKs to SaplingDiversifiableFullViewingKey
|
||||
Address TODOs in rpc-tests/wallet-accounts.py
|
||||
Add a few additional cases to z_sendmany RPC tests.
|
||||
Update librustzcash dependency.
|
||||
Fix nondeterministic test error (checking for the wrong error case).
|
||||
Use z_shieldcoinbase for Sprout funds in wallet_listreceived tests.
|
||||
Apply suggestions from code review
|
||||
Apply suggestions from code review.
|
||||
Fix nondeterministic test failure in wallet_listreceivedby.py
|
||||
Fix locking in z_getbalanceforaddress and z_getbalanceforaccount
|
||||
Replace z_getbalanceforaddress with z_getbalanceforviewingkey
|
||||
Clarify documentation of z_getbalanceforviewingkey for Sprout viewing keys.
|
||||
Implement PaymentAddressBelongsToWallet for unified addresses.
|
||||
Add unified address support to GetSourceForPaymentAddress
|
||||
Address comments from review.
|
||||
Add change field to z_listreceivedbyaddress for transparent addrs.
|
||||
Fix missing std::variant header that was breaking Darwin builds.
|
||||
Add test vectors for UFVK derivation
|
||||
Rename sapling-specific zip32 FFI methods.
|
||||
Make SaveRecipientMappings polymorphic in RecipientMapping type.
|
||||
Add Rust backend for Orchard components of the wallet.
|
||||
Add GetFilteredNotes to Orchard wallet.
|
||||
Add test for Orchard wallet note detection.
|
||||
Move parsing of unified addresses to UnifiedAddress.
|
||||
Remove txid field from TxNotes
|
||||
Apply suggestions from code review
|
||||
Add various bits of documentation
|
||||
Add Orchard components to unified address
|
||||
Add Orchard components to unified full viewing keys
|
||||
Add Orchard components to unified spending keys
|
||||
Remove OrchardSpendingKey serialization code
|
||||
Select Orchard notes in FindSpendableInputs
|
||||
GenerateNewKey must be guarded by a cs_wallet lock
|
||||
Filter returned Orchard notes by minimum confirmations.
|
||||
Log outpoint for failed Sapling witness lookup.
|
||||
Add a roundtrip test for Orchard merkle frontier serialization from the C++ side.
|
||||
Add test for Orchard contribution to z_gettotalbalance
|
||||
Respect minDepth argument for Orchard notes in GetFilteredNotes
|
||||
Update MSRV for lints.
|
||||
Update incrementalmerkletree version
|
||||
Split LoadWalletTx from AddToWallet
|
||||
Fix missing locks for GenerateNewUnifiedSpendingKey tests.
|
||||
Record when notes are detected as being spent in the Orchard wallet.
|
||||
Reset Orchard wallet state when rescanning from below NU5 activation.
|
||||
Check wallet latest anchor against hashFinalOrchardRoot in ChainTip.
|
||||
Remove assertions that require Orchard wallet persistence to satisfy.
|
||||
Add a test for Orchard note detection.
|
||||
Assert we never attempt to checkpoint the Orchard wallet at a negative block height.
|
||||
Apply suggestions from code review
|
||||
Add an `InPoint` type to the Orchard wallet to fix incorrect conflict data.
|
||||
Apply suggestions from code review
|
||||
Fix missing update to `last_checkpoint` on rewind.
|
||||
Track mined-ness instead of spent-ness of notes in the Orchard wallet.
|
||||
Apply suggestions from code review
|
||||
Respect maxDepth for Orchard notes in GetFilteredNotes
|
||||
Set number of confirmations for Orchard notes returned by FindSpendableInputs
|
||||
Update walletTx with decrypted Orchard action metadata.
|
||||
Persist Orchard action index/IVK mappings in CWalletTx
|
||||
Restore decrypted notes to the wallet.
|
||||
Update tests with rollback checks.
|
||||
Apply suggestions from code review
|
||||
Restore mined block heights when restoring decrypted notes.
|
||||
Apply suggestions from code review
|
||||
Return std::optional<CExtKey> from CExtKey::Master
|
||||
Ensure that Orchard spentness information is repopulated by LoadUnifiedCaches.
|
||||
Modify `join_network` in tests to skip mempool resync.
|
||||
Address suggestions from code review on #5637
|
||||
Apply suggestions from code review
|
||||
Serialize the Orchard note commitment tree to the wallet.
|
||||
Update orchard_wallet_add_notes_from_bundle documentation.
|
||||
Derive the new mnemonic seed from the legacy HD seed, if one is available.
|
||||
Fix indentation.
|
||||
Apply suggestions from code review
|
||||
Explicitly specify the change address in the Sapling migration.
|
||||
Add TODO for Orchard z_listunspent integration.
|
||||
Document that z_getnewaccount and z_getaddressforaccount replace z_getnewaddress.
|
||||
Add orchard support to z_getnotescount
|
||||
Document that Orchard keys are not supported in z_importkey help
|
||||
Return the correct error for `z_getbalance` if a UA does not correspond to an account in the wallet..
|
||||
Update documentation of z_importviewingkey and z_exportviewingkey to address unified keys.
|
||||
Add UnifiedAddress variant to ZTXOSelector
|
||||
Eliminate redundancy between the wallet and the keystore.
|
||||
Simplify retrieval of unified account by address.
|
||||
Add OrchardWallet::GetTxActions
|
||||
Apply suggestions from code review
|
||||
Update z_viewtransaction documentation to correctly represent field names.
|
||||
Add debug printing for receivers and recipient addresses.
|
||||
Correctly report change outputs in z_viewtransaction.
|
||||
Return the default unified address if we have the UFVK but no address metadata.
|
||||
Fix missing AllowRevealedSenders flag in test.
|
||||
Uncomment addtional tests that depend on z_viewtransaction.
|
||||
Minor rename & documentation improvement.
|
||||
Add RecipientType to GetPaymentAddressForRecipient result.
|
||||
Make CWallet::DefaultReceiverTypes height-dependent.
|
||||
Return failure rather than asserting on WriteRecipientMapping failure.
|
||||
Lock cs_main for accesses to chainActive in GetPaymentAddressForRecipient.
|
||||
Fix legacy address handling in CWallet::GetPaymentAddressForRecipient
|
||||
Documentation fix for UnifiedAddressForReciever (+ spelling corrections.)
|
||||
|
||||
Larry Ruane (12):
|
||||
add ParseArbitraryInt() for diversifier index
|
||||
add -orchardwallet experimental feature flag
|
||||
Add new and modify existing Orchard RPCs, non-functional
|
||||
mining: submitblock: log detailed equihash solution error
|
||||
allow UA as z_shieldcoinbase destination
|
||||
fix minconf parsing for z_getbalanceforaccount and z_getbalanceforaddress
|
||||
Update z_listreceivedbyaddress to support unified addresses (5467)
|
||||
fix wallet_listreceived.py, add blockdata to taddr output
|
||||
z_listreceivedbyaddress: reject UA component addr (#5537)
|
||||
add functional test
|
||||
document global variables
|
||||
update listaddresses RPC for UAs, Orchard
|
||||
|
||||
Marius Kjærstad (1):
|
||||
Update copyright year to 2022
|
||||
|
||||
Pieter Wuille (2):
|
||||
Fix csBestBlock/cvBlockChange waiting in rpc/mining
|
||||
Modernize best block mutex/cv/hash variable naming
|
||||
|
||||
Sean Bowe (5):
|
||||
wallet: consolidate unified key/address/account map reconstruction
|
||||
wallet: restore Orchard secret keys from mnemonic seed
|
||||
wallet: restore orchard address to IVK mappings during wallet loading
|
||||
wallet: rather than assert, error in case of inconsistency between FVK and address
|
||||
wallet: add logging for failure cases in unified cache loading
|
||||
|
||||
Steven Smith (8):
|
||||
Lock cs_main prior to calling blockToJSON
|
||||
Mark z_gettotalbalance and dumpwallet as deprecated
|
||||
Add Orchard support to the z_gettreestate RPC
|
||||
Update transaction size estimation to include V5 transactions
|
||||
Extend uniqueness check in z_sendmany to UA receivers
|
||||
Load previously persisted sent transaction recipient metadata back into the wallet.
|
||||
Add Orchard & unified address support to z_viewtransaction.
|
||||
Ensure z_viewtransaction returns Orchard details
|
||||
|
||||
Taylor Hornby (1):
|
||||
Untested, not working yet, use libtinfo from the debian packages
|
||||
|
||||
sasha (12):
|
||||
on Arch only, use Debian's libtinfo5_6.0 to satisfy clang
|
||||
explain the 0x0f0f[..]0f0f powLimit constant for regtest
|
||||
remove superfluous space at end of native_packages line
|
||||
gtests ordering: change wallet filename in WriteZkeyDirectToDb
|
||||
gtests ordering: ContextualCheckBlockTest's TearDown deactivates Blossom
|
||||
gtests ordering: CheckBlock.VersionTooLow calls SelectParams(MAIN)
|
||||
implement AtomicTimer::zeroize() that resets start_time and total_time
|
||||
gtests ordering: make Metrics.GetLocalSolPS idempotent
|
||||
gtests ordering: clean up wallet files before each WalletZkeysTest
|
||||
make librustzcash_init_zksnark_params idempotent
|
||||
move proof parameter loading out of gtest/main.cpp and into utiltest.cpp
|
||||
Call LoadProofParameters() in gtests that need proofs
|
||||
|
||||
Ying Tong Lai (18):
|
||||
Move SendManyRecipient to wallet.h and introduce optional ua field.
|
||||
SendTransaction: Introduce recipients argument.
|
||||
Implement read and write for (txid, recipient) -> ua mapping.
|
||||
z_sendmany: Only get ua if decoded is ua variant.
|
||||
ShieldToAddress: Factor out static shieldToAddress() helper.
|
||||
Docfixes.
|
||||
CSerializeRecipientAddress: add Read method and make constructor private.
|
||||
WriteRecipientMapping: Check that receiver exists in UA.
|
||||
wallet_sendmany_any_taddr.py: Test sending from a change taddr.
|
||||
wallet_sendmany_any_taddr.py: Test sending output from expired tx.
|
||||
FindSpendableInputs: Add nDepth < 0 check.
|
||||
wallet_sendmany_any_taddr.py: Expect expired tx to be ignored.
|
||||
Orchard: invalidate mempool transactions that use orphaned anchors.
|
||||
coins_tests.cpp: Add Orchard nullifier to TxWithNullifiers().
|
||||
coins_tests: Update tests to include Orchard case.
|
||||
CWallet::GetConflictS: Handle conflicting Orchard spends.
|
||||
z_getbalance: Handle Unified Address case.
|
||||
Adapt RPC tests to use z_getbalance for UAs.
|
||||
|
||||
ying tong (2):
|
||||
Apply docfixes from code review
|
||||
Style improvements in RPC tests.
|
||||
|
||||
Zancas Wilcox (4):
|
||||
blake2b/s is integrated into hashlib, drop external python package dependency
|
||||
update doctest in gtest suite to prefer hashlib
|
||||
enforce usage of the get_tests comptool interface as ComparisonTestFramework method
|
||||
All implementations of ComparisonTestFramework were overriding num_nodes
|
||||
|
|
@ -60,6 +60,7 @@ BASE_SCRIPTS= [
|
|||
'wallet_persistence.py',
|
||||
'wallet_listnotes.py',
|
||||
# vv Tests less than 60s vv
|
||||
'orchard_reorg.py',
|
||||
'fundrawtransaction.py',
|
||||
'reorg_limit.py',
|
||||
'mempool_limit.py',
|
||||
|
@ -72,6 +73,7 @@ BASE_SCRIPTS= [
|
|||
'wallet_changeindicator.py',
|
||||
'wallet_import_export.py',
|
||||
'wallet_isfromme.py',
|
||||
'wallet_orchard.py',
|
||||
'wallet_nullifiers.py',
|
||||
'wallet_sapling.py',
|
||||
'wallet_sendmany_any_taddr.py',
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
NU5_BRANCH_ID,
|
||||
assert_equal,
|
||||
connect_nodes_bi,
|
||||
get_coinbase_address,
|
||||
nuparams,
|
||||
start_nodes,
|
||||
wait_and_assert_operationid_status,
|
||||
)
|
||||
|
@ -17,6 +19,7 @@ from decimal import Decimal
|
|||
|
||||
SPROUT_TREE_EMPTY_ROOT = "59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7"
|
||||
SAPLING_TREE_EMPTY_ROOT = "3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb"
|
||||
ORCHARD_TREE_EMPTY_ROOT = "2fd8e51a03d9bbe2dd809831b1497aeb68a6e37ddf707ced4aa2d8dff13529ae"
|
||||
NULL_FIELD = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
# Verify block header field 'hashFinalSaplingRoot' (returned in rpc as 'finalsaplingroot')
|
||||
|
@ -25,17 +28,16 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.num_nodes = 4
|
||||
self.num_nodes = 2
|
||||
self.setup_clean_chain = True
|
||||
|
||||
def setup_network(self, split=False):
|
||||
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[
|
||||
'-txindex' # Avoid JSONRPC error: No information available about transaction
|
||||
'-txindex', # Avoid JSONRPC error: No information available about transaction
|
||||
nuparams(NU5_BRANCH_ID, 210),
|
||||
'-debug',
|
||||
]] * self.num_nodes)
|
||||
connect_nodes_bi(self.nodes,0,1)
|
||||
connect_nodes_bi(self.nodes,1,2)
|
||||
connect_nodes_bi(self.nodes,0,2)
|
||||
connect_nodes_bi(self.nodes,0,3)
|
||||
self.is_network_split=False
|
||||
self.sync_all()
|
||||
|
||||
|
@ -52,13 +54,19 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
|
||||
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
|
||||
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
|
||||
assert("skipHash" not in treestate["sprout"])
|
||||
assert "skipHash" not in treestate["sprout"]
|
||||
|
||||
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], NULL_FIELD)
|
||||
# There is no sapling state tree yet, and trying to find it in an earlier
|
||||
# block won't succeed (we're at genesis block), so skipHash is absent.
|
||||
assert("finalState" not in treestate["sapling"])
|
||||
assert("skipHash" not in treestate["sapling"])
|
||||
assert "finalState" not in treestate["sapling"]
|
||||
assert "skipHash" not in treestate["sapling"]
|
||||
|
||||
assert_equal(treestate["orchard"]["commitments"]["finalRoot"], NULL_FIELD)
|
||||
# There is no orchard state tree yet, and trying to find it in an earlier
|
||||
# block won't succeed (we're at genesis block), so skipHash is absent.
|
||||
assert "finalState" not in treestate["orchard"]
|
||||
assert "skipHash" not in treestate["orchard"]
|
||||
|
||||
# Verify all generated blocks contain the empty root of the Sapling tree.
|
||||
blockcount = self.nodes[0].getblockcount()
|
||||
|
@ -70,14 +78,18 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
assert_equal(treestate["height"], height)
|
||||
assert_equal(treestate["hash"], self.nodes[0].getblockhash(height))
|
||||
|
||||
assert("skipHash" not in treestate["sprout"])
|
||||
assert "skipHash" not in treestate["sprout"]
|
||||
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
|
||||
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
|
||||
|
||||
assert("skipHash" not in treestate["sapling"])
|
||||
|
||||
assert "skipHash" not in treestate["sapling"]
|
||||
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], SAPLING_TREE_EMPTY_ROOT)
|
||||
assert_equal(treestate["sapling"]["commitments"]["finalState"], "000000")
|
||||
|
||||
assert "skipHash" not in treestate["orchard"]
|
||||
assert_equal(treestate["orchard"]["commitments"]["finalRoot"], NULL_FIELD)
|
||||
|
||||
# Node 0 shields some funds
|
||||
taddr0 = get_coinbase_address(self.nodes[0])
|
||||
saplingAddr0 = self.nodes[0].z_getnewaddress('sapling')
|
||||
|
@ -93,8 +105,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
# Verify the final Sapling root has changed
|
||||
blk = self.nodes[0].getblock("201")
|
||||
root = blk["finalsaplingroot"]
|
||||
assert(root is not SAPLING_TREE_EMPTY_ROOT)
|
||||
assert(root is not NULL_FIELD)
|
||||
assert root is not SAPLING_TREE_EMPTY_ROOT
|
||||
assert root is not NULL_FIELD
|
||||
|
||||
# Verify there is a Sapling output description (its commitment was added to tree)
|
||||
result = self.nodes[0].getrawtransaction(mytxid, 1)
|
||||
|
@ -105,8 +117,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
|
||||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||
assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"])
|
||||
assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"])
|
||||
assert new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"]
|
||||
assert new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"]
|
||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
|
||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 70)
|
||||
treestate = new_treestate
|
||||
|
@ -145,8 +157,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
|
||||
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
||||
assert(new_treestate["sprout"]["commitments"]["finalRoot"] != treestate["sprout"]["commitments"]["finalRoot"])
|
||||
assert(new_treestate["sprout"]["commitments"]["finalState"] != treestate["sprout"]["commitments"]["finalState"])
|
||||
assert new_treestate["sprout"]["commitments"]["finalRoot"] != treestate["sprout"]["commitments"]["finalRoot"]
|
||||
assert new_treestate["sprout"]["commitments"]["finalState"] != treestate["sprout"]["commitments"]["finalState"]
|
||||
assert_equal(len(new_treestate["sprout"]["commitments"]["finalRoot"]), 64)
|
||||
assert_equal(len(new_treestate["sprout"]["commitments"]["finalState"]), 134)
|
||||
treestate = new_treestate
|
||||
|
@ -164,7 +176,7 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
|
||||
assert_equal(len(self.nodes[0].getblock("205")["tx"]), 2)
|
||||
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal("12.34"))
|
||||
assert(root is not self.nodes[0].getblock("205")["finalsaplingroot"])
|
||||
assert root is not self.nodes[0].getblock("205")["finalsaplingroot"]
|
||||
|
||||
# Verify there is a Sapling output description (its commitment was added to tree)
|
||||
result = self.nodes[0].getrawtransaction(mytxid, 1)
|
||||
|
@ -172,8 +184,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
|
||||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||
assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"])
|
||||
assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"])
|
||||
assert new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"]
|
||||
assert new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"]
|
||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
|
||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 136)
|
||||
treestate = new_treestate
|
||||
|
@ -200,6 +212,21 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
||||
|
||||
# Activate NU5; more testing should be added once we can mine orchard transactions.
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(4)
|
||||
self.sync_all()
|
||||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||
|
||||
# sprout and sapling results should not change
|
||||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
||||
|
||||
assert "skipHash" not in treestate["orchard"]
|
||||
assert_equal(new_treestate["orchard"]["commitments"]["finalRoot"], ORCHARD_TREE_EMPTY_ROOT)
|
||||
assert_equal(new_treestate["orchard"]["commitments"]["finalState"], "00")
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
FinalSaplingRootTest().main()
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
from decimal import Decimal
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.mininode import nuparams
|
||||
from test_framework.mininode import COIN, nuparams
|
||||
from test_framework.util import (
|
||||
BLOSSOM_BRANCH_ID,
|
||||
HEARTWOOD_BRANCH_ID,
|
||||
CANOPY_BRANCH_ID,
|
||||
NU5_BRANCH_ID,
|
||||
assert_equal,
|
||||
assert_raises,
|
||||
bitcoind_processes,
|
||||
|
@ -28,8 +30,12 @@ class ShieldCoinbaseTest (BitcoinTestFramework):
|
|||
|
||||
def start_node_with(self, index, extra_args=[]):
|
||||
args = [
|
||||
'-experimentalfeatures',
|
||||
'-orchardwallet',
|
||||
nuparams(BLOSSOM_BRANCH_ID, 1),
|
||||
nuparams(HEARTWOOD_BRANCH_ID, 10),
|
||||
nuparams(CANOPY_BRANCH_ID, 20),
|
||||
nuparams(NU5_BRANCH_ID, 20),
|
||||
"-nurejectoldversions=false",
|
||||
]
|
||||
return start_node(index, self.options.tmpdir, args + extra_args)
|
||||
|
@ -117,5 +123,50 @@ class ShieldCoinbaseTest (BitcoinTestFramework):
|
|||
assert_equal(self.nodes[0].z_getbalance(node0_taddr), 2)
|
||||
assert_equal(self.nodes[1].z_getbalance(node1_zaddr), 1)
|
||||
|
||||
# Generate a Unified Address for node 1
|
||||
self.nodes[1].z_getnewaccount()
|
||||
node1_addr0 = self.nodes[1].z_getaddressforaccount(0)
|
||||
assert_equal(node1_addr0['account'], 0)
|
||||
assert_equal(set(node1_addr0['pools']), set(['transparent', 'sapling', 'orchard']))
|
||||
node1_ua = node1_addr0['unifiedaddress']
|
||||
|
||||
# Set node 1's miner address to the UA
|
||||
self.nodes[1].stop()
|
||||
bitcoind_processes[1].wait()
|
||||
self.nodes[1] = self.start_node_with(1, [
|
||||
"-mineraddress=%s" % node1_ua,
|
||||
])
|
||||
connect_nodes(self.nodes[1], 0)
|
||||
|
||||
# The UA starts with zero balance.
|
||||
assert_equal(self.nodes[1].z_getbalanceforaccount(0)['pools'], {})
|
||||
|
||||
# Node 1 can mine blocks because the miner selects the Sapling receiver
|
||||
# of its UA.
|
||||
print("Mining block with node 1")
|
||||
self.nodes[1].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# The UA balance should show that Sapling funds were received.
|
||||
assert_equal(self.nodes[1].z_getbalanceforaccount(0)['pools'], {
|
||||
'sapling': {'valueZat': 5 * COIN },
|
||||
})
|
||||
|
||||
# Activate NU5
|
||||
print("Activating NU5")
|
||||
self.nodes[0].generate(7)
|
||||
self.sync_all()
|
||||
|
||||
# Now any block mined by node 1 should use the Orchard receiver of its UA.
|
||||
print("Mining block with node 1")
|
||||
self.nodes[1].generate(1)
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[1].z_getbalanceforaccount(0)['pools'], {
|
||||
'sapling': {'valueZat': 5 * COIN },
|
||||
# 6.25 ZEC because the FR always ends when Canopy activates, and
|
||||
# regtest has no defined funding streams.
|
||||
'orchard': {'valueZat': 6.25 * COIN },
|
||||
})
|
||||
|
||||
if __name__ == '__main__':
|
||||
ShieldCoinbaseTest().main()
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2022 The Zcash developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#
|
||||
# Test the effect of reorgs on the Orchard commitment tree.
|
||||
#
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
BLOSSOM_BRANCH_ID,
|
||||
HEARTWOOD_BRANCH_ID,
|
||||
CANOPY_BRANCH_ID,
|
||||
NU5_BRANCH_ID,
|
||||
assert_equal,
|
||||
get_coinbase_address,
|
||||
nuparams,
|
||||
start_nodes,
|
||||
wait_and_assert_operationid_status,
|
||||
)
|
||||
|
||||
from finalsaplingroot import ORCHARD_TREE_EMPTY_ROOT
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
class OrchardReorgTest(BitcoinTestFramework):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.num_nodes = 4
|
||||
self.setup_clean_chain = True
|
||||
|
||||
def setup_nodes(self):
|
||||
return start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[
|
||||
nuparams(BLOSSOM_BRANCH_ID, 1),
|
||||
nuparams(HEARTWOOD_BRANCH_ID, 5),
|
||||
nuparams(CANOPY_BRANCH_ID, 5),
|
||||
nuparams(NU5_BRANCH_ID, 10),
|
||||
'-nurejectoldversions=false',
|
||||
'-experimentalfeatures',
|
||||
'-orchardwallet',
|
||||
# '-debug',
|
||||
]] * self.num_nodes)
|
||||
|
||||
def run_test(self):
|
||||
# Activate NU5 so we can test Orchard.
|
||||
self.nodes[0].generate(10)
|
||||
self.sync_all()
|
||||
|
||||
# Generate a UA with only an Orchard receiver.
|
||||
account = self.nodes[0].z_getnewaccount()['account']
|
||||
addr = self.nodes[0].z_getaddressforaccount(account, ['orchard'])
|
||||
assert_equal(addr['account'], account)
|
||||
assert_equal(set(addr['pools']), set(['orchard']))
|
||||
ua = addr['unifiedaddress']
|
||||
|
||||
# Before mining any Orchard notes, finalorchardroot should be the empty Orchard root.
|
||||
assert_equal(
|
||||
ORCHARD_TREE_EMPTY_ROOT,
|
||||
self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'],
|
||||
)
|
||||
|
||||
# finalorchardroot should not change if we mine additional blocks without Orchard notes.
|
||||
self.nodes[0].generate(100)
|
||||
self.sync_all()
|
||||
assert_equal(
|
||||
ORCHARD_TREE_EMPTY_ROOT,
|
||||
self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'],
|
||||
)
|
||||
|
||||
# Create an Orchard note.
|
||||
recipients = [{'address': ua, 'amount': Decimal('12.5')}]
|
||||
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
# After mining a block, finalorchardroot should have changed.
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
orchardroot_oneleaf = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot']
|
||||
print("Root of Orchard commitment tree with one leaf:", orchardroot_oneleaf)
|
||||
assert(orchardroot_oneleaf != ORCHARD_TREE_EMPTY_ROOT)
|
||||
|
||||
# finalorchardroot should not change if we mine additional blocks without Orchard notes.
|
||||
self.nodes[0].generate(4)
|
||||
self.sync_all()
|
||||
assert_equal(
|
||||
orchardroot_oneleaf,
|
||||
self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'],
|
||||
)
|
||||
|
||||
# Split the network so we can test the effect of a reorg.
|
||||
print("Splitting the network")
|
||||
self.split_network()
|
||||
|
||||
# Create another Orchard note on node 0.
|
||||
recipients = [{'address': ua, 'amount': Decimal('12.5')}]
|
||||
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
# Mine two blocks on node 0.
|
||||
print("Mining 2 blocks on node 0")
|
||||
self.nodes[0].generate(2)
|
||||
self.sync_all()
|
||||
orchardroot_twoleaf = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot']
|
||||
print("Root of Orchard commitment tree with two leaves:", orchardroot_twoleaf)
|
||||
assert(orchardroot_twoleaf != ORCHARD_TREE_EMPTY_ROOT)
|
||||
assert(orchardroot_twoleaf != orchardroot_oneleaf)
|
||||
|
||||
# Generate 10 blocks on node 2.
|
||||
print("Mining alternate chain on node 2")
|
||||
self.nodes[2].generate(10)
|
||||
self.sync_all()
|
||||
assert_equal(
|
||||
orchardroot_oneleaf,
|
||||
self.nodes[2].getblock(self.nodes[2].getbestblockhash())['finalorchardroot'],
|
||||
)
|
||||
|
||||
# Reconnect the nodes; node 0 will re-org to node 2's chain.
|
||||
print("Re-joining the network so that node 0 reorgs")
|
||||
self.join_network()
|
||||
|
||||
# Verify that node 0's latest Orchard root matches what we expect.
|
||||
orchardroot_postreorg = self.nodes[0].getblock(self.nodes[2].getbestblockhash())['finalorchardroot']
|
||||
print("Root of Orchard commitment tree after reorg:", orchardroot_postreorg)
|
||||
assert_equal(orchardroot_postreorg, orchardroot_oneleaf)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
OrchardReorgTest().main()
|
|
@ -52,7 +52,7 @@ SPROUT_PROTO_VERSION = 170002 # past bip-31 for ping/pong
|
|||
OVERWINTER_PROTO_VERSION = 170003
|
||||
SAPLING_PROTO_VERSION = 170006
|
||||
BLOSSOM_PROTO_VERSION = 170008
|
||||
NU5_PROTO_VERSION = 170015
|
||||
NU5_PROTO_VERSION = 170040
|
||||
|
||||
MY_SUBVERSION = b"/python-mininode-tester:0.0.3/"
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class BitcoinTestFramework(object):
|
|||
def setup_nodes(self):
|
||||
return start_nodes(self.num_nodes, self.options.tmpdir)
|
||||
|
||||
def setup_network(self, split = False):
|
||||
def setup_network(self, split = False, do_mempool_sync = True):
|
||||
self.nodes = self.setup_nodes()
|
||||
|
||||
# Connect the nodes as a "chain". This allows us
|
||||
|
@ -64,12 +64,13 @@ class BitcoinTestFramework(object):
|
|||
if not split:
|
||||
connect_nodes_bi(self.nodes, 1, 2)
|
||||
sync_blocks(self.nodes[1:3])
|
||||
sync_mempools(self.nodes[1:3])
|
||||
if do_mempool_sync:
|
||||
sync_mempools(self.nodes[1:3])
|
||||
|
||||
connect_nodes_bi(self.nodes, 0, 1)
|
||||
connect_nodes_bi(self.nodes, 2, 3)
|
||||
self.is_network_split = split
|
||||
self.sync_all()
|
||||
self.sync_all(do_mempool_sync)
|
||||
|
||||
def split_network(self):
|
||||
"""
|
||||
|
@ -80,15 +81,17 @@ class BitcoinTestFramework(object):
|
|||
wait_bitcoinds()
|
||||
self.setup_network(True)
|
||||
|
||||
def sync_all(self):
|
||||
def sync_all(self, do_mempool_sync = True):
|
||||
if self.is_network_split:
|
||||
sync_blocks(self.nodes[:2])
|
||||
sync_blocks(self.nodes[2:])
|
||||
sync_mempools(self.nodes[:2])
|
||||
sync_mempools(self.nodes[2:])
|
||||
if do_mempool_sync:
|
||||
sync_mempools(self.nodes[:2])
|
||||
sync_mempools(self.nodes[2:])
|
||||
else:
|
||||
sync_blocks(self.nodes)
|
||||
sync_mempools(self.nodes)
|
||||
if do_mempool_sync:
|
||||
sync_mempools(self.nodes)
|
||||
|
||||
def join_network(self):
|
||||
"""
|
||||
|
@ -97,7 +100,7 @@ class BitcoinTestFramework(object):
|
|||
assert self.is_network_split
|
||||
stop_nodes(self.nodes)
|
||||
wait_bitcoinds()
|
||||
self.setup_network(False)
|
||||
self.setup_network(False, False)
|
||||
|
||||
def main(self):
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ SAPLING_BRANCH_ID = 0x76B809BB
|
|||
BLOSSOM_BRANCH_ID = 0x2BB40E60
|
||||
HEARTWOOD_BRANCH_ID = 0xF5B9230B
|
||||
CANOPY_BRANCH_ID = 0xE9FF75A6
|
||||
NU5_BRANCH_ID = 0x37519621
|
||||
NU5_BRANCH_ID = 0xC2D6D0B4
|
||||
|
||||
# The maximum number of nodes a single test can spawn
|
||||
MAX_NODES = 8
|
||||
|
|
|
@ -7,9 +7,12 @@ from test_framework.authproxy import JSONRPCException
|
|||
from test_framework.mininode import COIN
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
NU5_BRANCH_ID,
|
||||
assert_equal,
|
||||
assert_raises_message,
|
||||
assert_true,
|
||||
get_coinbase_address,
|
||||
nuparams,
|
||||
start_nodes,
|
||||
wait_and_assert_operationid_status,
|
||||
)
|
||||
|
@ -22,6 +25,7 @@ class WalletAccountsTest(BitcoinTestFramework):
|
|||
return start_nodes(self.num_nodes, self.options.tmpdir, [[
|
||||
'-experimentalfeatures',
|
||||
'-orchardwallet',
|
||||
nuparams(NU5_BRANCH_ID, 210),
|
||||
]] * self.num_nodes)
|
||||
|
||||
def check_receiver_types(self, ua, expected):
|
||||
|
@ -32,14 +36,19 @@ class WalletAccountsTest(BitcoinTestFramework):
|
|||
# Remember that empty pools are omitted from the output.
|
||||
def _check_balance_for_rpc(self, rpcmethod, node, account, expected, minconf):
|
||||
rpc = getattr(self.nodes[node], rpcmethod)
|
||||
actual = rpc(account) if minconf is None else rpc(account, minconf)
|
||||
actual = rpc(account, minconf)
|
||||
assert_equal(set(expected), set(actual['pools']))
|
||||
total_balance = 0
|
||||
for pool in expected:
|
||||
assert_equal(expected[pool] * COIN, actual['pools'][pool]['valueZat'])
|
||||
assert_equal(actual['minimum_confirmations'], 1 if minconf is None else minconf)
|
||||
total_balance += expected[pool]
|
||||
assert_equal(actual['minimum_confirmations'], minconf)
|
||||
return total_balance
|
||||
|
||||
def check_balance(self, node, account, address, expected, minconf=None):
|
||||
self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
|
||||
def check_balance(self, node, account, address, expected, minconf=1):
|
||||
acct_balance = self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
|
||||
z_getbalance = self.nodes[node].z_getbalance(address, minconf)
|
||||
assert_equal(acct_balance, z_getbalance)
|
||||
fvk = self.nodes[node].z_exportviewingkey(address)
|
||||
self._check_balance_for_rpc('z_getbalanceforviewingkey', node, fvk, expected, minconf)
|
||||
|
||||
|
@ -55,7 +64,7 @@ class WalletAccountsTest(BitcoinTestFramework):
|
|||
# Generate the first address for account 0.
|
||||
addr0 = self.nodes[0].z_getaddressforaccount(0)
|
||||
assert_equal(addr0['account'], 0)
|
||||
assert_equal(set(addr0['pools']), set(['transparent', 'sapling']))
|
||||
assert_equal(set(addr0['pools']), set(['transparent', 'sapling', 'orchard']))
|
||||
ua0 = addr0['unifiedaddress']
|
||||
|
||||
# We pick mnemonic phrases to ensure that we can always generate the default
|
||||
|
@ -70,24 +79,47 @@ class WalletAccountsTest(BitcoinTestFramework):
|
|||
'no address at diversifier index 0',
|
||||
self.nodes[0].z_getaddressforaccount, 0, [], 0)
|
||||
|
||||
# The second address for account 0 is different to the first address.
|
||||
addr0_2 = self.nodes[0].z_getaddressforaccount(0)
|
||||
assert_equal(addr0_2['account'], 0)
|
||||
assert_equal(set(addr0_2['pools']), set(['transparent', 'sapling', 'orchard']))
|
||||
ua0_2 = addr0_2['unifiedaddress']
|
||||
assert(ua0 != ua0_2)
|
||||
|
||||
# We can generate a fully-shielded address.
|
||||
addr0_3 = self.nodes[0].z_getaddressforaccount(0, ['sapling', 'orchard'])
|
||||
assert_equal(addr0_3['account'], 0)
|
||||
assert_equal(set(addr0_3['pools']), set(['sapling', 'orchard']))
|
||||
ua0_3 = addr0_3['unifiedaddress']
|
||||
|
||||
# We can generate an address without a Sapling receiver.
|
||||
addr0_4 = self.nodes[0].z_getaddressforaccount(0, ['transparent', 'orchard'])
|
||||
assert_equal(addr0_4['account'], 0)
|
||||
assert_equal(set(addr0_4['pools']), set(['transparent', 'orchard']))
|
||||
ua0_4 = addr0_4['unifiedaddress']
|
||||
|
||||
# The first address for account 1 is different to account 0.
|
||||
addr1 = self.nodes[0].z_getaddressforaccount(1)
|
||||
assert_equal(addr1['account'], 1)
|
||||
assert_equal(set(addr1['pools']), set(['transparent', 'sapling']))
|
||||
assert_equal(set(addr1['pools']), set(['transparent', 'sapling', 'orchard']))
|
||||
ua1 = addr1['unifiedaddress']
|
||||
assert(ua0 != ua1)
|
||||
|
||||
# The UA contains the expected receiver kinds.
|
||||
self.check_receiver_types(ua0, ['transparent', 'sapling'])
|
||||
self.check_receiver_types(ua1, ['transparent', 'sapling'])
|
||||
self.check_receiver_types(ua0, ['transparent', 'sapling', 'orchard'])
|
||||
self.check_receiver_types(ua0_2, ['transparent', 'sapling', 'orchard'])
|
||||
self.check_receiver_types(ua0_3, [ 'sapling', 'orchard'])
|
||||
self.check_receiver_types(ua0_4, ['transparent', 'orchard'])
|
||||
self.check_receiver_types(ua1, ['transparent', 'sapling', 'orchard'])
|
||||
|
||||
# The balances of the accounts are all zero.
|
||||
self.check_balance(0, 0, ua0, {})
|
||||
self.check_balance(0, 1, ua1, {})
|
||||
|
||||
# Manually send funds to one of the receivers in the UA.
|
||||
# Send coinbase funds to the UA.
|
||||
print('Sending coinbase funds to account')
|
||||
recipients = [{'address': ua0, 'amount': Decimal('10')}]
|
||||
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0)
|
||||
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||
txid = wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
# The wallet should detect the new note as belonging to the UA.
|
||||
|
@ -108,7 +140,8 @@ class WalletAccountsTest(BitcoinTestFramework):
|
|||
# The default minconf should now detect the balance.
|
||||
self.check_balance(0, 0, ua0, {'sapling': 10})
|
||||
|
||||
# Manually send funds from the UA receiver.
|
||||
# Send Sapling funds from the UA.
|
||||
print('Sending account funds to Sapling address')
|
||||
node1sapling = self.nodes[1].z_getnewaddress('sapling')
|
||||
recipients = [{'address': node1sapling, 'amount': Decimal('1')}]
|
||||
opid = self.nodes[0].z_sendmany(ua0, recipients, 1, 0)
|
||||
|
@ -128,6 +161,69 @@ class WalletAccountsTest(BitcoinTestFramework):
|
|||
self.check_balance(0, 0, ua0, {})
|
||||
self.check_balance(0, 0, ua0, {'sapling': 9}, 0)
|
||||
|
||||
# Activate NU5
|
||||
print('Activating NU5')
|
||||
self.nodes[2].generate(9)
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 210)
|
||||
|
||||
# Send more coinbase funds to the UA.
|
||||
print('Sending coinbase funds to account')
|
||||
recipients = [{'address': ua0, 'amount': Decimal('10')}]
|
||||
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||
txid = wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
# The wallet should detect the new note as belonging to the UA.
|
||||
tx_details = self.nodes[0].z_viewtransaction(txid)
|
||||
assert_equal(len(tx_details['outputs']), 1)
|
||||
assert_equal(tx_details['outputs'][0]['type'], 'orchard')
|
||||
assert_equal(tx_details['outputs'][0]['address'], ua0)
|
||||
|
||||
# The new balance should not be visible with the default minconf, but should be
|
||||
# visible with minconf=0.
|
||||
self.sync_all()
|
||||
self.check_balance(0, 0, ua0, {'sapling': 9})
|
||||
self.check_balance(0, 0, ua0, {'sapling': 9, 'orchard': 10}, 0)
|
||||
|
||||
# The total balance with the default minconf should be just the Sapling balance
|
||||
assert_equal('9.00', self.nodes[0].z_gettotalbalance()['private'])
|
||||
assert_equal('19.00', self.nodes[0].z_gettotalbalance(0)['private'])
|
||||
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# Send Orchard funds from the UA.
|
||||
print('Sending account funds to Orchard-only UA')
|
||||
node1account = self.nodes[1].z_getnewaccount()['account']
|
||||
node1orchard = self.nodes[1].z_getaddressforaccount(node1account, ['orchard'])['unifiedaddress']
|
||||
recipients = [{'address': node1orchard, 'amount': Decimal('1')}]
|
||||
opid = self.nodes[0].z_sendmany(ua0, recipients, 1, 0)
|
||||
txid = wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
# The wallet should detect the spent note as belonging to the UA.
|
||||
tx_details = self.nodes[0].z_viewtransaction(txid)
|
||||
assert_equal(len(tx_details['spends']), 1)
|
||||
assert_equal(tx_details['spends'][0]['type'], 'orchard')
|
||||
assert_equal(tx_details['spends'][0]['address'], ua0)
|
||||
|
||||
assert_equal(len(tx_details['outputs']), 2)
|
||||
outputs = sorted(tx_details['outputs'], key=lambda x: x['valueZat'])
|
||||
assert_equal(outputs[0]['type'], 'orchard')
|
||||
assert_equal(outputs[0]['address'], node1orchard)
|
||||
assert_equal(outputs[0]['valueZat'], 100000000)
|
||||
# outputs[1] is change
|
||||
assert_equal(outputs[1]['type'], 'orchard')
|
||||
assert_true('address' not in outputs[1]) #
|
||||
|
||||
# The balances of the account should reflect whether zero-conf transactions are
|
||||
# being considered. The Sapling balance should remain at 9, while the Orchard
|
||||
# balance will show either 0 (because the spent 10-ZEC note is never shown, as
|
||||
# that transaction has been created and broadcast, and _might_ get mined up until
|
||||
# the transaction expires), or 9 (if we include the unmined transaction).
|
||||
self.sync_all()
|
||||
self.check_balance(0, 0, ua0, {'sapling': 9})
|
||||
self.check_balance(0, 0, ua0, {'sapling': 9, 'orchard': 9}, 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletAccountsTest().main()
|
||||
|
|
|
@ -4,53 +4,162 @@
|
|||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal
|
||||
from test_framework.util import assert_equal, start_nodes, connect_nodes_bi, NU5_BRANCH_ID
|
||||
from test_framework.mininode import nuparams
|
||||
|
||||
# Test wallet address behaviour across network upgrades
|
||||
class WalletAddressesTest(BitcoinTestFramework):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# need 2 nodes to import addresses
|
||||
self.num_nodes = 2
|
||||
self.setup_clean_chain = True
|
||||
|
||||
def run_test(self):
|
||||
def addr_checks(default_type):
|
||||
# Check default type, as well as explicit types
|
||||
types_and_addresses = [
|
||||
(default_type, self.nodes[0].z_getnewaddress()),
|
||||
('sprout', self.nodes[0].z_getnewaddress('sprout')),
|
||||
('sapling', self.nodes[0].z_getnewaddress('sapling')),
|
||||
]
|
||||
|
||||
all_addresses = self.nodes[0].z_listaddresses()
|
||||
|
||||
for addr_type, addr in types_and_addresses:
|
||||
res = self.nodes[0].z_validateaddress(addr)
|
||||
assert(res['isvalid'])
|
||||
assert(res['ismine'])
|
||||
assert_equal(res['type'], addr_type)
|
||||
assert(addr in all_addresses)
|
||||
|
||||
listed_addresses = self.nodes[0].listaddresses()
|
||||
legacy_random_src = next(src for src in listed_addresses if src['source'] == 'legacy_random')
|
||||
legacy_hdseed_src = next(src for src in listed_addresses if src['source'] == 'legacy_hdseed')
|
||||
for addr_type, addr in types_and_addresses:
|
||||
if addr_type == 'sprout':
|
||||
assert(addr in legacy_random_src['sprout']['addresses'])
|
||||
if addr_type == 'sapling':
|
||||
assert(addr in [x for obj in legacy_hdseed_src['sapling'] for x in obj['addresses']])
|
||||
|
||||
# Sanity-check the test harness
|
||||
assert_equal(self.nodes[0].getblockcount(), 200)
|
||||
|
||||
# Current height = 200 -> Sapling
|
||||
# Default address type is Sapling
|
||||
print("Testing height 200 (Sapling)")
|
||||
addr_checks('sapling')
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
def setup_network(self):
|
||||
self.nodes = start_nodes(
|
||||
self.num_nodes, self.options.tmpdir,
|
||||
extra_args=[['-experimentalfeatures', '-orchardwallet', nuparams(NU5_BRANCH_ID, 2),]] * self.num_nodes)
|
||||
connect_nodes_bi(self.nodes, 0, 1)
|
||||
self.is_network_split = False
|
||||
self.sync_all()
|
||||
|
||||
# Current height = 201 -> Sapling
|
||||
# Default address type is Sapling
|
||||
print("Testing height 201 (Sapling)")
|
||||
addr_checks('sapling')
|
||||
def list_addresses(self, node, expected_sources):
|
||||
addrs = self.nodes[node].listaddresses()
|
||||
sources = [s['source'] for s in addrs]
|
||||
# Sources should be unique.
|
||||
assert_equal(len(set(sources)), len(sources))
|
||||
assert_equal(set(sources), set(expected_sources))
|
||||
|
||||
# Extract a list of all addresses from the output.
|
||||
all_addrs = [
|
||||
source.get('transparent', {}).get('addresses', []) +
|
||||
source.get('transparent', {}).get('changeAddresses', []) +
|
||||
source.get('sprout', {}).get('addresses', []) +
|
||||
[s['addresses'] for s in source.get('sapling', [])] +
|
||||
[[a['address'] for a in s['addresses']] for s in source.get('unified', [])]
|
||||
for source in addrs]
|
||||
all_addrs = [a for s in all_addrs for a in s]
|
||||
all_addrs = [a if type(a) == list else [a] for a in all_addrs]
|
||||
all_addrs = [a for s in all_addrs for a in s]
|
||||
|
||||
assert_equal(len(set(all_addrs)), len(all_addrs), "Duplicates in listaddresses output: %s" % addrs)
|
||||
return addrs
|
||||
|
||||
def run_test(self):
|
||||
def get_source(listed_addresses, source):
|
||||
return next(src for src in listed_addresses if src['source'] == source)
|
||||
|
||||
print("Testing height 1 (Sapling)")
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getblockcount(), 1)
|
||||
listed_addresses = self.list_addresses(0, ['mnemonic_seed'])
|
||||
# There should be a single address from the coinbase, which was derived
|
||||
# from the mnemonic seed.
|
||||
assert 'transparent' in get_source(listed_addresses, 'mnemonic_seed')
|
||||
|
||||
# If we import a t-address, we should see imported_watchonly as a source.
|
||||
taddr_import = self.nodes[1].getnewaddress()
|
||||
self.nodes[0].importaddress(taddr_import)
|
||||
listed_addresses = self.list_addresses(0, ['imported_watchonly', 'mnemonic_seed'])
|
||||
imported_watchonly_src = get_source(listed_addresses, 'imported_watchonly')
|
||||
assert_equal(imported_watchonly_src['transparent']['addresses'][0], taddr_import)
|
||||
|
||||
account = self.nodes[0].z_getnewaccount()['account']
|
||||
sprout_1 = self.nodes[0].z_getnewaddress('sprout')
|
||||
sapling_1 = self.nodes[0].z_getnewaddress('sapling')
|
||||
unified_1 = self.nodes[0].z_getaddressforaccount(account)['unifiedaddress']
|
||||
types_and_addresses = [
|
||||
('sprout', sprout_1),
|
||||
('sapling', sapling_1),
|
||||
('unified', unified_1),
|
||||
]
|
||||
|
||||
for addr_type, addr in types_and_addresses:
|
||||
res = self.nodes[0].z_validateaddress(addr)
|
||||
assert res['isvalid']
|
||||
# assert res['ismine'] # this isn't present for unified addresses
|
||||
assert_equal(res['type'], addr_type)
|
||||
|
||||
# We should see the following sources:
|
||||
# - imported_watchonly (for the previously-imported t-addr)
|
||||
# - legacy_random (for the new Sprout address)
|
||||
# - mnemonic_seed (for the previous t-addrs and the new Sapling and Unified addrs)
|
||||
listed_addresses = self.list_addresses(0, ['imported_watchonly', 'legacy_random', 'mnemonic_seed'])
|
||||
legacy_random_src = get_source(listed_addresses, 'legacy_random')
|
||||
mnemonic_seed_src = get_source(listed_addresses, 'mnemonic_seed')
|
||||
|
||||
# Check Sprout addrs
|
||||
assert_equal(legacy_random_src['sprout']['addresses'], [sprout_1])
|
||||
|
||||
# Check Sapling addrs
|
||||
assert_equal(
|
||||
set([(obj['zip32KeyPath'], x) for obj in mnemonic_seed_src['sapling'] for x in obj['addresses']]),
|
||||
set([("m/32'/1'/2147483647'/0'", sapling_1)]),
|
||||
)
|
||||
|
||||
# Check Unified addrs
|
||||
unified_obj = mnemonic_seed_src['unified']
|
||||
assert_equal(unified_obj[0]['account'], 0)
|
||||
assert_equal(unified_obj[0]['addresses'][0]['address'], unified_1)
|
||||
assert 'diversifier_index' in unified_obj[0]['addresses'][0]
|
||||
assert_equal(unified_obj[0]['addresses'][0]['receiver_types'], ['p2pkh', 'sapling', 'orchard'])
|
||||
|
||||
print("Testing height 2 (NU5)")
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getblockcount(), 2)
|
||||
# Sprout address generation is no longer allowed
|
||||
sapling_2 = self.nodes[0].z_getnewaddress('sapling')
|
||||
unified_2 = self.nodes[0].z_getaddressforaccount(account)['unifiedaddress']
|
||||
types_and_addresses = [
|
||||
('sapling', sapling_2),
|
||||
('unified', unified_2),
|
||||
]
|
||||
|
||||
for addr_type, addr in types_and_addresses:
|
||||
res = self.nodes[0].z_validateaddress(addr)
|
||||
assert res['isvalid']
|
||||
# assert res['ismine'] # this isn't present for unified addresses
|
||||
assert_equal(res['type'], addr_type)
|
||||
|
||||
# We should see the same sources (address generation does not change across the NU5 boundary).
|
||||
listed_addresses = self.list_addresses(0, ['imported_watchonly', 'legacy_random', 'mnemonic_seed'])
|
||||
legacy_random_src = get_source(listed_addresses, 'legacy_random')
|
||||
mnemonic_seed_src = get_source(listed_addresses, 'mnemonic_seed')
|
||||
|
||||
# Check Sprout addrs
|
||||
assert_equal(legacy_random_src['sprout']['addresses'], [sprout_1])
|
||||
|
||||
# Check Sapling addrs
|
||||
assert_equal(
|
||||
set([(obj['zip32KeyPath'], x) for obj in mnemonic_seed_src['sapling'] for x in obj['addresses']]),
|
||||
set([
|
||||
("m/32'/1'/2147483647'/0'", sapling_1),
|
||||
("m/32'/1'/2147483647'/1'", sapling_2),
|
||||
]),
|
||||
)
|
||||
|
||||
# Check Unified addrs
|
||||
unified_obj = mnemonic_seed_src['unified']
|
||||
assert_equal(unified_obj[0]['account'], 0)
|
||||
assert_equal(
|
||||
set([addr['address'] for addr in unified_obj[0]['addresses']]),
|
||||
set([unified_1, unified_2]),
|
||||
)
|
||||
assert 'diversifier_index' in unified_obj[0]['addresses'][0]
|
||||
assert 'diversifier_index' in unified_obj[0]['addresses'][1]
|
||||
assert_equal(unified_obj[0]['addresses'][0]['receiver_types'], ['p2pkh', 'sapling', 'orchard'])
|
||||
assert_equal(unified_obj[0]['addresses'][1]['receiver_types'], ['p2pkh', 'sapling', 'orchard'])
|
||||
|
||||
print("Generate mature coinbase, spend to create and detect change")
|
||||
self.nodes[0].generate(100)
|
||||
self.sync_all()
|
||||
self.nodes[0].sendmany('', {taddr_import: 1})
|
||||
listed_addresses = self.list_addresses(0, ['imported_watchonly', 'legacy_random', 'mnemonic_seed'])
|
||||
mnemonic_seed_src = get_source(listed_addresses, 'mnemonic_seed')
|
||||
assert len(mnemonic_seed_src['transparent']['changeAddresses']) > 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletAddressesTest().main()
|
||||
|
|
|
@ -68,7 +68,7 @@ class WalletListNotes(BitcoinTestFramework):
|
|||
change_amount_2 = receive_amount_1 - receive_amount_2 - DEFAULT_FEE
|
||||
assert_equal('sapling', self.nodes[0].z_validateaddress(saplingzaddr)['type'])
|
||||
recipients = [{"address": saplingzaddr, "amount":receive_amount_2}]
|
||||
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients, 1, DEFAULT_FEE, True)
|
||||
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients, 1, DEFAULT_FEE)
|
||||
txid_2 = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||
self.sync_all()
|
||||
|
||||
|
@ -107,7 +107,7 @@ class WalletListNotes(BitcoinTestFramework):
|
|||
receive_amount_3 = Decimal('2.0')
|
||||
change_amount_3 = change_amount_2 - receive_amount_3 - DEFAULT_FEE
|
||||
recipients = [{"address": saplingzaddr2, "amount":receive_amount_3}]
|
||||
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients, 1, DEFAULT_FEE, True)
|
||||
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients, 1, DEFAULT_FEE)
|
||||
txid_3 = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||
self.sync_all()
|
||||
unspent_tx = self.nodes[0].z_listunspent(0)
|
||||
|
|
|
@ -12,8 +12,10 @@ from test_framework.util import (
|
|||
assert_raises_message,
|
||||
connect_nodes_bi,
|
||||
get_coinbase_address,
|
||||
nuparams,
|
||||
DEFAULT_FEE,
|
||||
DEFAULT_FEE_ZATS
|
||||
DEFAULT_FEE_ZATS,
|
||||
NU5_BRANCH_ID,
|
||||
)
|
||||
from test_framework.util import wait_and_assert_operationid_status, start_nodes
|
||||
from decimal import Decimal
|
||||
|
@ -33,7 +35,12 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
def setup_network(self):
|
||||
self.nodes = start_nodes(
|
||||
self.num_nodes, self.options.tmpdir,
|
||||
extra_args=[['-experimentalfeatures', '-orchardwallet']] * self.num_nodes)
|
||||
extra_args=[[
|
||||
'-experimentalfeatures',
|
||||
'-orchardwallet',
|
||||
nuparams(NU5_BRANCH_ID, 225),
|
||||
]] * self.num_nodes
|
||||
)
|
||||
connect_nodes_bi(self.nodes, 0, 1)
|
||||
connect_nodes_bi(self.nodes, 1, 2)
|
||||
connect_nodes_bi(self.nodes, 0, 2)
|
||||
|
@ -381,9 +388,19 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
r = node.z_getaddressforaccount(account)
|
||||
unified_addr = r['unifiedaddress']
|
||||
receivers = node.z_listunifiedreceivers(unified_addr)
|
||||
assert_equal(len(receivers), 2)
|
||||
assert_equal(len(receivers), 3)
|
||||
assert 'transparent' in receivers
|
||||
assert 'sapling' in receivers
|
||||
assert 'orchard' in receivers
|
||||
assert_raises_message(
|
||||
JSONRPCException,
|
||||
"The provided address is a bare receiver from a Unified Address in this wallet.",
|
||||
node.z_listreceivedbyaddress, receivers['transparent'], 0)
|
||||
assert_raises_message(
|
||||
JSONRPCException,
|
||||
"The provided address is a bare receiver from a Unified Address in this wallet.",
|
||||
node.z_listreceivedbyaddress, receivers['sapling'], 0)
|
||||
|
||||
# Wallet contains no notes
|
||||
r = node.z_listreceivedbyaddress(unified_addr, 0)
|
||||
assert_equal(len(r), 0, "unified_addr should have received zero notes")
|
||||
|
@ -412,7 +429,7 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
assert_equal(r[0]['blockheight'], height+5)
|
||||
assert_equal(r[0]['blockindex'], 1)
|
||||
assert 'blocktime' in r[0]
|
||||
|
||||
|
||||
assert_equal(r[1]['pool'], 'transparent')
|
||||
assert_equal(r[1]['txid'], txid_taddr)
|
||||
assert_equal(r[1]['amount'], Decimal('0.2'))
|
||||
|
@ -425,9 +442,116 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
assert_equal(r[1]['blockindex'], -1) # not yet mined
|
||||
assert 'blocktime' in r[1]
|
||||
|
||||
def test_received_orchard(self, height):
|
||||
self.generate_and_sync(height+1)
|
||||
taddr = self.nodes[1].getnewaddress()
|
||||
acct1 = self.nodes[1].z_getnewaccount()['account']
|
||||
acct2 = self.nodes[1].z_getnewaccount()['account']
|
||||
|
||||
addrResO = self.nodes[1].z_getaddressforaccount(acct1, ['orchard'])
|
||||
assert_equal(addrResO['pools'], ['orchard'])
|
||||
uao = addrResO['unifiedaddress']
|
||||
|
||||
addrResSO = self.nodes[1].z_getaddressforaccount(acct2, ['sapling', 'orchard'])
|
||||
assert_equal(addrResSO['pools'], ['sapling', 'orchard'])
|
||||
uaso = addrResSO['unifiedaddress']
|
||||
|
||||
self.nodes[0].sendtoaddress(taddr, 4.0)
|
||||
self.generate_and_sync(height+2)
|
||||
|
||||
acct_node0 = self.nodes[0].z_getnewaccount()['account']
|
||||
ua_node0 = self.nodes[0].z_getaddressforaccount(acct_node0, ['sapling', 'orchard'])['unifiedaddress']
|
||||
|
||||
opid = self.nodes[1].z_sendmany(taddr, [
|
||||
{'address': uao, 'amount': 1, 'memo': my_memo},
|
||||
{'address': uaso, 'amount': 2},
|
||||
], 1, 0, 'AllowRevealedSenders')
|
||||
txid0 = wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||
self.sync_all()
|
||||
|
||||
# Decrypted transaction details should be correct, even though
|
||||
# the transaction is still just in the mempool
|
||||
pt = self.nodes[1].z_viewtransaction(txid0)
|
||||
|
||||
assert_equal(pt['txid'], txid0)
|
||||
assert_equal(len(pt['spends']), 0)
|
||||
assert_equal(len(pt['outputs']), 2)
|
||||
|
||||
# Outputs are not returned in a defined order but the amounts are deterministic
|
||||
outputs = sorted(pt['outputs'], key=lambda x: x['valueZat'])
|
||||
assert_equal(outputs[0]['type'], 'orchard')
|
||||
assert_equal(outputs[0]['address'], uao)
|
||||
assert_equal(outputs[0]['value'], Decimal('1'))
|
||||
assert_equal(outputs[0]['valueZat'], 100000000)
|
||||
assert_equal(outputs[0]['action'], 0)
|
||||
assert_equal(outputs[0]['outgoing'], False)
|
||||
assert_equal(outputs[0]['memo'], my_memo)
|
||||
assert_equal(outputs[0]['memoStr'], my_memo_str)
|
||||
|
||||
assert_equal(outputs[1]['type'], 'orchard')
|
||||
assert_equal(outputs[1]['address'], uaso)
|
||||
assert_equal(outputs[1]['value'], Decimal('2'))
|
||||
assert_equal(outputs[1]['valueZat'], 200000000)
|
||||
assert_equal(outputs[1]['action'], 1)
|
||||
assert_equal(outputs[1]['outgoing'], False)
|
||||
assert_equal(outputs[1]['memo'], no_memo)
|
||||
assert 'memoStr' not in outputs[1]
|
||||
|
||||
self.generate_and_sync(height+3)
|
||||
|
||||
opid = self.nodes[1].z_sendmany(uao, [
|
||||
{'address': uaso, 'amount': Decimal('0.3')},
|
||||
{'address': ua_node0, 'amount': Decimal('0.2')}
|
||||
])
|
||||
txid1 = wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||
self.sync_all()
|
||||
|
||||
pt = self.nodes[1].z_viewtransaction(txid1)
|
||||
|
||||
assert_equal(pt['txid'], txid1)
|
||||
assert_equal(len(pt['spends']), 1) # one spend we can see
|
||||
assert_equal(len(pt['outputs']), 3) # one output + one change output we can see
|
||||
|
||||
spends = pt['spends']
|
||||
assert_equal(spends[0]['type'], 'orchard')
|
||||
assert_equal(spends[0]['action'], 0)
|
||||
assert_equal(spends[0]['txidPrev'], txid0)
|
||||
assert_equal(spends[0]['actionPrev'], 0)
|
||||
assert_equal(spends[0]['address'], uao)
|
||||
assert_equal(spends[0]['value'], Decimal('1.0'))
|
||||
assert_equal(spends[0]['valueZat'], 100000000)
|
||||
|
||||
outputs = sorted(pt['outputs'], key=lambda x: x['valueZat'])
|
||||
assert_equal(outputs[0]['type'], 'orchard')
|
||||
assert_equal(outputs[0]['address'], ua_node0)
|
||||
assert_equal(outputs[0]['value'], Decimal('0.2'))
|
||||
assert_equal(outputs[0]['valueZat'], 20000000)
|
||||
assert_equal(outputs[0]['outgoing'], True)
|
||||
assert_equal(outputs[0]['walletInternal'], False)
|
||||
assert_equal(outputs[0]['memo'], no_memo)
|
||||
|
||||
assert_equal(outputs[1]['type'], 'orchard')
|
||||
assert_equal(outputs[1]['address'], uaso)
|
||||
assert_equal(outputs[1]['value'], Decimal('0.3'))
|
||||
assert_equal(outputs[1]['valueZat'], 30000000)
|
||||
assert_equal(outputs[1]['outgoing'], False)
|
||||
assert_equal(outputs[1]['walletInternal'], False)
|
||||
assert_equal(outputs[1]['memo'], no_memo)
|
||||
|
||||
# Verify that we observe the change output
|
||||
assert_equal(outputs[2]['type'], 'orchard')
|
||||
assert_equal(outputs[2]['value'], Decimal('0.49999'))
|
||||
assert_equal(outputs[2]['valueZat'], 49999000)
|
||||
assert_equal(outputs[2]['outgoing'], False)
|
||||
assert_equal(outputs[2]['walletInternal'], True)
|
||||
assert_equal(outputs[2]['memo'], no_memo)
|
||||
# The change address should have been erased
|
||||
assert_true('address' not in outputs[2])
|
||||
|
||||
def run_test(self):
|
||||
self.test_received_sprout(200)
|
||||
self.test_received_sapling(214)
|
||||
self.test_received_orchard(230)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2022 The Zcash developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
NU5_BRANCH_ID,
|
||||
assert_equal,
|
||||
get_coinbase_address,
|
||||
nuparams,
|
||||
start_nodes,
|
||||
wait_and_assert_operationid_status,
|
||||
)
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
# Test wallet behaviour with the Orchard protocol
|
||||
class WalletOrchardTest(BitcoinTestFramework):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.num_nodes = 4
|
||||
|
||||
def setup_nodes(self):
|
||||
return start_nodes(self.num_nodes, self.options.tmpdir, [[
|
||||
'-experimentalfeatures',
|
||||
'-orchardwallet',
|
||||
nuparams(NU5_BRANCH_ID, 210),
|
||||
]] * self.num_nodes)
|
||||
|
||||
def run_test(self):
|
||||
# Sanity-check the test harness
|
||||
assert_equal(self.nodes[0].getblockcount(), 200)
|
||||
|
||||
# Get a new orchard-only unified address
|
||||
acct1 = self.nodes[1].z_getnewaccount()['account']
|
||||
addrRes1 = self.nodes[1].z_getaddressforaccount(acct1, ['orchard'])
|
||||
assert_equal(acct1, addrRes1['account'])
|
||||
assert_equal(addrRes1['pools'], ['orchard'])
|
||||
ua1 = addrRes1['unifiedaddress']
|
||||
|
||||
# Verify that we have only an Orchard component
|
||||
receiver_types = self.nodes[0].z_listunifiedreceivers(ua1)
|
||||
assert_equal(set(['orchard']), set(receiver_types))
|
||||
|
||||
# Verify balance
|
||||
assert_equal({'pools': {}, 'minimum_confirmations': 1}, self.nodes[1].z_getbalanceforaccount(acct1))
|
||||
|
||||
# Send some sapling funds to node 2 for later spending after we split the network
|
||||
acct2 = self.nodes[2].z_getnewaccount()['account']
|
||||
addrRes2 = self.nodes[2].z_getaddressforaccount(acct2, ['sapling', 'orchard'])
|
||||
assert_equal(acct2, addrRes2['account'])
|
||||
ua2 = addrRes2['unifiedaddress']
|
||||
saplingAddr2 = self.nodes[2].z_listunifiedreceivers(ua2)['sapling']
|
||||
|
||||
recipients = [{"address": saplingAddr2, "amount": Decimal('10')}]
|
||||
myopid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||
|
||||
# Mine the tx & activate NU5
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(10)
|
||||
self.sync_all()
|
||||
|
||||
# Check the value sent to saplingAddr2 was received in node 2's account
|
||||
assert_equal(
|
||||
{'pools': {'sapling': {'valueZat': Decimal('1000000000')}}, 'minimum_confirmations': 1},
|
||||
self.nodes[2].z_getbalanceforaccount(acct2))
|
||||
|
||||
# Node 0 shields some funds
|
||||
# t-coinbase -> Orchard
|
||||
recipients = [{"address": ua1, "amount": Decimal('10')}]
|
||||
myopid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
assert_equal(
|
||||
{'pools': {'orchard': {'valueZat': Decimal('1000000000')}}, 'minimum_confirmations': 1},
|
||||
self.nodes[1].z_getbalanceforaccount(acct1))
|
||||
|
||||
# Split the network
|
||||
self.split_network()
|
||||
|
||||
# Send another tx to ua1
|
||||
recipients = [{"address": ua1, "amount": Decimal('10')}]
|
||||
myopid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||
|
||||
# Mine the tx & generate a majority chain on the 0/1 side of the split
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(10)
|
||||
self.sync_all()
|
||||
|
||||
assert_equal(
|
||||
{'pools': {'orchard': {'valueZat': Decimal('2000000000')}}, 'minimum_confirmations': 1},
|
||||
self.nodes[1].z_getbalanceforaccount(acct1))
|
||||
|
||||
# On the other side of the split, send some funds to node 3
|
||||
acct3 = self.nodes[3].z_getnewaccount()['account']
|
||||
addrRes3 = self.nodes[3].z_getaddressforaccount(acct3, ['sapling', 'orchard'])
|
||||
assert_equal(acct3, addrRes3['account'])
|
||||
ua3 = addrRes3['unifiedaddress']
|
||||
|
||||
recipients = [{"address": ua3, "amount": Decimal('1')}]
|
||||
myopid = self.nodes[2].z_sendmany(ua2, recipients, 1, 0)
|
||||
rollback_tx = wait_and_assert_operationid_status(self.nodes[2], myopid)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# The remaining change from ua2's Sapling note has been sent to the
|
||||
# account's internal Orchard change address.
|
||||
assert_equal(
|
||||
{'pools': {'orchard': {'valueZat': Decimal('900000000')}}, 'minimum_confirmations': 1},
|
||||
self.nodes[2].z_getbalanceforaccount(acct2))
|
||||
|
||||
assert_equal(
|
||||
{'pools': {'orchard': {'valueZat': Decimal('100000000')}}, 'minimum_confirmations': 1},
|
||||
self.nodes[3].z_getbalanceforaccount(acct3))
|
||||
|
||||
# Check that the mempools are empty
|
||||
for i in range(self.num_nodes):
|
||||
assert_equal(set([]), set(self.nodes[i].getrawmempool()))
|
||||
|
||||
# Reconnect the nodes; nodes 2 and 3 will re-org to node 0's chain.
|
||||
print("Re-joining the network so that nodes 2 and 3 reorg")
|
||||
self.join_network()
|
||||
|
||||
# split 0/1's chain should have won, so their wallet balance should be consistent
|
||||
assert_equal(
|
||||
{'pools': {'orchard': {'valueZat': Decimal('2000000000')}}, 'minimum_confirmations': 1},
|
||||
self.nodes[1].z_getbalanceforaccount(acct1))
|
||||
|
||||
# split 2/3's chain should have been rolled back, so their txn should have been
|
||||
# un-mined and returned to the mempool
|
||||
assert_equal(set([rollback_tx]), set(self.nodes[2].getrawmempool()))
|
||||
|
||||
# acct2's sole Orchard note is spent by a transaction in the mempool, so our
|
||||
# confirmed balance is currently 0
|
||||
assert_equal(
|
||||
{'pools': {}, 'minimum_confirmations': 1},
|
||||
self.nodes[2].z_getbalanceforaccount(acct2))
|
||||
|
||||
# acct2's incoming change (unconfirmed, still in the mempool) is 9 zec
|
||||
assert_equal(
|
||||
{'pools': {'orchard': {'valueZat': Decimal('900000000')}}, 'minimum_confirmations': 0},
|
||||
self.nodes[2].z_getbalanceforaccount(acct2, 0))
|
||||
|
||||
# The transaction was un-mined, so acct3 should have no confirmed balance
|
||||
assert_equal(
|
||||
{'pools': {}, 'minimum_confirmations': 1},
|
||||
self.nodes[3].z_getbalanceforaccount(acct3))
|
||||
|
||||
# acct3's unconfirmed balance is 1 zec
|
||||
assert_equal(
|
||||
{'pools': {'orchard': {'valueZat': Decimal('100000000')}}, 'minimum_confirmations': 0},
|
||||
self.nodes[3].z_getbalanceforaccount(acct3, 0))
|
||||
|
||||
# Manually resend the transaction in node 2's mempool
|
||||
self.nodes[2].resendwallettransactions()
|
||||
|
||||
# Sync the network
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# The un-mined transaction should now have been re-mined
|
||||
assert_equal(
|
||||
{'pools': {'orchard': {'valueZat': Decimal('900000000')}}, 'minimum_confirmations': 1},
|
||||
self.nodes[2].z_getbalanceforaccount(acct2))
|
||||
|
||||
assert_equal(
|
||||
{'pools': {'orchard': {'valueZat': Decimal('100000000')}}, 'minimum_confirmations': 1},
|
||||
self.nodes[3].z_getbalanceforaccount(acct3))
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletOrchardTest().main()
|
|
@ -6,17 +6,31 @@
|
|||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
connect_nodes_bi,
|
||||
start_nodes,
|
||||
sync_blocks,
|
||||
wait_and_assert_operationid_status,
|
||||
)
|
||||
|
||||
TX_EXPIRY_DELTA = 10
|
||||
TX_EXPIRING_SOON_THRESHOLD = 3
|
||||
|
||||
# Test ANY_TADDR special string in z_sendmany
|
||||
class WalletSendManyAnyTaddr(BitcoinTestFramework):
|
||||
def setup_nodes(self):
|
||||
return start_nodes(self.num_nodes, self.options.tmpdir,
|
||||
[[
|
||||
"-txexpirydelta=%d" % TX_EXPIRY_DELTA,
|
||||
]] * self.num_nodes)
|
||||
|
||||
def run_test(self):
|
||||
# Sanity-check the test harness
|
||||
assert_equal(self.nodes[0].getblockcount(), 200)
|
||||
|
||||
# Create the addresses we will be using.
|
||||
recipient = self.nodes[1].z_getnewaddress()
|
||||
node2zaddr = self.nodes[2].z_getnewaddress()
|
||||
node2taddr1 = self.nodes[2].getnewaddress()
|
||||
node3zaddr = self.nodes[3].z_getnewaddress()
|
||||
node3taddr1 = self.nodes[3].getnewaddress()
|
||||
node3taddr2 = self.nodes[3].getnewaddress()
|
||||
|
@ -67,5 +81,48 @@ class WalletSendManyAnyTaddr(BitcoinTestFramework):
|
|||
assert_equal(self.nodes[3].z_getbalance(node3taddr1), 0)
|
||||
assert_equal(self.nodes[3].z_getbalance(node3taddr2), 0)
|
||||
|
||||
# Send from a change t-address.
|
||||
wait_and_assert_operationid_status(
|
||||
self.nodes[3],
|
||||
self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 20}]),
|
||||
)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# The recipient has their funds!
|
||||
assert_equal(self.nodes[1].z_getbalance(recipient), 120)
|
||||
|
||||
# Check that ANY_TADDR note selection doesn't attempt a double-spend
|
||||
myopid = self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 20}])
|
||||
wait_and_assert_operationid_status(self.nodes[3], myopid, "failed", "Insufficient funds: have 14.99998, need 20.00001")
|
||||
|
||||
# Create an expired transaction on node 3.
|
||||
self.split_network()
|
||||
expire_transparent = self.nodes[3].sendtoaddress(node2taddr1, 14)
|
||||
assert(expire_transparent in self.nodes[3].getrawmempool())
|
||||
self.sync_all()
|
||||
assert_equal('waiting', self.nodes[2].gettransaction(expire_transparent)['status'])
|
||||
|
||||
self.nodes[0].generate(TX_EXPIRY_DELTA + TX_EXPIRING_SOON_THRESHOLD)
|
||||
self.sync_all()
|
||||
connect_nodes_bi(self.nodes, 1, 2)
|
||||
sync_blocks(self.nodes[1:3])
|
||||
assert_equal('expired', self.nodes[2].gettransaction(expire_transparent)['status'])
|
||||
|
||||
# Ensure that node 2 has no transparent funds.
|
||||
self.nodes[2].generate(100) # To ensure node 2's pending coinbase is spendable
|
||||
self.sync_all()
|
||||
wait_and_assert_operationid_status(
|
||||
self.nodes[2],
|
||||
self.nodes[2].z_shieldcoinbase("*", node2zaddr, 0)['opid'],
|
||||
)
|
||||
self.sync_all()
|
||||
assert_equal(0, self.nodes[2].getbalance())
|
||||
|
||||
# Check that ANY_TADDR doesn't select an expired output.
|
||||
wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 13}]), "failed", "Insufficient funds: have 0.00, need 13.00001")
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletSendManyAnyTaddr().main()
|
||||
|
|
|
@ -23,7 +23,8 @@ class WalletShieldCoinbaseUANU5(WalletShieldCoinbaseTest):
|
|||
assert('transparent' not in balances['pools'])
|
||||
assert('sprout' not in balances['pools'])
|
||||
# assert('sapling' not in balances['pools'])
|
||||
assert_equal(balances['pools']['sapling']['valueZat'], expected * COIN)
|
||||
sapling_balance = balances['pools']['sapling']['valueZat']
|
||||
assert_equal(sapling_balance, expected * COIN)
|
||||
# assert_equal(balances['pools']['orchard']['valueZat'], expected * COIN)
|
||||
|
||||
# While we're at it, check that z_listunspent only shows outputs with
|
||||
|
@ -35,6 +36,8 @@ class WalletShieldCoinbaseUANU5(WalletShieldCoinbaseTest):
|
|||
[{'type': x['type'], 'address': x['address']} for x in unspent],
|
||||
)
|
||||
|
||||
total_balance = node.z_getbalance(self.addr) * COIN
|
||||
assert_equal(total_balance, sapling_balance)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Test shielding to a unified address with NU5 activated")
|
||||
|
|
|
@ -21,7 +21,8 @@ class WalletShieldCoinbaseUASapling(WalletShieldCoinbaseTest):
|
|||
balances = node.z_getbalanceforaccount(self.account)
|
||||
assert('transparent' not in balances['pools'])
|
||||
assert('sprout' not in balances['pools'])
|
||||
assert_equal(balances['pools']['sapling']['valueZat'], expected * COIN)
|
||||
sapling_balance = balances['pools']['sapling']['valueZat']
|
||||
assert_equal(sapling_balance, expected * COIN)
|
||||
assert('orchard' not in balances['pools'])
|
||||
|
||||
# While we're at it, check that z_listunspent only shows outputs with
|
||||
|
@ -33,6 +34,8 @@ class WalletShieldCoinbaseUASapling(WalletShieldCoinbaseTest):
|
|||
[{'type': x['type'], 'address': x['address']} for x in unspent],
|
||||
)
|
||||
|
||||
total_balance = node.z_getbalance(self.addr) * COIN
|
||||
assert_equal(total_balance, sapling_balance)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Test shielding to a unified address with sapling activated (but not NU5)")
|
||||
|
|
|
@ -4,8 +4,15 @@
|
|||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_greater_than, connect_nodes_bi, \
|
||||
DEFAULT_FEE, start_nodes, wait_and_assert_operationid_status
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_greater_than,
|
||||
assert_raises_message,
|
||||
connect_nodes_bi,
|
||||
DEFAULT_FEE,
|
||||
start_nodes,
|
||||
wait_and_assert_operationid_status,
|
||||
)
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.mininode import COIN
|
||||
from decimal import Decimal
|
||||
|
@ -27,14 +34,19 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
|||
# Remember that empty pools are omitted from the output.
|
||||
def _check_balance_for_rpc(self, rpcmethod, node, account, expected, minconf):
|
||||
rpc = getattr(self.nodes[node], rpcmethod)
|
||||
actual = rpc(account) if minconf is None else rpc(account, minconf)
|
||||
actual = rpc(account, minconf)
|
||||
assert_equal(set(expected), set(actual['pools']))
|
||||
total_balance = 0
|
||||
for pool in expected:
|
||||
assert_equal(expected[pool] * COIN, actual['pools'][pool]['valueZat'])
|
||||
assert_equal(actual['minimum_confirmations'], 1 if minconf is None else minconf)
|
||||
total_balance += expected[pool]
|
||||
assert_equal(actual['minimum_confirmations'], minconf)
|
||||
return total_balance
|
||||
|
||||
def check_balance(self, node, account, address, expected, minconf=None):
|
||||
self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
|
||||
def check_balance(self, node, account, address, expected, minconf=1):
|
||||
acct_balance = self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
|
||||
z_getbalance = self.nodes[node].z_getbalance(address, minconf)
|
||||
assert_equal(acct_balance, z_getbalance)
|
||||
fvk = self.nodes[node].z_exportviewingkey(address)
|
||||
self._check_balance_for_rpc('z_getbalanceforviewingkey', node, fvk, expected, minconf)
|
||||
|
||||
|
@ -159,9 +171,36 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
|||
|
||||
# Change went to a fresh address, so use `ANY_TADDR` which
|
||||
# should hold the rest of our transparent funds.
|
||||
source = 'ANY_TADDR'
|
||||
recipients = []
|
||||
recipients.append({"address":n0ua0, "amount":10})
|
||||
opid = self.nodes[2].z_sendmany('ANY_TADDR', recipients, 1, 0)
|
||||
|
||||
# If we attempt to spend with the default privacy policy, z_sendmany
|
||||
# fails because it needs to spend transparent coins in a transaction
|
||||
# involving a Unified Address.
|
||||
revealed_senders_msg = 'This transaction requires selecting transparent coins, which is not enabled by default because it will publicly reveal transaction senders and amounts. THIS MAY AFFECT YOUR PRIVACY. Resubmit with the `privacyPolicy` parameter set to `AllowRevealedSenders` or weaker if you wish to allow this transaction to proceed anyway.'
|
||||
opid = self.nodes[2].z_sendmany(source, recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid, 'failed', revealed_senders_msg)
|
||||
|
||||
# We can't create a transaction with an unknown privacy policy.
|
||||
assert_raises_message(
|
||||
JSONRPCException,
|
||||
'Unknown privacy policy name \'ZcashIsAwesome\'',
|
||||
self.nodes[2].z_sendmany,
|
||||
source, recipients, 1, 0, 'ZcashIsAwesome')
|
||||
|
||||
# If we set any policy that does not include AllowRevealedSenders,
|
||||
# z_sendmany also fails.
|
||||
for policy in [
|
||||
'FullPrivacy',
|
||||
'AllowRevealedAmounts',
|
||||
'AllowRevealedRecipients',
|
||||
]:
|
||||
opid = self.nodes[2].z_sendmany(source, recipients, 1, 0, policy)
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid, 'failed', revealed_senders_msg)
|
||||
|
||||
# By setting the correct policy, we can create the transaction.
|
||||
opid = self.nodes[2].z_sendmany(source, recipients, 1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||
|
||||
self.nodes[2].generate(1)
|
||||
|
@ -176,7 +215,32 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
|||
# Send some funds to a specific legacy taddr that we can spend from
|
||||
recipients = []
|
||||
recipients.append({"address":mytaddr, "amount":5})
|
||||
opid = self.nodes[0].z_sendmany(n0ua0, recipients, 1, 0)
|
||||
|
||||
# If we attempt to spend with the default privacy policy, z_sendmany
|
||||
# returns an error because it needs to create a transparent recipient in
|
||||
# a transaction involving a Unified Address.
|
||||
assert_raises_message(
|
||||
JSONRPCException,
|
||||
'AllowRevealedRecipients',
|
||||
self.nodes[0].z_sendmany,
|
||||
n0ua0, recipients, 1, 0)
|
||||
|
||||
# If we set any policy that does not include AllowRevealedRecipients,
|
||||
# z_sendmany also returns an error.
|
||||
for policy in [
|
||||
'FullPrivacy',
|
||||
'AllowRevealedAmounts',
|
||||
'AllowRevealedSenders',
|
||||
'AllowLinkingAccountAddresses',
|
||||
]:
|
||||
assert_raises_message(
|
||||
JSONRPCException,
|
||||
'AllowRevealedRecipients',
|
||||
self.nodes[0].z_sendmany,
|
||||
n0ua0, recipients, 1, 0, policy)
|
||||
|
||||
# By setting the correct policy, we can create the transaction.
|
||||
opid = self.nodes[0].z_sendmany(n0ua0, recipients, 1, 0, 'AllowRevealedRecipients')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
|
@ -199,10 +263,12 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
|||
self.check_balance(0, 0, n0ua0, {'sapling': 2})
|
||||
assert_equal(Decimal(self.nodes[2].z_getbalance(myzaddr)), zbalance)
|
||||
|
||||
# Send funds back from the legacy taddr to the UA
|
||||
# Send funds back from the legacy taddr to the UA. This requires
|
||||
# AllowRevealedSenders, but we can also use any weaker policy that
|
||||
# includes it.
|
||||
recipients = []
|
||||
recipients.append({"address":n0ua0, "amount":4})
|
||||
opid = self.nodes[2].z_sendmany(mytaddr, recipients, 1, 0)
|
||||
opid = self.nodes[2].z_sendmany(mytaddr, recipients, 1, 0, 'AllowFullyTransparent')
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||
|
||||
self.nodes[2].generate(1)
|
||||
|
@ -225,5 +291,99 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
|||
self.check_balance(0, 0, n0ua0, {'sapling': 8})
|
||||
assert_equal(Decimal(self.nodes[2].z_getbalance(myzaddr)), zbalance)
|
||||
|
||||
#
|
||||
# Test that z_sendmany avoids UA linkability unless we allow it.
|
||||
#
|
||||
|
||||
# Generate a new account with two new addresses.
|
||||
n1account = self.nodes[1].z_getnewaccount()['account']
|
||||
n1ua0 = self.nodes[1].z_getaddressforaccount(n1account)['unifiedaddress']
|
||||
n1ua1 = self.nodes[1].z_getaddressforaccount(n1account)['unifiedaddress']
|
||||
|
||||
# Send funds to the transparent receivers of both addresses.
|
||||
for ua in [n1ua0, n1ua1]:
|
||||
taddr = self.nodes[1].z_listunifiedreceivers(ua)['transparent']
|
||||
self.nodes[0].sendtoaddress(taddr, 2)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# The account should see all funds.
|
||||
assert_equal(
|
||||
self.nodes[1].z_getbalanceforaccount(n1account)['pools'],
|
||||
{'transparent': {'valueZat': 4 * COIN}},
|
||||
)
|
||||
|
||||
# The addresses should see only the transparent funds sent to them.
|
||||
assert_equal(self.nodes[1].z_getbalance(n1ua0), 2)
|
||||
assert_equal(self.nodes[1].z_getbalance(n1ua1), 2)
|
||||
|
||||
# If we try to send 3 ZEC from n1ua0, it will fail with too-few funds.
|
||||
recipients = [{"address":n0ua0, "amount":3}]
|
||||
linked_addrs_msg = 'Insufficient funds: have 2.00, need 3.00 (This transaction may require selecting transparent coins that were sent to multiple Unified Addresses, which is not enabled by default because it would create a public link between the transparent receivers of these addresses. THIS MAY AFFECT YOUR PRIVACY. Resubmit with the `privacyPolicy` parameter set to `AllowLinkingAccountAddresses` or weaker if you wish to allow this transaction to proceed anyway.)'
|
||||
opid = self.nodes[1].z_sendmany(n1ua0, recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[1], opid, 'failed', linked_addrs_msg)
|
||||
|
||||
# If we try it again with any policy that is too strong, it also fails.
|
||||
for policy in [
|
||||
'FullPrivacy',
|
||||
'AllowRevealedAmounts',
|
||||
'AllowRevealedRecipients',
|
||||
'AllowRevealedSenders',
|
||||
'AllowFullyTransparent',
|
||||
]:
|
||||
opid = self.nodes[1].z_sendmany(n1ua0, recipients, 1, 0, policy)
|
||||
wait_and_assert_operationid_status(self.nodes[1], opid, 'failed', linked_addrs_msg)
|
||||
|
||||
# Once we provide a sufficiently-weak policy, the transaction succeeds.
|
||||
opid = self.nodes[1].z_sendmany(n1ua0, recipients, 1, 0, 'AllowLinkingAccountAddresses')
|
||||
wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# The account should see the remaining funds, and they should have been
|
||||
# sent to the Sapling change address (because NU5 is not active).
|
||||
assert_equal(
|
||||
self.nodes[1].z_getbalanceforaccount(n1account)['pools'],
|
||||
{'sapling': {'valueZat': 1 * COIN}},
|
||||
)
|
||||
|
||||
# The addresses should both show the same balance, as they both show the
|
||||
# Sapling balance.
|
||||
assert_equal(self.nodes[1].z_getbalance(n1ua0), 1)
|
||||
assert_equal(self.nodes[1].z_getbalance(n1ua1), 1)
|
||||
|
||||
#
|
||||
# Test NoPrivacy policy
|
||||
#
|
||||
|
||||
# Send some legacy transparent funds to n1ua0, creating Sapling outputs.
|
||||
recipients = [{"address":n1ua0, "amount":10}]
|
||||
# This requires the AllowRevealedSenders policy...
|
||||
opid = self.nodes[2].z_sendmany('ANY_TADDR', recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid, 'failed', revealed_senders_msg)
|
||||
# ... which we can always override with the NoPrivacy policy.
|
||||
opid = self.nodes[2].z_sendmany('ANY_TADDR', recipients, 1, 0, 'NoPrivacy')
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# Send some funds from node 1's account to a transparent address.
|
||||
recipients = [{"address":mytaddr, "amount":5}]
|
||||
# This requires the AllowRevealedRecipients policy...
|
||||
assert_raises_message(
|
||||
JSONRPCException,
|
||||
'AllowRevealedRecipients',
|
||||
self.nodes[1].z_sendmany,
|
||||
n1ua0, recipients, 1, 0)
|
||||
# ... which we can always override with the NoPrivacy policy.
|
||||
opid = self.nodes[1].z_sendmany(n1ua0, recipients, 1, 0, 'NoPrivacy')
|
||||
wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletZSendmanyTest().main()
|
||||
|
|
|
@ -46,6 +46,17 @@ RE_RPATH_RUNPATH = re.compile('No RPATH.*No RUNPATH')
|
|||
RE_FORTIFY_AVAILABLE = re.compile('FORTIFY_SOURCE support available.*Yes')
|
||||
RE_FORTIFY_USED = re.compile('Binary compiled with FORTIFY_SOURCE support.*Yes')
|
||||
|
||||
CXX_BINARIES = [
|
||||
'src/zcashd',
|
||||
'src/zcash-cli',
|
||||
'src/zcash-gtest',
|
||||
'src/zcash-tx',
|
||||
'src/test/test_bitcoin',
|
||||
]
|
||||
RUST_BINARIES = [
|
||||
'src/zcashd-wallet-tool',
|
||||
]
|
||||
|
||||
def test_rpath_runpath(filename):
|
||||
output = subprocess.check_output(
|
||||
[repofile('qa/zcash/checksec.sh'), '--file=' + repofile(filename)]
|
||||
|
@ -86,21 +97,14 @@ def check_security_hardening():
|
|||
if not magic.startswith(b'\x7fELF'):
|
||||
return ret
|
||||
|
||||
ret &= test_rpath_runpath('src/zcashd')
|
||||
ret &= test_rpath_runpath('src/zcash-cli')
|
||||
ret &= test_rpath_runpath('src/zcash-gtest')
|
||||
ret &= test_rpath_runpath('src/zcash-tx')
|
||||
ret &= test_rpath_runpath('src/test/test_bitcoin')
|
||||
for bin in CXX_BINARIES + RUST_BINARIES:
|
||||
ret &= test_rpath_runpath(bin)
|
||||
|
||||
# NOTE: checksec.sh does not reliably determine whether FORTIFY_SOURCE
|
||||
# is enabled for the entire binary. See issue #915.
|
||||
# FORTIFY_SOURCE does mostly nothing for Clang before 10, which we don't
|
||||
# pin yet, so we disable these tests.
|
||||
# ret &= test_fortify_source('src/zcashd')
|
||||
# ret &= test_fortify_source('src/zcash-cli')
|
||||
# ret &= test_fortify_source('src/zcash-gtest')
|
||||
# ret &= test_fortify_source('src/zcash-tx')
|
||||
# ret &= test_fortify_source('src/test/test_bitcoin')
|
||||
# FORTIFY_SOURCE is not applicable to Rust binaries.
|
||||
for bin in CXX_BINARIES:
|
||||
ret &= test_fortify_source(bin)
|
||||
|
||||
return ret
|
||||
|
||||
|
|
|
@ -5,23 +5,25 @@
|
|||
#
|
||||
|
||||
# Ccache 4.0 requires adding CMake to the depends system.
|
||||
native_ccache 4.0 2022-02-01
|
||||
native_ccache 4.1 2022-02-01
|
||||
native_ccache 4.2 2022-02-01
|
||||
native_ccache 4.2.1 2022-02-01
|
||||
native_ccache 4.3 2022-02-01
|
||||
native_ccache 4.4 2022-02-01
|
||||
native_ccache 4.4.1 2022-02-01
|
||||
native_ccache 4.4.2 2022-02-01
|
||||
native_ccache 4.5 2022-02-01
|
||||
native_ccache 4.5.1 2022-02-01
|
||||
native_ccache 4.0 2022-05-01
|
||||
native_ccache 4.1 2022-05-01
|
||||
native_ccache 4.2 2022-05-01
|
||||
native_ccache 4.2.1 2022-05-01
|
||||
native_ccache 4.3 2022-05-01
|
||||
native_ccache 4.4 2022-05-01
|
||||
native_ccache 4.4.1 2022-05-01
|
||||
native_ccache 4.4.2 2022-05-01
|
||||
native_ccache 4.5 2022-05-01
|
||||
native_ccache 4.5.1 2022-05-01
|
||||
native_ccache 4.6 2022-05-01
|
||||
|
||||
# Clang is currently pinned to LLVM 13
|
||||
|
||||
# Rust is currently pinned to 1.56.1
|
||||
# Rust is currently pinned to 1.59.0
|
||||
|
||||
bdb 18.1.40 2022-02-01
|
||||
# We're never updating to this version
|
||||
bdb 18.1.40 2024-02-01
|
||||
|
||||
# Google Test 1.10.0 requires adding CMake to the depends system.
|
||||
googletest 1.10.0 2022-02-01
|
||||
googletest 1.11.0 2022-02-01
|
||||
googletest 1.10.0 2022-05-01
|
||||
googletest 1.11.0 2022-05-01
|
||||
|
|
|
@ -328,7 +328,9 @@ def main():
|
|||
# packages.mk is not a dependency, it just specifies the list of them all.
|
||||
"packages",
|
||||
# This package doesn't have conventional version numbers
|
||||
"native_cctools"
|
||||
"native_cctools",
|
||||
# This package is pinned specifically for Arch.
|
||||
"native_libtinfo",
|
||||
]
|
||||
|
||||
print_row("NAME", "STATUS", "CURRENT VERSION", "NEWER VERSIONS")
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.56.1
|
||||
1.59.0
|
||||
|
|
|
@ -29,6 +29,9 @@ LIBSECP256K1=secp256k1/libsecp256k1.la
|
|||
LIBUNIVALUE=univalue/libunivalue.la
|
||||
LIBZCASH=libzcash.a
|
||||
|
||||
WALLET_TOOL_BIN=zcashd-wallet-tool$(EXEEXT)
|
||||
WALLET_TOOL_BUILD=$(top_builddir)/target/$(RUST_TARGET)/release/zcashd-wallet-tool$(EXEEXT)
|
||||
|
||||
if ENABLE_ZMQ
|
||||
LIBBITCOIN_ZMQ=libbitcoin_zmq.a
|
||||
endif
|
||||
|
@ -47,7 +50,7 @@ endif
|
|||
# - Note that this does not prevent the secp256k1-sys vendored code from being built; this
|
||||
# requires https://github.com/rust-bitcoin/rust-secp256k1/issues/380 to be addressed.
|
||||
RUST_ENV_VARS = RUSTC="$(RUSTC)" TERM=dumb RUSTFLAGS="--cfg=rust_secp_no_symbol_renaming"
|
||||
RUST_BUILD_OPTS = --lib --release --target $(RUST_TARGET)
|
||||
RUST_BUILD_OPTS = --release --target $(RUST_TARGET) --manifest-path $(top_srcdir)/Cargo.toml
|
||||
|
||||
rust_verbose = $(rust_verbose_@AM_V@)
|
||||
rust_verbose_ = $(rust_verbose_@AM_DEFAULT_V@)
|
||||
|
@ -72,10 +75,16 @@ $(CARGO_CONFIGURED): $(top_srcdir)/.cargo/config.offline
|
|||
$(AM_V_at)touch $@
|
||||
endif
|
||||
|
||||
cargo-build: $(CARGO_CONFIGURED)
|
||||
$(RUST_ENV_VARS) $(CARGO) build $(RUST_BUILD_OPTS) $(rust_verbose) --manifest-path $(top_srcdir)/Cargo.toml
|
||||
cargo-build-lib: $(CARGO_CONFIGURED)
|
||||
$(RUST_ENV_VARS) $(CARGO) build --lib $(RUST_BUILD_OPTS) $(rust_verbose)
|
||||
|
||||
$(LIBRUSTZCASH): cargo-build
|
||||
cargo-build-bins: $(CARGO_CONFIGURED)
|
||||
$(RUST_ENV_VARS) $(CARGO) build --bins $(RUST_BUILD_OPTS) $(rust_verbose)
|
||||
|
||||
$(WALLET_TOOL_BIN): cargo-build-bins
|
||||
$(AM_V_at)cp $(WALLET_TOOL_BUILD) $@
|
||||
|
||||
$(LIBRUSTZCASH): cargo-build-lib
|
||||
|
||||
$(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*)
|
||||
$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F)
|
||||
|
@ -99,6 +108,7 @@ lib_LTLIBRARIES = $(LIBZCASH_SCRIPT)
|
|||
|
||||
bin_PROGRAMS =
|
||||
noinst_PROGRAMS =
|
||||
bin_SCRIPTS =
|
||||
TESTS =
|
||||
BENCHMARKS =
|
||||
|
||||
|
@ -108,6 +118,7 @@ endif
|
|||
|
||||
if BUILD_BITCOIN_UTILS
|
||||
bin_PROGRAMS += zcash-cli zcash-tx
|
||||
bin_SCRIPTS += $(WALLET_TOOL_BIN)
|
||||
endif
|
||||
|
||||
LIBZCASH_H = \
|
||||
|
@ -116,7 +127,7 @@ LIBZCASH_H = \
|
|||
zcash/Address.hpp \
|
||||
zcash/address/transparent.h \
|
||||
zcash/address/mnemonic.h \
|
||||
zcash/address/orchard.h \
|
||||
zcash/address/orchard.hpp \
|
||||
zcash/address/sapling.hpp \
|
||||
zcash/address/sprout.hpp \
|
||||
zcash/address/unified.h \
|
||||
|
@ -129,7 +140,7 @@ LIBZCASH_H = \
|
|||
zcash/util.h \
|
||||
zcash/Zcash.h
|
||||
|
||||
.PHONY: FORCE cargo-build check-symbols check-security
|
||||
.PHONY: FORCE cargo-build-lib cargo-build-bins check-symbols check-security
|
||||
# bitcoin core #
|
||||
BITCOIN_CORE_H = \
|
||||
addrdb.h \
|
||||
|
@ -234,6 +245,7 @@ BITCOIN_CORE_H = \
|
|||
uint252.h \
|
||||
undo.h \
|
||||
util.h \
|
||||
util/match.h \
|
||||
utilmoneystr.h \
|
||||
utilstrencodings.h \
|
||||
utiltest.h \
|
||||
|
@ -247,6 +259,7 @@ BITCOIN_CORE_H = \
|
|||
wallet/asyncrpcoperation_shieldcoinbase.h \
|
||||
wallet/crypter.h \
|
||||
wallet/db.h \
|
||||
wallet/orchard.h \
|
||||
wallet/paymentdisclosure.h \
|
||||
wallet/paymentdisclosuredb.h \
|
||||
wallet/rpcwallet.h \
|
||||
|
@ -334,6 +347,7 @@ libbitcoin_wallet_a_SOURCES = \
|
|||
wallet/asyncrpcoperation_shieldcoinbase.cpp \
|
||||
wallet/crypter.cpp \
|
||||
wallet/db.cpp \
|
||||
wallet/orchard.cpp \
|
||||
wallet/paymentdisclosure.cpp \
|
||||
wallet/paymentdisclosuredb.cpp \
|
||||
wallet/rpcdisclosure.cpp \
|
||||
|
@ -601,7 +615,7 @@ CTAES_DIST += crypto/ctaes/ctaes.h
|
|||
CTAES_DIST += crypto/ctaes/README.md
|
||||
CTAES_DIST += crypto/ctaes/test.c
|
||||
|
||||
CLEANFILES = *.gcda *.gcno */*.gcno wallet/*/*.gcno
|
||||
CLEANFILES = *.gcda *.gcno */*.gcno wallet/*/*.gcno $(bin_SCRIPTS)
|
||||
|
||||
DISTCLEANFILES = obj/build.h
|
||||
|
||||
|
@ -625,16 +639,17 @@ clean-local:
|
|||
$(AM_V_CXX) $(OBJCXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
|
||||
$(CPPFLAGS) $(AM_CXXFLAGS) $(AM_CXXFLAGS) $(PIE_FLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
check-symbols: $(bin_PROGRAMS)
|
||||
check-symbols: $(bin_PROGRAMS) $(bin_SCRIPTS)
|
||||
if GLIBC_BACK_COMPAT
|
||||
@echo "Checking glibc back compat of [$(bin_PROGRAMS)]..."
|
||||
$(AM_V_at) READELF=$(READELF) CPPFILT=$(CPPFILT) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS)
|
||||
@echo "Checking glibc back compat of [$(bin_PROGRAMS) $(bin_SCRIPTS)]..."
|
||||
$(AM_V_at) READELF=$(READELF) CPPFILT=$(CPPFILT) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) $(bin_SCRIPTS)
|
||||
endif
|
||||
|
||||
check-security: $(bin_PROGRAMS)
|
||||
check-security: $(bin_PROGRAMS) $(bin_SCRIPTS)
|
||||
if HARDEN
|
||||
@echo "Checking binary security of [$(bin_PROGRAMS)]..."
|
||||
@echo "Checking binary security of [$(bin_PROGRAMS) $(bin_SCRIPTS)]..."
|
||||
$(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS)
|
||||
$(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) $(top_srcdir)/contrib/devtools/security-check.py --allow-no-canary $(bin_SCRIPTS)
|
||||
endif
|
||||
|
||||
%.pb.cc %.pb.h: %.proto
|
||||
|
|
|
@ -54,12 +54,15 @@ zcash_gtest_SOURCES += \
|
|||
gtest/test_timedata.cpp \
|
||||
gtest/test_transaction.cpp \
|
||||
gtest/test_transaction_builder.cpp \
|
||||
gtest/test_transaction_builder.h \
|
||||
gtest/test_txid.cpp \
|
||||
gtest/test_upgrades.cpp \
|
||||
gtest/test_validation.cpp \
|
||||
gtest/test_zip32.cpp
|
||||
if ENABLE_WALLET
|
||||
zcash_gtest_SOURCES += \
|
||||
wallet/gtest/test_note_selection.cpp \
|
||||
wallet/gtest/test_orchard_wallet.cpp \
|
||||
wallet/gtest/test_paymentdisclosure.cpp \
|
||||
wallet/gtest/test_wallet.cpp
|
||||
endif
|
||||
|
|
|
@ -39,7 +39,7 @@ std::string HelpMessageCli()
|
|||
strUsage += HelpMessageGroup(_("Options:"));
|
||||
strUsage += HelpMessageOpt("-?", _("This help message"));
|
||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME));
|
||||
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
|
||||
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory (this path cannot use '~')"));
|
||||
strUsage += HelpMessageOpt("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). If first extra argument is `walletpassphrase` then the first line(password) will not be echoed."));
|
||||
AppendParamsHelpMessages(strUsage);
|
||||
strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), DEFAULT_RPCCONNECT));
|
||||
|
|
|
@ -133,7 +133,7 @@ public:
|
|||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight = 1046400;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].hashActivationBlock =
|
||||
uint256S("00000000002038016f976744c369dce7419fca30e7171dfac703af5e5f7ad1d4");
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170017;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170100;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight =
|
||||
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nProtocolVersion = 0x7FFFFFFF;
|
||||
|
@ -417,8 +417,9 @@ public:
|
|||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight = 1028500;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].hashActivationBlock =
|
||||
uint256S("01a4d7c6aada30c87762c1bf33fff5df7266b1fd7616bfdb5227fa59bd79e7a2");
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170015;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight = 1599200;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170050;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight =
|
||||
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nProtocolVersion = 0x7FFFFFFF;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nActivationHeight =
|
||||
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||
|
@ -633,7 +634,7 @@ public:
|
|||
static_assert(equihash_parameters_acceptable(N, K));
|
||||
consensus.nEquihashN = N;
|
||||
consensus.nEquihashK = K;
|
||||
consensus.powLimit = uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f");
|
||||
consensus.powLimit = uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"); // if this is any larger, the for loop in GetNextWorkRequired can overflow bnTot
|
||||
consensus.nPowAveragingWindow = 17;
|
||||
assert(maxUint/UintToArith256(consensus.powLimit) >= consensus.nPowAveragingWindow);
|
||||
consensus.nPowMaxAdjustDown = 0; // Turn off adjustment down
|
||||
|
@ -663,7 +664,7 @@ public:
|
|||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nProtocolVersion = 170012;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight =
|
||||
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170015;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170040;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight =
|
||||
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nProtocolVersion = 0x7FFFFFFF;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Copyright (c) 2016-2021 The Zcash developers
|
||||
// Copyright (c) 2016-2022 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
|
@ -16,9 +16,9 @@
|
|||
|
||||
//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
|
||||
#define CLIENT_VERSION_MAJOR 4
|
||||
#define CLIENT_VERSION_MINOR 6
|
||||
#define CLIENT_VERSION_MINOR 7
|
||||
#define CLIENT_VERSION_REVISION 0
|
||||
#define CLIENT_VERSION_BUILD 51
|
||||
#define CLIENT_VERSION_BUILD 25
|
||||
|
||||
//! Set to true for release, false for prerelease or test build
|
||||
#define CLIENT_VERSION_IS_RELEASE true
|
||||
|
@ -27,7 +27,7 @@
|
|||
* Copyright year (2009-this)
|
||||
* Todo: update this when changing our copyright comments in the source
|
||||
*/
|
||||
#define COPYRIGHT_YEAR 2021
|
||||
#define COPYRIGHT_YEAR 2022
|
||||
|
||||
#endif //HAVE_CONFIG_H
|
||||
|
||||
|
|
|
@ -683,6 +683,14 @@ void CCoinsViewCache::PopAnchor(const uint256 &newrt, ShieldedType type) {
|
|||
hashSaplingAnchor
|
||||
);
|
||||
break;
|
||||
case ORCHARD:
|
||||
AbstractPopAnchor<OrchardMerkleFrontier, CAnchorsOrchardMap, CAnchorsOrchardCacheEntry>(
|
||||
newrt,
|
||||
ORCHARD,
|
||||
cacheOrchardAnchors,
|
||||
hashOrchardAnchor
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Unknown shielded type");
|
||||
}
|
||||
|
|
|
@ -12,6 +12,15 @@
|
|||
#include "util/match.h"
|
||||
|
||||
namespace Consensus {
|
||||
std::optional<int> Params::GetActivationHeight(Consensus::UpgradeIndex idx) const {
|
||||
auto nActivationHeight = vUpgrades[idx].nActivationHeight;
|
||||
if (nActivationHeight == Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return nActivationHeight;
|
||||
}
|
||||
}
|
||||
|
||||
bool Params::NetworkUpgradeActive(int nHeight, Consensus::UpgradeIndex idx) const {
|
||||
return NetworkUpgradeState(nHeight, *this, idx) == UPGRADE_ACTIVE;
|
||||
}
|
||||
|
|
|
@ -225,6 +225,11 @@ static const unsigned int PRE_BLOSSOM_REGTEST_HALVING_INTERVAL = 144;
|
|||
* Parameters that influence chain consensus.
|
||||
*/
|
||||
struct Params {
|
||||
/**
|
||||
* Returns the activation height for the specified network upgrade, if any.
|
||||
*/
|
||||
std::optional<int> GetActivationHeight(Consensus::UpgradeIndex idx) const;
|
||||
|
||||
/**
|
||||
* Returns true if the given network upgrade is active as of the given block
|
||||
* height. Caller must check that the height is >= 0 (and handle unknown
|
||||
|
|
|
@ -45,7 +45,7 @@ const struct NUInfo NetworkUpgradeInfo[Consensus::MAX_NETWORK_UPGRADES] = {
|
|||
.strInfo = "See https://z.cash/upgrade/canopy/ for details.",
|
||||
},
|
||||
{
|
||||
.nBranchId = 0x37519621,
|
||||
.nBranchId = 0xc2d6d0b4,
|
||||
.strName = "NU5",
|
||||
.strInfo = "See https://z.cash/upgrade/nu5/ for details.",
|
||||
},
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "key.h"
|
||||
#include "pubkey.h"
|
||||
#include "util.h"
|
||||
#include "utiltest.h"
|
||||
|
||||
#include "librustzcash.h"
|
||||
#include <sodium.h>
|
||||
|
@ -19,26 +20,6 @@ int main(int argc, char **argv) {
|
|||
assert(sodium_init() != -1);
|
||||
ECC_Start();
|
||||
|
||||
fs::path sapling_spend = ZC_GetParamsDir() / "sapling-spend.params";
|
||||
fs::path sapling_output = ZC_GetParamsDir() / "sapling-output.params";
|
||||
fs::path sprout_groth16 = ZC_GetParamsDir() / "sprout-groth16.params";
|
||||
|
||||
static_assert(
|
||||
sizeof(fs::path::value_type) == sizeof(codeunit),
|
||||
"librustzcash not configured correctly");
|
||||
auto sapling_spend_str = sapling_spend.native();
|
||||
auto sapling_output_str = sapling_output.native();
|
||||
auto sprout_groth16_str = sprout_groth16.native();
|
||||
|
||||
librustzcash_init_zksnark_params(
|
||||
reinterpret_cast<const codeunit*>(sapling_spend_str.c_str()),
|
||||
sapling_spend_str.length(),
|
||||
reinterpret_cast<const codeunit*>(sapling_output_str.c_str()),
|
||||
sapling_output_str.length(),
|
||||
reinterpret_cast<const codeunit*>(sprout_groth16_str.c_str()),
|
||||
sprout_groth16_str.length()
|
||||
);
|
||||
|
||||
testing::InitGoogleMock(&argc, argv);
|
||||
|
||||
// The "threadsafe" style is necessary for correct operation of death/exit
|
||||
|
|
|
@ -34,6 +34,9 @@ TEST(CheckBlock, VersionTooLow) {
|
|||
block.nVersion = 1;
|
||||
|
||||
MockCValidationState state;
|
||||
|
||||
SelectParams(CBaseChainParams::MAIN);
|
||||
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "version-too-low", false, "")).Times(1);
|
||||
EXPECT_FALSE(CheckBlock(block, state, Params(), verifier, false, false, true));
|
||||
}
|
||||
|
@ -89,7 +92,7 @@ protected:
|
|||
|
||||
void TearDown() override {
|
||||
// Revert to test default. No-op on mainnet params.
|
||||
RegtestDeactivateSapling();
|
||||
RegtestDeactivateBlossom();
|
||||
}
|
||||
|
||||
// Returns a valid but empty mutable transaction at block height 1.
|
||||
|
|
|
@ -1159,6 +1159,8 @@ TEST(ChecktransactionTests, InvalidShieldedCoinbase) {
|
|||
}
|
||||
|
||||
TEST(ChecktransactionTests, HeartwoodAcceptsShieldedCoinbase) {
|
||||
LoadProofParameters();
|
||||
|
||||
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
auto chainparams = Params();
|
||||
|
||||
|
@ -1239,6 +1241,8 @@ TEST(ChecktransactionTests, HeartwoodAcceptsShieldedCoinbase) {
|
|||
// bindingSig from https://zips.z.cash/protocol/protocol.pdf#txnencoding are
|
||||
// applied to coinbase transactions.
|
||||
TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
|
||||
LoadProofParameters();
|
||||
|
||||
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
auto chainparams = Params();
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include "transaction_builder.h"
|
||||
#include "utiltest.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
TEST(RecursiveDynamicUsageTests, TestTransactionTransparent)
|
||||
{
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
@ -20,7 +22,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparent)
|
|||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||
CTxDestination taddr = tsk.GetPubKey().GetID();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
||||
builder.AddTransparentOutput(taddr, 40000);
|
||||
|
||||
|
@ -48,12 +50,14 @@ TEST(RecursiveDynamicUsageTests, TestTransactionJoinSplit)
|
|||
|
||||
TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToSapling)
|
||||
{
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
||||
|
||||
|
@ -67,6 +71,8 @@ TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToSapling)
|
|||
|
||||
TEST(RecursiveDynamicUsageTests, TestTransactionTransparentToSapling)
|
||||
{
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
CBasicKeyStore keystore;
|
||||
|
@ -74,7 +80,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparentToSapling)
|
|||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 40000, {});
|
||||
|
||||
|
@ -88,6 +94,8 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparentToSapling)
|
|||
|
||||
TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToTransparent)
|
||||
{
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
CBasicKeyStore keystore;
|
||||
|
@ -96,7 +104,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToTransparent)
|
|||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddTransparentOutput(taddr, 40000);
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "primitives/transaction.h"
|
||||
#include "proof_verifier.h"
|
||||
#include "transaction_builder.h"
|
||||
#include "utiltest.h"
|
||||
#include "zcash/JoinSplit.hpp"
|
||||
#include "zcash/Note.hpp"
|
||||
#include "zcash/NoteEncryption.hpp"
|
||||
|
@ -309,6 +310,8 @@ void increment_note_witnesses(
|
|||
|
||||
TEST(Joinsplit, FullApiTest)
|
||||
{
|
||||
LoadProofParameters();
|
||||
|
||||
{
|
||||
std::vector<SproutWitness> witnesses;
|
||||
SproutMerkleTree tree;
|
||||
|
|
|
@ -69,34 +69,8 @@ TEST(Keys, EncodeAndDecodeSapling)
|
|||
#define MAKE_STRING(x) std::string((x), (x) + sizeof(x))
|
||||
|
||||
namespace libzcash {
|
||||
class ReceiverToString {
|
||||
public:
|
||||
ReceiverToString() {}
|
||||
|
||||
std::string operator()(const SaplingPaymentAddress &zaddr) const {
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << zaddr;
|
||||
return tfm::format("Sapling(%s)", HexStr(ss.begin(), ss.end()));
|
||||
}
|
||||
|
||||
std::string operator()(const CScriptID &p2sh) const {
|
||||
return tfm::format("P2SH(%s)", p2sh.GetHex());
|
||||
}
|
||||
|
||||
std::string operator()(const CKeyID &p2pkh) const {
|
||||
return tfm::format("P2PKH(%s)", p2pkh.GetHex());
|
||||
}
|
||||
|
||||
std::string operator()(const UnknownReceiver &unknown) const {
|
||||
return tfm::format(
|
||||
"Unknown(%x, %s)",
|
||||
unknown.typecode,
|
||||
HexStr(unknown.data.begin(), unknown.data.end()));
|
||||
}
|
||||
};
|
||||
|
||||
void PrintTo(const Receiver& receiver, std::ostream* os) {
|
||||
*os << std::visit(ReceiverToString(), receiver);
|
||||
*os << DebugPrintReceiver(receiver);
|
||||
}
|
||||
void PrintTo(const UnifiedAddress& ua, std::ostream* os) {
|
||||
*os << "UnifiedAddress(" << testing::PrintToString(ua.GetReceiversAsParsed()) << ")";
|
||||
|
@ -126,8 +100,11 @@ TEST(Keys, EncodeAndDecodeUnifiedAddresses)
|
|||
// These were added to the UA in preference order by the Python test vectors.
|
||||
if (!test[3].isNull()) {
|
||||
auto data = ParseHex(test[3].get_str());
|
||||
libzcash::UnknownReceiver r(0x03, data);
|
||||
ua.AddReceiver(r);
|
||||
CDataStream ss(
|
||||
data,
|
||||
SER_NETWORK,
|
||||
PROTOCOL_VERSION);
|
||||
ua.AddReceiver(libzcash::OrchardRawAddress::Read(ss));
|
||||
}
|
||||
if (!test[2].isNull()) {
|
||||
auto data = ParseHex(test[2].get_str());
|
||||
|
@ -186,6 +163,16 @@ TEST(Keys, DeriveUnifiedFullViewingKeys)
|
|||
if (test.size() == 1) continue; // comment
|
||||
|
||||
try {
|
||||
// [
|
||||
// t_key_bytes,
|
||||
// sapling_fvk_bytes,
|
||||
// orchard_fvk_bytes,
|
||||
// unknown_fvk_typecode,
|
||||
// unknown_fvk_bytes,
|
||||
// unified_fvk,
|
||||
// root_seed,
|
||||
// account,
|
||||
// ],
|
||||
auto seed_hex = test[6].get_str();
|
||||
auto seed_data = ParseHex(seed_hex);
|
||||
RawHDSeed raw_seed(seed_data.begin(), seed_data.end());
|
||||
|
@ -235,6 +222,24 @@ TEST(Keys, DeriveUnifiedFullViewingKeys)
|
|||
auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss);
|
||||
EXPECT_EQ(key, saplingKey);
|
||||
}
|
||||
if (!test[2].isNull()) {
|
||||
auto expectedHex = test[2].get_str();
|
||||
|
||||
// Ensure that the serialized Orchard fvk matches the test data.
|
||||
auto orchardKey = ufvk.GetOrchardKey().value();
|
||||
CDataStream ssEncode(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssEncode << orchardKey;
|
||||
EXPECT_EQ(ssEncode.size(), 96);
|
||||
auto skeyHex = HexStr(ssEncode.begin(), ssEncode.end());
|
||||
EXPECT_EQ(expectedHex, skeyHex);
|
||||
|
||||
// Ensure that parsing the test data derives the correct dfvk
|
||||
auto data = ParseHex(expectedHex);
|
||||
ASSERT_EQ(data.size(), 96);
|
||||
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
|
||||
auto key = libzcash::OrchardFullViewingKey::Read(ss);
|
||||
EXPECT_EQ(key, orchardKey);
|
||||
}
|
||||
// Enable the following after Orchard keys are supported.
|
||||
//{
|
||||
// auto fvk_data = ParseHex(test[5].get_str());
|
||||
|
@ -284,14 +289,13 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys)
|
|||
auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss);
|
||||
ASSERT_TRUE(builder.AddSaplingKey(key));
|
||||
}
|
||||
|
||||
// Orchard keys and unknown items are not yet supported; instead,
|
||||
// we just test that we're able to parse the unified key string
|
||||
// and that the constituent items match the elements; if no Sapling
|
||||
// key is present then UFVK construction would fail because it might
|
||||
// presume the UFVK to be transparent-only.
|
||||
if (test[1].isNull())
|
||||
continue;
|
||||
if (!test[2].isNull()) {
|
||||
auto data = ParseHex(test[2].get_str());
|
||||
ASSERT_EQ(data.size(), 96);
|
||||
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
|
||||
auto key = libzcash::OrchardFullViewingKey::Read(ss);
|
||||
ASSERT_TRUE(builder.AddOrchardKey(key));
|
||||
}
|
||||
|
||||
auto built = builder.build();
|
||||
ASSERT_TRUE(built.has_value());
|
||||
|
@ -304,5 +308,6 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys)
|
|||
|
||||
EXPECT_EQ(decoded.value().GetTransparentKey(), built.value().GetTransparentKey());
|
||||
EXPECT_EQ(decoded.value().GetSaplingKey(), built.value().GetSaplingKey());
|
||||
EXPECT_EQ(decoded.value().GetOrchardKey(), built.value().GetOrchardKey());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -557,20 +557,55 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) {
|
|||
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
|
||||
|
||||
auto addrPair = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Sapling}));
|
||||
|
||||
|
||||
EXPECT_TRUE(addrPair.first.GetSaplingReceiver().has_value());
|
||||
auto saplingReceiver = addrPair.first.GetSaplingReceiver().value();
|
||||
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
|
||||
EXPECT_FALSE(ufvkmeta.has_value());
|
||||
|
||||
// We detect this even though we haven't added the Sapling address, because
|
||||
// we trial-decrypt diversifiers (which also means we learn the index).
|
||||
auto ufvkmetaUnadded = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
|
||||
EXPECT_TRUE(ufvkmetaUnadded.has_value());
|
||||
EXPECT_EQ(ufvkmetaUnadded.value().GetUFVKId(), ufvkid);
|
||||
EXPECT_EQ(ufvkmetaUnadded.value().GetDiversifierIndex().value(), addrPair.second);
|
||||
|
||||
// Adding the Sapling addr -> ivk map entry causes us to find the same UFVK,
|
||||
// and since we trial-decrypt with both external and internal IVKs to
|
||||
// verify whether it's an internal address, we learn the index.
|
||||
auto saplingIvk = zufvk.GetSaplingKey().value().ToIncomingViewingKey();
|
||||
keyStore.AddSaplingPaymentAddress(saplingIvk, saplingReceiver);
|
||||
|
||||
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
|
||||
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
|
||||
EXPECT_TRUE(ufvkmeta.has_value());
|
||||
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
|
||||
EXPECT_FALSE(ufvkmeta.value().second.has_value());
|
||||
EXPECT_EQ(ufvkmeta.value().GetUFVKId(), ufvkid);
|
||||
EXPECT_TRUE(ufvkmeta.value().GetDiversifierIndex().has_value());
|
||||
}
|
||||
|
||||
TEST(KeystoreTests, StoreAndRetrieveUFVKByOrchard) {
|
||||
SelectParams(CBaseChainParams::TESTNET);
|
||||
CBasicKeyStore keyStore;
|
||||
|
||||
auto seed = MnemonicSeed::Random(SLIP44_TESTNET_TYPE);
|
||||
auto usk = ZcashdUnifiedSpendingKey::ForAccount(seed, SLIP44_TESTNET_TYPE, 0);
|
||||
EXPECT_TRUE(usk.has_value());
|
||||
|
||||
auto ufvk = usk.value().ToFullViewingKey();
|
||||
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
|
||||
auto ufvkid = zufvk.GetKeyID();
|
||||
EXPECT_FALSE(keyStore.GetUnifiedFullViewingKey(ufvkid).has_value());
|
||||
|
||||
EXPECT_TRUE(keyStore.AddUnifiedFullViewingKey(zufvk));
|
||||
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
|
||||
|
||||
auto addrPair = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Orchard}));
|
||||
EXPECT_TRUE(addrPair.first.GetOrchardReceiver().has_value());
|
||||
auto orchardReceiver = addrPair.first.GetOrchardReceiver().value();
|
||||
|
||||
// We don't store Orchard addresses in CBasicKeyStore (the addr -> ivk
|
||||
// mapping is stored in the Rust wallet), but we still detect this because
|
||||
// we trial-decrypt diversifiers (which also means we learn the index).
|
||||
auto ufvkmetaUnadded = keyStore.GetUFVKMetadataForReceiver(orchardReceiver);
|
||||
EXPECT_TRUE(ufvkmetaUnadded.has_value());
|
||||
EXPECT_EQ(ufvkmetaUnadded.value().GetUFVKId(), ufvkid);
|
||||
EXPECT_EQ(ufvkmetaUnadded.value().GetDiversifierIndex().value(), addrPair.second);
|
||||
}
|
||||
|
||||
TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
|
||||
|
@ -593,7 +628,7 @@ TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
|
|||
|
||||
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(addrPair.first.GetP2PKHReceiver().value());
|
||||
EXPECT_TRUE(ufvkmeta.has_value());
|
||||
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
|
||||
EXPECT_EQ(ufvkmeta.value().GetUFVKId(), ufvkid);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -110,6 +110,8 @@ TEST(MempoolLimitTests, WeightedTxTreeCheckSizeAfterDropping)
|
|||
|
||||
TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
||||
{
|
||||
LoadProofParameters();
|
||||
|
||||
// The transaction creation is based on the test:
|
||||
// test_transaction_builder.cpp/TEST(TransactionBuilder, SetFee)
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
@ -119,7 +121,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
|||
|
||||
// Default fee
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
|
||||
|
||||
|
@ -130,7 +132,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
|||
|
||||
// Lower than standard fee
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
|
||||
static_assert(DEFAULT_FEE == 1000);
|
||||
|
@ -143,7 +145,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
|||
|
||||
// Larger Tx
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
||||
|
|
|
@ -310,5 +310,15 @@ TEST(orchardMerkleTree, appendBundle) {
|
|||
uint256 anchor(merkle_roots_orchard[i].anchor);
|
||||
|
||||
ASSERT_EQ(newTree.root(), anchor);
|
||||
|
||||
// Sanity check roundtrip serialization of the updated tree
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << newTree;
|
||||
|
||||
OrchardMerkleFrontier readBack;
|
||||
ss >> readBack;
|
||||
|
||||
EXPECT_NE(newTree.root(), OrchardMerkleFrontier::empty_root());
|
||||
EXPECT_EQ(newTree.root(), readBack.root());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,12 @@ TEST(Metrics, GetLocalSolPS) {
|
|||
// Increment time
|
||||
SetMockTime(104);
|
||||
EXPECT_EQ(1, GetLocalSolPS());
|
||||
|
||||
miningTimer.stop();
|
||||
miningTimer.zeroize();
|
||||
solutionTargetChecks.decrement();
|
||||
solutionTargetChecks.decrement();
|
||||
solutionTargetChecks.decrement();
|
||||
}
|
||||
|
||||
TEST(Metrics, EstimateNetHeight) {
|
||||
|
|
|
@ -7,73 +7,20 @@
|
|||
#include "pubkey.h"
|
||||
#include "rpc/protocol.h"
|
||||
#include "transaction_builder.h"
|
||||
#include "gtest/test_transaction_builder.h"
|
||||
#include "utiltest.h"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "zcash/address/mnemonic.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
// Fake an empty view
|
||||
class TransactionBuilderCoinsViewDB : public CCoinsView {
|
||||
public:
|
||||
std::map<uint256, SproutMerkleTree> sproutTrees;
|
||||
|
||||
TransactionBuilderCoinsViewDB() {}
|
||||
|
||||
bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
|
||||
auto it = sproutTrees.find(rt);
|
||||
if (it != sproutTrees.end()) {
|
||||
tree = it->second;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HaveCoins(const uint256 &txid) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 GetBestBlock() const {
|
||||
uint256 a;
|
||||
return a;
|
||||
}
|
||||
|
||||
uint256 GetBestAnchor(ShieldedType type) const {
|
||||
uint256 a;
|
||||
return a;
|
||||
}
|
||||
|
||||
bool BatchWrite(CCoinsMap &mapCoins,
|
||||
const uint256 &hashBlock,
|
||||
const uint256 &hashSproutAnchor,
|
||||
const uint256 &hashSaplingAnchor,
|
||||
CAnchorsSproutMap &mapSproutAnchors,
|
||||
CAnchorsSaplingMap &mapSaplingAnchors,
|
||||
CNullifiersMap &mapSproutNullifiers,
|
||||
CNullifiersMap saplingNullifiersMap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetStats(CCoinsStats &stats) const {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(TransactionBuilder, TransparentToSapling)
|
||||
{
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
CBasicKeyStore keystore;
|
||||
|
@ -92,7 +39,7 @@ TEST(TransactionBuilder, TransparentToSapling)
|
|||
|
||||
// Create a shielding transaction from transparent to Sapling
|
||||
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
|
||||
builder.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -113,6 +60,8 @@ TEST(TransactionBuilder, TransparentToSapling)
|
|||
}
|
||||
|
||||
TEST(TransactionBuilder, SaplingToSapling) {
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
|
@ -124,7 +73,7 @@ TEST(TransactionBuilder, SaplingToSapling) {
|
|||
|
||||
// Create a Sapling-only transaction
|
||||
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, default fee, 0.00005 z-ZEC change
|
||||
auto builder = TransactionBuilder(consensusParams, 2);
|
||||
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
|
||||
// Check that trying to add a different anchor fails
|
||||
|
@ -150,6 +99,8 @@ TEST(TransactionBuilder, SaplingToSapling) {
|
|||
}
|
||||
|
||||
TEST(TransactionBuilder, SaplingToSprout) {
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
|
@ -165,7 +116,7 @@ TEST(TransactionBuilder, SaplingToSprout) {
|
|||
// - 0.0004 Sapling-ZEC in - 0.00025 Sprout-ZEC out
|
||||
// - 0.00005 Sapling-ZEC change
|
||||
// - default t-ZEC fee
|
||||
auto builder = TransactionBuilder(consensusParams, 2, nullptr);
|
||||
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt, nullptr);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSproutOutput(sproutAddr, 25000);
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -188,6 +139,8 @@ TEST(TransactionBuilder, SaplingToSprout) {
|
|||
}
|
||||
|
||||
TEST(TransactionBuilder, SproutToSproutAndSapling) {
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
|
@ -217,7 +170,7 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) {
|
|||
// - 0.00005 Sprout-ZEC change
|
||||
// - 0.00005 Sapling-ZEC out
|
||||
// - 0.00005 t-ZEC fee
|
||||
auto builder = TransactionBuilder(consensusParams, 2, nullptr, &view);
|
||||
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt, nullptr, &view);
|
||||
builder.SetFee(5000);
|
||||
builder.AddSproutInput(sproutSk, sproutNote, sproutWitness);
|
||||
builder.AddSproutOutput(sproutAddr, 6000);
|
||||
|
@ -248,12 +201,60 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) {
|
|||
RegtestDeactivateSapling();
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, TransparentToOrchard)
|
||||
{
|
||||
auto consensusParams = RegtestActivateNU5();
|
||||
|
||||
CBasicKeyStore keystore;
|
||||
CKey tsk = AddTestCKeyToKeyStore(keystore);
|
||||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||
|
||||
auto coinType = Params().BIP44CoinType();
|
||||
auto seed = MnemonicSeed::Random(coinType);
|
||||
auto sk = libzcash::OrchardSpendingKey::ForAccount(seed, coinType, 0);
|
||||
auto fvk = sk.ToFullViewingKey();
|
||||
auto ivk = fvk.ToIncomingViewingKey();
|
||||
libzcash::diversifier_index_t j(0);
|
||||
auto recipient = ivk.Address(j);
|
||||
|
||||
TransactionBuilderCoinsViewDB fakeDB;
|
||||
auto orchardAnchor = fakeDB.GetBestAnchor(ShieldedType::ORCHARD);
|
||||
|
||||
// Create a shielding transaction from transparent to Orchard
|
||||
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
|
||||
auto builder = TransactionBuilder(consensusParams, 1, orchardAnchor, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
|
||||
builder.AddOrchardOutput(std::nullopt, recipient, 40000, std::nullopt);
|
||||
auto maybeTx = builder.Build();
|
||||
EXPECT_TRUE(maybeTx.IsTx());
|
||||
if (maybeTx.IsError()) {
|
||||
std::cerr << "Failed to build transaction: " << maybeTx.GetError() << std::endl;
|
||||
GTEST_FAIL();
|
||||
}
|
||||
auto tx = maybeTx.GetTxOrThrow();
|
||||
|
||||
EXPECT_EQ(tx.vin.size(), 1);
|
||||
EXPECT_EQ(tx.vout.size(), 0);
|
||||
EXPECT_EQ(tx.vJoinSplit.size(), 0);
|
||||
EXPECT_EQ(tx.vShieldedSpend.size(), 0);
|
||||
EXPECT_EQ(tx.vShieldedOutput.size(), 0);
|
||||
EXPECT_TRUE(tx.GetOrchardBundle().IsPresent());
|
||||
EXPECT_EQ(tx.GetOrchardBundle().GetValueBalance(), -40000);
|
||||
|
||||
CValidationState state;
|
||||
EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 2, true));
|
||||
EXPECT_EQ(state.GetRejectReason(), "");
|
||||
|
||||
// Revert to default
|
||||
RegtestDeactivateNU5();
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, ThrowsOnTransparentInputWithoutKeyStore)
|
||||
{
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
const Consensus::Params& consensusParams = Params().GetConsensus();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
ASSERT_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error);
|
||||
}
|
||||
|
||||
|
@ -264,12 +265,14 @@ TEST(TransactionBuilder, RejectsInvalidTransparentOutput)
|
|||
|
||||
// Default CTxDestination type is an invalid address
|
||||
CTxDestination taddr;
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue);
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, FailsWithNegativeChange)
|
||||
{
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
// Generate dummy Sapling address
|
||||
|
@ -289,13 +292,13 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
|
|||
|
||||
// Fail if there is only a Sapling output
|
||||
// 0.0005 z-ZEC out, default fee
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingOutput(fvk.ovk, pa, 50000, {});
|
||||
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
||||
|
||||
// Fail if there is only a transparent output
|
||||
// 0.0005 t-ZEC out, default fee
|
||||
builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentOutput(taddr, 50000);
|
||||
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
||||
|
||||
|
@ -314,6 +317,8 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
|
|||
|
||||
TEST(TransactionBuilder, ChangeOutput)
|
||||
{
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
// Generate dummy Sapling address
|
||||
|
@ -336,14 +341,14 @@ TEST(TransactionBuilder, ChangeOutput)
|
|||
|
||||
// No change address and no Sapling spends
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||
EXPECT_EQ("Could not determine change address", builder.Build().GetError());
|
||||
}
|
||||
|
||||
// Change to the same address as the first Sapling spend
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -358,7 +363,7 @@ TEST(TransactionBuilder, ChangeOutput)
|
|||
|
||||
// Change to a Sapling address
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||
builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -373,7 +378,7 @@ TEST(TransactionBuilder, ChangeOutput)
|
|||
|
||||
// Change to a transparent address
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||
builder.SendChangeTo(tkeyid, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -393,6 +398,8 @@ TEST(TransactionBuilder, ChangeOutput)
|
|||
|
||||
TEST(TransactionBuilder, SetFee)
|
||||
{
|
||||
LoadProofParameters();
|
||||
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
||||
// Generate dummy Sapling address
|
||||
|
@ -405,7 +412,7 @@ TEST(TransactionBuilder, SetFee)
|
|||
|
||||
// Default fee
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -420,7 +427,7 @@ TEST(TransactionBuilder, SetFee)
|
|||
|
||||
// Configured fee
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
|
||||
builder.SetFee(20000);
|
||||
|
@ -449,7 +456,7 @@ TEST(TransactionBuilder, CheckSaplingTxVersion)
|
|||
auto pk = sk.default_address();
|
||||
|
||||
// Cannot add Sapling outputs to a non-Sapling transaction
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
try {
|
||||
builder.AddSaplingOutput(uint256(), pk, 12345, {});
|
||||
} catch (std::runtime_error const & err) {
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) 2022 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#ifndef ZCASH_GTEST_TEST_TRANSACTION_BUILDER_H
|
||||
#define ZCASH_GTEST_TEST_TRANSACTION_BUILDER_H
|
||||
|
||||
#include "coins.h"
|
||||
|
||||
// Fake an empty view
|
||||
class TransactionBuilderCoinsViewDB : public CCoinsView {
|
||||
public:
|
||||
std::map<uint256, SproutMerkleTree> sproutTrees;
|
||||
|
||||
TransactionBuilderCoinsViewDB() {}
|
||||
|
||||
bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
|
||||
auto it = sproutTrees.find(rt);
|
||||
if (it != sproutTrees.end()) {
|
||||
tree = it->second;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetOrchardAnchorAt(const uint256 &rt, OrchardMerkleFrontier &tree) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HaveCoins(const uint256 &txid) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 GetBestBlock() const {
|
||||
uint256 a;
|
||||
return a;
|
||||
}
|
||||
|
||||
uint256 GetBestAnchor(ShieldedType type) const {
|
||||
uint256 a;
|
||||
return a;
|
||||
}
|
||||
|
||||
bool BatchWrite(CCoinsMap &mapCoins,
|
||||
const uint256 &hashBlock,
|
||||
const uint256 &hashSproutAnchor,
|
||||
const uint256 &hashSaplingAnchor,
|
||||
CAnchorsSproutMap &mapSproutAnchors,
|
||||
CAnchorsSaplingMap &mapSaplingAnchors,
|
||||
CNullifiersMap &mapSproutNullifiers,
|
||||
CNullifiersMap saplingNullifiersMap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetStats(CCoinsStats &stats) const {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
|
@ -173,7 +173,7 @@ TEST(Validation, ContextualCheckInputsDetectsOldBranchId) {
|
|||
|
||||
// Create a transparent transaction that spends the coin, targeting
|
||||
// a height during the Overwinter epoch.
|
||||
auto builder = TransactionBuilder(consensusParams, 15, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 15, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(utxo, scriptPubKey, coinValue);
|
||||
builder.AddTransparentOutput(destination, 40000);
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
|
|
@ -305,7 +305,7 @@ bool static Bind(const CService &addr, unsigned int flags) {
|
|||
|
||||
void OnRPCStopped()
|
||||
{
|
||||
cvBlockChange.notify_all();
|
||||
g_best_block_cv.notify_all();
|
||||
LogPrint("rpc", "RPC stopped.\n");
|
||||
}
|
||||
|
||||
|
@ -341,7 +341,7 @@ std::string HelpMessage(HelpMessageMode mode)
|
|||
strUsage += HelpMessageOpt("-daemon", _("Run in the background as a daemon and accept commands"));
|
||||
#endif
|
||||
}
|
||||
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
|
||||
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory (this path cannot use '~')"));
|
||||
strUsage += HelpMessageOpt("-paramsdir=<dir>", _("Specify Zcash network parameters directory"));
|
||||
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
|
||||
strUsage += HelpMessageOpt("-debuglogfile=<file>", strprintf(_("Specify location of debug log file: this can be an absolute path or a path relative to the data directory (default: %s)"), DEFAULT_DEBUGLOGFILE));
|
||||
|
@ -1105,7 +1105,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||
#ifdef ENABLE_MINING
|
||||
if (mapArgs.count("-mineraddress")) {
|
||||
auto addr = keyIO.DecodePaymentAddress(mapArgs["-mineraddress"]);
|
||||
if (!(addr.has_value() && std::visit(ExtractMinerAddress(), addr.value()).has_value())) {
|
||||
if (!(addr.has_value() && std::visit(ExtractMinerAddress(chainparams.GetConsensus(), 0), addr.value()).has_value())) {
|
||||
return InitError(strprintf(
|
||||
_("Invalid address for -mineraddress=<addr>: '%s' (must be a Sapling or transparent P2PKH address)"),
|
||||
mapArgs["-mineraddress"]));
|
||||
|
@ -1683,7 +1683,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||
if (!zaddr.has_value()) {
|
||||
return InitError(_("-mineraddress is not a valid " PACKAGE_NAME " address."));
|
||||
}
|
||||
auto ztxoSelector = pwalletMain->ZTXOSelectorForAddress(zaddr.value(), true);
|
||||
auto ztxoSelector = pwalletMain->ZTXOSelectorForAddress(zaddr.value(), true, false);
|
||||
minerAddressInLocalWallet = ztxoSelector.has_value();
|
||||
}
|
||||
if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) {
|
||||
|
|
|
@ -290,7 +290,7 @@ std::optional<CExtKey> CExtKey::Derive(unsigned int _nChild) const {
|
|||
}
|
||||
}
|
||||
|
||||
CExtKey CExtKey::Master(const unsigned char *seed, unsigned int nSeedLen) {
|
||||
std::optional<CExtKey> CExtKey::Master(const unsigned char *seed, unsigned int nSeedLen) {
|
||||
CExtKey xk;
|
||||
static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'};
|
||||
std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
|
||||
|
@ -301,7 +301,11 @@ CExtKey CExtKey::Master(const unsigned char *seed, unsigned int nSeedLen) {
|
|||
xk.nChild = 0;
|
||||
memset(xk.vchFingerprint, 0, sizeof(xk.vchFingerprint));
|
||||
|
||||
return xk;
|
||||
if (xk.key.IsValid()) {
|
||||
return xk;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
CExtPubKey CExtKey::Neuter() const {
|
||||
|
|
|
@ -166,7 +166,7 @@ struct CExtKey {
|
|||
a.key == b.key;
|
||||
}
|
||||
|
||||
static CExtKey Master(const unsigned char* seed, unsigned int nSeedLen);
|
||||
static std::optional<CExtKey> Master(const unsigned char* seed, unsigned int nSeedLen);
|
||||
|
||||
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
|
||||
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
|
||||
|
|
106
src/key_io.cpp
106
src/key_io.cpp
|
@ -56,6 +56,7 @@ class DataLenForReceiver {
|
|||
public:
|
||||
DataLenForReceiver() {}
|
||||
|
||||
size_t operator()(const libzcash::OrchardRawAddress &zaddr) const { return 43; }
|
||||
size_t operator()(const libzcash::SaplingPaymentAddress &zaddr) const { return 43; }
|
||||
size_t operator()(const CScriptID &p2sh) const { return 20; }
|
||||
size_t operator()(const CKeyID &p2pkh) const { return 20; }
|
||||
|
@ -76,6 +77,13 @@ class CopyDataForReceiver {
|
|||
public:
|
||||
CopyDataForReceiver(unsigned char* data, size_t length) : data(data), length(length) {}
|
||||
|
||||
void operator()(const libzcash::OrchardRawAddress &zaddr) const {
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << zaddr;
|
||||
assert(length == ss.size());
|
||||
memcpy(data, ss.data(), ss.size());
|
||||
}
|
||||
|
||||
void operator()(const libzcash::SaplingPaymentAddress &zaddr) const {
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << zaddr;
|
||||
|
@ -247,7 +255,7 @@ const size_t ConvertedSaplingExtendedFullViewingKeySize = (ZIP32_XFVK_SIZE * 8 +
|
|||
const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5;
|
||||
} // namespace
|
||||
|
||||
CTxDestination KeyIO::DecodeDestination(const std::string& str)
|
||||
CTxDestination KeyIO::DecodeDestination(const std::string& str) const
|
||||
{
|
||||
std::vector<unsigned char> data;
|
||||
uint160 hash;
|
||||
|
@ -271,7 +279,7 @@ CTxDestination KeyIO::DecodeDestination(const std::string& str)
|
|||
return CNoDestination();
|
||||
};
|
||||
|
||||
CKey KeyIO::DecodeSecret(const std::string& str)
|
||||
CKey KeyIO::DecodeSecret(const std::string& str) const
|
||||
{
|
||||
CKey key;
|
||||
std::vector<unsigned char> data;
|
||||
|
@ -287,7 +295,7 @@ CKey KeyIO::DecodeSecret(const std::string& str)
|
|||
return key;
|
||||
}
|
||||
|
||||
std::string KeyIO::EncodeSecret(const CKey& key)
|
||||
std::string KeyIO::EncodeSecret(const CKey& key) const
|
||||
{
|
||||
assert(key.IsValid());
|
||||
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::SECRET_KEY);
|
||||
|
@ -300,7 +308,7 @@ std::string KeyIO::EncodeSecret(const CKey& key)
|
|||
return ret;
|
||||
}
|
||||
|
||||
CExtPubKey KeyIO::DecodeExtPubKey(const std::string& str)
|
||||
CExtPubKey KeyIO::DecodeExtPubKey(const std::string& str) const
|
||||
{
|
||||
CExtPubKey key;
|
||||
std::vector<unsigned char> data;
|
||||
|
@ -313,7 +321,7 @@ CExtPubKey KeyIO::DecodeExtPubKey(const std::string& str)
|
|||
return key;
|
||||
}
|
||||
|
||||
std::string KeyIO::EncodeExtPubKey(const CExtPubKey& key)
|
||||
std::string KeyIO::EncodeExtPubKey(const CExtPubKey& key) const
|
||||
{
|
||||
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::EXT_PUBLIC_KEY);
|
||||
size_t size = data.size();
|
||||
|
@ -323,7 +331,7 @@ std::string KeyIO::EncodeExtPubKey(const CExtPubKey& key)
|
|||
return ret;
|
||||
}
|
||||
|
||||
CExtKey KeyIO::DecodeExtKey(const std::string& str)
|
||||
CExtKey KeyIO::DecodeExtKey(const std::string& str) const
|
||||
{
|
||||
CExtKey key;
|
||||
std::vector<unsigned char> data;
|
||||
|
@ -336,7 +344,7 @@ CExtKey KeyIO::DecodeExtKey(const std::string& str)
|
|||
return key;
|
||||
}
|
||||
|
||||
std::string KeyIO::EncodeExtKey(const CExtKey& key)
|
||||
std::string KeyIO::EncodeExtKey(const CExtKey& key) const
|
||||
{
|
||||
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::EXT_SECRET_KEY);
|
||||
size_t size = data.size();
|
||||
|
@ -347,17 +355,17 @@ std::string KeyIO::EncodeExtKey(const CExtKey& key)
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::string KeyIO::EncodeDestination(const CTxDestination& dest)
|
||||
std::string KeyIO::EncodeDestination(const CTxDestination& dest) const
|
||||
{
|
||||
return std::visit(DestinationEncoder(keyConstants), dest);
|
||||
}
|
||||
|
||||
bool KeyIO::IsValidDestinationString(const std::string& str)
|
||||
bool KeyIO::IsValidDestinationString(const std::string& str) const
|
||||
{
|
||||
return IsValidDestination(DecodeDestination(str));
|
||||
}
|
||||
|
||||
std::string KeyIO::EncodePaymentAddress(const libzcash::PaymentAddress& zaddr)
|
||||
std::string KeyIO::EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) const
|
||||
{
|
||||
return std::visit(PaymentAddressEncoder(keyConstants), zaddr);
|
||||
}
|
||||
|
@ -433,71 +441,12 @@ std::optional<T1> DecodeAny(
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* `raw` MUST be 43 bytes.
|
||||
*/
|
||||
static bool AddSaplingReceiver(void* ua, const unsigned char* raw)
|
||||
{
|
||||
CDataStream ss(
|
||||
reinterpret_cast<const char*>(raw),
|
||||
reinterpret_cast<const char*>(raw + 43),
|
||||
SER_NETWORK,
|
||||
PROTOCOL_VERSION);
|
||||
libzcash::SaplingPaymentAddress receiver;
|
||||
ss >> receiver;
|
||||
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* `raw` MUST be 20 bytes.
|
||||
*/
|
||||
static bool AddP2SHReceiver(void* ua, const unsigned char* raw)
|
||||
{
|
||||
CDataStream ss(
|
||||
reinterpret_cast<const char*>(raw),
|
||||
reinterpret_cast<const char*>(raw + 20),
|
||||
SER_NETWORK,
|
||||
PROTOCOL_VERSION);
|
||||
CScriptID receiver;
|
||||
ss >> receiver;
|
||||
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* `raw` MUST be 20 bytes.
|
||||
*/
|
||||
static bool AddP2PKHReceiver(void* ua, const unsigned char* raw)
|
||||
{
|
||||
CDataStream ss(
|
||||
reinterpret_cast<const char*>(raw),
|
||||
reinterpret_cast<const char*>(raw + 20),
|
||||
SER_NETWORK,
|
||||
PROTOCOL_VERSION);
|
||||
CKeyID receiver;
|
||||
ss >> receiver;
|
||||
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
|
||||
}
|
||||
|
||||
static bool AddUnknownReceiver(void* ua, uint32_t typecode, const unsigned char* data, size_t len)
|
||||
{
|
||||
libzcash::UnknownReceiver receiver(typecode, std::vector(data, data + len));
|
||||
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
|
||||
}
|
||||
|
||||
std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::string& str)
|
||||
std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::string& str) const
|
||||
{
|
||||
// Try parsing as a Unified Address.
|
||||
libzcash::UnifiedAddress ua;
|
||||
if (zcash_address_parse_unified(
|
||||
str.c_str(),
|
||||
keyConstants.NetworkIDString().c_str(),
|
||||
&ua,
|
||||
AddSaplingReceiver,
|
||||
AddP2SHReceiver,
|
||||
AddP2PKHReceiver,
|
||||
AddUnknownReceiver)
|
||||
) {
|
||||
return ua;
|
||||
auto ua = libzcash::UnifiedAddress::Parse(keyConstants, str);
|
||||
if (ua.has_value()) {
|
||||
return ua.value();
|
||||
}
|
||||
|
||||
// Try parsing as a Sapling address
|
||||
|
@ -535,16 +484,17 @@ std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::s
|
|||
}, DecodeDestination(str));
|
||||
}
|
||||
|
||||
bool KeyIO::IsValidPaymentAddressString(const std::string& str) {
|
||||
bool KeyIO::IsValidPaymentAddressString(const std::string& str) const
|
||||
{
|
||||
return DecodePaymentAddress(str).has_value();
|
||||
}
|
||||
|
||||
std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk)
|
||||
std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk) const
|
||||
{
|
||||
return std::visit(ViewingKeyEncoder(keyConstants), vk);
|
||||
}
|
||||
|
||||
std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& str)
|
||||
std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& str) const
|
||||
{
|
||||
// Try parsing as a Unified full viewing key
|
||||
auto ufvk = libzcash::UnifiedFullViewingKey::Decode(str, keyConstants);
|
||||
|
@ -563,12 +513,12 @@ std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& s
|
|||
);
|
||||
}
|
||||
|
||||
std::string KeyIO::EncodeSpendingKey(const libzcash::SpendingKey& zkey)
|
||||
std::string KeyIO::EncodeSpendingKey(const libzcash::SpendingKey& zkey) const
|
||||
{
|
||||
return std::visit(SpendingKeyEncoder(keyConstants), zkey);
|
||||
}
|
||||
|
||||
std::optional<libzcash::SpendingKey> KeyIO::DecodeSpendingKey(const std::string& str)
|
||||
std::optional<libzcash::SpendingKey> KeyIO::DecodeSpendingKey(const std::string& str) const
|
||||
{
|
||||
|
||||
return DecodeAny<libzcash::SpendingKey,
|
||||
|
|
32
src/key_io.h
32
src/key_io.h
|
@ -23,28 +23,28 @@ private:
|
|||
public:
|
||||
KeyIO(const KeyConstants& keyConstants): keyConstants(keyConstants) { }
|
||||
|
||||
CKey DecodeSecret(const std::string& str);
|
||||
std::string EncodeSecret(const CKey& key);
|
||||
CKey DecodeSecret(const std::string& str) const;
|
||||
std::string EncodeSecret(const CKey& key) const;
|
||||
|
||||
CExtKey DecodeExtKey(const std::string& str);
|
||||
std::string EncodeExtKey(const CExtKey& extkey);
|
||||
CExtPubKey DecodeExtPubKey(const std::string& str);
|
||||
std::string EncodeExtPubKey(const CExtPubKey& extpubkey);
|
||||
CExtKey DecodeExtKey(const std::string& str) const;
|
||||
std::string EncodeExtKey(const CExtKey& extkey) const;
|
||||
CExtPubKey DecodeExtPubKey(const std::string& str) const;
|
||||
std::string EncodeExtPubKey(const CExtPubKey& extpubkey) const;
|
||||
|
||||
std::string EncodeDestination(const CTxDestination& dest);
|
||||
CTxDestination DecodeDestination(const std::string& str);
|
||||
std::string EncodeDestination(const CTxDestination& dest) const;
|
||||
CTxDestination DecodeDestination(const std::string& str) const;
|
||||
|
||||
bool IsValidDestinationString(const std::string& str);
|
||||
bool IsValidDestinationString(const std::string& str) const;
|
||||
|
||||
std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr);
|
||||
std::optional<libzcash::PaymentAddress> DecodePaymentAddress(const std::string& str);
|
||||
bool IsValidPaymentAddressString(const std::string& str);
|
||||
std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) const;
|
||||
std::optional<libzcash::PaymentAddress> DecodePaymentAddress(const std::string& str) const;
|
||||
bool IsValidPaymentAddressString(const std::string& str) const;
|
||||
|
||||
std::string EncodeViewingKey(const libzcash::ViewingKey& vk);
|
||||
std::optional<libzcash::ViewingKey> DecodeViewingKey(const std::string& str);
|
||||
std::string EncodeViewingKey(const libzcash::ViewingKey& vk) const;
|
||||
std::optional<libzcash::ViewingKey> DecodeViewingKey(const std::string& str) const;
|
||||
|
||||
std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey);
|
||||
std::optional<libzcash::SpendingKey> DecodeSpendingKey(const std::string& str);
|
||||
std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey) const;
|
||||
std::optional<libzcash::SpendingKey> DecodeSpendingKey(const std::string& str) const;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_KEY_IO_H
|
||||
|
|
182
src/keystore.cpp
182
src/keystore.cpp
|
@ -252,6 +252,10 @@ bool CBasicKeyStore::GetSproutViewingKey(
|
|||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Sapling Keys
|
||||
//
|
||||
|
||||
bool CBasicKeyStore::GetSaplingFullViewingKey(
|
||||
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
libzcash::SaplingExtendedFullViewingKey &extfvkOut) const
|
||||
|
@ -308,14 +312,26 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
|
|||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
||||
auto ufvkId = ufvk.GetKeyID();
|
||||
|
||||
// Add the Orchard component of the UFVK to the wallet.
|
||||
auto orchardKey = ufvk.GetOrchardKey();
|
||||
if (orchardKey.has_value()) {
|
||||
auto ivk = orchardKey.value().ToIncomingViewingKey();
|
||||
mapOrchardKeyUnified.insert(std::make_pair(ivk, ufvkId));
|
||||
|
||||
auto ivkInternal = orchardKey.value().ToInternalIncomingViewingKey();
|
||||
mapOrchardKeyUnified.insert(std::make_pair(ivkInternal, ufvkId));
|
||||
}
|
||||
|
||||
// Add the Sapling component of the UFVK to the wallet.
|
||||
auto saplingKey = ufvk.GetSaplingKey();
|
||||
if (saplingKey.has_value()) {
|
||||
auto ivk = saplingKey.value().ToIncomingViewingKey();
|
||||
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvk.GetKeyID()));
|
||||
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvkId));
|
||||
|
||||
auto changeIvk = saplingKey.value().GetChangeIVK();
|
||||
mapSaplingKeyUnified.insert(std::make_pair(changeIvk, ufvk.GetKeyID()));
|
||||
mapSaplingKeyUnified.insert(std::make_pair(changeIvk, ufvkId));
|
||||
}
|
||||
|
||||
// We can't reasonably add the transparent component here, because
|
||||
|
@ -325,7 +341,7 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
|
|||
// transparent part of the address must be added to the keystore.
|
||||
|
||||
// Add the UFVK by key identifier.
|
||||
mapUnifiedFullViewingKeys.insert({ufvk.GetKeyID(), ufvk});
|
||||
mapUnifiedFullViewingKeys.insert({ufvkId, ufvk});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -367,12 +383,55 @@ std::optional<libzcash::ZcashdUnifiedFullViewingKey> CBasicKeyStore::GetUnifiedF
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
std::optional<AddressUFVKMetadata>
|
||||
CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) const
|
||||
{
|
||||
return std::visit(FindUFVKId(*this), receiver);
|
||||
}
|
||||
|
||||
std::optional<AddressUFVKMetadata>
|
||||
CBasicKeyStore::GetUFVKMetadataForAddress(const libzcash::UnifiedAddress& addr) const
|
||||
{
|
||||
std::optional<libzcash::UFVKId> ufvkId;
|
||||
std::optional<libzcash::diversifier_index_t> j;
|
||||
bool jConflict = false;
|
||||
for (const auto& receiver : addr) {
|
||||
auto rmeta = GetUFVKMetadataForReceiver(receiver);
|
||||
if (rmeta.has_value()) {
|
||||
// We should never generate unified addresses with internal receivers
|
||||
assert(rmeta.value().IsExternalAddress());
|
||||
|
||||
if (ufvkId.has_value()) {
|
||||
// If the unified address contains receivers that are associated with
|
||||
// different UFVKs, we cannot return a singular value.
|
||||
if (rmeta.value().GetUFVKId() != ufvkId.value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (rmeta.value().GetDiversifierIndex().has_value()) {
|
||||
if (j.has_value()) {
|
||||
if (rmeta.value().GetDiversifierIndex().value() != j.value()) {
|
||||
jConflict = true;
|
||||
j = std::nullopt;
|
||||
}
|
||||
} else if (!jConflict) {
|
||||
j = rmeta.value().GetDiversifierIndex().value();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ufvkId = rmeta.value().GetUFVKId();
|
||||
j = rmeta.value().GetDiversifierIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ufvkId.has_value()) {
|
||||
return AddressUFVKMetadata(ufvkId.value(), j, true);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const libzcash::ViewingKey& vk) const
|
||||
{
|
||||
std::optional<libzcash::UFVKId> result;
|
||||
|
@ -386,6 +445,15 @@ std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const lib
|
|||
}
|
||||
},
|
||||
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
|
||||
const auto orchardFvk = ufvk.GetOrchardKey();
|
||||
if (orchardFvk.has_value()) {
|
||||
const auto orchardIvk = orchardFvk.value().ToIncomingViewingKey();
|
||||
const auto ufvkId = mapOrchardKeyUnified.find(orchardIvk);
|
||||
if (ufvkId != mapOrchardKeyUnified.end()) {
|
||||
result = ufvkId->second;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto saplingDfvk = ufvk.GetSaplingKey();
|
||||
if (saplingDfvk.has_value()) {
|
||||
const auto saplingIvk = saplingDfvk.value().ToIncomingViewingKey();
|
||||
|
@ -399,39 +467,81 @@ std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const lib
|
|||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
|
||||
const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr);
|
||||
if (saplingIvk != keystore.mapSaplingIncomingViewingKeys.end()) {
|
||||
const auto ufvkId = keystore.mapSaplingKeyUnified.find(saplingIvk->second);
|
||||
if (ufvkId != keystore.mapSaplingKeyUnified.end()) {
|
||||
return std::make_pair(ufvkId->second, std::nullopt);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
//
|
||||
// FindUFVKId :: (KeyStore, Receiver) -> std::optional<AddressUFVKMetadata>
|
||||
//
|
||||
|
||||
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
|
||||
for (const auto& [k, v] : keystore.mapUnifiedFullViewingKeys) {
|
||||
auto fvk = v.GetOrchardKey();
|
||||
if (fvk.has_value()) {
|
||||
auto d_idx = fvk.value().DecryptDiversifier(orchardAddr);
|
||||
if (d_idx.has_value()) {
|
||||
return AddressUFVKMetadata(k, d_idx->first, d_idx->second);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
FindUFVKId::operator()(const CScriptID& scriptId) const {
|
||||
const auto metadata = keystore.mapP2SHUnified.find(scriptId);
|
||||
if (metadata != keystore.mapP2SHUnified.end()) {
|
||||
return metadata->second;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
FindUFVKId::operator()(const CKeyID& keyId) const {
|
||||
const auto metadata = keystore.mapP2PKHUnified.find(keyId);
|
||||
if (metadata != keystore.mapP2PKHUnified.end()) {
|
||||
return metadata->second;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
FindUFVKId::operator()(const libzcash::UnknownReceiver& receiver) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
|
||||
const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr);
|
||||
if (saplingIvk != keystore.mapSaplingIncomingViewingKeys.end()) {
|
||||
// We have either generated this as a receiver via `z_getaddressforaccount` or a
|
||||
// legacy Sapling address via `z_getnewaddress`, or we have previously detected
|
||||
// this via trial-decryption of a note.
|
||||
const auto ufvkId = keystore.mapSaplingKeyUnified.find(saplingIvk->second);
|
||||
if (ufvkId != keystore.mapSaplingKeyUnified.end()) {
|
||||
// We know that we have a UFVK, and that it has a Sapling key that
|
||||
// produced this address, so decrypt the diversifier to determine
|
||||
// whether it was an internal or external address
|
||||
auto ufvk = keystore.GetUnifiedFullViewingKey(ufvkId->second).value();
|
||||
auto saplingKey = ufvk.GetSaplingKey().value();
|
||||
auto d_idx = saplingKey.DecryptDiversifier(saplingAddr).value();
|
||||
return AddressUFVKMetadata(ufvkId->second, d_idx.first, d_idx.second);
|
||||
}
|
||||
|
||||
// If we have the addr -> ivk map entry but not the ivk -> UFVK map entry,
|
||||
// then this is definitely a legacy Sapling address.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// We haven't generated this receiver via `z_getaddressforaccount` (or this is a
|
||||
// recovery from a backed-up mnemonic which doesn't store receiver types selected by
|
||||
// users). Trial-decrypt the diversifier of the Sapling address with every UFVK in the
|
||||
// wallet, to check directly if it belongs to any of them.
|
||||
for (const auto& [k, v] : keystore.mapUnifiedFullViewingKeys) {
|
||||
auto dfvk = v.GetSaplingKey();
|
||||
if (dfvk.has_value()) {
|
||||
auto d_idx = dfvk.value().DecryptDiversifier(saplingAddr);
|
||||
if (d_idx.has_value()) {
|
||||
return AddressUFVKMetadata(k, d_idx->first, d_idx->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We definitely don't know of any UFVK linked to this Sapling address.
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const CScriptID& scriptId) const {
|
||||
const auto metadata = keystore.mapP2SHUnified.find(scriptId);
|
||||
if (metadata != keystore.mapP2SHUnified.end()) {
|
||||
// At present we never generate transparent internal addresses, so this
|
||||
// must be an external address
|
||||
return AddressUFVKMetadata(metadata->second.first, metadata->second.second, true);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const CKeyID& keyId) const {
|
||||
const auto metadata = keystore.mapP2PKHUnified.find(keyId);
|
||||
if (metadata != keystore.mapP2PKHUnified.end()) {
|
||||
// At present we never generate transparent internal addresses, so this
|
||||
// must be an external address
|
||||
return AddressUFVKMetadata(metadata->second.first, metadata->second.second, true);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const libzcash::UnknownReceiver& receiver) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,20 @@
|
|||
|
||||
#include <boost/signals2/signal.hpp>
|
||||
|
||||
class AddressUFVKMetadata {
|
||||
private:
|
||||
libzcash::UFVKId ufvkId;
|
||||
std::optional<libzcash::diversifier_index_t> j;
|
||||
bool externalAddress;
|
||||
public:
|
||||
AddressUFVKMetadata(libzcash::UFVKId ufvkId, std::optional<libzcash::diversifier_index_t> j, bool externalAddress)
|
||||
: ufvkId(ufvkId), j(j), externalAddress(externalAddress) {}
|
||||
|
||||
libzcash::UFVKId GetUFVKId() const { return ufvkId; }
|
||||
std::optional<libzcash::diversifier_index_t> GetDiversifierIndex() const { return j; }
|
||||
bool IsExternalAddress() const { return externalAddress; }
|
||||
};
|
||||
|
||||
/** A virtual base class for key stores */
|
||||
class CKeyStore
|
||||
{
|
||||
|
@ -125,18 +139,21 @@ public:
|
|||
const libzcash::UnifiedAddress& ua) = 0;
|
||||
|
||||
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
|
||||
const libzcash::UFVKId& keyId
|
||||
) const = 0;
|
||||
const libzcash::UFVKId& keyId) const = 0;
|
||||
|
||||
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
GetUFVKMetadataForReceiver(
|
||||
const libzcash::Receiver& receiver
|
||||
) const = 0;
|
||||
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForReceiver(
|
||||
const libzcash::Receiver& receiver) const = 0;
|
||||
|
||||
virtual std::optional<libzcash::UFVKId>
|
||||
GetUFVKIdForViewingKey(
|
||||
const libzcash::ViewingKey& vk
|
||||
) const = 0;
|
||||
/**
|
||||
* If all the receivers of the specified address correspond to a single
|
||||
* UFVK, return that key's metadata. If all the receivers correspond to
|
||||
* the same diversifier index, that diversifier index is also returned.
|
||||
*/
|
||||
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForAddress(
|
||||
const libzcash::UnifiedAddress& addr) const = 0;
|
||||
|
||||
virtual std::optional<libzcash::UFVKId> GetUFVKIdForViewingKey(
|
||||
const libzcash::ViewingKey& vk) const = 0;
|
||||
};
|
||||
|
||||
typedef std::map<CKeyID, CKey> KeyMap;
|
||||
|
@ -185,6 +202,7 @@ protected:
|
|||
std::map<CKeyID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2PKHUnified;
|
||||
std::map<CScriptID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2SHUnified;
|
||||
std::map<libzcash::SaplingIncomingViewingKey, libzcash::UFVKId> mapSaplingKeyUnified;
|
||||
std::map<libzcash::OrchardIncomingViewingKey, libzcash::UFVKId> mapOrchardKeyUnified;
|
||||
std::map<libzcash::UFVKId, libzcash::ZcashdUnifiedFullViewingKey> mapUnifiedFullViewingKeys;
|
||||
|
||||
friend class FindUFVKId;
|
||||
|
@ -373,15 +391,34 @@ public:
|
|||
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
|
||||
const libzcash::UFVKId& keyId) const;
|
||||
|
||||
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
GetUFVKMetadataForReceiver(
|
||||
const libzcash::Receiver& receiver
|
||||
) const;
|
||||
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForReceiver(
|
||||
const libzcash::Receiver& receiver) const;
|
||||
|
||||
virtual std::optional<libzcash::UFVKId>
|
||||
GetUFVKIdForViewingKey(
|
||||
const libzcash::ViewingKey& vk
|
||||
) const;
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUFVKForReceiver(
|
||||
const libzcash::Receiver& receiver) const {
|
||||
auto ufvkMeta = GetUFVKMetadataForReceiver(receiver);
|
||||
if (ufvkMeta.has_value()) {
|
||||
return GetUnifiedFullViewingKey(ufvkMeta.value().GetUFVKId());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForAddress(
|
||||
const libzcash::UnifiedAddress& addr) const;
|
||||
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUFVKForAddress(
|
||||
const libzcash::UnifiedAddress& addr) const {
|
||||
auto ufvkMeta = GetUFVKMetadataForAddress(addr);
|
||||
if (ufvkMeta.has_value()) {
|
||||
return GetUnifiedFullViewingKey(ufvkMeta.value().GetUFVKId());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::optional<libzcash::UFVKId> GetUFVKIdForViewingKey(
|
||||
const libzcash::ViewingKey& vk) const;
|
||||
};
|
||||
|
||||
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
|
||||
|
@ -398,14 +435,11 @@ private:
|
|||
public:
|
||||
FindUFVKId(const CBasicKeyStore& keystore): keystore(keystore) {}
|
||||
|
||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
|
||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
operator()(const CScriptID& scriptId) const;
|
||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
operator()(const CKeyID& keyId) const;
|
||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
||||
operator()(const libzcash::UnknownReceiver& receiver) const;
|
||||
std::optional<AddressUFVKMetadata> operator()(const libzcash::OrchardRawAddress& orchardAddr) const;
|
||||
std::optional<AddressUFVKMetadata> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
|
||||
std::optional<AddressUFVKMetadata> operator()(const CScriptID& scriptId) const;
|
||||
std::optional<AddressUFVKMetadata> operator()(const CKeyID& keyId) const;
|
||||
std::optional<AddressUFVKMetadata> operator()(const libzcash::UnknownReceiver& receiver) const;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_KEYSTORE_H
|
||||
|
|
45
src/main.cpp
45
src/main.cpp
|
@ -66,8 +66,10 @@ BlockMap mapBlockIndex;
|
|||
CChain chainActive;
|
||||
CBlockIndex *pindexBestHeader = NULL;
|
||||
static std::atomic<int64_t> nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block
|
||||
CWaitableCriticalSection csBestBlock;
|
||||
CConditionVariable cvBlockChange;
|
||||
CWaitableCriticalSection g_best_block_mutex;
|
||||
CConditionVariable g_best_block_cv;
|
||||
uint256 g_best_block;
|
||||
int g_best_block_height;
|
||||
int nScriptCheckThreads = 0;
|
||||
std::atomic_bool fImporting(false);
|
||||
std::atomic_bool fReindex(false);
|
||||
|
@ -2945,6 +2947,17 @@ static DisconnectResult DisconnectBlock(const CBlock& block, CValidationState& s
|
|||
view.PopAnchor(SaplingMerkleTree::empty_root(), SAPLING);
|
||||
}
|
||||
|
||||
// Set the old best Orchard anchor back. We can get this from the
|
||||
// `hashFinalOrchardRoot` of the last block. However, if the last
|
||||
// block was not on or after the Orchard activation height, this
|
||||
// will be set to `null`. For logical consistency, in this case we
|
||||
// set the last anchor to the empty root.
|
||||
if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->pprev->nHeight, Consensus::UPGRADE_NU5)) {
|
||||
view.PopAnchor(pindex->pprev->hashFinalOrchardRoot, ORCHARD);
|
||||
} else {
|
||||
view.PopAnchor(OrchardMerkleFrontier::empty_root(), ORCHARD);
|
||||
}
|
||||
|
||||
// This is guaranteed to be filled by LoadBlockIndex.
|
||||
assert(pindex->nCachedBranchId);
|
||||
auto consensusBranchId = pindex->nCachedBranchId.value();
|
||||
|
@ -3172,7 +3185,19 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
|||
assert(view.GetSaplingAnchorAt(view.GetBestAnchor(SAPLING), sapling_tree));
|
||||
|
||||
OrchardMerkleFrontier orchard_tree;
|
||||
assert(view.GetOrchardAnchorAt(view.GetBestAnchor(ORCHARD), orchard_tree));
|
||||
if (pindex->pprev && chainparams.GetConsensus().NetworkUpgradeActive(pindex->pprev->nHeight, Consensus::UPGRADE_NU5)) {
|
||||
// Verify that the view's current state corresponds to the previous block.
|
||||
assert(pindex->pprev->hashFinalOrchardRoot == view.GetBestAnchor(ORCHARD));
|
||||
// We only call ConnectBlock() on top of the active chain's tip.
|
||||
assert(!pindex->pprev->hashFinalOrchardRoot.IsNull());
|
||||
|
||||
assert(view.GetOrchardAnchorAt(pindex->pprev->hashFinalOrchardRoot, orchard_tree));
|
||||
} else {
|
||||
if (pindex->pprev) {
|
||||
assert(pindex->pprev->hashFinalOrchardRoot.IsNull());
|
||||
}
|
||||
assert(view.GetOrchardAnchorAt(OrchardMerkleFrontier::empty_root(), orchard_tree));
|
||||
}
|
||||
|
||||
// Grab the consensus branch ID for this block and its parent
|
||||
auto consensusBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus());
|
||||
|
@ -3823,7 +3848,12 @@ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) {
|
|||
RenderPoolMetrics("sapling", saplingPool);
|
||||
RenderPoolMetrics("transparent", transparentPool);
|
||||
|
||||
cvBlockChange.notify_all();
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(g_best_block_mutex);
|
||||
g_best_block = pindexNew->GetBlockHash();
|
||||
g_best_block_height = pindexNew->nHeight;
|
||||
g_best_block_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3841,6 +3871,7 @@ bool static DisconnectTip(CValidationState &state, const CChainParams& chainpara
|
|||
// Apply the block atomically to the chain state.
|
||||
uint256 sproutAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
|
||||
uint256 saplingAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
|
||||
uint256 orchardAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(ORCHARD);
|
||||
int64_t nStart = GetTimeMicros();
|
||||
{
|
||||
CCoinsViewCache view(pcoinsTip);
|
||||
|
@ -3852,6 +3883,7 @@ bool static DisconnectTip(CValidationState &state, const CChainParams& chainpara
|
|||
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
|
||||
uint256 sproutAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
|
||||
uint256 saplingAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
|
||||
uint256 orchardAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(ORCHARD);
|
||||
// Write the chain state to disk, if necessary.
|
||||
if (!FlushStateToDisk(chainparams, state, FLUSH_STATE_IF_NEEDED))
|
||||
return false;
|
||||
|
@ -3875,6 +3907,11 @@ bool static DisconnectTip(CValidationState &state, const CChainParams& chainpara
|
|||
// in which case we don't want to evict from the mempool yet!
|
||||
mempool.removeWithAnchor(saplingAnchorBeforeDisconnect, SAPLING);
|
||||
}
|
||||
if (orchardAnchorBeforeDisconnect != orchardAnchorAfterDisconnect) {
|
||||
// The anchor may not change between block disconnects,
|
||||
// in which case we don't want to evict from the mempool yet!
|
||||
mempool.removeWithAnchor(orchardAnchorBeforeDisconnect, ORCHARD);
|
||||
}
|
||||
}
|
||||
|
||||
// Update chainActive and related variables.
|
||||
|
|
15
src/main.h
15
src/main.h
|
@ -160,8 +160,19 @@ extern BlockMap mapBlockIndex;
|
|||
extern std::optional<uint64_t> last_block_num_txs;
|
||||
extern std::optional<uint64_t> last_block_size;
|
||||
extern const std::string strMessageMagic;
|
||||
extern CWaitableCriticalSection csBestBlock;
|
||||
extern CConditionVariable cvBlockChange;
|
||||
|
||||
//! These four variables are used to notify getblocktemplate RPC of new tips.
|
||||
//! When UpdateTip() establishes a new tip (best block), it must awaken a
|
||||
//! waiting getblocktemplate RPC (if there is one) immediately. But upon waking
|
||||
//! up, getblocktemplate cannot call chainActive->Tip() because it does not
|
||||
//! (and cannot) hold cs_main. So the g_best_block_height and g_best_block variables
|
||||
//! (protected by g_best_block_mutex) provide the needed height and block
|
||||
//! hash respectively to getblocktemplate without it requiring cs_main.
|
||||
extern CWaitableCriticalSection g_best_block_mutex;
|
||||
extern CConditionVariable g_best_block_cv;
|
||||
extern int g_best_block_height;
|
||||
extern uint256 g_best_block;
|
||||
|
||||
extern std::atomic_bool fImporting;
|
||||
extern std::atomic_bool fReindex;
|
||||
extern int nScriptCheckThreads;
|
||||
|
|
|
@ -50,6 +50,17 @@ void AtomicTimer::stop()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void AtomicTimer::zeroize()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
// only zeroize it if there's no more threads (same semantics as start())
|
||||
if (threads < 1) {
|
||||
start_time = 0;
|
||||
total_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool AtomicTimer::running()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
*/
|
||||
void stop();
|
||||
|
||||
void zeroize();
|
||||
|
||||
bool running();
|
||||
|
||||
uint64_t threadCount();
|
||||
|
|
118
src/miner.cpp
118
src/miner.cpp
|
@ -214,37 +214,87 @@ public:
|
|||
return miner_reward + nFees;
|
||||
}
|
||||
|
||||
void ComputeBindingSig(void* ctx) const {
|
||||
void ComputeBindingSig(void* saplingCtx, std::optional<orchard::UnauthorizedBundle> orchardBundle) const {
|
||||
// Empty output script.
|
||||
uint256 dataToBeSigned;
|
||||
CScript scriptCode;
|
||||
try {
|
||||
// This is a shielded coinbase transaction, so the sighash is either pre-v5
|
||||
// and doesn't use the allPrevOutputs field of PrecomputedTransactionData), or
|
||||
// v5+ and S.2 of ZIP 244 defers to T.2, causing allPrevOutputs to be ignored.
|
||||
// We therefore can set it to the empty list here.
|
||||
PrecomputedTransactionData txdata(mtx, {});
|
||||
dataToBeSigned = SignatureHash(
|
||||
scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0,
|
||||
CurrentEpochBranchId(nHeight, chainparams.GetConsensus()),
|
||||
txdata);
|
||||
if (orchardBundle.has_value()) {
|
||||
// Orchard is only usable with v5+ transactions.
|
||||
dataToBeSigned = ProduceZip244SignatureHash(mtx, {}, orchardBundle.value());
|
||||
} else {
|
||||
CScript scriptCode;
|
||||
PrecomputedTransactionData txdata(mtx, {});
|
||||
dataToBeSigned = SignatureHash(
|
||||
scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0,
|
||||
CurrentEpochBranchId(nHeight, chainparams.GetConsensus()),
|
||||
txdata);
|
||||
}
|
||||
} catch (std::logic_error ex) {
|
||||
librustzcash_sapling_proving_ctx_free(ctx);
|
||||
librustzcash_sapling_proving_ctx_free(saplingCtx);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (orchardBundle.has_value()) {
|
||||
auto authorizedBundle = orchardBundle.value().ProveAndSign({}, dataToBeSigned);
|
||||
if (authorizedBundle.has_value()) {
|
||||
mtx.orchardBundle = authorizedBundle.value();
|
||||
} else {
|
||||
librustzcash_sapling_proving_ctx_free(saplingCtx);
|
||||
throw new std::runtime_error("Failed to create Orchard proof or signatures");
|
||||
}
|
||||
}
|
||||
|
||||
bool success = librustzcash_sapling_binding_sig(
|
||||
ctx,
|
||||
saplingCtx,
|
||||
mtx.valueBalanceSapling,
|
||||
dataToBeSigned.begin(),
|
||||
mtx.bindingSig.data());
|
||||
|
||||
if (!success) {
|
||||
librustzcash_sapling_proving_ctx_free(ctx);
|
||||
librustzcash_sapling_proving_ctx_free(saplingCtx);
|
||||
throw new std::runtime_error("An error occurred computing the binding signature.");
|
||||
}
|
||||
}
|
||||
|
||||
// Create Orchard output
|
||||
void operator()(const libzcash::OrchardRawAddress &to) const {
|
||||
auto ctx = librustzcash_sapling_proving_ctx_init();
|
||||
|
||||
// `enableSpends` must be set to `false` for coinbase transactions. This
|
||||
// means the Orchard anchor is unconstrained, so we set it to the empty
|
||||
// tree root via a null (all zeroes) uint256.
|
||||
uint256 orchardAnchor;
|
||||
auto builder = orchard::Builder(false, true, orchardAnchor);
|
||||
|
||||
// Shielded coinbase outputs must be recoverable with an all-zeroes ovk.
|
||||
uint256 ovk;
|
||||
auto miner_reward = SetFoundersRewardAndGetMinerValue(ctx);
|
||||
builder.AddOutput(ovk, to, miner_reward, std::nullopt);
|
||||
|
||||
// orchard::Builder pads to two Actions, but does so using a "no OVK" policy for
|
||||
// dummy outputs, which violates coinbase rules requiring all shielded outputs to
|
||||
// be recoverable. We manually add a dummy output to sidestep this issue.
|
||||
// TODO: If/when we have funding streams going to Orchard recipients, this dummy
|
||||
// output can be removed.
|
||||
RawHDSeed rawSeed(32, 0);
|
||||
GetRandBytes(rawSeed.data(), 32);
|
||||
auto dummyTo = libzcash::OrchardSpendingKey::ForAccount(HDSeed(rawSeed), Params().BIP44CoinType(), 0)
|
||||
.ToFullViewingKey()
|
||||
.ToIncomingViewingKey()
|
||||
.Address(0);
|
||||
builder.AddOutput(ovk, dummyTo, 0, std::nullopt);
|
||||
|
||||
auto bundle = builder.Build();
|
||||
if (!bundle.has_value()) {
|
||||
librustzcash_sapling_proving_ctx_free(ctx);
|
||||
throw new std::runtime_error("Failed to create shielded output for miner");
|
||||
}
|
||||
|
||||
ComputeBindingSig(ctx, std::move(bundle));
|
||||
|
||||
librustzcash_sapling_proving_ctx_free(ctx);
|
||||
}
|
||||
|
||||
// Create shielded output
|
||||
void operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
||||
auto ctx = librustzcash_sapling_proving_ctx_init();
|
||||
|
@ -264,7 +314,7 @@ public:
|
|||
}
|
||||
mtx.vShieldedOutput.push_back(odesc.value());
|
||||
|
||||
ComputeBindingSig(ctx);
|
||||
ComputeBindingSig(ctx, std::nullopt);
|
||||
|
||||
librustzcash_sapling_proving_ctx_free(ctx);
|
||||
}
|
||||
|
@ -283,7 +333,7 @@ public:
|
|||
mtx.vout[0] = CTxOut(value, coinbaseScript->reserveScript);
|
||||
|
||||
if (mtx.vShieldedOutput.size() > 0) {
|
||||
ComputeBindingSig(ctx);
|
||||
ComputeBindingSig(ctx, std::nullopt);
|
||||
}
|
||||
|
||||
librustzcash_sapling_proving_ctx_free(ctx);
|
||||
|
@ -728,23 +778,38 @@ std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::Sapl
|
|||
return addr;
|
||||
}
|
||||
std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::UnifiedAddress &addr) const {
|
||||
for (const auto& receiver: addr) {
|
||||
if (std::holds_alternative<libzcash::SaplingPaymentAddress>(receiver)) {
|
||||
return std::get<libzcash::SaplingPaymentAddress>(receiver);
|
||||
}
|
||||
auto preferred = addr.GetPreferredRecipientAddress(consensus, height);
|
||||
if (preferred.has_value()) {
|
||||
std::optional<MinerAddress> ret;
|
||||
std::visit(match {
|
||||
[&](const libzcash::OrchardRawAddress addr) { ret = MinerAddress(addr); },
|
||||
[&](const libzcash::SaplingPaymentAddress addr) { ret = MinerAddress(addr); },
|
||||
[&](const CKeyID keyID) { ret = operator()(keyID); },
|
||||
[&](const auto other) { ret = std::nullopt; }
|
||||
}, preferred.value());
|
||||
return ret;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
void GetMinerAddress(MinerAddress &minerAddress)
|
||||
void GetMinerAddress(std::optional<MinerAddress> &minerAddress)
|
||||
{
|
||||
KeyIO keyIO(Params());
|
||||
|
||||
// If the user sets a UA miner address with an Orchard component, we want to ensure we
|
||||
// start using it once we reach that height.
|
||||
int height;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
height = chainActive.Height() + 1;
|
||||
}
|
||||
|
||||
auto mAddrArg = GetArg("-mineraddress", "");
|
||||
auto zaddr0 = keyIO.DecodePaymentAddress(mAddrArg);
|
||||
if (zaddr0.has_value()) {
|
||||
auto zaddr = std::visit(ExtractMinerAddress(), zaddr0.value());
|
||||
auto zaddr = std::visit(ExtractMinerAddress(Params().GetConsensus(), height), zaddr0.value());
|
||||
if (zaddr.has_value()) {
|
||||
minerAddress = zaddr.value();
|
||||
}
|
||||
|
@ -815,8 +880,8 @@ void static BitcoinMiner(const CChainParams& chainparams)
|
|||
// Each thread has its own counter
|
||||
unsigned int nExtraNonce = 0;
|
||||
|
||||
MinerAddress minerAddress;
|
||||
GetMainSignals().AddressForMining(minerAddress);
|
||||
std::optional<MinerAddress> maybeMinerAddress;
|
||||
GetMainSignals().AddressForMining(maybeMinerAddress);
|
||||
|
||||
unsigned int n = chainparams.GetConsensus().nEquihashN;
|
||||
unsigned int k = chainparams.GetConsensus().nEquihashK;
|
||||
|
@ -837,9 +902,10 @@ void static BitcoinMiner(const CChainParams& chainparams)
|
|||
|
||||
try {
|
||||
// Throw an error if no address valid for mining was provided.
|
||||
if (!std::visit(IsValidMinerAddress(), minerAddress)) {
|
||||
if (!(maybeMinerAddress.has_value() && std::visit(IsValidMinerAddress(), maybeMinerAddress.value()))) {
|
||||
throw std::runtime_error("No miner address available (mining requires a wallet or -mineraddress)");
|
||||
}
|
||||
auto minerAddress = maybeMinerAddress.value();
|
||||
|
||||
while (true) {
|
||||
if (chainparams.MiningRequiresPeers()) {
|
||||
|
|
13
src/miner.h
13
src/miner.h
|
@ -24,13 +24,18 @@ static const int DEFAULT_GENERATE_THREADS = 1;
|
|||
static const bool DEFAULT_PRINTPRIORITY = false;
|
||||
|
||||
typedef std::variant<
|
||||
libzcash::OrchardRawAddress,
|
||||
libzcash::SaplingPaymentAddress,
|
||||
boost::shared_ptr<CReserveScript>> MinerAddress;
|
||||
|
||||
class ExtractMinerAddress
|
||||
{
|
||||
const Consensus::Params& consensus;
|
||||
int height;
|
||||
|
||||
public:
|
||||
ExtractMinerAddress() {}
|
||||
ExtractMinerAddress(const Consensus::Params& consensus, int height) :
|
||||
consensus(consensus), height(height) {}
|
||||
|
||||
std::optional<MinerAddress> operator()(const CKeyID &keyID) const;
|
||||
std::optional<MinerAddress> operator()(const CScriptID &addr) const;
|
||||
|
@ -44,6 +49,7 @@ class KeepMinerAddress
|
|||
public:
|
||||
KeepMinerAddress() {}
|
||||
|
||||
void operator()(const libzcash::OrchardRawAddress &addr) const {}
|
||||
void operator()(const libzcash::SaplingPaymentAddress &pa) const {}
|
||||
void operator()(const boost::shared_ptr<CReserveScript> &coinbaseScript) const {
|
||||
coinbaseScript->KeepScript();
|
||||
|
@ -57,6 +63,9 @@ class IsValidMinerAddress
|
|||
public:
|
||||
IsValidMinerAddress() {}
|
||||
|
||||
bool operator()(const libzcash::OrchardRawAddress &addr) const {
|
||||
return true;
|
||||
}
|
||||
bool operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
||||
return true;
|
||||
}
|
||||
|
@ -89,7 +98,7 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const MinerAddre
|
|||
|
||||
#ifdef ENABLE_MINING
|
||||
/** Get -mineraddress */
|
||||
void GetMinerAddress(MinerAddress &minerAddress);
|
||||
void GetMinerAddress(std::optional<MinerAddress> &minerAddress);
|
||||
/** Modify the extranonce in a block */
|
||||
void IncrementExtraNonce(
|
||||
CBlockTemplate* pblocktemplate,
|
||||
|
|
|
@ -9,11 +9,15 @@
|
|||
|
||||
#include <amount.h>
|
||||
#include <rust/orchard.h>
|
||||
#include <rust/orchard/wallet.h>
|
||||
#include "zcash/address/orchard.hpp"
|
||||
|
||||
class OrchardMerkleFrontier;
|
||||
class OrchardWallet;
|
||||
namespace orchard { class UnauthorizedBundle; }
|
||||
|
||||
/**
|
||||
* The Orchard component of a transaction.
|
||||
* The Orchard component of an authorized transaction.
|
||||
*/
|
||||
class OrchardBundle
|
||||
{
|
||||
|
@ -22,7 +26,11 @@ private:
|
|||
/// Memory is allocated by Rust.
|
||||
std::unique_ptr<OrchardBundlePtr, decltype(&orchard_bundle_free)> inner;
|
||||
|
||||
OrchardBundle(OrchardBundlePtr* bundle) : inner(bundle, orchard_bundle_free) {}
|
||||
|
||||
friend class OrchardMerkleFrontier;
|
||||
friend class OrchardWallet;
|
||||
friend class orchard::UnauthorizedBundle;
|
||||
public:
|
||||
OrchardBundle() : inner(nullptr, orchard_bundle_free) {}
|
||||
|
||||
|
|
|
@ -81,6 +81,11 @@ std::string SaplingOutPoint::ToString() const
|
|||
return strprintf("SaplingOutPoint(%s, %u)", hash.ToString().substr(0, 10), n);
|
||||
}
|
||||
|
||||
std::string OrchardOutPoint::ToString() const
|
||||
{
|
||||
return strprintf("OrchardOutPoint(%s, %u)", hash.ToString().substr(0, 10), n);
|
||||
}
|
||||
|
||||
CTxIn::CTxIn(COutPoint prevoutIn, CScript scriptSigIn, uint32_t nSequenceIn)
|
||||
{
|
||||
prevout = prevoutIn;
|
||||
|
|
|
@ -523,6 +523,16 @@ public:
|
|||
std::string ToString() const;
|
||||
};
|
||||
|
||||
/** An outpoint - a combination of a txid and an index n into its orchard
|
||||
* actions */
|
||||
class OrchardOutPoint : public BaseOutPoint
|
||||
{
|
||||
public:
|
||||
OrchardOutPoint() : BaseOutPoint() {};
|
||||
OrchardOutPoint(uint256 hashIn, uint32_t nIn) : BaseOutPoint(hashIn, nIn) {};
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
/** An input of a transaction. It contains the location of the previous
|
||||
* transaction's output that it claims and a signature that matches the
|
||||
* output's public key.
|
||||
|
|
|
@ -1247,6 +1247,13 @@ UniValue z_gettreestate(const UniValue& params, bool fHelp)
|
|||
" \"finalRoot\": \"hex\", (string)\n"
|
||||
" \"finalState\": \"hex\" (string)\n"
|
||||
" }\n"
|
||||
" },\n"
|
||||
" \"orchard\": {\n"
|
||||
" \"skipHash\": \"hash\", (string) hash of most recent block with more information\n"
|
||||
" \"commitments\": {\n"
|
||||
" \"finalRoot\": \"hex\", (string)\n"
|
||||
" \"finalState\": \"hex\" (string)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
|
@ -1327,6 +1334,31 @@ UniValue z_gettreestate(const UniValue& params, bool fHelp)
|
|||
res.pushKV("sapling", sapling_result);
|
||||
}
|
||||
|
||||
// orchard
|
||||
{
|
||||
UniValue orchard_result(UniValue::VOBJ);
|
||||
UniValue orchard_commitments(UniValue::VOBJ);
|
||||
orchard_commitments.pushKV("finalRoot", pindex->hashFinalOrchardRoot.GetHex());
|
||||
bool need_skiphash = false;
|
||||
OrchardMerkleFrontier tree;
|
||||
if (pcoinsTip->GetOrchardAnchorAt(pindex->hashFinalOrchardRoot, tree)) {
|
||||
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
||||
s << tree;
|
||||
orchard_commitments.pushKV("finalState", HexStr(s.begin(), s.end()));
|
||||
} else {
|
||||
// Set skipHash to the most recent block that has a finalState.
|
||||
const CBlockIndex* pindex_skip = pindex->pprev;
|
||||
while (pindex_skip && !pcoinsTip->GetOrchardAnchorAt(pindex_skip->hashFinalOrchardRoot, tree)) {
|
||||
pindex_skip = pindex_skip->pprev;
|
||||
}
|
||||
if (pindex_skip) {
|
||||
orchard_result.pushKV("skipHash", pindex_skip->GetBlockHash().GetHex());
|
||||
}
|
||||
}
|
||||
orchard_result.pushKV("commitments", orchard_commitments);
|
||||
res.pushKV("orchard", orchard_result);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,6 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "z_sendmany", 1},
|
||||
{ "z_sendmany", 2},
|
||||
{ "z_sendmany", 3},
|
||||
{ "z_sendmany", 4},
|
||||
{ "z_shieldcoinbase", 2},
|
||||
{ "z_shieldcoinbase", 3},
|
||||
{ "z_getoperationstatus", 0},
|
||||
|
|
|
@ -186,19 +186,25 @@ UniValue generate(const UniValue& params, bool fHelp)
|
|||
int nHeight = 0;
|
||||
int nGenerate = params[0].get_int();
|
||||
|
||||
MinerAddress minerAddress;
|
||||
GetMainSignals().AddressForMining(minerAddress);
|
||||
|
||||
// If the keypool is exhausted, no script is returned at all. Catch this.
|
||||
auto resv = std::get_if<boost::shared_ptr<CReserveScript>>(&minerAddress);
|
||||
if (resv && !resv->get()) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
|
||||
}
|
||||
std::optional<MinerAddress> maybeMinerAddress;
|
||||
GetMainSignals().AddressForMining(maybeMinerAddress);
|
||||
|
||||
// Throw an error if no address valid for mining was provided.
|
||||
if (!std::visit(IsValidMinerAddress(), minerAddress)) {
|
||||
if (!maybeMinerAddress.has_value()) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "No miner address available (mining requires a wallet or -mineraddress)");
|
||||
} else {
|
||||
// Detect and handle keypool exhaustion separately from IsValidMinerAddress().
|
||||
auto resv = std::get_if<boost::shared_ptr<CReserveScript>>(&maybeMinerAddress.value());
|
||||
if (resv && !resv->get()) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
|
||||
}
|
||||
|
||||
// Catch any other invalid miner address issues.
|
||||
if (!std::visit(IsValidMinerAddress(), maybeMinerAddress.value())) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Miner address is invalid");
|
||||
}
|
||||
}
|
||||
auto minerAddress = maybeMinerAddress.value();
|
||||
|
||||
{ // Don't keep cs_main locked
|
||||
LOCK(cs_main);
|
||||
|
@ -569,9 +575,14 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
|
|||
if (IsInitialBlockDownload(Params().GetConsensus()))
|
||||
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Zcash is downloading blocks...");
|
||||
|
||||
std::optional<MinerAddress> maybeMinerAddress;
|
||||
GetMainSignals().AddressForMining(maybeMinerAddress);
|
||||
|
||||
MinerAddress minerAddress;
|
||||
GetMainSignals().AddressForMining(minerAddress);
|
||||
// Throw an error if no address valid for mining was provided.
|
||||
if (!(maybeMinerAddress.has_value() && std::visit(IsValidMinerAddress(), maybeMinerAddress.value()))) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "No miner address available (getblocktemplate requires a wallet or -mineraddress)");
|
||||
}
|
||||
auto minerAddress = maybeMinerAddress.value();
|
||||
|
||||
static unsigned int nTransactionsUpdatedLast;
|
||||
static std::optional<CMutableTransaction> cached_next_cb_mtx;
|
||||
|
@ -607,15 +618,15 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
|
|||
nTransactionsUpdatedLastLP = nTransactionsUpdatedLast;
|
||||
}
|
||||
|
||||
// Release the main lock while waiting
|
||||
// Don't call chainActive->Tip() without holding cs_main
|
||||
LEAVE_CRITICAL_SECTION(cs_main);
|
||||
{
|
||||
checktxtime = boost::get_system_time() + boost::posix_time::seconds(10);
|
||||
|
||||
boost::unique_lock<boost::mutex> lock(csBestBlock);
|
||||
while (chainActive.Tip()->GetBlockHash() == hashWatchedChain && IsRPCRunning())
|
||||
boost::unique_lock<boost::mutex> lock(g_best_block_mutex);
|
||||
while (g_best_block == hashWatchedChain && IsRPCRunning())
|
||||
{
|
||||
// Release the main lock while waiting
|
||||
LEAVE_CRITICAL_SECTION(cs_main);
|
||||
|
||||
// Before waiting, generate the coinbase for the block following the next
|
||||
// block (since this is cpu-intensive), so that when next block arrives,
|
||||
// we can quickly respond with a template for following block.
|
||||
|
@ -628,12 +639,11 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
|
|||
Params(), CAmount{0}, minerAddress, cached_next_cb_height);
|
||||
next_cb_mtx = cached_next_cb_mtx;
|
||||
}
|
||||
bool timedout = !cvBlockChange.timed_wait(lock, checktxtime);
|
||||
ENTER_CRITICAL_SECTION(cs_main);
|
||||
bool timedout = !g_best_block_cv.timed_wait(lock, checktxtime);
|
||||
|
||||
// Optimization: even if timed out, a new block may have arrived
|
||||
// while waiting for cs_main; if so, don't discard next_cb_mtx.
|
||||
if (chainActive.Tip()->GetBlockHash() != hashWatchedChain) break;
|
||||
if (g_best_block != hashWatchedChain) break;
|
||||
|
||||
// Timeout: Check transactions for update
|
||||
if (timedout && mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) {
|
||||
|
@ -643,11 +653,12 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
|
|||
}
|
||||
checktxtime += boost::posix_time::seconds(10);
|
||||
}
|
||||
if (chainActive.Tip()->nHeight != nHeight + 1) {
|
||||
if (g_best_block_height != nHeight + 1) {
|
||||
// Unexpected height (reorg or >1 blocks arrived while waiting) invalidates coinbase tx.
|
||||
next_cb_mtx = nullopt;
|
||||
}
|
||||
}
|
||||
ENTER_CRITICAL_SECTION(cs_main);
|
||||
|
||||
if (!IsRPCRunning())
|
||||
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
|
||||
|
|
|
@ -431,8 +431,7 @@ void JSONRequest::parse(const UniValue& valRequest)
|
|||
if (!valMethod.isStr())
|
||||
throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string");
|
||||
strMethod = valMethod.get_str();
|
||||
if (strMethod != "getblocktemplate")
|
||||
LogPrint("rpc", "ThreadRPCServer method=%s\n", SanitizeString(strMethod));
|
||||
LogPrint("rpc", "ThreadRPCServer method=%s\n", SanitizeString(strMethod));
|
||||
|
||||
// Parse params
|
||||
UniValue valParams = find_value(request, "params");
|
||||
|
|
|
@ -6,6 +6,11 @@ the `zcashd` full node.
|
|||
The FFI API does not have any stability guarantees, and will change as required
|
||||
by `zcashd`.
|
||||
|
||||
# zcashd-wallet-tool
|
||||
|
||||
`zcashd-wallet-tool` is a command-line tool that allows confirming to a `zcashd`
|
||||
node that the emergency recovery phrase of the node's wallet has been backed up.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
|
|
@ -0,0 +1,646 @@
|
|||
use std::cmp::min;
|
||||
use std::env::{self, consts::EXE_EXTENSION};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, Stdin, Write};
|
||||
use std::iter;
|
||||
use std::panic;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{self, ChildStdin, Command, Output, Stdio};
|
||||
use std::str::from_utf8;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use anyhow::{self, Context};
|
||||
use backtrace::Backtrace;
|
||||
use gumdrop::{Options, ParsingStyle};
|
||||
use rand::{thread_rng, Rng};
|
||||
use secrecy::{ExposeSecret, SecretString};
|
||||
use thiserror::Error;
|
||||
use time::macros::format_description;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::debug;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
struct CliOptions {
|
||||
#[options(no_short, help = "Print this help output")]
|
||||
help: bool,
|
||||
|
||||
#[options(
|
||||
no_short,
|
||||
help = "Specify configuration filename, relative to the data directory (default: zcash.conf)",
|
||||
meta = "FILENAME"
|
||||
)]
|
||||
conf: Option<String>,
|
||||
|
||||
#[options(
|
||||
no_short,
|
||||
help = "Specify data directory (this path cannot use '~')",
|
||||
meta = "PATH"
|
||||
)]
|
||||
datadir: Option<String>,
|
||||
|
||||
#[options(no_short, help = "Use the test chain")]
|
||||
testnet: bool,
|
||||
|
||||
#[options(
|
||||
no_short,
|
||||
help = "Send commands to node running on IPADDR (default: 127.0.0.1)",
|
||||
meta = "IPADDR"
|
||||
)]
|
||||
rpcconnect: Option<String>,
|
||||
|
||||
#[options(
|
||||
no_short,
|
||||
help = "Connect to JSON-RPC on PORT (default: 8232 or testnet: 18232)",
|
||||
meta = "PORT"
|
||||
)]
|
||||
rpcport: Option<u16>,
|
||||
|
||||
#[options(
|
||||
no_short,
|
||||
help = "Username for JSON-RPC connections",
|
||||
meta = "USERNAME"
|
||||
)]
|
||||
rpcuser: Option<String>,
|
||||
|
||||
#[options(
|
||||
no_short,
|
||||
help = "Password for JSON-RPC connections",
|
||||
meta = "PASSWORD"
|
||||
)]
|
||||
rpcpassword: Option<String>,
|
||||
|
||||
#[options(
|
||||
no_short,
|
||||
help = "Timeout in seconds during HTTP requests, or 0 for no timeout. (default: 900)",
|
||||
meta = "SECONDS"
|
||||
)]
|
||||
rpcclienttimeout: Option<u32>,
|
||||
}
|
||||
|
||||
impl CliOptions {
|
||||
fn to_zcash_cli_options(&self) -> Vec<String> {
|
||||
iter::empty::<String>()
|
||||
.chain(self.conf.as_ref().map(|o| format!("-conf={}", o)))
|
||||
.chain(self.datadir.as_ref().map(|o| format!("-datadir={}", o)))
|
||||
.chain(if self.testnet {
|
||||
Some("-testnet".into())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.chain(
|
||||
self.rpcconnect
|
||||
.as_ref()
|
||||
.map(|o| format!("-rpcconnect={}", o)),
|
||||
)
|
||||
.chain(self.rpcport.as_ref().map(|o| format!("-rpcport={}", o)))
|
||||
.chain(self.rpcuser.as_ref().map(|o| format!("-rpcuser={}", o)))
|
||||
.chain(
|
||||
self.rpcpassword
|
||||
.as_ref()
|
||||
.map(|o| format!("-rpcpassword={}", o)),
|
||||
)
|
||||
.chain(
|
||||
self.rpcclienttimeout
|
||||
.as_ref()
|
||||
.map(|o| format!("-rpcclienttimeout={}", o)),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum WalletToolError {
|
||||
#[error("zcash-cli executable not found")]
|
||||
ZcashCliNotFound,
|
||||
|
||||
#[error("Unexpected response from zcash-cli or zcashd")]
|
||||
UnexpectedResponse,
|
||||
|
||||
#[error("Could not connect to zcashd")]
|
||||
ZcashdConnection,
|
||||
|
||||
#[error("zcashd -exportdir option not set")]
|
||||
ExportDirNotSet,
|
||||
|
||||
#[error("Could not parse a recovery phrase from the export file")]
|
||||
RecoveryPhraseNotFound,
|
||||
|
||||
#[error("Unexpected EOF in input")]
|
||||
UnexpectedEof,
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
// Log to stderr, configured by the RUST_LOG environment variable.
|
||||
fmt()
|
||||
.with_writer(io::stderr)
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
// Allow either Bitcoin-style or GNU-style arguments.
|
||||
let mut args = env::args();
|
||||
let command = args.next().expect("argv[0] should exist");
|
||||
let args: Vec<_> = args
|
||||
.map(|s| {
|
||||
if s.starts_with('-') && !s.starts_with("--") {
|
||||
format!("-{}", s)
|
||||
} else {
|
||||
s
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
const USAGE_NOTE: &str = concat!(
|
||||
"Options can be given in GNU style (`--conf=CONF` or `--conf CONF`),\n",
|
||||
"or in Bitcoin style with a single hyphen (`-conf=CONF`).\n\n",
|
||||
"The environment variable RUST_LOG controls debug output, e.g.\n",
|
||||
"`RUST_LOG=debug`.\n",
|
||||
);
|
||||
|
||||
let opts = CliOptions::parse_args(&args, ParsingStyle::default()).unwrap_or_else(|e| {
|
||||
eprintln!(
|
||||
"{}: {}\n\nUsage: {} [OPTIONS]\n\n{}\n\n{}",
|
||||
command,
|
||||
e,
|
||||
command,
|
||||
CliOptions::usage(),
|
||||
USAGE_NOTE,
|
||||
);
|
||||
process::exit(2);
|
||||
});
|
||||
|
||||
if opts.help_requested() {
|
||||
println!(
|
||||
"Usage: {} [OPTIONS]\n\n{}\n\n{}",
|
||||
command,
|
||||
opts.self_usage(),
|
||||
USAGE_NOTE
|
||||
);
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
if let Err(e) = run(&opts) {
|
||||
eprintln!("{}: {}", command, e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn run(opts: &CliOptions) -> anyhow::Result<()> {
|
||||
let cli_options: Vec<String> = opts.to_zcash_cli_options();
|
||||
|
||||
println!(concat!(
|
||||
"To reduce the risk of loss of funds, we're going to confirm that the\n",
|
||||
"zcashd wallet is backed up reliably.\n\n",
|
||||
" 👛 ➜ 🗃️ \n"
|
||||
));
|
||||
|
||||
println!("Checking that we can connect to zcashd...");
|
||||
let zcash_cli = zcash_cli_path()?;
|
||||
|
||||
// Pass an invalid filename, "\x01", and use the error message to distinguish
|
||||
// whether zcashd is running with the -exportdir option, running without that
|
||||
// option, or not running / cannot connect.
|
||||
let mut cli_args = cli_options.clone();
|
||||
cli_args.extend_from_slice(&["z_exportwallet".to_string(), "\x01".to_string()]);
|
||||
let out = exec(&zcash_cli, &cli_args, None)?;
|
||||
let cli_err: Vec<_> = from_utf8(&out.stderr)
|
||||
.with_context(|| "Output from zcash-cli was not UTF-8")?
|
||||
.lines()
|
||||
.map(|s| s.trim_end_matches('\r'))
|
||||
.collect();
|
||||
debug!("stderr {:?}", cli_err);
|
||||
|
||||
if !cli_err.is_empty() {
|
||||
if cli_err[0].starts_with("Error reading configuration file") {
|
||||
println!(
|
||||
"\nNo, we could not read the zcashd configuration file, expected to be at\n{:?}.",
|
||||
Path::new(opts.datadir.as_ref().map_or("~/.zcash", |s| &s[..])).join(Path::new(
|
||||
opts.conf.as_ref().map_or("zcash.conf", |s| &s[..])
|
||||
)),
|
||||
);
|
||||
println!(concat!(
|
||||
"If it is not at that path, please try again with the '-datadir' and/or\n",
|
||||
"'-conf' options set correctly (see '--help' for details). Also make sure\n",
|
||||
"that the current user has permission to read the configuration file.\n",
|
||||
));
|
||||
return Err(WalletToolError::ZcashdConnection.into());
|
||||
}
|
||||
if cli_err[0].starts_with("error: couldn't connect") {
|
||||
println!(concat!(
|
||||
"\nNo, we could not connect. zcashd might not be running; in that case\n",
|
||||
"please start it. The '-exportdir' option should be set to the absolute\n",
|
||||
"path of the directory you want to save the wallet export file to.\n\n",
|
||||
"(Don't forget to restart zcashd without '-exportdir' after finishing\n",
|
||||
"the backup, if running it long-term with that option is not desired\n",
|
||||
"or would be a security hazard in your environment.)\n\n",
|
||||
"If you believe zcashd is running, it might be using an unexpected port,\n",
|
||||
"address, or authentication options for the RPC interface, for example.\n",
|
||||
"In that case try to connect to it using zcash-cli, and if successful,\n",
|
||||
"use the same connection options for zcashd-wallet-tool (see '--help' for\n",
|
||||
"accepted options) as for zcash-cli.\n"
|
||||
));
|
||||
return Err(WalletToolError::ZcashdConnection.into());
|
||||
}
|
||||
if cli_err[0] == "error code: -28" {
|
||||
println!(concat!(
|
||||
"\nNo, we could not connect. zcashd seems to be initializing; please try\n",
|
||||
"again once it has finished.\n",
|
||||
));
|
||||
return Err(WalletToolError::ZcashdConnection.into());
|
||||
}
|
||||
}
|
||||
|
||||
const REMINDER_MSG: &str = concat!(
|
||||
"\n\nPlease start or restart zcashd with '-exportdir' set to the absolute\n",
|
||||
"path of the directory you want to save the wallet export file to.\n",
|
||||
"(Don't forget to restart zcashd without '-exportdir' after finishing\n",
|
||||
"the backup, if running it long-term with that option is not desired\n",
|
||||
"or would be a security hazard in your environment.)\n",
|
||||
);
|
||||
|
||||
if cli_err.len() >= 3
|
||||
&& cli_err[0] == "error code: -4"
|
||||
&& cli_err[2].contains("zcashd -exportdir")
|
||||
{
|
||||
println!(
|
||||
"\nIt looks like zcashd is running without the '-exportdir' option.{}",
|
||||
REMINDER_MSG
|
||||
);
|
||||
return Err(WalletToolError::ExportDirNotSet.into());
|
||||
}
|
||||
if !(cli_err.len() >= 3
|
||||
&& cli_err[0] == "error code: -4"
|
||||
&& cli_err[2].starts_with("Filename is invalid"))
|
||||
{
|
||||
println!(
|
||||
"\nThere was an unexpected response from zcash-cli or zcashd:\n> {}{}",
|
||||
cli_err.join("\n> "),
|
||||
REMINDER_MSG,
|
||||
);
|
||||
return Err(WalletToolError::UnexpectedResponse.into());
|
||||
}
|
||||
println!("Yes, and it is running with the '-exportdir' option as required.");
|
||||
|
||||
let mut stdin = io::stdin();
|
||||
|
||||
let base = default_filename_base();
|
||||
let mut r = 0u32;
|
||||
let out = loop {
|
||||
let default_filename = if r != 0 {
|
||||
format!("{}r{}", base, r)
|
||||
} else {
|
||||
base.to_string()
|
||||
};
|
||||
println!(
|
||||
concat!(
|
||||
"\nEnter the filename for the wallet export file, using only characters\n",
|
||||
"a-z, A-Z and 0-9 (default '{}')."
|
||||
),
|
||||
default_filename
|
||||
);
|
||||
let response = prompt(&mut stdin)?;
|
||||
let response = strip(&response);
|
||||
let filename = if response.is_empty() {
|
||||
r = r.saturating_add(1);
|
||||
&default_filename
|
||||
} else {
|
||||
response
|
||||
};
|
||||
debug!("Using filename {:?}", filename);
|
||||
|
||||
let mut cli_args = cli_options.clone();
|
||||
cli_args.extend_from_slice(&["z_exportwallet".to_string(), filename.to_string()]);
|
||||
let out = exec(&zcash_cli, &cli_args, None)?;
|
||||
let cli_err: Vec<_> = from_utf8(&out.stderr)
|
||||
.with_context(|| "Output from zcash-cli was not UTF-8")?
|
||||
.lines()
|
||||
.map(|s| s.trim_end_matches('\r'))
|
||||
.collect();
|
||||
debug!("stderr {:?}", cli_err);
|
||||
|
||||
if cli_err.len() >= 3
|
||||
&& cli_err[0] == "error code: -8"
|
||||
&& cli_err[1] == "error message:"
|
||||
&& cli_err[2].contains("overwrite existing")
|
||||
{
|
||||
println!(concat!(
|
||||
"That file already exists. Please pick a unique filename in the\n",
|
||||
"directory specified by the '-exportdir' option to zcashd."
|
||||
));
|
||||
continue;
|
||||
} else {
|
||||
break out;
|
||||
}
|
||||
};
|
||||
|
||||
let cli_out: Vec<_> = from_utf8(&out.stdout)
|
||||
.with_context(|| "Output from zcash-cli was not UTF-8")?
|
||||
.lines()
|
||||
.map(|s| s.trim_end_matches('\r'))
|
||||
.collect();
|
||||
debug!("stdout {:?}", cli_out);
|
||||
|
||||
if cli_out.is_empty() {
|
||||
return Err(WalletToolError::UnexpectedResponse.into());
|
||||
}
|
||||
let export_path = cli_out[0];
|
||||
|
||||
println!("\nSaved the export file to '{}'.", export_path);
|
||||
println!("IMPORTANT: This file contains secrets that allow spending all wallet funds.\n");
|
||||
|
||||
let export_file = File::open(export_path)
|
||||
.with_context(|| format!("Could not open {:?} for reading", export_path))?;
|
||||
|
||||
// TODO: ensure the buffer will be zeroized (#5650)
|
||||
let phrase_line: Vec<_> = io::BufReader::new(export_file)
|
||||
.lines()
|
||||
.map(|line| line.map(SecretString::new))
|
||||
.filter(|s| {
|
||||
s.as_ref()
|
||||
.map(|t| t.expose_secret().starts_with("# - recovery_phrase=\""))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let phrase = match &phrase_line[..] {
|
||||
[Ok(line)] => line
|
||||
.expose_secret()
|
||||
.trim_start_matches("# - recovery_phrase=\"")
|
||||
.trim_end_matches('"'),
|
||||
_ => return Err(WalletToolError::RecoveryPhraseNotFound.into()),
|
||||
};
|
||||
|
||||
// This panic hook allows us to make a best effort to clear the screen (and then print
|
||||
// another reminder about secrets in the export file) even if a panic occurs.
|
||||
|
||||
let old_hook = panic::take_hook();
|
||||
{
|
||||
let export_path = export_path.to_string();
|
||||
panic::set_hook(Box::new(move |panic_info| {
|
||||
clear_and_show_cautions(&export_path);
|
||||
|
||||
let s = panic_info.payload().downcast_ref::<&str>().unwrap_or(&"");
|
||||
eprintln!("\nPanic: {}\n{:?}", s, Backtrace::new());
|
||||
}));
|
||||
}
|
||||
|
||||
let res = (|| -> anyhow::Result<()> {
|
||||
println!("The recovery phrase is:\n");
|
||||
|
||||
const WORDS_PER_LINE: usize = 3;
|
||||
|
||||
let words: Vec<_> = phrase.split(' ').collect();
|
||||
let max_len = words.iter().map(|w| w.len()).max().unwrap_or(0);
|
||||
|
||||
for (i, word) in words.iter().enumerate() {
|
||||
print!("{0:2}: {1:2$}", i + 1, word, max_len + 2);
|
||||
if (i + 1) % WORDS_PER_LINE == 0 {
|
||||
println!();
|
||||
}
|
||||
}
|
||||
if words.len() % WORDS_PER_LINE != 0 {
|
||||
println!();
|
||||
}
|
||||
|
||||
println!(concat!(
|
||||
"\nPlease write down this phrase (including the numbering of words) on\n",
|
||||
"something durable that you will keep in a secure location.\n",
|
||||
"Press Enter when finished; then the phrase will disappear and you'll be\n",
|
||||
"asked to re-enter a selection of words from it."
|
||||
));
|
||||
|
||||
let mut stdin = io::stdin();
|
||||
prompt(&mut stdin)?;
|
||||
|
||||
// The only reliable and portable way to make sure the recovery phrase
|
||||
// is no longer displayed is to clear the whole terminal (including
|
||||
// scrollback, if possible). The text is only printed if clearing fails.
|
||||
try_to_clear(concat!(
|
||||
"\n\n\n\n\n\n\n\n\n\n\n\n",
|
||||
"Please adjust the terminal window so that you can't see the\n",
|
||||
"recovery phrase above. After finishing the backup, close the\n",
|
||||
"terminal window or clear it"
|
||||
));
|
||||
|
||||
println!("\nNow we're going to confirm that you backed up the recovery phrase.");
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let mut unconfirmed: Vec<usize> = (0..words.len()).collect();
|
||||
for _ in 0..min(3, words.len()) {
|
||||
let index: usize = rng.gen_range(0..unconfirmed.len());
|
||||
let n = unconfirmed[index];
|
||||
unconfirmed[index] = unconfirmed[unconfirmed.len() - 1];
|
||||
unconfirmed.pop().expect("should be nonempty");
|
||||
|
||||
loop {
|
||||
println!("\nPlease enter the {} word:", ordinal(n + 1));
|
||||
let line = prompt(&mut stdin)?;
|
||||
if words[n] == strip(&line) {
|
||||
break;
|
||||
}
|
||||
println!("That's not correct, please try again.");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
panic::set_hook(old_hook);
|
||||
clear_and_show_cautions(export_path);
|
||||
res?;
|
||||
|
||||
let mut cli_args = cli_options;
|
||||
cli_args.extend_from_slice(&["-stdin".to_string(), "walletconfirmbackup".to_string()]);
|
||||
exec(&zcash_cli, &cli_args, Some(phrase))
|
||||
.and_then(|out| {
|
||||
let cli_err: Vec<_> = from_utf8(&out.stderr)
|
||||
.with_context(|| "Output from zcash-cli was not UTF-8")?
|
||||
.lines()
|
||||
.map(|s| s.trim_end_matches('\r'))
|
||||
.collect();
|
||||
debug!("stderr {:?}", cli_err);
|
||||
|
||||
if !cli_err.is_empty() {
|
||||
if cli_err[0].starts_with("error: couldn't connect") {
|
||||
println!("\nWe could not connect to zcashd; it may have exited.");
|
||||
return Err(WalletToolError::ZcashdConnection.into());
|
||||
} else {
|
||||
println!(
|
||||
"\nThere was an unexpected response from zcash-cli or zcashd:\n> {}",
|
||||
cli_err.join("\n> "),
|
||||
);
|
||||
return Err(WalletToolError::UnexpectedResponse.into());
|
||||
}
|
||||
}
|
||||
println!(concat!(
|
||||
"\nThe backup of the emergency recovery phrase for the zcashd\n",
|
||||
"wallet has been successfully confirmed 🙂. You can now use the\n",
|
||||
"zcashd RPC methods that create keys and addresses in that wallet.\n\n",
|
||||
"If you use other wallets, their recovery information will need\n",
|
||||
"to be backed up separately.\n"
|
||||
));
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| {
|
||||
println!(concat!(
|
||||
"\nzcash-wallet-tool was unable to communicate to zcashd that the\n",
|
||||
"backup was confirmed. This can happen if zcashd stopped, in which\n",
|
||||
"case you should try again. If zcashd is still running, please seek\n",
|
||||
"help or try to use 'zcash-cli -stdin walletconfirmbackup' manually.\n"
|
||||
));
|
||||
e
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const MAX_USER_INPUT_LEN: usize = 100;
|
||||
|
||||
fn prompt(input: &mut Stdin) -> anyhow::Result<SecretString> {
|
||||
let mut buf = String::with_capacity(MAX_USER_INPUT_LEN);
|
||||
let res = input
|
||||
.read_line(&mut buf)
|
||||
.with_context(|| "Error reading from stdin");
|
||||
|
||||
// Ensure the buffer is zeroized even on error.
|
||||
let line = SecretString::new(buf);
|
||||
res.and_then(|_| {
|
||||
if line.expose_secret().ends_with('\n') {
|
||||
Ok(line)
|
||||
} else {
|
||||
Err(WalletToolError::UnexpectedEof.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn strip(input: &SecretString) -> &str {
|
||||
input
|
||||
.expose_secret()
|
||||
.trim_end_matches(|c| c == '\r' || c == '\n')
|
||||
.trim()
|
||||
}
|
||||
|
||||
fn ordinal(num: usize) -> String {
|
||||
let suffix = if (11..=13).contains(&(num % 100)) {
|
||||
"th"
|
||||
} else {
|
||||
match num % 10 {
|
||||
1 => "st",
|
||||
2 => "nd",
|
||||
3 => "rd",
|
||||
_ => "th",
|
||||
}
|
||||
};
|
||||
format!("{}{}", num, suffix)
|
||||
}
|
||||
|
||||
fn zcash_cli_path() -> anyhow::Result<PathBuf> {
|
||||
// First look for `zcash_cli[.exe]` as a sibling of the executable.
|
||||
let mut exe = env::current_exe()
|
||||
.with_context(|| "Cannot determine the path of the running executable")?;
|
||||
exe.set_file_name("zcash-cli");
|
||||
exe.set_extension(EXE_EXTENSION);
|
||||
|
||||
debug!("Testing for zcash-cli at {:?}", exe);
|
||||
if exe.exists() {
|
||||
return Ok(exe);
|
||||
}
|
||||
// If not found there, look in `../src/zcash_cli[.exe]` provided
|
||||
// that `src` is a sibling of `target`.
|
||||
exe.pop(); // strip filename
|
||||
exe.pop(); // ..
|
||||
if exe.file_name() != Some(OsStr::new("target")) {
|
||||
// or in `../../src/zcash_cli[.exe]` under the same proviso
|
||||
exe.pop(); // ../..
|
||||
if exe.file_name() != Some(OsStr::new("target")) {
|
||||
return Err(WalletToolError::ZcashCliNotFound.into());
|
||||
}
|
||||
}
|
||||
// Replace 'target/' with 'src/'.
|
||||
exe.set_file_name("src");
|
||||
exe.push("zcash-cli");
|
||||
exe.set_extension(EXE_EXTENSION);
|
||||
|
||||
debug!("Testing for zcash-cli at {:?}", exe);
|
||||
if !exe.exists() {
|
||||
return Err(WalletToolError::ZcashCliNotFound.into());
|
||||
}
|
||||
Ok(exe)
|
||||
}
|
||||
|
||||
fn exec(exe_path: &Path, args: &[String], stdin: Option<&str>) -> anyhow::Result<Output> {
|
||||
debug!("Running {:?} {:?}", exe_path, args);
|
||||
let mut cmd = Command::new(exe_path);
|
||||
let cli = cmd.args(args);
|
||||
match stdin {
|
||||
None => Ok(cli.output()?),
|
||||
Some(data) => {
|
||||
let mut cli_process = cli
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
cli_process
|
||||
.stdin
|
||||
.take()
|
||||
.with_context(|| "Could not open pipe to zcash-cli's stdin")
|
||||
.and_then(|mut pipe: ChildStdin| -> anyhow::Result<()> {
|
||||
pipe.write_all(data.as_bytes())?;
|
||||
pipe.write_all("\n".as_bytes())?;
|
||||
Ok(())
|
||||
})
|
||||
.with_context(|| "Could not write to zcash-cli's stdin")?;
|
||||
Ok(cli_process.wait_with_output()?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_filename_base() -> String {
|
||||
let format = format_description!("export[year][month][day]");
|
||||
|
||||
// We use the UTC date because there is a security issue in obtaining the local date
|
||||
// from either `chrono` or `time`: <https://github.com/chronotope/chrono/issues/602>.
|
||||
// We could use the approach in
|
||||
// <https://github.com/ArekPiekarz/rusty-tax-break/commit/3aac8f0c26fd96b7365619509a544f78b59627fe>
|
||||
// if it were important, but it isn't worth the dependency on `tz-rs`.
|
||||
|
||||
OffsetDateTime::from(SystemTime::now())
|
||||
.format(&format)
|
||||
.unwrap_or_else(|_| "export".to_string())
|
||||
}
|
||||
|
||||
fn clear_and_show_cautions(export_path: &str) {
|
||||
try_to_clear(concat!(
|
||||
"\nCAUTION: This terminal window might be showing secrets (or have\n",
|
||||
"them in the scrollback). Please copy any useful information and\n",
|
||||
"then close it, or clear it"
|
||||
));
|
||||
|
||||
println!(
|
||||
concat!(
|
||||
"\nIMPORTANT: Secrets that allow spending all zcashd wallet funds\n",
|
||||
"have been left in the file '{}'.\n\n",
|
||||
"Don't forget to restart zcashd without '-exportdir', if running it\n",
|
||||
"long-term with that option is not desired or would be a security\n",
|
||||
"hazard in your environment.\n\n",
|
||||
"When choosing a location for the physical backup of your emergency\n",
|
||||
"recovery phrase, please make sure to consider both risk of theft,\n",
|
||||
"and your long-term ability to remember where it is kept."
|
||||
),
|
||||
export_path,
|
||||
);
|
||||
}
|
||||
|
||||
fn try_to_clear(error_blurb: &str) {
|
||||
if let Err(e) = clearscreen::clear() {
|
||||
eprintln!("Unable to clear screen: {}.", e);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const HOW_TO_CLEAR: &str = "using 'cls'";
|
||||
#[cfg(target_os = "macos")]
|
||||
const HOW_TO_CLEAR: &str = "by pressing Command + K";
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||
const HOW_TO_CLEAR: &str = "using 'clear'";
|
||||
|
||||
println!("{} {}.", error_blurb, HOW_TO_CLEAR);
|
||||
}
|
||||
}
|
|
@ -5,10 +5,13 @@
|
|||
#ifndef ZCASH_RUST_INCLUDE_RUST_ADDRESS_H
|
||||
#define ZCASH_RUST_INCLUDE_RUST_ADDRESS_H
|
||||
|
||||
#include "rust/orchard/keys.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef bool (*orchard_receiver_t)(void* ua, OrchardRawAddressPtr* addr);
|
||||
typedef bool (*raw_to_receiver_t)(void* ua, const unsigned char* raw);
|
||||
typedef bool (*unknown_receiver_t)(
|
||||
void* ua,
|
||||
|
@ -24,6 +27,7 @@ bool zcash_address_parse_unified(
|
|||
const char* str,
|
||||
const char* network,
|
||||
void* ua,
|
||||
orchard_receiver_t orchard_cb,
|
||||
raw_to_receiver_t sapling_cb,
|
||||
raw_to_receiver_t p2sh_cb,
|
||||
raw_to_receiver_t p2pkh_cb,
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) 2022 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#ifndef ZCASH_RUST_INCLUDE_RUST_BUILDER_H
|
||||
#define ZCASH_RUST_INCLUDE_RUST_BUILDER_H
|
||||
|
||||
#include "rust/orchard.h"
|
||||
#include "rust/orchard/keys.h"
|
||||
#include "rust/transaction.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// A type-safe pointer to a Rust-allocated struct containing the information
|
||||
/// needed to spend an Orchard note.
|
||||
struct OrchardSpendInfoPtr;
|
||||
typedef struct OrchardSpendInfoPtr OrchardSpendInfoPtr;
|
||||
|
||||
/// Pointer to Rust-allocated Orchard bundle builder.
|
||||
struct OrchardBuilderPtr;
|
||||
typedef struct OrchardBuilderPtr OrchardBuilderPtr;
|
||||
|
||||
/// Pointer to Rust-allocated Orchard bundle without proofs
|
||||
/// or authorizing data.
|
||||
struct OrchardUnauthorizedBundlePtr;
|
||||
typedef struct OrchardUnauthorizedBundlePtr OrchardUnauthorizedBundlePtr;
|
||||
|
||||
/// Frees the memory associated with an Orchard spend info struct that was
|
||||
/// allocated by Rust.
|
||||
void orchard_spend_info_free(OrchardSpendInfoPtr* ptr);
|
||||
|
||||
/// Construct a new Orchard transaction builder.
|
||||
///
|
||||
/// If `anchor` is `null`, the root of the empty Orchard commitment tree is used.
|
||||
OrchardBuilderPtr* orchard_builder_new(
|
||||
bool spends_enabled,
|
||||
bool outputs_enabled,
|
||||
const unsigned char* anchor);
|
||||
|
||||
/// Frees an Orchard builder returned from `orchard_builder_new`.
|
||||
void orchard_builder_free(OrchardBuilderPtr* ptr);
|
||||
|
||||
/// Adds a note to be spent in this bundle.
|
||||
///
|
||||
/// Returns `false` if the Merkle path in `spend_info` does not have the
|
||||
/// required anchor.
|
||||
///
|
||||
/// `spend_info` is always freed by this method, whether or not it succeeds.
|
||||
bool orchard_builder_add_spend(
|
||||
OrchardBuilderPtr* ptr,
|
||||
OrchardSpendInfoPtr* spend_info);
|
||||
|
||||
/// Adds an address which will receive funds in this bundle.
|
||||
///
|
||||
/// `ovk` is a pointer to the outgoing viewing key to make this recipient recoverable by,
|
||||
/// or `null` to make the recipient unrecoverable by the sender.
|
||||
///
|
||||
/// `memo` is a pointer to the 512-byte memo field encoding, or `null` for "no memo".
|
||||
bool orchard_builder_add_recipient(
|
||||
OrchardBuilderPtr* ptr,
|
||||
const unsigned char* ovk,
|
||||
const OrchardRawAddressPtr* recipient,
|
||||
uint64_t value,
|
||||
const unsigned char* memo);
|
||||
|
||||
/// Builds a bundle containing the given spent notes and recipients.
|
||||
///
|
||||
/// Returns `null` if an error occurs.
|
||||
///
|
||||
/// `builder` is always freed by this method.
|
||||
OrchardUnauthorizedBundlePtr* orchard_builder_build(OrchardBuilderPtr* builder);
|
||||
|
||||
/// Frees an Orchard bundle returned from `orchard_bundle_build`.
|
||||
void orchard_unauthorized_bundle_free(OrchardUnauthorizedBundlePtr* bundle);
|
||||
|
||||
/// Adds proofs and signatures to the bundle.
|
||||
///
|
||||
/// Returns `null` if an error occurs.
|
||||
///
|
||||
/// `bundle` is always freed by this method.
|
||||
OrchardBundlePtr* orchard_unauthorized_bundle_prove_and_sign(
|
||||
OrchardUnauthorizedBundlePtr* bundle,
|
||||
const OrchardSpendingKeyPtr** keys,
|
||||
size_t keys_len,
|
||||
const unsigned char* sighash);
|
||||
|
||||
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
|
||||
/// transaction.
|
||||
///
|
||||
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
|
||||
/// will be unaltered.
|
||||
///
|
||||
/// `preTx` is always freed by this method.
|
||||
bool zcash_builder_zip244_shielded_signature_digest(
|
||||
PrecomputedTxParts* preTx,
|
||||
const OrchardUnauthorizedBundlePtr* bundle,
|
||||
unsigned char* sighash_ret);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ZCASH_RUST_INCLUDE_RUST_BUILDER_H
|
|
@ -13,6 +13,7 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Typesafe pointer to a Rust-allocated orchard::bundle::Bundle value
|
||||
struct OrchardBundlePtr;
|
||||
typedef struct OrchardBundlePtr OrchardBundlePtr;
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ void orchard_merkle_frontier_root(
|
|||
// The total number of leaves that have been appended to obtain
|
||||
// the current state of the frontier. Subtract 1 from this value
|
||||
// to obtain the position of the most recently appended leaf.
|
||||
size_t orchard_merkle_frontier_num_leaves(
|
||||
uint64_t orchard_merkle_frontier_num_leaves(
|
||||
const OrchardMerkleFrontierPtr* tree_ptr);
|
||||
|
||||
// Estimate the amount of memory consumed by the merkle frontier.
|
||||
|
|
|
@ -32,6 +32,43 @@ OrchardRawAddressPtr* orchard_address_clone(
|
|||
*/
|
||||
void orchard_address_free(OrchardRawAddressPtr* ptr);
|
||||
|
||||
/**
|
||||
* Parses Orchard raw address bytes from the given stream.
|
||||
*
|
||||
* - If the key does not parse correctly, the returned pointer will be null.
|
||||
*/
|
||||
OrchardRawAddressPtr* orchard_raw_address_parse(
|
||||
void* stream,
|
||||
read_callback_t read_cb);
|
||||
|
||||
|
||||
/**
|
||||
* Serializes Orchard raw address bytes to the given stream.
|
||||
*
|
||||
* This will return `false` and leave the stream unmodified if
|
||||
* `raw_address == nullptr`;
|
||||
*/
|
||||
bool orchard_raw_address_serialize(
|
||||
const OrchardRawAddressPtr* raw_address,
|
||||
void* stream,
|
||||
write_callback_t write_cb);
|
||||
|
||||
/**
|
||||
* Implements the "equal" operation for comparing two Orchard addresses.
|
||||
*/
|
||||
bool orchard_address_eq(
|
||||
const OrchardRawAddressPtr* k0,
|
||||
const OrchardRawAddressPtr* k1);
|
||||
|
||||
/**
|
||||
* Implements the "less than" operation `k0 < k1` for comparing two Orchard
|
||||
* addresses. This is a comparison of the raw bytes, only useful for cases
|
||||
* where a semantically irrelevant ordering is needed (such as for map keys).
|
||||
*/
|
||||
bool orchard_address_lt(
|
||||
const OrchardRawAddressPtr* k0,
|
||||
const OrchardRawAddressPtr* k1);
|
||||
|
||||
//
|
||||
// Incoming Viewing Keys
|
||||
//
|
||||
|
@ -60,6 +97,18 @@ OrchardRawAddressPtr* orchard_incoming_viewing_key_to_address(
|
|||
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
|
||||
const unsigned char* j);
|
||||
|
||||
/**
|
||||
* Decrypts the diversifier component of an Orchard raw address with the
|
||||
* specified IVK, and verifies that the address was derived from that IVK.
|
||||
*
|
||||
* Returns `false` and leaves the `j_ret` parameter unmodified if the address
|
||||
* was not derived from the specified IVK.
|
||||
*/
|
||||
bool orchard_incoming_viewing_key_decrypt_diversifier(
|
||||
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
|
||||
const OrchardRawAddressPtr* addr,
|
||||
uint8_t *j_ret);
|
||||
|
||||
/**
|
||||
* Parses an Orchard incoming viewing key from the given stream.
|
||||
*
|
||||
|
@ -69,8 +118,12 @@ OrchardIncomingViewingKeyPtr* orchard_incoming_viewing_key_parse(
|
|||
void* stream,
|
||||
read_callback_t read_cb);
|
||||
|
||||
|
||||
/**
|
||||
* Serializes an Orchard incoming viewing key to the given stream.
|
||||
*
|
||||
* This will return `false` and leave the stream unmodified if
|
||||
* `incoming_viewing_key == nullptr`.
|
||||
*/
|
||||
bool orchard_incoming_viewing_key_serialize(
|
||||
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
|
||||
|
@ -123,6 +176,9 @@ OrchardFullViewingKeyPtr* orchard_full_viewing_key_parse(
|
|||
|
||||
/**
|
||||
* Serializes an Orchard full viewing key to the given stream.
|
||||
*
|
||||
* This will return `false` and leave the stream unmodified if
|
||||
* `full_viewing_key == nullptr`.
|
||||
*/
|
||||
bool orchard_full_viewing_key_serialize(
|
||||
const OrchardFullViewingKeyPtr* full_viewing_key,
|
||||
|
@ -135,6 +191,30 @@ bool orchard_full_viewing_key_serialize(
|
|||
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_incoming_viewing_key(
|
||||
const OrchardFullViewingKeyPtr* key);
|
||||
|
||||
/**
|
||||
* Returns the internal incoming viewing key for the specified full viewing key.
|
||||
*/
|
||||
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_internal_incoming_viewing_key(
|
||||
const OrchardFullViewingKeyPtr* key);
|
||||
|
||||
/**
|
||||
* Returns the external outgoing viewing key for the specified full viewing key.
|
||||
*
|
||||
* `ovk_ret` must be 32 bytes.
|
||||
*/
|
||||
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_external_outgoing_viewing_key(
|
||||
const OrchardFullViewingKeyPtr* fvk,
|
||||
uint8_t *ovk_ret);
|
||||
|
||||
/**
|
||||
* Returns the internal outgoing viewing key for the specified full viewing key.
|
||||
*
|
||||
* `ovk_ret` must be 32 bytes.
|
||||
*/
|
||||
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_internal_outgoing_viewing_key(
|
||||
const OrchardFullViewingKeyPtr* fvk,
|
||||
uint8_t *ovk_ret);
|
||||
|
||||
/**
|
||||
* Implements equality testing between full viewing keys.
|
||||
*/
|
||||
|
@ -173,25 +253,11 @@ OrchardSpendingKeyPtr* orchard_spending_key_clone(
|
|||
*/
|
||||
void orchard_spending_key_free(OrchardSpendingKeyPtr* ptr);
|
||||
|
||||
/**
|
||||
* Parses an Orchard spending key from the given stream.
|
||||
*
|
||||
* - If the key does not parse correctly, the returned pointer will be null.
|
||||
*/
|
||||
OrchardSpendingKeyPtr* orchard_spending_key_parse(
|
||||
void* stream,
|
||||
read_callback_t read_cb);
|
||||
|
||||
/**
|
||||
* Serializes an Orchard spending key to the given stream.
|
||||
*/
|
||||
bool orchard_spending_key_serialize(
|
||||
const OrchardSpendingKeyPtr* spending_key,
|
||||
void* stream,
|
||||
write_callback_t write_cb);
|
||||
|
||||
/**
|
||||
* Returns the full viewing key for the specified spending key.
|
||||
*
|
||||
* The resulting pointer must be ultimately freed by the caller
|
||||
* using `orchard_full_viewing_key_free`.
|
||||
*/
|
||||
OrchardFullViewingKeyPtr* orchard_spending_key_to_full_viewing_key(
|
||||
const OrchardSpendingKeyPtr* key);
|
||||
|
|
|
@ -0,0 +1,379 @@
|
|||
// Copyright (c) 2021 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#ifndef ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
|
||||
#define ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
|
||||
|
||||
#include "rust/orchard/keys.h"
|
||||
#include "rust/builder.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A type-safe pointer type for an Orchard wallet.
|
||||
*/
|
||||
struct OrchardWalletPtr;
|
||||
typedef struct OrchardWalletPtr OrchardWalletPtr;
|
||||
|
||||
/**
|
||||
* Constructs a new empty Orchard wallet and return a pointer to it.
|
||||
* Memory is allocated by Rust and must be manually freed using
|
||||
* `orchard_wallet_free`.
|
||||
*/
|
||||
OrchardWalletPtr* orchard_wallet_new();
|
||||
|
||||
/**
|
||||
* Frees the memory associated with an Orchard wallet that was allocated
|
||||
* by Rust.
|
||||
*/
|
||||
void orchard_wallet_free(OrchardWalletPtr* wallet);
|
||||
|
||||
/**
|
||||
* Reset the state of the wallet to be suitable for rescan from the NU5 activation
|
||||
* height. This removes all witness and spentness information from the wallet. The
|
||||
* keystore is unmodified and decrypted note, nullifier, and conflict data are left
|
||||
* in place with the expectation that they will be overwritten and/or updated in
|
||||
* the rescan process.
|
||||
*/
|
||||
bool orchard_wallet_reset(OrchardWalletPtr* wallet);
|
||||
|
||||
/**
|
||||
* Checkpoint the note commitment tree. This returns `false` and leaves the note
|
||||
* commitment tree unmodified if the block height specified is not the successor
|
||||
* to the last block height checkpointed.
|
||||
*/
|
||||
bool orchard_wallet_checkpoint(
|
||||
OrchardWalletPtr* wallet,
|
||||
uint32_t blockHeight
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns whether or not the wallet has any checkpointed state to which it can rewind.
|
||||
* If so, `blockHeightRet` will be modified to contain the last block height at which a
|
||||
* checkpoint was created.
|
||||
*/
|
||||
bool orchard_wallet_get_last_checkpoint(
|
||||
const OrchardWalletPtr* wallet,
|
||||
uint32_t* blockHeightRet);
|
||||
|
||||
/**
|
||||
* Rewinds to the most recent checkpoint, and marks as unspent any notes previously
|
||||
* identified as having been spent by transactions in the latest block.
|
||||
*
|
||||
* The `blockHeight` argument provides the height to which the witness tree should be
|
||||
* rewound, such that after the rewind this height corresponds to the latest block
|
||||
* appended to the tree. The number of blocks that were removed from the witness
|
||||
* tree in the rewind process is returned via `blocksRewoundRet`.
|
||||
*
|
||||
* Returns `true` if the rewind is successful, in which case the number of blocks that were
|
||||
* removed from the witness tree in the rewind process is returned via `blocksRewoundRet`;
|
||||
* this returns `false` and leaves `blocksRewoundRet` unmodified.
|
||||
*/
|
||||
bool orchard_wallet_rewind(
|
||||
OrchardWalletPtr* wallet,
|
||||
uint32_t blockHeight,
|
||||
uint32_t* blocksRewoundRet
|
||||
);
|
||||
|
||||
/**
|
||||
* A C struct used to transfer action_idx/IVK pairs back from Rust across the FFI
|
||||
* boundary. This must have the same in-memory representation as the `FFIActionIVK` type
|
||||
* in orchard_ffi/wallet.rs.
|
||||
*
|
||||
* Values of the `ivk` pointer must be freed manually; the best way to do this is to
|
||||
* wrap this pointer in an `OrchardIncomingViewingKey` which handles deallocation
|
||||
* in the object destructor.
|
||||
*/
|
||||
struct RawOrchardActionIVK {
|
||||
uint64_t actionIdx;
|
||||
OrchardIncomingViewingKeyPtr* ivk;
|
||||
};
|
||||
static_assert(
|
||||
sizeof(RawOrchardActionIVK) == 16,
|
||||
"RawOrchardActionIVK struct should have exactly a 128-bit in-memory representation.");
|
||||
static_assert(alignof(RawOrchardActionIVK) == 8, "RawOrchardActionIVK struct alignment is not 64 bits.");
|
||||
|
||||
typedef void (*push_action_ivk_callback_t)(void* rec, const RawOrchardActionIVK actionIvk);
|
||||
|
||||
typedef void (*push_spend_action_idx_callback_t)(void* rec, uint32_t actionIdx);
|
||||
|
||||
/**
|
||||
* Searches the provided bundle for notes that are visible to the specified wallet's
|
||||
* incoming viewing keys, and adds those notes to the wallet. For each note decryptable
|
||||
* by one of the wallet's keys, this method will insert a `RawOrchardActionIVK` value into
|
||||
* the provided `callbackReceiver` referent using the `push_cb` callback. Note that
|
||||
* this callback can perform transformations on the provided RawOrchardActionIVK in this
|
||||
* process. For each action spending one of the wallet's notes, this method will pass
|
||||
* a `uint32_t` action index corresponding to that action to the `callbackReceiver` referent;
|
||||
* using the specified callback; usually, this will push the value into a result vector owned
|
||||
* by the caller.
|
||||
*
|
||||
* The provided bundle must be a component of the transaction from which `txid` was
|
||||
* derived.
|
||||
*
|
||||
* Returns `true` if the bundle is involved with the wallet; i.e. if it contains
|
||||
* notes spendable by the wallet, or spends any of the wallet's notes.
|
||||
*/
|
||||
bool orchard_wallet_add_notes_from_bundle(
|
||||
OrchardWalletPtr* wallet,
|
||||
const unsigned char txid[32],
|
||||
const OrchardBundlePtr* bundle,
|
||||
void* callbackReceiver,
|
||||
push_action_ivk_callback_t push_cb,
|
||||
push_spend_action_idx_callback_t spend_cb
|
||||
);
|
||||
|
||||
/**
|
||||
* Decrypts a selection of notes from the bundle with specified incoming viewing
|
||||
* keys, and adds those notes to the wallet.
|
||||
*
|
||||
* The provided bundle must be a component of the transaction from which
|
||||
* `txid` was derived.
|
||||
*
|
||||
* The value the `blockHeight` pointer points to be set to the height at which the
|
||||
* transaction was mined, or `nullptr` if the transaction is not in the main chain.
|
||||
*/
|
||||
bool orchard_wallet_load_bundle(
|
||||
OrchardWalletPtr* wallet,
|
||||
const uint32_t* blockHeight,
|
||||
const unsigned char txid[32],
|
||||
const OrchardBundlePtr* bundle,
|
||||
const RawOrchardActionIVK* actionIvks,
|
||||
size_t actionIvksLen,
|
||||
const uint32_t* actionsSpendingWalletNotes,
|
||||
size_t actionsSpendingWalletNotesLen
|
||||
);
|
||||
|
||||
/**
|
||||
* Add the note commitment values for the specified bundle to the wallet's note
|
||||
* commitment tree, and mark any Orchard notes that belong to the wallet so
|
||||
* that we can construct authentication paths to these notes in the future.
|
||||
*
|
||||
* This requires the block height and the index of the block within the
|
||||
* transaction in order to guarantee that note commitments are appended in the
|
||||
* correct order. Returns `false` if the provided bundle is not in the correct
|
||||
* position to have its note commitments appended to the note commitment tree.
|
||||
*/
|
||||
bool orchard_wallet_append_bundle_commitments(
|
||||
OrchardWalletPtr* wallet,
|
||||
const uint32_t block_height,
|
||||
const size_t block_tx_idx,
|
||||
const unsigned char txid[32],
|
||||
const OrchardBundlePtr* bundle
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the root of the wallet's note commitment tree.
|
||||
*/
|
||||
void orchard_wallet_commitment_tree_root(
|
||||
const OrchardWalletPtr* wallet,
|
||||
unsigned char* root_ret);
|
||||
|
||||
/**
|
||||
* Returns whether the specified transaction involves any Orchard notes that belong to
|
||||
* this wallet.
|
||||
*/
|
||||
bool orchard_wallet_tx_involves_my_notes(
|
||||
const OrchardWalletPtr* wallet,
|
||||
const unsigned char txid[32]);
|
||||
|
||||
/**
|
||||
* Add the specified spending key to the wallet's key store. This will also compute and
|
||||
* add the associated full and incoming viewing keys.
|
||||
*/
|
||||
void orchard_wallet_add_spending_key(
|
||||
OrchardWalletPtr* wallet,
|
||||
const OrchardSpendingKeyPtr* sk);
|
||||
|
||||
/**
|
||||
* Add the specified full viewing key to the wallet's key store. This will also compute
|
||||
* and add the associated incoming viewing key.
|
||||
*/
|
||||
void orchard_wallet_add_full_viewing_key(
|
||||
OrchardWalletPtr* wallet,
|
||||
const OrchardFullViewingKeyPtr* fvk);
|
||||
|
||||
/**
|
||||
* Add the specified raw address to the wallet's key store, associated with the incoming
|
||||
* viewing key from which that address was derived.
|
||||
*/
|
||||
bool orchard_wallet_add_raw_address(
|
||||
OrchardWalletPtr* wallet,
|
||||
const OrchardRawAddressPtr* addr,
|
||||
const OrchardIncomingViewingKeyPtr* ivk);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the Orchard spending key corresponding to the specified raw
|
||||
* address, if it is known to the wallet, or `nullptr` otherwise.
|
||||
*
|
||||
* Memory is allocated by Rust and must be manually freed using
|
||||
* `orchard_spending_key_free`.
|
||||
*/
|
||||
OrchardSpendingKeyPtr* orchard_wallet_get_spending_key_for_address(
|
||||
const OrchardWalletPtr* wallet,
|
||||
const OrchardRawAddressPtr* addr);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the Orchard incoming viewing key corresponding to the specified
|
||||
* raw address, if it is known to the wallet, or `nullptr` otherwise.
|
||||
*
|
||||
* Memory is allocated by Rust and must be manually freed using
|
||||
* `orchard_incoming_viewing_key_free`.
|
||||
*/
|
||||
OrchardIncomingViewingKeyPtr* orchard_wallet_get_ivk_for_address(
|
||||
const OrchardWalletPtr* wallet,
|
||||
const OrchardRawAddressPtr* addr);
|
||||
|
||||
/**
|
||||
* A C struct used to transfer note metadata information across the Rust FFI boundary.
|
||||
* This must have the same in-memory representation as the `FFINoteMetadata` type in
|
||||
* orchard_ffi/wallet.rs.
|
||||
*/
|
||||
struct RawOrchardNoteMetadata {
|
||||
unsigned char txid[32];
|
||||
uint32_t actionIdx;
|
||||
OrchardRawAddressPtr* addr;
|
||||
CAmount noteValue;
|
||||
unsigned char memo[512];
|
||||
};
|
||||
|
||||
typedef void (*push_note_callback_t)(void* resultVector, const RawOrchardNoteMetadata noteMeta);
|
||||
|
||||
/**
|
||||
* Finds notes that belong to the wallet that were sent to addresses derived from the
|
||||
* specified incoming viewing key, subject to the specified flags, and uses the provided
|
||||
* callback to push RawOrchardNoteMetadata values corresponding to those notes on to the
|
||||
* provided result vector. Note that the push_cb callback can perform any necessary
|
||||
* conversion from a RawOrchardNoteMetadata value in addition to modifying the provided
|
||||
* result vector.
|
||||
*
|
||||
* If `ivk` is null, all notes belonging to the wallet will be returned. The
|
||||
* `RawOrchardNoteMetadata::addr` pointers for values provided to the callback must be
|
||||
* manually freed by the caller.
|
||||
*/
|
||||
void orchard_wallet_get_filtered_notes(
|
||||
const OrchardWalletPtr* wallet,
|
||||
const OrchardIncomingViewingKeyPtr* ivk,
|
||||
bool ignoreMined,
|
||||
bool requireSpendingKey,
|
||||
void* resultVector,
|
||||
push_note_callback_t push_cb
|
||||
);
|
||||
|
||||
/**
|
||||
* A C struct used to transfer Orchard action spend information across the FFI boundary.
|
||||
* This must have the same in-memory representation as the `FFIActionSpend` type in
|
||||
* orchard_ffi/wallet.rs.
|
||||
*/
|
||||
struct RawOrchardActionSpend {
|
||||
uint32_t spendActionIdx;
|
||||
unsigned char outpointTxId[32];
|
||||
uint32_t outpointActionIdx;
|
||||
OrchardRawAddressPtr* receivedAt;
|
||||
CAmount noteValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* A C struct used to transfer Orchard action output information across the FFI boundary.
|
||||
* This must have the same in-memory representation as the `FFIActionOutput` type in
|
||||
* orchard_ffi/wallet.rs.
|
||||
*/
|
||||
struct RawOrchardActionOutput {
|
||||
uint32_t outputActionIdx;
|
||||
OrchardRawAddressPtr* recipient;
|
||||
CAmount noteValue;
|
||||
unsigned char memo[512];
|
||||
bool isOutgoing;
|
||||
};
|
||||
|
||||
typedef void (*push_spend_t)(void* callbackReceiver, const RawOrchardActionSpend data);
|
||||
|
||||
typedef void (*push_output_t)(void* callbackReceiver, const RawOrchardActionOutput data);
|
||||
|
||||
/**
|
||||
* Trial-decrypts the specfied Orchard bundle, and uses the provided callbacks to pass
|
||||
* `RawOrchardActionSpend` and `RawOrchardActionOutput` values (corresponding to the
|
||||
* actions of that bundle) to the provided result receiver.
|
||||
*
|
||||
* Note that the callbacks can perform any necessary conversion from a
|
||||
* `RawOrchardActionSpend` or `RawOrchardActionOutput` value in addition to modifying the
|
||||
* provided result receiver.
|
||||
*
|
||||
* `raw_ovks` must be a pointer to an array of `unsigned char[32]`.
|
||||
*
|
||||
* The `recipient` pointer for each `RawOrchardActionOutput` value, and the `receivedAt`
|
||||
* pointer for each `RawOrchardActionSpend` value, must be freed using
|
||||
* `orchard_address_free`.
|
||||
*/
|
||||
bool orchard_wallet_get_txdata(
|
||||
const OrchardWalletPtr* wallet,
|
||||
const OrchardBundlePtr* bundle,
|
||||
const unsigned char* raw_ovks,
|
||||
size_t raw_ovks_len,
|
||||
void* callbackReceiver,
|
||||
push_spend_t push_spend_cb,
|
||||
push_output_t push_output_cb
|
||||
);
|
||||
|
||||
typedef void (*push_txid_callback_t)(void* resultVector, unsigned char txid[32]);
|
||||
|
||||
/**
|
||||
* Returns a vector of transaction IDs for transactions that have been observed as
|
||||
* spending the given outpoint (transaction ID and action index) by using the `push_cb`
|
||||
* callback to push transaction IDs onto the provided result vector.
|
||||
*/
|
||||
void orchard_wallet_get_potential_spends(
|
||||
const OrchardWalletPtr* wallet,
|
||||
const unsigned char txid[32],
|
||||
const uint32_t action_idx,
|
||||
void* resultVector,
|
||||
push_txid_callback_t push_cb
|
||||
);
|
||||
|
||||
/**
|
||||
* Fetches the information needed to spend the wallet note at the given outpoint,
|
||||
* relative to the current root known to the wallet of the Orchard commitment
|
||||
* tree.
|
||||
*
|
||||
* Returns `null` if the outpoint is not known to the wallet, or the Orchard
|
||||
* bundle containing the note has not been passed to
|
||||
* `orchard_wallet_append_bundle_commitments`.
|
||||
*/
|
||||
OrchardSpendInfoPtr* orchard_wallet_get_spend_info(
|
||||
const OrchardWalletPtr* wallet,
|
||||
const unsigned char txid[32],
|
||||
uint32_t action_idx);
|
||||
|
||||
/**
|
||||
* Run the garbage collection operation on the wallet's note commitment
|
||||
* tree.
|
||||
*/
|
||||
void orchard_wallet_gc_note_commitment_tree(OrchardWalletPtr* wallet);
|
||||
|
||||
/**
|
||||
* Write the wallet's note commitment tree to the provided stream.
|
||||
*/
|
||||
bool orchard_wallet_write_note_commitment_tree(
|
||||
const OrchardWalletPtr* wallet,
|
||||
void* stream,
|
||||
write_callback_t write_cb);
|
||||
|
||||
/**
|
||||
* Read a note commitment tree from the provided stream, and update the wallet's internal
|
||||
* note commitment tree state to equal the value that was read.
|
||||
*/
|
||||
bool orchard_wallet_load_note_commitment_tree(
|
||||
OrchardWalletPtr* wallet,
|
||||
void* stream,
|
||||
read_callback_t read_cb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
|
||||
|
||||
|
|
@ -84,6 +84,22 @@ bool unified_full_viewing_key_read_sapling(
|
|||
const UnifiedFullViewingKeyPtr* full_viewing_key,
|
||||
unsigned char* skeyout);
|
||||
|
||||
/**
|
||||
* Reads the Orchard component of a unified full viewing key.
|
||||
*
|
||||
* `skeyout` must be of length 96.
|
||||
*
|
||||
* Returns `true` if the UFVK contained an Orchard component, `false` otherwise.
|
||||
* The bytes of the Orchard Raw Full Viewing Key, in the encoding given in
|
||||
* section 5.6.4.4 of the Zcash Protocol Specification, will be copied to
|
||||
* `skeyout` if `true` is returned.
|
||||
*
|
||||
* If `false` is returned then `skeyout` will be unchanged.
|
||||
*/
|
||||
bool unified_full_viewing_key_read_orchard(
|
||||
const UnifiedFullViewingKeyPtr* full_viewing_key,
|
||||
unsigned char* skeyout);
|
||||
|
||||
/**
|
||||
* Constructs a unified full viewing key from the binary encodings
|
||||
* of its constituent parts.
|
||||
|
@ -101,7 +117,8 @@ bool unified_full_viewing_key_read_sapling(
|
|||
*/
|
||||
UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components(
|
||||
const unsigned char* t_key,
|
||||
const unsigned char* sapling_key);
|
||||
const unsigned char* sapling_key,
|
||||
const unsigned char* orchard_key);
|
||||
|
||||
/**
|
||||
* Derive the internal and external OVKs for the binary encoding
|
||||
|
|
|
@ -12,6 +12,8 @@ use zcash_address::{
|
|||
use zcash_primitives::sapling;
|
||||
|
||||
pub type UnifiedAddressObj = NonNull<c_void>;
|
||||
pub type AddOrchardReceiverCb =
|
||||
unsafe extern "C" fn(ua: Option<UnifiedAddressObj>, orchard: *const orchard::Address) -> bool;
|
||||
pub type AddReceiverCb =
|
||||
unsafe extern "C" fn(ua: Option<UnifiedAddressObj>, raw: *const u8) -> bool;
|
||||
pub type UnknownReceiverCb = unsafe extern "C" fn(
|
||||
|
@ -53,10 +55,12 @@ impl FromAddress for UnifiedAddressHelper {
|
|||
}
|
||||
|
||||
impl UnifiedAddressHelper {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn into_cpp(
|
||||
self,
|
||||
network: Network,
|
||||
ua_obj: Option<UnifiedAddressObj>,
|
||||
orchard_cb: Option<AddOrchardReceiverCb>,
|
||||
sapling_cb: Option<AddReceiverCb>,
|
||||
p2sh_cb: Option<AddReceiverCb>,
|
||||
p2pkh_cb: Option<AddReceiverCb>,
|
||||
|
@ -79,16 +83,13 @@ impl UnifiedAddressHelper {
|
|||
// ZIP 316: Consumers MUST reject Unified Addresses/Viewing Keys in
|
||||
// which any constituent Item does not meet the validation
|
||||
// requirements of its encoding.
|
||||
if orchard::Address::from_raw_address_bytes(&data)
|
||||
.is_none()
|
||||
.into()
|
||||
{
|
||||
let addr = orchard::Address::from_raw_address_bytes(&data);
|
||||
if addr.is_none().into() {
|
||||
tracing::error!("Unified Address contains invalid Orchard receiver");
|
||||
false
|
||||
} else {
|
||||
unsafe {
|
||||
// TODO: Replace with Orchard support.
|
||||
(unknown_cb.unwrap())(ua_obj, 0x03, data.as_ptr(), data.len())
|
||||
(orchard_cb.unwrap())(ua_obj, Box::into_raw(Box::new(addr.unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +123,7 @@ pub extern "C" fn zcash_address_parse_unified(
|
|||
encoded: *const c_char,
|
||||
network: *const c_char,
|
||||
ua_obj: Option<UnifiedAddressObj>,
|
||||
orchard_cb: Option<AddOrchardReceiverCb>,
|
||||
sapling_cb: Option<AddReceiverCb>,
|
||||
p2sh_cb: Option<AddReceiverCb>,
|
||||
p2pkh_cb: Option<AddReceiverCb>,
|
||||
|
@ -149,7 +151,9 @@ pub extern "C" fn zcash_address_parse_unified(
|
|||
}
|
||||
};
|
||||
|
||||
ua.into_cpp(network, ua_obj, sapling_cb, p2sh_cb, p2pkh_cb, unknown_cb)
|
||||
ua.into_cpp(
|
||||
network, ua_obj, orchard_cb, sapling_cb, p2sh_cb, p2pkh_cb, unknown_cb,
|
||||
)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -171,14 +175,9 @@ pub extern "C" fn zcash_address_serialize_unified(
|
|||
Ok(
|
||||
match unsafe { (typecode_cb.unwrap())(ua_obj, i) }.try_into()? {
|
||||
unified::Typecode::Orchard => {
|
||||
// TODO: Replace with Orchard support.
|
||||
let data_len = unsafe { (receiver_len_cb.unwrap())(ua_obj, i) };
|
||||
let mut data = vec![0; data_len];
|
||||
unsafe { (receiver_cb.unwrap())(ua_obj, i, data.as_mut_ptr(), data_len) };
|
||||
unified::Receiver::Unknown {
|
||||
typecode: 0x03,
|
||||
data,
|
||||
}
|
||||
let mut data = [0; 43];
|
||||
unsafe { (receiver_cb.unwrap())(ua_obj, i, data.as_mut_ptr(), data.len()) };
|
||||
unified::Receiver::Orchard(data)
|
||||
}
|
||||
unified::Typecode::Sapling => {
|
||||
let mut data = [0; 43];
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
use std::convert::TryInto;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
|
||||
use incrementalmerkletree::Hashable;
|
||||
use libc::size_t;
|
||||
use orchard::keys::SpendingKey;
|
||||
use orchard::{
|
||||
builder::{Builder, InProgress, Unauthorized, Unproven},
|
||||
bundle::{Authorized, Flags},
|
||||
keys::{FullViewingKey, OutgoingViewingKey},
|
||||
tree::{MerkleHashOrchard, MerklePath},
|
||||
value::NoteValue,
|
||||
Bundle, Note,
|
||||
};
|
||||
use rand_core::OsRng;
|
||||
use tracing::error;
|
||||
use zcash_primitives::transaction::{
|
||||
components::{sapling, Amount},
|
||||
sighash::SignableInput,
|
||||
sighash_v5::v5_signature_hash,
|
||||
txid::TxIdDigester,
|
||||
Authorization, TransactionData, TxVersion,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
transaction_ffi::{PrecomputedTxParts, TransparentAuth},
|
||||
ORCHARD_PK,
|
||||
};
|
||||
|
||||
pub struct OrchardSpendInfo {
|
||||
fvk: FullViewingKey,
|
||||
note: Note,
|
||||
merkle_path: MerklePath,
|
||||
}
|
||||
|
||||
impl OrchardSpendInfo {
|
||||
pub fn from_parts(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Self {
|
||||
OrchardSpendInfo {
|
||||
fvk,
|
||||
note,
|
||||
merkle_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_spend_info_free(spend_info: *mut OrchardSpendInfo) {
|
||||
if !spend_info.is_null() {
|
||||
drop(unsafe { Box::from_raw(spend_info) });
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_builder_new(
|
||||
spends_enabled: bool,
|
||||
outputs_enabled: bool,
|
||||
anchor: *const [u8; 32],
|
||||
) -> *mut Builder {
|
||||
let anchor = unsafe { anchor.as_ref() }
|
||||
.map(|a| orchard::Anchor::from_bytes(*a).unwrap())
|
||||
.unwrap_or_else(|| MerkleHashOrchard::empty_root(32.into()).into());
|
||||
Box::into_raw(Box::new(Builder::new(
|
||||
Flags::from_parts(spends_enabled, outputs_enabled),
|
||||
anchor,
|
||||
)))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_builder_add_spend(
|
||||
builder: *mut Builder,
|
||||
orchard_spend_info: *mut OrchardSpendInfo,
|
||||
) -> bool {
|
||||
let builder = unsafe { builder.as_mut() }.expect("Builder may not be null.");
|
||||
let orchard_spend_info = unsafe { Box::from_raw(orchard_spend_info) };
|
||||
|
||||
match builder.add_spend(
|
||||
orchard_spend_info.fvk,
|
||||
orchard_spend_info.note,
|
||||
orchard_spend_info.merkle_path,
|
||||
) {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
error!("Failed to add Orchard spend: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_builder_add_recipient(
|
||||
builder: *mut Builder,
|
||||
ovk: *const [u8; 32],
|
||||
recipient: *const orchard::Address,
|
||||
value: u64,
|
||||
memo: *const [u8; 512],
|
||||
) -> bool {
|
||||
let builder = unsafe { builder.as_mut() }.expect("Builder may not be null.");
|
||||
let ovk = unsafe { ovk.as_ref() }
|
||||
.copied()
|
||||
.map(OutgoingViewingKey::from);
|
||||
let recipient = unsafe { recipient.as_ref() }.expect("Recipient may not be null.");
|
||||
let value = NoteValue::from_raw(value);
|
||||
let memo = unsafe { memo.as_ref() }.copied();
|
||||
|
||||
match builder.add_recipient(ovk, *recipient, value, memo) {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
error!("Failed to add Orchard recipient: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_builder_free(builder: *mut Builder) {
|
||||
if !builder.is_null() {
|
||||
drop(unsafe { Box::from_raw(builder) });
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_builder_build(
|
||||
builder: *mut Builder,
|
||||
) -> *mut Bundle<InProgress<Unproven, Unauthorized>, Amount> {
|
||||
if builder.is_null() {
|
||||
error!("Called with null builder");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let builder = unsafe { Box::from_raw(builder) };
|
||||
|
||||
match builder.build(OsRng) {
|
||||
Ok(bundle) => Box::into_raw(Box::new(bundle)),
|
||||
Err(e) => {
|
||||
error!("Failed to build Orchard bundle: {:?}", e);
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_unauthorized_bundle_free(
|
||||
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||
) {
|
||||
if !bundle.is_null() {
|
||||
drop(unsafe { Box::from_raw(bundle) });
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_unauthorized_bundle_prove_and_sign(
|
||||
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||
keys: *const *const SpendingKey,
|
||||
keys_len: size_t,
|
||||
sighash: *const [u8; 32],
|
||||
) -> *mut Bundle<Authorized, Amount> {
|
||||
let bundle = unsafe { Box::from_raw(bundle) };
|
||||
let keys = unsafe { slice::from_raw_parts(keys, keys_len) };
|
||||
let sighash = unsafe { sighash.as_ref() }.expect("sighash pointer may not be null.");
|
||||
let pk = unsafe { ORCHARD_PK.as_ref() }.unwrap();
|
||||
|
||||
let signing_keys = keys
|
||||
.iter()
|
||||
.map(|sk| {
|
||||
unsafe { sk.as_ref() }
|
||||
.expect("SpendingKey pointers must not be null")
|
||||
.into()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut rng = OsRng;
|
||||
let res = bundle
|
||||
.create_proof(pk, &mut rng)
|
||||
.and_then(|b| b.apply_signatures(&mut rng, *sighash, &signing_keys));
|
||||
|
||||
match res {
|
||||
Ok(signed) => Box::into_raw(Box::new(signed)),
|
||||
Err(e) => {
|
||||
error!(
|
||||
"An error occurred while authorizing the orchard bundle: {:?}",
|
||||
e
|
||||
);
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
|
||||
/// transaction.
|
||||
///
|
||||
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
|
||||
/// will be unaltered.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcash_builder_zip244_shielded_signature_digest(
|
||||
precomputed_tx: *mut PrecomputedTxParts,
|
||||
bundle: *const Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||
sighash_ret: *mut [u8; 32],
|
||||
) -> bool {
|
||||
let precomputed_tx = if !precomputed_tx.is_null() {
|
||||
unsafe { Box::from_raw(precomputed_tx) }
|
||||
} else {
|
||||
error!("Invalid precomputed transaction");
|
||||
return false;
|
||||
};
|
||||
if matches!(
|
||||
precomputed_tx.tx.version(),
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling,
|
||||
) {
|
||||
error!("Cannot calculate ZIP 244 digest for pre-v5 transaction");
|
||||
return false;
|
||||
}
|
||||
let bundle = unsafe { bundle.as_ref().unwrap() };
|
||||
|
||||
struct Signable {}
|
||||
impl Authorization for Signable {
|
||||
type TransparentAuth = TransparentAuth;
|
||||
type SaplingAuth = sapling::Authorized;
|
||||
type OrchardAuth = InProgress<Unproven, Unauthorized>;
|
||||
}
|
||||
|
||||
let txdata: TransactionData<Signable> =
|
||||
precomputed_tx
|
||||
.tx
|
||||
.map_bundles(|b| b, |b| b, |_| Some(bundle.clone()));
|
||||
let txid_parts = txdata.digest(TxIdDigester);
|
||||
|
||||
let sighash = v5_signature_hash(&txdata, &SignableInput::Shielded, &txid_parts);
|
||||
|
||||
// `v5_signature_hash` output is always 32 bytes.
|
||||
*unsafe { &mut *sighash_ret } = sighash.as_ref().try_into().unwrap();
|
||||
true
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use incrementalmerkletree::{
|
||||
bridgetree::{BridgeTree, Checkpoint},
|
||||
Hashable,
|
||||
};
|
||||
use zcash_encoding::Vector;
|
||||
use zcash_primitives::merkle_tree::{
|
||||
incremental::{
|
||||
read_bridge_v1, read_leu64_usize, read_position, write_bridge_v1, write_position,
|
||||
write_usize_leu64, SER_V1,
|
||||
},
|
||||
HashSer,
|
||||
};
|
||||
|
||||
pub fn write_checkpoint_v1<H: HashSer + Ord, W: Write>(
|
||||
mut writer: W,
|
||||
checkpoint: &Checkpoint<H>,
|
||||
) -> io::Result<()> {
|
||||
write_usize_leu64(&mut writer, checkpoint.bridges_len())?;
|
||||
writer.write_u8(if checkpoint.is_witnessed() { 1 } else { 0 })?;
|
||||
Vector::write_sized(
|
||||
&mut writer,
|
||||
checkpoint.forgotten().iter(),
|
||||
|mut w, ((pos, leaf_value), idx)| {
|
||||
write_position(&mut w, *pos)?;
|
||||
leaf_value.write(&mut w)?;
|
||||
write_usize_leu64(&mut w, *idx)
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_checkpoint_v1<H: HashSer + Ord, R: Read>(mut reader: R) -> io::Result<Checkpoint<H>> {
|
||||
Ok(Checkpoint::from_parts(
|
||||
read_leu64_usize(&mut reader)?,
|
||||
reader.read_u8()? == 1,
|
||||
Vector::read_collected(&mut reader, |mut r| {
|
||||
Ok((
|
||||
(read_position(&mut r)?, H::read(&mut r)?),
|
||||
read_leu64_usize(&mut r)?,
|
||||
))
|
||||
})?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn write_tree_v1<H: Hashable + HashSer + Ord, W: Write>(
|
||||
mut writer: W,
|
||||
tree: &BridgeTree<H, 32>,
|
||||
) -> io::Result<()> {
|
||||
Vector::write(&mut writer, tree.bridges(), |w, b| write_bridge_v1(w, b))?;
|
||||
Vector::write_sized(
|
||||
&mut writer,
|
||||
tree.witnessed_indices().iter(),
|
||||
|mut w, ((pos, a), i)| {
|
||||
write_position(&mut w, *pos)?;
|
||||
a.write(&mut w)?;
|
||||
write_usize_leu64(&mut w, *i)
|
||||
},
|
||||
)?;
|
||||
Vector::write(&mut writer, tree.checkpoints(), |w, c| {
|
||||
write_checkpoint_v1(w, c)
|
||||
})?;
|
||||
write_usize_leu64(&mut writer, tree.max_checkpoints())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::redundant_closure)]
|
||||
pub fn read_tree_v1<H: Hashable + HashSer + Ord + Clone, R: Read>(
|
||||
mut reader: R,
|
||||
) -> io::Result<BridgeTree<H, 32>> {
|
||||
BridgeTree::from_parts(
|
||||
Vector::read(&mut reader, |r| read_bridge_v1(r))?,
|
||||
Vector::read_collected(&mut reader, |mut r| {
|
||||
Ok((
|
||||
(read_position(&mut r)?, H::read(&mut r)?),
|
||||
read_leu64_usize(&mut r)?,
|
||||
))
|
||||
})?,
|
||||
Vector::read(&mut reader, |r| read_checkpoint_v1(r))?,
|
||||
read_leu64_usize(&mut reader)?,
|
||||
)
|
||||
.map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"Consistency violation found when attempting to deserialize Merkle tree: {:?}",
|
||||
err
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_tree<H: Hashable + HashSer + Ord, W: Write>(
|
||||
mut writer: W,
|
||||
tree: &BridgeTree<H, 32>,
|
||||
) -> io::Result<()> {
|
||||
writer.write_u8(SER_V1)?;
|
||||
write_tree_v1(&mut writer, tree)
|
||||
}
|
||||
|
||||
pub fn read_tree<H: Hashable + HashSer + Ord + Clone, R: Read>(
|
||||
mut reader: R,
|
||||
) -> io::Result<BridgeTree<H, 32>> {
|
||||
match reader.read_u8()? {
|
||||
SER_V1 => read_tree_v1(&mut reader),
|
||||
flag => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("Unrecognized tree serialization version: {:?}", flag),
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -123,13 +123,13 @@ pub extern "C" fn orchard_merkle_frontier_root(
|
|||
#[no_mangle]
|
||||
pub extern "C" fn orchard_merkle_frontier_num_leaves(
|
||||
tree: *const bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
|
||||
) -> usize {
|
||||
) -> u64 {
|
||||
let tree = unsafe {
|
||||
tree.as_ref()
|
||||
.expect("Orchard note commitment tree pointer may not be null.")
|
||||
};
|
||||
|
||||
tree.position().map_or(0, |p| (<u64>::from(p) + 1) as usize)
|
||||
tree.position().map_or(0, |p| <u64>::from(p) + 1)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
|
|
@ -2,10 +2,15 @@ use std::io::{Read, Write};
|
|||
use std::slice;
|
||||
use tracing::error;
|
||||
|
||||
use orchard::keys::{DiversifierIndex, FullViewingKey, IncomingViewingKey, SpendingKey};
|
||||
use orchard::Address;
|
||||
use orchard::{
|
||||
keys::{DiversifierIndex, FullViewingKey, IncomingViewingKey, OutgoingViewingKey, SpendingKey},
|
||||
Address,
|
||||
};
|
||||
|
||||
use crate::streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb};
|
||||
use crate::{
|
||||
streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb},
|
||||
zcashd_orchard::OrderedAddress,
|
||||
};
|
||||
|
||||
//
|
||||
// Addresses
|
||||
|
@ -25,6 +30,63 @@ pub extern "C" fn orchard_address_free(addr: *mut Address) {
|
|||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_raw_address_parse(
|
||||
stream: Option<StreamObj>,
|
||||
read_cb: Option<ReadCb>,
|
||||
) -> *mut Address {
|
||||
let mut reader = CppStreamReader::from_raw_parts(stream, read_cb.unwrap());
|
||||
|
||||
let mut buf = [0u8; 43];
|
||||
match reader.read_exact(&mut buf) {
|
||||
Err(e) => {
|
||||
error!("Stream failure reading bytes of Orchard raw address: {}", e);
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
Ok(()) => {
|
||||
let read = Address::from_raw_address_bytes(&buf);
|
||||
if read.is_some().into() {
|
||||
Box::into_raw(Box::new(read.unwrap()))
|
||||
} else {
|
||||
error!("Failed to parse Orchard raw address.");
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_raw_address_serialize(
|
||||
key: *const Address,
|
||||
stream: Option<StreamObj>,
|
||||
write_cb: Option<WriteCb>,
|
||||
) -> bool {
|
||||
let key = unsafe { key.as_ref() }.expect("Orchard raw address pointer may not be null.");
|
||||
|
||||
let mut writer = CppStreamWriter::from_raw_parts(stream, write_cb.unwrap());
|
||||
match writer.write_all(&key.to_raw_address_bytes()) {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
error!("Stream failure writing Orchard raw address: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_address_eq(a0: *const Address, a1: *const Address) -> bool {
|
||||
let a0 = unsafe { a0.as_ref() };
|
||||
let a1 = unsafe { a1.as_ref() };
|
||||
a0 == a1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_address_lt(a0: *const Address, a1: *const Address) -> bool {
|
||||
let a0 = unsafe { a0.as_ref() };
|
||||
let a1 = unsafe { a1.as_ref() };
|
||||
a0.map(|a| OrderedAddress::new(*a)) < a1.map(|a| OrderedAddress::new(*a))
|
||||
}
|
||||
|
||||
//
|
||||
// Incoming viewing keys
|
||||
//
|
||||
|
@ -85,6 +147,26 @@ pub extern "C" fn orchard_incoming_viewing_key_to_address(
|
|||
Box::into_raw(Box::new(key.address_at(diversifier_index)))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_incoming_viewing_key_decrypt_diversifier(
|
||||
key: *const IncomingViewingKey,
|
||||
addr: *const Address,
|
||||
j_ret: *mut [u8; 11],
|
||||
) -> bool {
|
||||
let key =
|
||||
unsafe { key.as_ref() }.expect("Orchard incoming viewing key pointer may not be null.");
|
||||
let addr = unsafe { addr.as_ref() }.expect("Orchard raw address pointer may not be null.");
|
||||
let j_ret = unsafe { j_ret.as_mut() }.expect("j_ret may not be null.");
|
||||
|
||||
match key.diversifier_index(addr) {
|
||||
Some(j) => {
|
||||
j_ret.copy_from_slice(j.to_bytes());
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_incoming_viewing_key_serialize(
|
||||
key: *const IncomingViewingKey,
|
||||
|
@ -190,6 +272,43 @@ pub extern "C" fn orchard_full_viewing_key_to_incoming_viewing_key(
|
|||
.unwrap_or(std::ptr::null_mut())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_full_viewing_key_to_internal_incoming_viewing_key(
|
||||
fvk: *const FullViewingKey,
|
||||
) -> *mut IncomingViewingKey {
|
||||
unsafe { fvk.as_ref() }
|
||||
.map(|fvk| {
|
||||
let internal_fvk = fvk.derive_internal();
|
||||
Box::into_raw(Box::new(IncomingViewingKey::from(&internal_fvk)))
|
||||
})
|
||||
.unwrap_or(std::ptr::null_mut())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_full_viewing_key_to_external_outgoing_viewing_key(
|
||||
fvk: *const FullViewingKey,
|
||||
ovk_ret: *mut [u8; 32],
|
||||
) {
|
||||
let fvk = unsafe { fvk.as_ref() }.expect("fvk must not be null");
|
||||
let ovk_ret = unsafe { ovk_ret.as_mut() }.expect("ovk_ret must not be null");
|
||||
|
||||
let ovk = OutgoingViewingKey::from(fvk);
|
||||
*ovk_ret = *ovk.as_ref();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_full_viewing_key_to_internal_outgoing_viewing_key(
|
||||
fvk: *const FullViewingKey,
|
||||
ovk_ret: *mut [u8; 32],
|
||||
) {
|
||||
let fvk = unsafe { fvk.as_ref() }.expect("fvk must not be null");
|
||||
let ovk_ret = unsafe { ovk_ret.as_mut() }.expect("ovk_ret must not be null");
|
||||
|
||||
let internal_fvk = fvk.derive_internal();
|
||||
let ovk = OutgoingViewingKey::from(&internal_fvk);
|
||||
*ovk_ret = *ovk.as_ref();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_full_viewing_key_eq(
|
||||
k0: *const FullViewingKey,
|
||||
|
|
|
@ -29,6 +29,7 @@ use std::fs::File;
|
|||
use std::io::BufReader;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::slice;
|
||||
use std::sync::Once;
|
||||
use subtle::CtOption;
|
||||
use tracing::{error, info};
|
||||
|
||||
|
@ -69,14 +70,18 @@ mod ed25519;
|
|||
mod metrics_ffi;
|
||||
mod streams_ffi;
|
||||
mod tracing_ffi;
|
||||
mod zcashd_orchard;
|
||||
|
||||
mod address_ffi;
|
||||
mod builder_ffi;
|
||||
mod history_ffi;
|
||||
mod incremental_merkle_tree;
|
||||
mod incremental_merkle_tree_ffi;
|
||||
mod orchard_ffi;
|
||||
mod orchard_keys_ffi;
|
||||
mod transaction_ffi;
|
||||
mod unified_keys_ffi;
|
||||
mod wallet;
|
||||
mod zip339_ffi;
|
||||
|
||||
mod test_harness_ffi;
|
||||
|
@ -84,6 +89,7 @@ mod test_harness_ffi;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
static PROOF_PARAMETERS_LOADED: Once = Once::new();
|
||||
static mut SAPLING_SPEND_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
||||
static mut SAPLING_OUTPUT_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
||||
static mut SPROUT_GROTH16_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
||||
|
@ -127,64 +133,66 @@ pub extern "C" fn librustzcash_init_zksnark_params(
|
|||
#[cfg(target_os = "windows")] sprout_path: *const u16,
|
||||
sprout_path_len: usize,
|
||||
) {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let (spend_path, output_path, sprout_path) = {
|
||||
(
|
||||
OsStr::from_bytes(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
|
||||
OsStr::from_bytes(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
|
||||
if sprout_path.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(sprout_path, sprout_path_len)
|
||||
}))
|
||||
},
|
||||
)
|
||||
};
|
||||
PROOF_PARAMETERS_LOADED.call_once(|| {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let (spend_path, output_path, sprout_path) = {
|
||||
(
|
||||
OsStr::from_bytes(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
|
||||
OsStr::from_bytes(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
|
||||
if sprout_path.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(sprout_path, sprout_path_len)
|
||||
}))
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let (spend_path, output_path, sprout_path) = {
|
||||
(
|
||||
OsString::from_wide(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
|
||||
OsString::from_wide(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
|
||||
if sprout_path.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(OsString::from_wide(unsafe {
|
||||
slice::from_raw_parts(sprout_path, sprout_path_len)
|
||||
}))
|
||||
},
|
||||
)
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
let (spend_path, output_path, sprout_path) = {
|
||||
(
|
||||
OsString::from_wide(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
|
||||
OsString::from_wide(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
|
||||
if sprout_path.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(OsString::from_wide(unsafe {
|
||||
slice::from_raw_parts(sprout_path, sprout_path_len)
|
||||
}))
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
let (spend_path, output_path, sprout_path) = (
|
||||
Path::new(&spend_path),
|
||||
Path::new(&output_path),
|
||||
sprout_path.as_ref().map(|p| Path::new(p)),
|
||||
);
|
||||
let (spend_path, output_path, sprout_path) = (
|
||||
Path::new(&spend_path),
|
||||
Path::new(&output_path),
|
||||
sprout_path.as_ref().map(Path::new),
|
||||
);
|
||||
|
||||
// Load params
|
||||
let params = load_parameters(spend_path, output_path, sprout_path);
|
||||
// Load params
|
||||
let params = load_parameters(spend_path, output_path, sprout_path);
|
||||
|
||||
// Generate Orchard parameters.
|
||||
info!(target: "main", "Loading Orchard parameters");
|
||||
let orchard_pk = orchard::circuit::ProvingKey::build();
|
||||
let orchard_vk = orchard::circuit::VerifyingKey::build();
|
||||
// Generate Orchard parameters.
|
||||
info!(target: "main", "Loading Orchard parameters");
|
||||
let orchard_pk = orchard::circuit::ProvingKey::build();
|
||||
let orchard_vk = orchard::circuit::VerifyingKey::build();
|
||||
|
||||
// Caller is responsible for calling this function once, so
|
||||
// these global mutations are safe.
|
||||
unsafe {
|
||||
SAPLING_SPEND_PARAMS = Some(params.spend_params);
|
||||
SAPLING_OUTPUT_PARAMS = Some(params.output_params);
|
||||
SPROUT_GROTH16_PARAMS_PATH = sprout_path.map(|p| p.to_owned());
|
||||
// Caller is responsible for calling this function once, so
|
||||
// these global mutations are safe.
|
||||
unsafe {
|
||||
SAPLING_SPEND_PARAMS = Some(params.spend_params);
|
||||
SAPLING_OUTPUT_PARAMS = Some(params.output_params);
|
||||
SPROUT_GROTH16_PARAMS_PATH = sprout_path.map(|p| p.to_owned());
|
||||
|
||||
SAPLING_SPEND_VK = Some(params.spend_vk);
|
||||
SAPLING_OUTPUT_VK = Some(params.output_vk);
|
||||
SPROUT_GROTH16_VK = params.sprout_vk;
|
||||
SAPLING_SPEND_VK = Some(params.spend_vk);
|
||||
SAPLING_OUTPUT_VK = Some(params.output_vk);
|
||||
SPROUT_GROTH16_VK = params.sprout_vk;
|
||||
|
||||
ORCHARD_PK = Some(orchard_pk);
|
||||
ORCHARD_VK = Some(orchard_vk);
|
||||
}
|
||||
ORCHARD_PK = Some(orchard_pk);
|
||||
ORCHARD_VK = Some(orchard_vk);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Writes the "uncommitted" note value for empty leaves of the Merkle tree.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue