Merge pull request #5469 from nuttycom/feature/wallet_unified_addresses-z_sendmany
Add support for unified addresses to z_sendmany
This commit is contained in:
commit
9439c79bab
|
@ -213,6 +213,15 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-modes"
|
||||
version = "0.8.1"
|
||||
|
@ -380,6 +389,15 @@ version = "0.1.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.11.1"
|
||||
|
@ -397,7 +415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest",
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
|
@ -422,6 +440,17 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.0",
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "4.0.1"
|
||||
|
@ -472,7 +501,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
|||
[[package]]
|
||||
name = "equihash"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
dependencies = [
|
||||
"blake2b_simd 1.0.0",
|
||||
"byteorder",
|
||||
|
@ -481,7 +510,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "f4jumble"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
dependencies = [
|
||||
"blake2b_simd 1.0.0",
|
||||
]
|
||||
|
@ -624,6 +653,17 @@ dependencies = [
|
|||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hdwallet"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/nuttycom/hdwallet?rev=576683b9f2865f1118c309017ff36e01f84420c9#576683b9f2865f1118c309017ff36e01f84420c9"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"rand_core 0.6.3",
|
||||
"ring",
|
||||
"secp256k1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
|
@ -646,7 +686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"digest",
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1349,7 +1389,7 @@ checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6"
|
|||
dependencies = [
|
||||
"blake2b_simd 0.5.11",
|
||||
"byteorder",
|
||||
"digest",
|
||||
"digest 0.9.0",
|
||||
"group",
|
||||
"jubjub",
|
||||
"pasta_curves",
|
||||
|
@ -1404,6 +1444,30 @@ version = "0.6.25"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ripemd"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea54b64a1a8410c48395c154adadbad7e1bcd02debca79fc3694386cf73e5799"
|
||||
dependencies = [
|
||||
"digest 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -1454,10 +1518,10 @@ version = "0.9.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
|
@ -1493,6 +1557,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -1715,6 +1785,12 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
|
@ -1841,10 +1917,9 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_address"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
dependencies = [
|
||||
"bech32",
|
||||
"blake2b_simd 1.0.0",
|
||||
"bs58",
|
||||
"f4jumble",
|
||||
"zcash_encoding",
|
||||
|
@ -1853,7 +1928,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_encoding"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"nonempty",
|
||||
|
@ -1862,7 +1937,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_history"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
dependencies = [
|
||||
"bigint",
|
||||
"blake2b_simd 1.0.0",
|
||||
|
@ -1872,7 +1947,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_note_encryption"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"chacha20poly1305",
|
||||
|
@ -1883,7 +1958,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_primitives"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bip0039",
|
||||
|
@ -1891,23 +1966,25 @@ dependencies = [
|
|||
"blake2b_simd 1.0.0",
|
||||
"blake2s_simd 1.0.0",
|
||||
"bls12_381",
|
||||
"bs58",
|
||||
"byteorder",
|
||||
"chacha20poly1305",
|
||||
"equihash",
|
||||
"ff",
|
||||
"fpe",
|
||||
"group",
|
||||
"hdwallet",
|
||||
"hex",
|
||||
"incrementalmerkletree",
|
||||
"jubjub",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memuse",
|
||||
"nonempty",
|
||||
"orchard",
|
||||
"pasta_curves",
|
||||
"rand",
|
||||
"rand_core 0.6.3",
|
||||
"ripemd",
|
||||
"secp256k1",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"zcash_encoding",
|
||||
|
@ -1917,7 +1994,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_proofs"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
dependencies = [
|
||||
"bellman",
|
||||
"blake2b_simd 1.0.0",
|
||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -47,7 +47,7 @@ tracing-appender = "0.2"
|
|||
zcash_address = "0.0"
|
||||
zcash_history = "0.2"
|
||||
zcash_note_encryption = "0.1"
|
||||
zcash_primitives = "0.5"
|
||||
zcash_primitives = { version = "0.5", features = ["transparent-inputs"] }
|
||||
zcash_proofs = "0.5"
|
||||
ed25519-zebra = "3"
|
||||
zeroize = "1.4.2"
|
||||
|
@ -71,8 +71,9 @@ panic = 'abort'
|
|||
codegen-units = 1
|
||||
|
||||
[patch.crates-io]
|
||||
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" }
|
||||
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" }
|
||||
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" }
|
||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" }
|
||||
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" }
|
||||
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
|
||||
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
|
|
|
@ -99,9 +99,7 @@ class WalletAccountsTest(BitcoinTestFramework):
|
|||
self.check_balance(1, ua1, {})
|
||||
|
||||
# Manually send funds to one of the receivers in the UA.
|
||||
# TODO: Once z_sendmany supports UAs, receive to the UA instead of the receiver.
|
||||
sapling0 = self.nodes[0].z_listunifiedreceivers(ua0)['sapling']
|
||||
recipients = [{'address': sapling0, 'amount': Decimal('10')}]
|
||||
recipients = [{'address': ua0, 'amount': Decimal('10')}]
|
||||
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0)
|
||||
txid = wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
|
@ -124,10 +122,9 @@ class WalletAccountsTest(BitcoinTestFramework):
|
|||
self.check_balance(0, ua0, {'sapling': 10})
|
||||
|
||||
# Manually send funds from the UA receiver.
|
||||
# TODO: Once z_sendmany supports UAs, send from the UA instead of the receiver.
|
||||
node1sapling = self.nodes[1].z_getnewaddress('sapling')
|
||||
recipients = [{'address': node1sapling, 'amount': Decimal('1')}]
|
||||
opid = self.nodes[0].z_sendmany(sapling0, recipients, 1, 0)
|
||||
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.
|
||||
|
|
|
@ -3,8 +3,17 @@
|
|||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_true, assert_false, DEFAULT_FEE, DEFAULT_FEE_ZATS
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_true,
|
||||
assert_false,
|
||||
assert_raises_message,
|
||||
get_coinbase_address,
|
||||
DEFAULT_FEE,
|
||||
DEFAULT_FEE_ZATS
|
||||
)
|
||||
from test_framework.util import wait_and_assert_operationid_status
|
||||
from decimal import Decimal
|
||||
|
||||
|
@ -15,7 +24,6 @@ my_memo = my_memo + '0'*(1024-len(my_memo))
|
|||
no_memo = 'f6' + ('0'*1022) # see section 5.5 of the protocol spec
|
||||
|
||||
class ListReceivedTest (BitcoinTestFramework):
|
||||
|
||||
def generate_and_sync(self, new_height):
|
||||
current_height = self.nodes[0].getblockcount()
|
||||
assert(new_height > current_height)
|
||||
|
@ -23,11 +31,166 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
self.sync_all()
|
||||
assert_equal(new_height, self.nodes[0].getblockcount())
|
||||
|
||||
def run_test_release(self, release, height):
|
||||
def test_received_sprout(self, height):
|
||||
self.generate_and_sync(height+2)
|
||||
|
||||
zaddr1 = self.nodes[1].z_getnewaddress('sprout')
|
||||
|
||||
# Send 10 ZEC each zaddr1 and zaddrExt via z_shieldcoinbase
|
||||
result = self.nodes[0].z_shieldcoinbase(get_coinbase_address(self.nodes[0]), zaddr1, 0, 1)
|
||||
txid_shielding1 = wait_and_assert_operationid_status(self.nodes[0], result['opid'])
|
||||
|
||||
zaddrExt = self.nodes[3].z_getnewaddress('sprout')
|
||||
result = self.nodes[0].z_shieldcoinbase(get_coinbase_address(self.nodes[0]), zaddrExt, 0, 1)
|
||||
txid_shieldingExt = wait_and_assert_operationid_status(self.nodes[0], result['opid'])
|
||||
|
||||
self.sync_all()
|
||||
|
||||
# Decrypted transaction details should not be visible on node 0
|
||||
pt = self.nodes[0].z_viewtransaction(txid_shielding1)
|
||||
assert_equal(pt['txid'], txid_shielding1)
|
||||
assert_equal(len(pt['spends']), 0)
|
||||
assert_equal(len(pt['outputs']), 0)
|
||||
|
||||
# Decrypted transaction details should be correct on node 1
|
||||
pt = self.nodes[1].z_viewtransaction(txid_shielding1)
|
||||
assert_equal(pt['txid'], txid_shielding1)
|
||||
assert_equal(len(pt['spends']), 0)
|
||||
assert_equal(len(pt['outputs']), 1)
|
||||
assert_equal(pt['outputs'][0]['type'], 'sprout')
|
||||
assert_equal(pt['outputs'][0]['js'], 0)
|
||||
assert_equal(pt['outputs'][0]['address'], zaddr1)
|
||||
assert_equal(pt['outputs'][0]['value'], Decimal('10'))
|
||||
assert_equal(pt['outputs'][0]['valueZat'], 1000000000)
|
||||
assert_equal(pt['outputs'][0]['memo'], no_memo)
|
||||
jsOutputPrev = pt['outputs'][0]['jsOutput']
|
||||
|
||||
# Second transaction should not be known to node 1
|
||||
assert_raises_message(
|
||||
JSONRPCException,
|
||||
"Invalid or non-wallet transaction id",
|
||||
self.nodes[1].z_viewtransaction,
|
||||
txid_shieldingExt)
|
||||
|
||||
# Second transaction should be visible on node3
|
||||
pt = self.nodes[3].z_viewtransaction(txid_shieldingExt)
|
||||
assert_equal(pt['txid'], txid_shieldingExt)
|
||||
assert_equal(len(pt['spends']), 0)
|
||||
assert_equal(len(pt['outputs']), 1)
|
||||
assert_equal(pt['outputs'][0]['type'], 'sprout')
|
||||
assert_equal(pt['outputs'][0]['js'], 0)
|
||||
assert_equal(pt['outputs'][0]['address'], zaddrExt)
|
||||
assert_equal(pt['outputs'][0]['value'], Decimal('10'))
|
||||
assert_equal(pt['outputs'][0]['valueZat'], 1000000000)
|
||||
assert_equal(pt['outputs'][0]['memo'], no_memo)
|
||||
|
||||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1)
|
||||
assert_equal(0, len(r), "Should have received no confirmed note")
|
||||
c = self.nodes[1].z_getnotescount()
|
||||
assert_equal(0, c['sprout'], "Count of confirmed notes should be 0")
|
||||
|
||||
# No confirmation required, one note should be present
|
||||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0)
|
||||
assert_equal(1, len(r), "Should have received one (unconfirmed) note")
|
||||
assert_equal(txid_shielding1, r[0]['txid'])
|
||||
assert_equal(10, r[0]['amount'])
|
||||
assert_equal(1000000000, r[0]['amountZat'])
|
||||
assert_false(r[0]['change'], "Note should not be change")
|
||||
assert_equal(no_memo, r[0]['memo'])
|
||||
assert_equal(0, r[0]['confirmations'])
|
||||
assert_equal(-1, r[0]['blockindex'])
|
||||
assert_equal(0, r[0]['blockheight'])
|
||||
|
||||
c = self.nodes[1].z_getnotescount(0)
|
||||
assert_equal(1, c['sprout'], "Count of unconfirmed notes should be 1")
|
||||
|
||||
# Confirm transaction (10 ZEC shielded)
|
||||
self.generate_and_sync(height+3)
|
||||
|
||||
# Require one confirmation, note should be present
|
||||
r0 = self.nodes[1].z_listreceivedbyaddress(zaddr1)
|
||||
assert_equal(1, len(r0), "Should have received one (unconfirmed) note")
|
||||
assert_equal(txid_shielding1, r0[0]['txid'])
|
||||
assert_equal(10, r0[0]['amount'])
|
||||
assert_equal(1000000000, r0[0]['amountZat'])
|
||||
assert_false(r0[0]['change'], "Note should not be change")
|
||||
assert_equal(no_memo, r0[0]['memo'])
|
||||
assert_equal(1, r0[0]['confirmations'])
|
||||
assert_equal(height + 3, r0[0]['blockheight'])
|
||||
|
||||
taddr = self.nodes[1].getnewaddress()
|
||||
# Generate some change by sending part of zaddr1 back to taddr
|
||||
opid = self.nodes[1].z_sendmany(zaddr1, [{'address': taddr, 'amount': 0.6}])
|
||||
txid = wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||
|
||||
self.generate_and_sync(height+4)
|
||||
|
||||
# Decrypted transaction details should be correct
|
||||
pt = self.nodes[1].z_viewtransaction(txid)
|
||||
assert_equal(pt['txid'], txid)
|
||||
assert_equal(len(pt['spends']), 1)
|
||||
# TODO: enable once z_viewtransaction displays transparent elements
|
||||
# assert_equal(len(pt['outputs']), 2)
|
||||
assert_equal(len(pt['outputs']), 1)
|
||||
|
||||
assert_equal(pt['spends'][0]['type'], 'sprout')
|
||||
assert_equal(pt['spends'][0]['txidPrev'], txid_shielding1)
|
||||
assert_equal(pt['spends'][0]['js'], 0)
|
||||
assert_equal(pt['spends'][0]['jsPrev'], 0)
|
||||
assert_equal(pt['spends'][0]['jsOutputPrev'], jsOutputPrev)
|
||||
assert_equal(pt['spends'][0]['address'], zaddr1)
|
||||
assert_equal(pt['spends'][0]['value'], Decimal('10.0'))
|
||||
assert_equal(pt['spends'][0]['valueZat'], 1000000000)
|
||||
|
||||
# We expect a transparent output and a Sprout output, but the RPC does
|
||||
# not define any particular ordering of these within the returned JSON.
|
||||
outputs = [{
|
||||
'type': output['type'],
|
||||
'address': output['address'],
|
||||
'value': output['value'],
|
||||
'valueZat': output['valueZat'],
|
||||
} for output in pt['outputs']]
|
||||
for (i, output) in enumerate(pt['outputs']):
|
||||
if 'memo' in output:
|
||||
outputs[i]['memo'] = output['memo']
|
||||
|
||||
# TODO: enable once z_viewtransaction displays transparent elements
|
||||
# assert({
|
||||
# 'type': 'transparent',
|
||||
# 'address': taddr,
|
||||
# 'value': Decimal('0.6'),
|
||||
# 'valueZat': 60000000,
|
||||
# } in outputs)
|
||||
assert({
|
||||
'type': 'sprout',
|
||||
'address': zaddr1,
|
||||
'value': Decimal('9.4') - DEFAULT_FEE,
|
||||
'valueZat': 940000000 - DEFAULT_FEE_ZATS,
|
||||
'memo': no_memo,
|
||||
} in outputs)
|
||||
|
||||
# zaddr1 should have a note with change
|
||||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0)
|
||||
r = sorted(r, key = lambda received: received['amount'])
|
||||
assert_equal(2, len(r), "zaddr1 Should have received 2 notes")
|
||||
|
||||
assert_equal(txid, r[0]['txid'])
|
||||
assert_equal(Decimal('9.4')-DEFAULT_FEE, r[0]['amount'])
|
||||
assert_equal(940000000-DEFAULT_FEE_ZATS, r[0]['amountZat'])
|
||||
assert_true(r[0]['change'], "Note valued at (9.4-"+str(DEFAULT_FEE)+") should be change")
|
||||
assert_equal(no_memo, r[0]['memo'])
|
||||
|
||||
# The old note still exists (it's immutable), even though it is spent
|
||||
assert_equal(Decimal('10.0'), r[1]['amount'])
|
||||
assert_equal(1000000000, r[1]['amountZat'])
|
||||
assert_false(r[1]['change'], "Note valued at 10.0 should not be change")
|
||||
assert_equal(no_memo, r[1]['memo'])
|
||||
|
||||
def test_received_sapling(self, height):
|
||||
self.generate_and_sync(height+1)
|
||||
taddr = self.nodes[1].getnewaddress()
|
||||
zaddr1 = self.nodes[1].z_getnewaddress(release)
|
||||
zaddrExt = self.nodes[3].z_getnewaddress(release)
|
||||
zaddr1 = self.nodes[1].z_getnewaddress('sapling')
|
||||
zaddrExt = self.nodes[3].z_getnewaddress('sapling')
|
||||
|
||||
self.nodes[0].sendtoaddress(taddr, 4.0)
|
||||
self.generate_and_sync(height+2)
|
||||
|
@ -42,19 +205,20 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
|
||||
# Decrypted transaction details should be correct
|
||||
pt = self.nodes[1].z_viewtransaction(txid)
|
||||
|
||||
assert_equal(pt['txid'], txid)
|
||||
assert_equal(len(pt['spends']), 0)
|
||||
assert_equal(len(pt['outputs']), 1 if release == 'sprout' else 2)
|
||||
assert_equal(len(pt['outputs']), 2)
|
||||
|
||||
# Output orders can be randomized, so we check the output
|
||||
# Expect one internal output and one external.
|
||||
assert_equal(len([output for output in pt['outputs'] if output['outgoing']]), 1)
|
||||
|
||||
# Outputs are not returned in a defined order so we check the output
|
||||
# positions and contents separately
|
||||
outputs = []
|
||||
|
||||
assert_equal(pt['outputs'][0]['type'], release)
|
||||
if release == 'sprout':
|
||||
assert_equal(pt['outputs'][0]['js'], 0)
|
||||
jsOutputPrev = pt['outputs'][0]['jsOutput']
|
||||
elif pt['outputs'][0]['address'] == zaddr1:
|
||||
assert_equal(pt['outputs'][0]['type'], 'sapling')
|
||||
if pt['outputs'][0]['address'] == zaddr1:
|
||||
assert_equal(pt['outputs'][0]['outgoing'], False)
|
||||
assert_equal(pt['outputs'][0]['memoStr'], my_memo_str)
|
||||
else:
|
||||
|
@ -66,19 +230,18 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
'memo': pt['outputs'][0]['memo'],
|
||||
})
|
||||
|
||||
if release != 'sprout':
|
||||
assert_equal(pt['outputs'][1]['type'], release)
|
||||
if pt['outputs'][1]['address'] == zaddr1:
|
||||
assert_equal(pt['outputs'][1]['outgoing'], False)
|
||||
assert_equal(pt['outputs'][1]['memoStr'], my_memo_str)
|
||||
else:
|
||||
assert_equal(pt['outputs'][1]['outgoing'], True)
|
||||
outputs.append({
|
||||
'address': pt['outputs'][1]['address'],
|
||||
'value': pt['outputs'][1]['value'],
|
||||
'valueZat': pt['outputs'][1]['valueZat'],
|
||||
'memo': pt['outputs'][1]['memo'],
|
||||
})
|
||||
assert_equal(pt['outputs'][1]['type'], 'sapling')
|
||||
if pt['outputs'][1]['address'] == zaddr1:
|
||||
assert_equal(pt['outputs'][1]['outgoing'], False)
|
||||
assert_equal(pt['outputs'][1]['memoStr'], my_memo_str)
|
||||
else:
|
||||
assert_equal(pt['outputs'][1]['outgoing'], True)
|
||||
outputs.append({
|
||||
'address': pt['outputs'][1]['address'],
|
||||
'value': pt['outputs'][1]['value'],
|
||||
'valueZat': pt['outputs'][1]['valueZat'],
|
||||
'memo': pt['outputs'][1]['memo'],
|
||||
})
|
||||
|
||||
assert({
|
||||
'address': zaddr1,
|
||||
|
@ -86,18 +249,18 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
'valueZat': 100000000,
|
||||
'memo': my_memo,
|
||||
} in outputs)
|
||||
if release != 'sprout':
|
||||
assert({
|
||||
'address': zaddrExt,
|
||||
'value': Decimal('2'),
|
||||
'valueZat': 200000000,
|
||||
'memo': no_memo,
|
||||
} in outputs)
|
||||
|
||||
assert({
|
||||
'address': zaddrExt,
|
||||
'value': Decimal('2'),
|
||||
'valueZat': 200000000,
|
||||
'memo': no_memo,
|
||||
} in outputs)
|
||||
|
||||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1)
|
||||
assert_equal(0, len(r), "Should have received no confirmed note")
|
||||
c = self.nodes[1].z_getnotescount()
|
||||
assert_equal(0, c[release], "Count of confirmed notes should be 0")
|
||||
assert_equal(0, c['sapling'], "Count of confirmed notes should be 0")
|
||||
|
||||
# No confirmation required, one note should be present
|
||||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0)
|
||||
|
@ -112,7 +275,7 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
assert_equal(0, r[0]['blockheight'])
|
||||
|
||||
c = self.nodes[1].z_getnotescount(0)
|
||||
assert_equal(1, c[release], "Count of unconfirmed notes should be 1")
|
||||
assert_equal(1, c['sapling'], "Count of unconfirmed notes should be 1")
|
||||
|
||||
# Confirm transaction (1 ZEC from taddr to zaddr1)
|
||||
self.generate_and_sync(height+3)
|
||||
|
@ -129,7 +292,7 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
|
||||
# Generate some change by sending part of zaddr1 to zaddr2
|
||||
txidPrev = txid
|
||||
zaddr2 = self.nodes[1].z_getnewaddress(release)
|
||||
zaddr2 = self.nodes[1].z_getnewaddress('sapling')
|
||||
opid = self.nodes[1].z_sendmany(zaddr1,
|
||||
[{'address': zaddr2, 'amount': 0.6}])
|
||||
txid = wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||
|
@ -142,28 +305,21 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
assert_equal(len(pt['spends']), 1)
|
||||
assert_equal(len(pt['outputs']), 2)
|
||||
|
||||
assert_equal(pt['spends'][0]['type'], release)
|
||||
assert_equal(pt['spends'][0]['type'], 'sapling')
|
||||
assert_equal(pt['spends'][0]['txidPrev'], txidPrev)
|
||||
if release == 'sprout':
|
||||
assert_equal(pt['spends'][0]['js'], 0)
|
||||
# jsSpend is randomised during transaction creation
|
||||
assert_equal(pt['spends'][0]['jsPrev'], 0)
|
||||
assert_equal(pt['spends'][0]['jsOutputPrev'], jsOutputPrev)
|
||||
else:
|
||||
assert_equal(pt['spends'][0]['spend'], 0)
|
||||
assert_equal(pt['spends'][0]['outputPrev'], 0)
|
||||
assert_equal(pt['spends'][0]['spend'], 0)
|
||||
assert_equal(pt['spends'][0]['outputPrev'], 0)
|
||||
assert_equal(pt['spends'][0]['address'], zaddr1)
|
||||
assert_equal(pt['spends'][0]['value'], Decimal('1.0'))
|
||||
assert_equal(pt['spends'][0]['valueZat'], 100000000)
|
||||
|
||||
# Output orders can be randomized, so we check the output
|
||||
# Outputs are not returned in a defined order so we check the output
|
||||
# positions and contents separately
|
||||
outputs = []
|
||||
|
||||
assert_equal(pt['outputs'][0]['type'], release)
|
||||
if release == 'sapling':
|
||||
assert_equal(pt['outputs'][0]['output'], 0)
|
||||
assert_equal(pt['outputs'][0]['outgoing'], False)
|
||||
assert_equal(pt['outputs'][0]['type'], 'sapling')
|
||||
assert_equal(pt['outputs'][0]['output'], 0)
|
||||
assert_equal(pt['outputs'][0]['outgoing'], False)
|
||||
outputs.append({
|
||||
'address': pt['outputs'][0]['address'],
|
||||
'value': pt['outputs'][0]['value'],
|
||||
|
@ -171,10 +327,9 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
'memo': pt['outputs'][0]['memo'],
|
||||
})
|
||||
|
||||
assert_equal(pt['outputs'][1]['type'], release)
|
||||
if release == 'sapling':
|
||||
assert_equal(pt['outputs'][1]['output'], 1)
|
||||
assert_equal(pt['outputs'][1]['outgoing'], False)
|
||||
assert_equal(pt['outputs'][1]['type'], 'sapling')
|
||||
assert_equal(pt['outputs'][1]['output'], 1)
|
||||
assert_equal(pt['outputs'][1]['outgoing'], False)
|
||||
outputs.append({
|
||||
'address': pt['outputs'][1]['address'],
|
||||
'value': pt['outputs'][1]['value'],
|
||||
|
@ -223,11 +378,11 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
assert_equal(no_memo, r[0]['memo'])
|
||||
|
||||
c = self.nodes[1].z_getnotescount(0)
|
||||
assert_equal(3, c[release], "Count of unconfirmed notes should be 3(2 in zaddr1 + 1 in zaddr2)")
|
||||
assert_equal(3, c['sapling'], "Count of unconfirmed notes should be 3(2 in zaddr1 + 1 in zaddr2)")
|
||||
|
||||
def run_test(self):
|
||||
#self.run_test_release('sprout', 200)
|
||||
self.run_test_release('sapling', 214)
|
||||
self.test_received_sprout(200)
|
||||
self.test_received_sapling(214)
|
||||
|
||||
if __name__ == '__main__':
|
||||
ListReceivedTest().main()
|
||||
|
|
|
@ -7,18 +7,36 @@ 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.authproxy import JSONRPCException
|
||||
from test_framework.mininode import COIN
|
||||
from decimal import Decimal
|
||||
|
||||
# Test wallet address behaviour across network upgrades
|
||||
class WalletZSendmanyTest(BitcoinTestFramework):
|
||||
def setup_network(self, split=False):
|
||||
self.nodes = start_nodes(3, self.options.tmpdir)
|
||||
self.nodes = start_nodes(3, self.options.tmpdir, [[
|
||||
'-experimentalfeatures',
|
||||
'-orchardwallet',
|
||||
]] * self.num_nodes)
|
||||
connect_nodes_bi(self.nodes,0,1)
|
||||
connect_nodes_bi(self.nodes,1,2)
|
||||
connect_nodes_bi(self.nodes,0,2)
|
||||
self.is_network_split=False
|
||||
self.sync_all()
|
||||
|
||||
# Check we only have balances in the expected pools.
|
||||
# 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)
|
||||
assert_equal(set(expected), set(actual['pools']))
|
||||
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)
|
||||
|
||||
def check_balance(self, node, account, address, expected, minconf=None):
|
||||
self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
|
||||
self._check_balance_for_rpc('z_getbalanceforaddress', node, address, expected, minconf)
|
||||
|
||||
def run_test(self):
|
||||
# z_sendmany is expected to fail if tx size breaks limit
|
||||
myzaddr = self.nodes[0].z_getnewaddress()
|
||||
|
@ -65,8 +83,8 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
|||
# send node 2 taddr to zaddr
|
||||
recipients = []
|
||||
recipients.append({"address":myzaddr, "amount":7})
|
||||
|
||||
mytxid = wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(mytaddr, recipients))
|
||||
opid = self.nodes[2].z_sendmany(mytaddr, recipients)
|
||||
mytxid = wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||
|
||||
self.sync_all()
|
||||
|
||||
|
@ -87,13 +105,14 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
|||
assert_equal(self.nodes[2].getbalance("*"), node2utxobalance)
|
||||
|
||||
# check zaddr balance with z_getbalance
|
||||
assert_equal(self.nodes[2].z_getbalance(myzaddr), zsendmanynotevalue)
|
||||
zbalance = zsendmanynotevalue
|
||||
assert_equal(self.nodes[2].z_getbalance(myzaddr), zbalance)
|
||||
|
||||
# check via z_gettotalbalance
|
||||
resp = self.nodes[2].z_gettotalbalance()
|
||||
assert_equal(Decimal(resp["transparent"]), node2utxobalance)
|
||||
assert_equal(Decimal(resp["private"]), zsendmanynotevalue)
|
||||
assert_equal(Decimal(resp["total"]), node2utxobalance + zsendmanynotevalue)
|
||||
assert_equal(Decimal(resp["private"]), zbalance)
|
||||
assert_equal(Decimal(resp["total"]), node2utxobalance + zbalance)
|
||||
|
||||
# check confirmed shielded balance with getwalletinfo
|
||||
wallet_info = self.nodes[2].getwalletinfo()
|
||||
|
@ -118,7 +137,9 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
|||
recipients.append({"address":self.nodes[0].getnewaddress(), "amount":1})
|
||||
recipients.append({"address":self.nodes[2].getnewaddress(), "amount":1.0})
|
||||
|
||||
wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(myzaddr, recipients))
|
||||
opid = self.nodes[2].z_sendmany(myzaddr, recipients)
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||
zbalance -= Decimal('2.0') + zsendmanyfee
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[2].generate(1)
|
||||
|
@ -131,5 +152,77 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
|||
assert_equal(Decimal(self.nodes[2].getbalance()), node2balance)
|
||||
assert_equal(Decimal(self.nodes[2].getbalance("*")), node2balance)
|
||||
|
||||
# Get a new unified account on node 2 & generate a UA
|
||||
n0account0 = self.nodes[0].z_getnewaccount()['account']
|
||||
n0ua0 = self.nodes[0].z_getaddressforaccount(n0account0)['unifiedaddress']
|
||||
|
||||
# Change went to a fresh address, so use `ANY_TADDR` which
|
||||
# should hold the rest of our transparent funds.
|
||||
recipients = []
|
||||
recipients.append({"address":n0ua0, "amount":10})
|
||||
opid = self.nodes[2].z_sendmany('ANY_TADDR', recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
node2balance -= Decimal('10.0')
|
||||
node0balance += Decimal('10.0')
|
||||
assert_equal(Decimal(self.nodes[2].getbalance()), node2balance)
|
||||
assert_equal(Decimal(self.nodes[0].getbalance()), node0balance)
|
||||
self.check_balance(0, 0, n0ua0, {'sapling': 10})
|
||||
|
||||
# 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)
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
node2balance += Decimal('5.0')
|
||||
self.check_balance(0, 0, n0ua0, {'sapling': 5})
|
||||
assert_equal(Decimal(self.nodes[2].getbalance()), node2balance)
|
||||
|
||||
# Send some funds to a legacy sapling address that we can spend from
|
||||
recipients = []
|
||||
recipients.append({"address":myzaddr, "amount":3})
|
||||
opid = self.nodes[0].z_sendmany(n0ua0, recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
zbalance += Decimal('3.0')
|
||||
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
|
||||
recipients = []
|
||||
recipients.append({"address":n0ua0, "amount":4})
|
||||
opid = self.nodes[2].z_sendmany(mytaddr, recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
node2balance -= Decimal('4.0')
|
||||
self.check_balance(0, 0, n0ua0, {'sapling': 6})
|
||||
assert_equal(Decimal(self.nodes[2].getbalance()), node2balance)
|
||||
|
||||
# Send funds back from the legacy zaddr to the UA
|
||||
recipients = []
|
||||
recipients.append({"address":n0ua0, "amount":2})
|
||||
opid = self.nodes[2].z_sendmany(myzaddr, recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
zbalance -= Decimal('2.0')
|
||||
self.check_balance(0, 0, n0ua0, {'sapling': 8})
|
||||
assert_equal(Decimal(self.nodes[2].z_getbalance(myzaddr)), zbalance)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletZSendmanyTest().main()
|
||||
|
|
|
@ -114,7 +114,7 @@ LIBZCASH_H = \
|
|||
zcash/IncrementalMerkleTree.hpp \
|
||||
zcash/NoteEncryption.hpp \
|
||||
zcash/Address.hpp \
|
||||
zcash/address/bip44.h \
|
||||
zcash/address/transparent.h \
|
||||
zcash/address/mnemonic.h \
|
||||
zcash/address/orchard.h \
|
||||
zcash/address/sapling.hpp \
|
||||
|
@ -547,7 +547,7 @@ libzcash_a_SOURCES = \
|
|||
zcash/IncrementalMerkleTree.cpp \
|
||||
zcash/NoteEncryption.cpp \
|
||||
zcash/Address.cpp \
|
||||
zcash/address/bip44.cpp \
|
||||
zcash/address/transparent.cpp \
|
||||
zcash/address/mnemonic.cpp \
|
||||
zcash/address/orchard.cpp \
|
||||
zcash/address/sapling.cpp \
|
||||
|
|
|
@ -251,7 +251,7 @@ TEST(KeystoreTests, StoreAndRetrieveSaplingSpendingKey) {
|
|||
|
||||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
auto addr = sk.ToXFVK().DefaultAddress();
|
||||
|
||||
// Sanity-check: we can't get a key we haven't added
|
||||
|
@ -260,9 +260,6 @@ TEST(KeystoreTests, StoreAndRetrieveSaplingSpendingKey) {
|
|||
// Sanity-check: we can't get a full viewing key we haven't added
|
||||
EXPECT_FALSE(keyStore.HaveSaplingFullViewingKey(ivk));
|
||||
EXPECT_FALSE(keyStore.GetSaplingFullViewingKey(ivk, extfvkOut));
|
||||
// Sanity-check: we can't get an incoming viewing key we haven't added
|
||||
EXPECT_FALSE(keyStore.HaveSaplingIncomingViewingKey(addr));
|
||||
EXPECT_FALSE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut));
|
||||
|
||||
// When we specify the default address, we get the full mapping
|
||||
keyStore.AddSaplingSpendingKey(sk);
|
||||
|
@ -270,6 +267,12 @@ TEST(KeystoreTests, StoreAndRetrieveSaplingSpendingKey) {
|
|||
EXPECT_TRUE(keyStore.GetSaplingSpendingKey(extfvk, skOut));
|
||||
EXPECT_TRUE(keyStore.HaveSaplingFullViewingKey(ivk));
|
||||
EXPECT_TRUE(keyStore.GetSaplingFullViewingKey(ivk, extfvkOut));
|
||||
|
||||
// We can't get an incoming viewing key for an address we haven't added
|
||||
EXPECT_FALSE(keyStore.HaveSaplingIncomingViewingKey(addr));
|
||||
EXPECT_FALSE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut));
|
||||
|
||||
keyStore.AddSaplingPaymentAddress(ivk, addr);
|
||||
EXPECT_TRUE(keyStore.HaveSaplingIncomingViewingKey(addr));
|
||||
EXPECT_TRUE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut));
|
||||
EXPECT_EQ(sk, skOut);
|
||||
|
@ -285,7 +288,7 @@ TEST(KeystoreTests, StoreAndRetrieveSaplingFullViewingKey) {
|
|||
|
||||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
auto addr = sk.ToXFVK().DefaultAddress();
|
||||
|
||||
// Sanity-check: we can't get a full viewing key we haven't added
|
||||
|
@ -309,11 +312,14 @@ TEST(KeystoreTests, StoreAndRetrieveSaplingFullViewingKey) {
|
|||
EXPECT_TRUE(keyStore.GetSaplingFullViewingKey(ivk, extfvkOut));
|
||||
EXPECT_EQ(extfvk, extfvkOut);
|
||||
|
||||
// We should still not have the spending key...
|
||||
// We should still not have the spending key or
|
||||
// be able to retrieve the IVK by the default address...
|
||||
EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(extfvk));
|
||||
EXPECT_FALSE(keyStore.GetSaplingSpendingKey(extfvk, skOut));
|
||||
EXPECT_FALSE(keyStore.HaveSaplingIncomingViewingKey(addr));
|
||||
|
||||
// ... but we should have an incoming viewing key
|
||||
// The IVK must be manually associated with the address...
|
||||
keyStore.AddSaplingPaymentAddress(ivk, addr);
|
||||
EXPECT_TRUE(keyStore.HaveSaplingIncomingViewingKey(addr));
|
||||
EXPECT_TRUE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut));
|
||||
EXPECT_EQ(ivk, ivkOut);
|
||||
|
@ -550,14 +556,16 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) {
|
|||
EXPECT_TRUE(keyStore.AddUnifiedFullViewingKey(zufvk));
|
||||
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
|
||||
|
||||
auto addrPair = zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Sapling}).value();
|
||||
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());
|
||||
|
||||
auto saplingIvk = zufvk.GetSaplingKey().value().fvk.in_viewing_key();
|
||||
keyStore.AddSaplingIncomingViewingKey(saplingIvk, saplingReceiver);
|
||||
auto saplingIvk = zufvk.GetSaplingKey().value().ToIncomingViewingKey();
|
||||
keyStore.AddSaplingPaymentAddress(saplingIvk, saplingReceiver);
|
||||
|
||||
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
|
||||
EXPECT_TRUE(ufvkmeta.has_value());
|
||||
|
@ -576,7 +584,7 @@ TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
|
|||
auto ufvk = usk.value().ToFullViewingKey();
|
||||
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
|
||||
auto ufvkid = zufvk.GetKeyID();
|
||||
auto addrPair = zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::P2PKH, ReceiverType::Sapling}).value();
|
||||
auto addrPair = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::P2PKH, ReceiverType::Sapling}));
|
||||
EXPECT_TRUE(addrPair.first.GetP2PKHReceiver().has_value());
|
||||
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(addrPair.first.GetP2PKHReceiver().value());
|
||||
EXPECT_FALSE(ufvkmeta.has_value());
|
||||
|
|
|
@ -268,17 +268,6 @@ TEST(TransactionBuilder, RejectsInvalidTransparentOutput)
|
|||
ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue);
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, RejectsInvalidTransparentChangeAddress)
|
||||
{
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
const Consensus::Params& consensusParams = Params().GetConsensus();
|
||||
|
||||
// Default CTxDestination type is an invalid address
|
||||
CTxDestination taddr;
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
ASSERT_THROW(builder.SendChangeTo(taddr), UniValue);
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, FailsWithNegativeChange)
|
||||
{
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
@ -344,7 +333,6 @@ TEST(TransactionBuilder, ChangeOutput)
|
|||
CKey tsk = AddTestCKeyToKeyStore(keystore);
|
||||
auto tkeyid = tsk.GetPubKey().GetID();
|
||||
auto scriptPubKey = GetScriptForDestination(tkeyid);
|
||||
CTxDestination taddr = tkeyid;
|
||||
|
||||
// No change address and no Sapling spends
|
||||
{
|
||||
|
@ -387,7 +375,7 @@ TEST(TransactionBuilder, ChangeOutput)
|
|||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||
builder.SendChangeTo(taddr);
|
||||
builder.SendChangeTo(tkeyid, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
||||
EXPECT_EQ(tx.vin.size(), 1);
|
||||
|
|
|
@ -172,3 +172,17 @@ TEST(ZIP32, diversifier_index_t_lt)
|
|||
EXPECT_FALSE(libzcash::diversifier_index_t(0xffffffff) < libzcash::diversifier_index_t(0x01));
|
||||
}
|
||||
|
||||
TEST(ZIP32, DeriveChangeAddress)
|
||||
{
|
||||
std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||
HDSeed seed(rawSeed);
|
||||
|
||||
auto accountSk = libzcash::SaplingExtendedSpendingKey::ForAccount(seed, 1, 0);
|
||||
auto extfvk = accountSk.first.ToXFVK();
|
||||
auto changeSk = accountSk.first.DeriveInternalKey();
|
||||
|
||||
EXPECT_EQ(changeSk.ToXFVK().DefaultAddress(), extfvk.GetChangeAddress());
|
||||
}
|
||||
|
||||
|
|
|
@ -193,16 +193,16 @@ bool CBasicKeyStore::AddSaplingFullViewingKey(
|
|||
const libzcash::SaplingExtendedFullViewingKey &extfvk)
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
mapSaplingFullViewingKeys[ivk] = extfvk;
|
||||
|
||||
return CBasicKeyStore::AddSaplingIncomingViewingKey(ivk, extfvk.DefaultAddress());
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function updates the wallet's internal address->ivk map.
|
||||
// If we add an address that is already in the map, the map will
|
||||
// remain unchanged as each address only has one ivk.
|
||||
bool CBasicKeyStore::AddSaplingIncomingViewingKey(
|
||||
bool CBasicKeyStore::AddSaplingPaymentAddress(
|
||||
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
const libzcash::SaplingPaymentAddress &addr)
|
||||
{
|
||||
|
@ -311,8 +311,11 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
|
|||
// Add the Sapling component of the UFVK to the wallet.
|
||||
auto saplingKey = ufvk.GetSaplingKey();
|
||||
if (saplingKey.has_value()) {
|
||||
auto ivk = saplingKey.value().fvk.in_viewing_key();
|
||||
auto ivk = saplingKey.value().ToIncomingViewingKey();
|
||||
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvk.GetKeyID()));
|
||||
|
||||
auto changeIvk = saplingKey.value().GetChangeIVK();
|
||||
mapSaplingKeyUnified.insert(std::make_pair(changeIvk, ufvk.GetKeyID()));
|
||||
}
|
||||
|
||||
// We can't reasonably add the transparent component here, because
|
||||
|
|
|
@ -86,8 +86,8 @@ public:
|
|||
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
libzcash::SaplingExtendedFullViewingKey& extfvkOut) const =0;
|
||||
|
||||
//! Sapling incoming viewing keys
|
||||
virtual bool AddSaplingIncomingViewingKey(
|
||||
//! Sapling payment addresses & incoming viewing keys
|
||||
virtual bool AddSaplingPaymentAddress(
|
||||
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
const libzcash::SaplingPaymentAddress &addr) =0;
|
||||
virtual bool HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const =0;
|
||||
|
@ -324,7 +324,7 @@ public:
|
|||
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
libzcash::SaplingExtendedFullViewingKey& extfvkOut) const;
|
||||
|
||||
virtual bool AddSaplingIncomingViewingKey(
|
||||
virtual bool AddSaplingPaymentAddress(
|
||||
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
const libzcash::SaplingPaymentAddress &addr);
|
||||
virtual bool HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const;
|
||||
|
|
|
@ -98,6 +98,16 @@ bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChi
|
|||
return true;
|
||||
}
|
||||
|
||||
std::optional<CChainablePubKey> CChainablePubKey::Derive(unsigned int nChild) const {
|
||||
CPubKey pubkeyChild;
|
||||
ChainCode ccChild;
|
||||
if (pubkey.Derive(pubkeyChild, ccChild, nChild, chaincode)) {
|
||||
return CChainablePubKey::FromParts(ccChild, pubkeyChild);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void CExtPubKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const {
|
||||
code[0] = nDepth;
|
||||
memcpy(code+1, vchFingerprint, 4);
|
||||
|
|
|
@ -221,6 +221,8 @@ public:
|
|||
return pubkey;
|
||||
}
|
||||
|
||||
std::optional<CChainablePubKey> Derive(unsigned int nChild) const;
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
|
@ -272,6 +274,10 @@ struct CExtPubKey {
|
|||
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
|
||||
bool Derive(CExtPubKey& out, unsigned int nChild) const;
|
||||
|
||||
std::optional<CChainablePubKey> ToChainablePubKey() const {
|
||||
return CChainablePubKey::FromParts(chaincode, pubkey);
|
||||
}
|
||||
|
||||
void Serialize(CSizeComputer& s) const
|
||||
{
|
||||
// Optimized implementation for ::GetSerializeSize that avoids copying.
|
||||
|
|
|
@ -299,6 +299,12 @@ extern "C" {
|
|||
unsigned char *xsk_i
|
||||
);
|
||||
|
||||
/// Derive a internal ExtendedSpendingKey from an external key
|
||||
void librustzcash_zip32_xsk_derive_internal(
|
||||
const unsigned char *xsk_external,
|
||||
unsigned char *xsk_internal
|
||||
);
|
||||
|
||||
/// Derive a child ExtendedFullViewingKey from a parent.
|
||||
bool librustzcash_zip32_xfvk_derive(
|
||||
const unsigned char *xfvk_parent,
|
||||
|
@ -306,6 +312,17 @@ extern "C" {
|
|||
unsigned char *xfvk_i
|
||||
);
|
||||
|
||||
/**
|
||||
* Derive the Sapling internal FVK corresponding to the given
|
||||
* Sapling external FVK.
|
||||
*/
|
||||
void librustzcash_zip32_sapling_derive_internal_fvk(
|
||||
const unsigned char *fvk,
|
||||
const unsigned char *dk,
|
||||
unsigned char *fvk_ret,
|
||||
unsigned char *dk_ret
|
||||
);
|
||||
|
||||
/**
|
||||
* Derive a PaymentAddress from a (SaplingFullViewingKey, DiversifierKey)
|
||||
* pair. Returns 'false' if no valid address can be derived for the
|
||||
|
|
|
@ -103,6 +103,22 @@ UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components(
|
|||
const unsigned char* t_key,
|
||||
const unsigned char* sapling_key);
|
||||
|
||||
/**
|
||||
* Derive the internal and external OVKs for the binary encoding
|
||||
* of a transparent FVK (the concatenated bytes of the serialized
|
||||
* `(ChainCode, CPubKey)` pair.)
|
||||
*
|
||||
* Returns `true` if `t_key` was successfully deserialized,
|
||||
* in which case `internal_ovk_ret` and `external_ovk_ret` (which
|
||||
* should both point to 32-byte arrays) will have been updated
|
||||
* with the appropriate key bytes; otherwise, this procedure
|
||||
* returns `false` and the return values are unmodified.
|
||||
*/
|
||||
bool transparent_key_ovks(
|
||||
const unsigned char* t_key,
|
||||
unsigned char* internal_ovk_ret,
|
||||
unsigned char* external_ovk_ret);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -30,7 +30,7 @@ use std::io::BufReader;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::slice;
|
||||
use subtle::CtOption;
|
||||
use tracing::info;
|
||||
use tracing::{error, info};
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use std::ffi::OsStr;
|
||||
|
@ -55,7 +55,7 @@ use zcash_primitives::{
|
|||
},
|
||||
sapling::{merkle_hash, spend_sig},
|
||||
transaction::components::Amount,
|
||||
zip32::{self, sapling_address, sapling_find_address},
|
||||
zip32::{self, sapling_address, sapling_derive_internal_fvk, sapling_find_address},
|
||||
};
|
||||
use zcash_proofs::{
|
||||
circuit::sapling::TREE_DEPTH as SAPLING_TREE_DEPTH,
|
||||
|
@ -532,13 +532,23 @@ pub extern "C" fn librustzcash_eh_isvalid(
|
|||
soln: *const c_uchar,
|
||||
soln_len: size_t,
|
||||
) -> bool {
|
||||
if (k >= n) || (n % 8 != 0) || (soln_len != (1 << k) * ((n / (k + 1)) as usize + 1) / 8) {
|
||||
let expected_soln_len = (1 << k) * ((n / (k + 1)) as usize + 1) / 8;
|
||||
if (k >= n) || (n % 8 != 0) || (soln_len != expected_soln_len) {
|
||||
error!(
|
||||
"eh_isvalid: params wrong, n={}, k={}, soln_len={} expected={}",
|
||||
n, k, soln_len, expected_soln_len,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
let rs_input = unsafe { slice::from_raw_parts(input, input_len) };
|
||||
let rs_nonce = unsafe { slice::from_raw_parts(nonce, nonce_len) };
|
||||
let rs_soln = unsafe { slice::from_raw_parts(soln, soln_len) };
|
||||
equihash::is_valid_solution(n, k, rs_input, rs_nonce, rs_soln).is_ok()
|
||||
if let Err(e) = equihash::is_valid_solution(n, k, rs_input, rs_nonce, rs_soln) {
|
||||
error!("eh_isvalid: is_valid_solution: {}", e);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a Sapling verification context. Please free this when you're done.
|
||||
|
@ -1059,6 +1069,23 @@ pub extern "C" fn librustzcash_zip32_xsk_derive(
|
|||
.expect("should be able to serialize an ExtendedSpendingKey");
|
||||
}
|
||||
|
||||
/// Derive the Sapling internal spending key from the external extended
|
||||
/// spending key
|
||||
#[no_mangle]
|
||||
pub extern "C" fn librustzcash_zip32_xsk_derive_internal(
|
||||
xsk_external: *const [c_uchar; 169],
|
||||
xsk_internal_ret: *mut [c_uchar; 169],
|
||||
) {
|
||||
let xsk_external = zip32::ExtendedSpendingKey::read(&unsafe { *xsk_external }[..])
|
||||
.expect("valid ExtendedSpendingKey");
|
||||
|
||||
let xsk_internal = xsk_external.derive_internal();
|
||||
|
||||
xsk_internal
|
||||
.write(&mut (unsafe { &mut *xsk_internal_ret })[..])
|
||||
.expect("should be able to serialize an ExtendedSpendingKey");
|
||||
}
|
||||
|
||||
/// Derive a child ExtendedFullViewingKey from a parent.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn librustzcash_zip32_xfvk_derive(
|
||||
|
@ -1081,6 +1108,25 @@ pub extern "C" fn librustzcash_zip32_xfvk_derive(
|
|||
true
|
||||
}
|
||||
|
||||
/// Derive the Sapling internal full viewing key from the corresponding external full viewing key
|
||||
#[no_mangle]
|
||||
pub extern "C" fn librustzcash_zip32_sapling_derive_internal_fvk(
|
||||
fvk: *const [c_uchar; 96],
|
||||
dk: *const [c_uchar; 32],
|
||||
fvk_ret: *mut [c_uchar; 96],
|
||||
dk_ret: *mut [c_uchar; 32],
|
||||
) {
|
||||
let fvk = FullViewingKey::read(&unsafe { *fvk }[..]).expect("valid Sapling FullViewingKey");
|
||||
let dk = zip32::DiversifierKey(unsafe { *dk });
|
||||
|
||||
let (fvk_internal, dk_internal) = sapling_derive_internal_fvk(&fvk, &dk);
|
||||
let fvk_ret = unsafe { &mut *fvk_ret };
|
||||
let dk_ret = unsafe { &mut *dk_ret };
|
||||
|
||||
fvk_ret.copy_from_slice(&fvk_internal.to_bytes());
|
||||
dk_ret.copy_from_slice(&dk_internal.0);
|
||||
}
|
||||
|
||||
/// Derive a PaymentAddress from an ExtendedFullViewingKey.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn librustzcash_zip32_sapling_address(
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::ffi::{CStr, CString};
|
|||
use tracing::error;
|
||||
|
||||
use zcash_address::unified::{Container, Encoding, Fvk, Ufvk};
|
||||
use zcash_primitives::legacy::keys::AccountPubKey;
|
||||
|
||||
use crate::address_ffi::network_from_cstr;
|
||||
|
||||
|
@ -165,3 +166,29 @@ pub extern "C" fn unified_full_viewing_key_from_components(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn transparent_key_ovks(
|
||||
t_key: *const [u8; 65],
|
||||
internal_ovk_ret: *mut [u8; 32],
|
||||
external_ovk_ret: *mut [u8; 32],
|
||||
) -> bool {
|
||||
let key_bytes = unsafe { t_key.as_ref() }.expect("Transparent FVK pointer may not be null.");
|
||||
let internal_ovk = unsafe { &mut *internal_ovk_ret };
|
||||
let external_ovk = unsafe { &mut *external_ovk_ret };
|
||||
match AccountPubKey::deserialize(key_bytes) {
|
||||
Ok(epubkey) => {
|
||||
let (internal, external) = epubkey.ovks_for_shielding();
|
||||
*internal_ovk = internal.as_bytes();
|
||||
*external_ovk = external.as_bytes();
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"An error occurred parsing the transparent full viewing key: {:?}",
|
||||
e
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,29 +280,31 @@ void TransactionBuilder::SetFee(CAmount fee)
|
|||
this->fee = fee;
|
||||
}
|
||||
|
||||
void TransactionBuilder::SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, uint256 ovk)
|
||||
{
|
||||
saplingChangeAddr = std::make_pair(ovk, changeAddr);
|
||||
sproutChangeAddr = std::nullopt;
|
||||
// TODO: remove support for transparent change?
|
||||
void TransactionBuilder::SendChangeTo(
|
||||
const libzcash::RecipientAddress& changeAddr,
|
||||
const uint256& ovk) {
|
||||
tChangeAddr = std::nullopt;
|
||||
}
|
||||
|
||||
void TransactionBuilder::SendChangeTo(libzcash::SproutPaymentAddress changeAddr)
|
||||
{
|
||||
sproutChangeAddr = changeAddr;
|
||||
saplingChangeAddr = std::nullopt;
|
||||
tChangeAddr = std::nullopt;
|
||||
}
|
||||
|
||||
void TransactionBuilder::SendChangeTo(CTxDestination& changeAddr)
|
||||
{
|
||||
if (!IsValidDestination(changeAddr)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid change address, not a valid taddr.");
|
||||
}
|
||||
|
||||
tChangeAddr = changeAddr;
|
||||
saplingChangeAddr = std::nullopt;
|
||||
sproutChangeAddr = std::nullopt;
|
||||
|
||||
std::visit(match {
|
||||
[&](const CKeyID& keyId) {
|
||||
tChangeAddr = keyId;
|
||||
},
|
||||
[&](const CScriptID& scriptId) {
|
||||
tChangeAddr = scriptId;
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& changeDest) {
|
||||
saplingChangeAddr = std::make_pair(ovk, changeDest);
|
||||
}
|
||||
}, changeAddr);
|
||||
}
|
||||
|
||||
void TransactionBuilder::SendChangeToSprout(const libzcash::SproutPaymentAddress& zaddr) {
|
||||
tChangeAddr = std::nullopt;
|
||||
saplingChangeAddr = std::nullopt;
|
||||
sproutChangeAddr = zaddr;
|
||||
}
|
||||
|
||||
TransactionBuilderResult TransactionBuilder::Build()
|
||||
|
|
|
@ -170,11 +170,8 @@ public:
|
|||
|
||||
void AddTransparentOutput(const CTxDestination& to, CAmount value);
|
||||
|
||||
void SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, uint256 ovk);
|
||||
|
||||
void SendChangeTo(libzcash::SproutPaymentAddress);
|
||||
|
||||
void SendChangeTo(CTxDestination& changeAddr);
|
||||
void SendChangeTo(const libzcash::RecipientAddress& changeAddr, const uint256& ovk);
|
||||
void SendChangeToSprout(const libzcash::SproutPaymentAddress& changeAddr);
|
||||
|
||||
TransactionBuilderResult Build();
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
extern UniValue signrawtransaction(const UniValue& params, bool fHelp);
|
||||
|
||||
// TODO: instead of passing a TestMode flag, tests should override `CommitTransaction`
|
||||
// on the wallet.
|
||||
UniValue SendTransaction(CTransaction& tx, std::optional<std::reference_wrapper<CReserveKey>> reservekey, bool testmode) {
|
||||
UniValue o(UniValue::VOBJ);
|
||||
// Send the transaction
|
||||
|
|
|
@ -205,8 +205,8 @@ libzcash::SaplingPaymentAddress AsyncRPCOperation_saplingmigration::getMigration
|
|||
}
|
||||
|
||||
// TODO: move off of legacy addresses.
|
||||
auto generatedKey = pwalletMain->GenerateLegacySaplingZKey(0);
|
||||
return generatedKey.first.ToXFVK().DefaultAddress();
|
||||
auto generated = pwalletMain->GenerateLegacySaplingZKey(0);
|
||||
return generated.first;
|
||||
}
|
||||
|
||||
void AsyncRPCOperation_saplingmigration::cancel() {
|
||||
|
|
|
@ -60,29 +60,11 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
|||
assert(!recipients_.empty());
|
||||
assert(ztxoSelector.RequireSpendingKeys());
|
||||
|
||||
std::visit(match {
|
||||
[&](const AccountZTXOPattern& acct) {
|
||||
isfromtaddr_ =
|
||||
acct.GetReceiverTypes().empty() ||
|
||||
acct.GetReceiverTypes().count(ReceiverType::P2PKH) > 0 ||
|
||||
acct.GetReceiverTypes().count(ReceiverType::P2SH) > 0;
|
||||
},
|
||||
[&](const CKeyID& keyId) {
|
||||
isfromtaddr_ = true;
|
||||
},
|
||||
[&](const CScriptID& scriptId) {
|
||||
isfromtaddr_ = true;
|
||||
},
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
isfromsprout_ = true;
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
isfromsapling_ = true;
|
||||
}
|
||||
}, ztxoSelector.GetPattern());
|
||||
sendFromAccount_ = pwalletMain->FindAccountForSelector(ztxoSelector_).value_or(ZCASH_LEGACY_ACCOUNT);
|
||||
|
||||
if ((isfromsprout_ || isfromsapling_) && minDepth == 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be zero when sending from a shielded address");
|
||||
// we always allow shielded change when not sending from the legacy account
|
||||
if (sendFromAccount_ != ZCASH_LEGACY_ACCOUNT) {
|
||||
allowedChangeTypes_.insert(libzcash::ChangeType::Sapling);
|
||||
}
|
||||
|
||||
// calculate the target totals
|
||||
|
@ -91,14 +73,16 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
|||
[&](const CKeyID& addr) {
|
||||
transparentRecipients_ += 1;
|
||||
txOutputAmounts_.t_outputs_total += recipient.amount;
|
||||
allowedChangeTypes_.insert(libzcash::ChangeType::Transparent);
|
||||
},
|
||||
[&](const CScriptID& addr) {
|
||||
transparentRecipients_ += 1;
|
||||
txOutputAmounts_.t_outputs_total += recipient.amount;
|
||||
allowedChangeTypes_.insert(libzcash::ChangeType::Transparent);
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
txOutputAmounts_.z_outputs_total += recipient.amount;
|
||||
if (isfromsprout_ && !allowRevealedAmounts_) {
|
||||
txOutputAmounts_.sapling_outputs_total += recipient.amount;
|
||||
if (ztxoSelector_.SelectsSprout() && !allowRevealedAmounts_) {
|
||||
throw JSONRPCError(
|
||||
RPC_INVALID_PARAMETER,
|
||||
"Sending between shielded pools is not enabled by default because it will "
|
||||
|
@ -192,11 +176,7 @@ void AsyncRPCOperation_sendmany::main() {
|
|||
//
|
||||
// At least 4. and 5. differ from the Rust transaction builder.
|
||||
uint256 AsyncRPCOperation_sendmany::main_impl() {
|
||||
// TODO UA: this check will become meaningless.
|
||||
bool isfromzaddr_ = isfromsprout_ || isfromsapling_;
|
||||
assert(isfromtaddr_ != isfromzaddr_);
|
||||
|
||||
CAmount sendAmount = txOutputAmounts_.z_outputs_total + txOutputAmounts_.t_outputs_total;
|
||||
CAmount sendAmount = txOutputAmounts_.sapling_outputs_total + txOutputAmounts_.t_outputs_total;
|
||||
CAmount targetAmount = sendAmount + fee_;
|
||||
|
||||
builder_.SetFee(fee_);
|
||||
|
@ -245,14 +225,6 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
|
||||
spendable.LogInputs(getId());
|
||||
|
||||
// At least one of z_sprout_inputs_ and z_sapling_inputs_ must be empty by design
|
||||
//
|
||||
// TODO: This restriction is true by construction as we have no mechanism
|
||||
// for filtering for notes that will select both Sprout and Sapling notes
|
||||
// simultaneously, but even if we did it would likely be safe to remove
|
||||
// this limitation.
|
||||
assert(spendable.sproutNoteEntries.empty() || spendable.saplingNoteEntries.empty());
|
||||
|
||||
CAmount t_inputs_total{0};
|
||||
CAmount z_inputs_total{0};
|
||||
for (const auto& t : spendable.utxos) {
|
||||
|
@ -265,25 +237,16 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
z_inputs_total += t.note.value();
|
||||
}
|
||||
|
||||
// TODO UA: these restrictions should be removed.
|
||||
assert(!isfromtaddr_ || z_inputs_total == 0);
|
||||
assert(!isfromzaddr_ || t_inputs_total == 0);
|
||||
|
||||
if (isfromtaddr_ && (t_inputs_total < targetAmount)) {
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
|
||||
strprintf("Insufficient transparent funds, have %s, need %s",
|
||||
FormatMoney(t_inputs_total), FormatMoney(targetAmount)));
|
||||
}
|
||||
if (isfromzaddr_ && (z_inputs_total < targetAmount)) {
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
|
||||
strprintf("Insufficient shielded funds, have %s, need %s",
|
||||
FormatMoney(z_inputs_total), FormatMoney(targetAmount)));
|
||||
if (z_inputs_total > 0 && mindepth_ == 0) {
|
||||
throw JSONRPCError(
|
||||
RPC_INVALID_PARAMETER,
|
||||
"Minconf cannot be zero when sending from a shielded address");
|
||||
}
|
||||
|
||||
// When spending transparent coinbase outputs, all inputs must be fully
|
||||
// consumed, and they may only be sent to shielded recipients.
|
||||
if (spendable.HasTransparentCoinbase()) {
|
||||
if (t_inputs_total != targetAmount) {
|
||||
if (t_inputs_total + z_inputs_total != targetAmount) {
|
||||
throw JSONRPCError(
|
||||
RPC_WALLET_ERROR,
|
||||
strprintf(
|
||||
|
@ -299,66 +262,71 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
}
|
||||
}
|
||||
|
||||
if (isfromtaddr_) {
|
||||
LogPrint("zrpc", "%s: spending %s to send %s with fee %s\n",
|
||||
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(fee_));
|
||||
} else {
|
||||
LogPrint("zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
|
||||
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(fee_));
|
||||
}
|
||||
LogPrint("zrpc", "%s: transparent input: %s (to choose from)\n", getId(), FormatMoney(t_inputs_total));
|
||||
LogPrint("zrpcunsafe", "%s: private input: %s (to choose from)\n", getId(), FormatMoney(z_inputs_total));
|
||||
LogPrint("zrpc", "%s: transparent output: %s\n", getId(), FormatMoney(txOutputAmounts_.t_outputs_total));
|
||||
LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(txOutputAmounts_.z_outputs_total));
|
||||
LogPrint("zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
|
||||
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(fee_));
|
||||
LogPrint("zrpc", "%s: total transparent input: %s (to choose from)\n", getId(), FormatMoney(t_inputs_total));
|
||||
LogPrint("zrpcunsafe", "%s: total shielded input: %s (to choose from)\n", getId(), FormatMoney(z_inputs_total));
|
||||
LogPrint("zrpc", "%s: total transparent output: %s\n", getId(), FormatMoney(txOutputAmounts_.t_outputs_total));
|
||||
LogPrint("zrpcunsafe", "%s: total shielded Sapling output: %s\n", getId(), FormatMoney(txOutputAmounts_.sapling_outputs_total));
|
||||
LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(fee_));
|
||||
|
||||
CReserveKey keyChange(pwalletMain);
|
||||
uint256 ovk;
|
||||
|
||||
auto getDefaultOVK = [&]() {
|
||||
HDSeed seed = pwalletMain->GetHDSeedForRPC();
|
||||
return ovkForShieldingFromTaddr(seed);
|
||||
};
|
||||
|
||||
auto setTransparentChangeRecipient = [&]() {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
CPubKey vchPubKey;
|
||||
bool ret = keyChange.GetReservedKey(vchPubKey);
|
||||
if (!ret) {
|
||||
// should never fail, as we just unlocked
|
||||
throw JSONRPCError(
|
||||
RPC_WALLET_KEYPOOL_RAN_OUT,
|
||||
"Could not generate a taddr to use as a change address");
|
||||
}
|
||||
|
||||
CTxDestination changeAddr = vchPubKey.GetID();
|
||||
builder_.SendChangeTo(changeAddr);
|
||||
};
|
||||
|
||||
// FIXME: use the appropriate shielded pool change address for the
|
||||
// source unified address account (or the legacy account), and the
|
||||
// associated OVK
|
||||
auto ovks = this->SelectOVKs(spendable);
|
||||
std::visit(match {
|
||||
[&](const CKeyID& keyId) {
|
||||
allowedChangeTypes_.insert(libzcash::ChangeType::Transparent);
|
||||
auto changeAddr = pwalletMain->GenerateChangeAddressForAccount(
|
||||
sendFromAccount_, allowedChangeTypes_);
|
||||
assert(changeAddr.has_value());
|
||||
builder_.SendChangeTo(changeAddr.value(), ovks.first);
|
||||
},
|
||||
[&](const CScriptID& scriptId) {
|
||||
allowedChangeTypes_.insert(libzcash::ChangeType::Transparent);
|
||||
auto changeAddr = pwalletMain->GenerateChangeAddressForAccount(
|
||||
sendFromAccount_, allowedChangeTypes_);
|
||||
assert(changeAddr.has_value());
|
||||
builder_.SendChangeTo(changeAddr.value(), ovks.first);
|
||||
},
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
ovk = getDefaultOVK();
|
||||
builder_.SendChangeTo(addr);
|
||||
// for Sprout, we return change to the originating address.
|
||||
builder_.SendChangeToSprout(addr);
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
libzcash::SaplingExtendedSpendingKey saplingKey;
|
||||
assert(pwalletMain->GetSaplingExtendedSpendingKey(addr, saplingKey));
|
||||
|
||||
ovk = saplingKey.expsk.full_viewing_key().ovk;
|
||||
builder_.SendChangeTo(addr, ovk);
|
||||
// for Sapling, if using a legacy address, return change to the
|
||||
// originating address; otherwise return it to the Sapling internal
|
||||
// address corresponding to the UFVK.
|
||||
if (sendFromAccount_ == ZCASH_LEGACY_ACCOUNT) {
|
||||
builder_.SendChangeTo(addr, ovks.first);
|
||||
} else {
|
||||
auto changeAddr = pwalletMain->GenerateChangeAddressForAccount(
|
||||
sendFromAccount_, allowedChangeTypes_);
|
||||
assert(changeAddr.has_value());
|
||||
builder_.SendChangeTo(changeAddr.value(), ovks.first);
|
||||
}
|
||||
},
|
||||
[&](const auto& other) {
|
||||
ovk = getDefaultOVK();
|
||||
setTransparentChangeRecipient();
|
||||
[&](const AccountZTXOPattern& acct) {
|
||||
for (ReceiverType rtype : acct.GetReceiverTypes()) {
|
||||
switch (rtype) {
|
||||
case ReceiverType::P2PKH:
|
||||
case ReceiverType::P2SH:
|
||||
allowedChangeTypes_.insert(libzcash::ChangeType::Transparent);
|
||||
break;
|
||||
case ReceiverType::Sapling:
|
||||
allowedChangeTypes_.insert(libzcash::ChangeType::Sapling);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto changeAddr = pwalletMain->GenerateChangeAddressForAccount(
|
||||
acct.GetAccountId(),
|
||||
allowedChangeTypes_);
|
||||
|
||||
assert(changeAddr.has_value());
|
||||
builder_.SendChangeTo(changeAddr.value(), ovks.first);
|
||||
}
|
||||
}, ztxoSelector_.GetPattern());
|
||||
|
||||
// Track the total of notes that we've added to the builder
|
||||
// Track the total of notes that we've added to the builder. This
|
||||
// shouldn't strictly be necessary, given `spendable.LimitToAmount`
|
||||
CAmount sum = 0;
|
||||
|
||||
// Create Sapling outpoints
|
||||
|
@ -410,7 +378,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
auto value = r.amount;
|
||||
auto memo = get_memo_from_hex_string(r.memo.has_value() ? r.memo.value() : "");
|
||||
|
||||
builder_.AddSaplingOutput(ovk, addr, value, memo);
|
||||
builder_.AddSaplingOutput(ovks.second, addr, value, memo);
|
||||
}
|
||||
}, r.address);
|
||||
}
|
||||
|
@ -466,12 +434,78 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
auto buildResult = builder_.Build();
|
||||
auto tx = buildResult.GetTxOrThrow();
|
||||
|
||||
UniValue sendResult = SendTransaction(tx, keyChange, testmode);
|
||||
UniValue sendResult = SendTransaction(tx, std::nullopt, testmode);
|
||||
set_result(sendResult);
|
||||
|
||||
return tx.GetHash();
|
||||
}
|
||||
|
||||
std::pair<uint256, uint256> AsyncRPCOperation_sendmany::SelectOVKs(const SpendableInputs& spendable) const {
|
||||
uint256 internalOVK;
|
||||
uint256 externalOVK;
|
||||
if (!spendable.saplingNoteEntries.empty()) {
|
||||
std::optional<SaplingDiversifiableFullViewingKey> dfvk;
|
||||
std::visit(match {
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
libzcash::SaplingExtendedSpendingKey extsk;
|
||||
assert(pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk));
|
||||
dfvk = extsk.ToXFVK();
|
||||
},
|
||||
[&](const AccountZTXOPattern& acct) {
|
||||
auto ufvk = pwalletMain->GetUnifiedFullViewingKeyByAccount(acct.GetAccountId());
|
||||
dfvk = ufvk.value().GetSaplingKey().value();
|
||||
},
|
||||
[&](const auto& other) {
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
}, this->ztxoSelector_.GetPattern());
|
||||
assert(dfvk.has_value());
|
||||
|
||||
auto ovks = dfvk.value().GetOVKs();
|
||||
internalOVK = ovks.first;
|
||||
externalOVK = ovks.second;
|
||||
} else if (!spendable.utxos.empty()) {
|
||||
std::optional<transparent::AccountPubKey> tfvk;
|
||||
std::visit(match {
|
||||
[&](const CKeyID& keyId) {
|
||||
tfvk = pwalletMain->GetLegacyAccountKey().ToAccountPubKey();
|
||||
},
|
||||
[&](const CScriptID& keyId) {
|
||||
tfvk = pwalletMain->GetLegacyAccountKey().ToAccountPubKey();
|
||||
},
|
||||
[&](const AccountZTXOPattern& acct) {
|
||||
if (acct.GetAccountId() == ZCASH_LEGACY_ACCOUNT) {
|
||||
tfvk = pwalletMain->GetLegacyAccountKey().ToAccountPubKey();
|
||||
} else {
|
||||
auto ufvk = pwalletMain->GetUnifiedFullViewingKeyByAccount(acct.GetAccountId()).value();
|
||||
tfvk = ufvk.GetTransparentKey().value();
|
||||
}
|
||||
},
|
||||
[&](const auto& other) {
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
}, this->ztxoSelector_.GetPattern());
|
||||
assert(tfvk.has_value());
|
||||
|
||||
auto ovks = tfvk.value().GetOVKsForShielding();
|
||||
internalOVK = ovks.first;
|
||||
externalOVK = ovks.second;
|
||||
} else if (!spendable.sproutNoteEntries.empty()) {
|
||||
// use the legacy transparent account OVKs when sending from Sprout
|
||||
auto tfvk = pwalletMain->GetLegacyAccountKey().ToAccountPubKey();
|
||||
auto ovks = tfvk.GetOVKsForShielding();
|
||||
internalOVK = ovks.first;
|
||||
externalOVK = ovks.second;
|
||||
} else {
|
||||
// This should be unreachable; it is left in place as a guard to ensure
|
||||
// that when new input types are added to SpendableInputs in the future
|
||||
// that we do not accidentally return the all-zeros OVK.
|
||||
throw std::runtime_error("No spendable inputs.");
|
||||
}
|
||||
|
||||
return std::make_pair(internalOVK, externalOVK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a dust threshold based upon a standard p2pkh txout.
|
||||
*/
|
||||
|
@ -523,4 +557,3 @@ UniValue AsyncRPCOperation_sendmany::getStatus() const {
|
|||
obj.pushKV("params", contextinfo_ );
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
class TxOutputAmounts {
|
||||
public:
|
||||
CAmount t_outputs_total{0};
|
||||
CAmount z_outputs_total{0};
|
||||
CAmount sapling_outputs_total{0};
|
||||
};
|
||||
|
||||
class AsyncRPCOperation_sendmany : public AsyncRPCOperation {
|
||||
|
@ -75,13 +75,20 @@ private:
|
|||
CAmount fee_;
|
||||
UniValue contextinfo_; // optional data to include in return value from getStatus()
|
||||
|
||||
bool isfromtaddr_{false};
|
||||
bool isfromsprout_{false};
|
||||
bool isfromsapling_{false};
|
||||
bool allowRevealedAmounts_{false};
|
||||
uint32_t transparentRecipients_{0};
|
||||
AccountId sendFromAccount_;
|
||||
std::set<libzcash::ChangeType> allowedChangeTypes_;
|
||||
TxOutputAmounts txOutputAmounts_;
|
||||
|
||||
/**
|
||||
* Compute the internal and external OVKs to use in transaction construction, given
|
||||
* the spendable inputs.
|
||||
*/
|
||||
std::pair<uint256, uint256> SelectOVKs(const SpendableInputs& spendable) const;
|
||||
|
||||
static CAmount DefaultDustThreshold();
|
||||
|
||||
static std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
|
||||
|
@ -89,7 +96,6 @@ private:
|
|||
uint256 main_impl();
|
||||
};
|
||||
|
||||
|
||||
// To test private methods, a friend class can act as a proxy
|
||||
class TEST_FRIEND_AsyncRPCOperation_sendmany {
|
||||
public:
|
||||
|
|
|
@ -258,6 +258,7 @@ bool ShieldToAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) c
|
|||
// generate a common one from the HD seed. This ensures the data is
|
||||
// recoverable, while keeping it logically separate from the ZIP 32
|
||||
// Sapling key hierarchy, which the user might not be using.
|
||||
// FIXME: update to use the ZIP-316 OVK
|
||||
HDSeed seed = pwalletMain->GetHDSeedForRPC();
|
||||
uint256 ovk = ovkForShieldingFromTaddr(seed);
|
||||
|
||||
|
|
|
@ -667,7 +667,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
|
|||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
auto pk = extfvk.DefaultAddress();
|
||||
|
||||
ASSERT_TRUE(wallet.AddSaplingZKey(sk));
|
||||
|
@ -1042,7 +1042,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
|
|||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
auto pk = extfvk.DefaultAddress();
|
||||
|
||||
// Generate Sapling note A
|
||||
|
@ -1991,7 +1991,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
|||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
auto pk = extfvk.DefaultAddress();
|
||||
|
||||
ASSERT_TRUE(wallet.AddSaplingZKey(sk));
|
||||
|
@ -2166,13 +2166,13 @@ TEST(WalletTests, GenerateUnifiedAddress) {
|
|||
(void) RegtestActivateSapling();
|
||||
TestWallet wallet(Params());
|
||||
|
||||
UAGenerationResult uaResult = wallet.GenerateUnifiedAddress(0, {ReceiverType::P2PKH, ReceiverType::Sapling});
|
||||
auto uaResult = wallet.GenerateUnifiedAddress(0, {ReceiverType::P2PKH, ReceiverType::Sapling});
|
||||
|
||||
// If the wallet does not have a mnemonic seed available, it is
|
||||
// treated as if the wallet is encrypted.
|
||||
EXPECT_FALSE(wallet.IsCrypted());
|
||||
EXPECT_FALSE(wallet.GetMnemonicSeed().has_value());
|
||||
UAGenerationResult expected = AddressGenerationError::WalletEncrypted;
|
||||
WalletUAGenerationResult expected = WalletUAGenerationError::WalletEncrypted;
|
||||
EXPECT_EQ(uaResult, expected);
|
||||
|
||||
wallet.GenerateNewSeed();
|
||||
|
@ -2183,7 +2183,7 @@ TEST(WalletTests, GenerateUnifiedAddress) {
|
|||
// we cannot create an address for the account corresponding
|
||||
// to that spending key.
|
||||
uaResult = wallet.GenerateUnifiedAddress(0, {ReceiverType::P2PKH, ReceiverType::Sapling});
|
||||
expected = AddressGenerationError::NoSuchAccount;
|
||||
expected = WalletUAGenerationError::NoSuchAccount;
|
||||
EXPECT_EQ(uaResult, expected);
|
||||
|
||||
// Create an account, then generate an address for that account.
|
||||
|
@ -2192,22 +2192,21 @@ TEST(WalletTests, GenerateUnifiedAddress) {
|
|||
auto ua = std::get_if<std::pair<libzcash::UnifiedAddress, libzcash::diversifier_index_t>>(&uaResult);
|
||||
EXPECT_NE(ua, nullptr);
|
||||
|
||||
EXPECT_TRUE(ua->first.GetSaplingReceiver().has_value());
|
||||
|
||||
auto uaSaplingReceiver = ua->first.GetSaplingReceiver();
|
||||
EXPECT_TRUE(uaSaplingReceiver.has_value());
|
||||
auto ufvk = skpair.first.ToFullViewingKey();
|
||||
EXPECT_EQ(
|
||||
ua->first.GetSaplingReceiver(),
|
||||
ufvk.GetSaplingKey().value().Address(ua->second));
|
||||
EXPECT_EQ(uaSaplingReceiver.value(), ufvk.GetSaplingKey().value().Address(ua->second));
|
||||
|
||||
auto u4r = wallet.GetUnifiedForReceiver(ua->first.GetSaplingReceiver().value());
|
||||
EXPECT_EQ(u4r, ua->first);
|
||||
auto u4r = wallet.FindUnifiedAddressByReceiver(uaSaplingReceiver.value());
|
||||
EXPECT_TRUE(u4r.has_value());
|
||||
EXPECT_EQ(u4r.value(), ua->first);
|
||||
|
||||
// Explicitly trigger the invalid transparent child index failure
|
||||
uaResult = wallet.GenerateUnifiedAddress(
|
||||
0,
|
||||
{ReceiverType::P2PKH, ReceiverType::Sapling},
|
||||
MAX_TRANSPARENT_CHILD_IDX.succ().value());
|
||||
expected = AddressGenerationError::InvalidTransparentChildIndex;
|
||||
expected = UnifiedAddressGenerationError::InvalidTransparentChildIndex;
|
||||
EXPECT_EQ(uaResult, expected);
|
||||
|
||||
// Attempt to generate a UA at the maximum transparent child index. This might fail
|
||||
|
@ -2220,12 +2219,12 @@ TEST(WalletTests, GenerateUnifiedAddress) {
|
|||
MAX_TRANSPARENT_CHILD_IDX);
|
||||
ua = std::get_if<std::pair<libzcash::UnifiedAddress, libzcash::diversifier_index_t>>(&uaResult);
|
||||
if (ua == nullptr) {
|
||||
expected = AddressGenerationError::NoAddressForDiversifier;
|
||||
expected = UnifiedAddressGenerationError::NoAddressForDiversifier;
|
||||
EXPECT_EQ(uaResult, expected);
|
||||
} else {
|
||||
// the previous generation attempt succeeded, so this one should definitely fail.
|
||||
uaResult = wallet.GenerateUnifiedAddress(0, {ReceiverType::P2PKH, ReceiverType::Sapling});
|
||||
expected = AddressGenerationError::DiversifierSpaceExhausted;
|
||||
expected = UnifiedAddressGenerationError::InvalidTransparentChildIndex;
|
||||
EXPECT_EQ(uaResult, expected);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
* This test covers Sapling methods on CWallet
|
||||
* GenerateNewLegacySaplingZKey()
|
||||
* AddSaplingZKey()
|
||||
* AddSaplingIncomingViewingKey()
|
||||
* AddSaplingPaymentAddress()
|
||||
* LoadSaplingZKey()
|
||||
* LoadSaplingIncomingViewingKey()
|
||||
* LoadSaplingZKeyMetadata()
|
||||
|
@ -59,11 +59,11 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
|
|||
wallet.GetSaplingSpendingKey(extfvk, keyOut);
|
||||
ASSERT_EQ(sk, keyOut);
|
||||
|
||||
// verify there are two keys
|
||||
// verify there is still only one address; adding the spending
|
||||
// key no longer adds the default address
|
||||
wallet.GetSaplingPaymentAddresses(addrs);
|
||||
EXPECT_EQ(2, addrs.size());
|
||||
EXPECT_EQ(1, addrs.size());
|
||||
EXPECT_EQ(1, addrs.count(address));
|
||||
EXPECT_EQ(1, addrs.count(sk.ToXFVK().DefaultAddress()));
|
||||
|
||||
// Find a diversified address that does not use the same diversifier as the default address.
|
||||
// By starting our search at `10` we ensure there's no more than a 2^-10 chance that we
|
||||
|
@ -71,13 +71,16 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
|
|||
libzcash::diversifier_index_t j(10);
|
||||
auto dpa = sk.ToXFVK().FindAddress(j).first;
|
||||
|
||||
// add the default address
|
||||
EXPECT_TRUE(wallet.AddSaplingPaymentAddress(sk.ToXFVK().ToIncomingViewingKey(), sk.ToXFVK().DefaultAddress()));
|
||||
|
||||
// verify wallet only has the default address
|
||||
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.ToXFVK().DefaultAddress()));
|
||||
EXPECT_FALSE(wallet.HaveSaplingIncomingViewingKey(dpa));
|
||||
|
||||
// manually add a diversified address
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
EXPECT_TRUE(wallet.AddSaplingIncomingViewingKey(ivk, dpa));
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
EXPECT_TRUE(wallet.AddSaplingPaymentAddress(ivk, dpa));
|
||||
|
||||
// verify wallet did add it
|
||||
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.ToXFVK().DefaultAddress()));
|
||||
|
@ -98,7 +101,7 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
|
|||
|
||||
// Load a diversified address for the third key into the wallet
|
||||
auto dpa2 = sk2.ToXFVK().FindAddress(j).first;
|
||||
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk2.ToXFVK().DefaultAddress()));
|
||||
EXPECT_FALSE(wallet.HaveSaplingIncomingViewingKey(sk2.ToXFVK().DefaultAddress()));
|
||||
EXPECT_FALSE(wallet.HaveSaplingIncomingViewingKey(dpa2));
|
||||
EXPECT_TRUE(wallet.LoadSaplingPaymentAddress(dpa2, ivk2));
|
||||
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa2));
|
||||
|
@ -454,7 +457,7 @@ TEST(WalletZkeysTest, WriteCryptedSaplingZkeyDirectToDb) {
|
|||
|
||||
// Add diversified address to the wallet
|
||||
auto ivk = extsk.expsk.full_viewing_key().in_viewing_key();
|
||||
EXPECT_TRUE(wallet.AddSaplingIncomingViewingKey(ivk, dpa));
|
||||
EXPECT_TRUE(wallet.AddSaplingPaymentAddress(ivk, dpa));
|
||||
|
||||
// encrypt wallet
|
||||
SecureString strWalletPass;
|
||||
|
@ -469,6 +472,11 @@ TEST(WalletZkeysTest, WriteCryptedSaplingZkeyDirectToDb) {
|
|||
wallet.Unlock(strWalletPass);
|
||||
auto address2 = wallet.GenerateNewLegacySaplingZKey();
|
||||
|
||||
// wallet should have three addresses: the default addresses for the two
|
||||
// generated keys, and the added diversified address.
|
||||
wallet.GetSaplingPaymentAddresses(addrs);
|
||||
ASSERT_EQ(3, addrs.size());
|
||||
|
||||
// flush the wallet to prevent race conditions
|
||||
wallet.Flush();
|
||||
|
||||
|
|
|
@ -404,7 +404,7 @@ UniValue importwallet_impl(const UniValue& params, bool fImportZKeys)
|
|||
std::optional<std::string> seedFpStr = (vstr.size() > 3) ? std::optional<std::string>(vstr[3]) : std::nullopt;
|
||||
if (spendingkey.has_value()) {
|
||||
auto addResult = std::visit(
|
||||
AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus(), nTime, hdKeypath, seedFpStr, true), spendingkey.value());
|
||||
AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus(), nTime, hdKeypath, seedFpStr, true, true), spendingkey.value());
|
||||
if (addResult == KeyAlreadyExists){
|
||||
LogPrint("zrpc", "Skipping import of zaddr (key already present)\n");
|
||||
} else if (addResult == KeyNotAdded) {
|
||||
|
@ -878,7 +878,7 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp)
|
|||
result.pushKV("type", addrInfo.first);
|
||||
result.pushKV("address", strAddress);
|
||||
|
||||
auto addResult = std::visit(AddViewingKeyToWallet(pwalletMain), viewingkey.value());
|
||||
auto addResult = std::visit(AddViewingKeyToWallet(pwalletMain, true), viewingkey.value());
|
||||
if (addResult == SpendingKeyExists) {
|
||||
throw JSONRPCError(
|
||||
RPC_WALLET_ERROR,
|
||||
|
|
|
@ -3157,36 +3157,49 @@ UniValue z_getaddressforaccount(const UniValue& params, bool fHelp)
|
|||
j.setNumStr(ArbitraryIntStr(std::vector(addr.second.begin(), addr.second.end())));
|
||||
result.pushKV("diversifier_index", j);
|
||||
},
|
||||
[&](AddressGenerationError err) {
|
||||
[&](WalletUAGenerationError err) {
|
||||
std::string strErr;
|
||||
switch (err) {
|
||||
case AddressGenerationError::NoSuchAccount:
|
||||
case WalletUAGenerationError::NoSuchAccount:
|
||||
strErr = tfm::format("Error: account %d has not been generated by z_getnewaccount.", account);
|
||||
break;
|
||||
case AddressGenerationError::ExistingAddressMismatch:
|
||||
case WalletUAGenerationError::ExistingAddressMismatch:
|
||||
strErr = tfm::format(
|
||||
"Error: address at diversifier index %s was already generated with different receiver types.",
|
||||
params[2].getValStr());
|
||||
break;
|
||||
case AddressGenerationError::NoAddressForDiversifier:
|
||||
case WalletUAGenerationError::WalletEncrypted:
|
||||
// By construction, we should never see this error; this case is included
|
||||
// only for future-proofing.
|
||||
strErr = tfm::format("Error: wallet is encrypted.");
|
||||
}
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strErr);
|
||||
},
|
||||
[&](UnifiedAddressGenerationError err) {
|
||||
std::string strErr;
|
||||
switch (err) {
|
||||
case UnifiedAddressGenerationError::NoAddressForDiversifier:
|
||||
strErr = tfm::format(
|
||||
"Error: no address at diversifier index %s.",
|
||||
ArbitraryIntStr(std::vector(j.value().begin(), j.value().end())));
|
||||
break;
|
||||
case AddressGenerationError::InvalidTransparentChildIndex:
|
||||
case UnifiedAddressGenerationError::InvalidTransparentChildIndex:
|
||||
strErr = tfm::format(
|
||||
"Error: diversifier index %s cannot generate an address with a transparent receiver.",
|
||||
ArbitraryIntStr(std::vector(j.value().begin(), j.value().end())));
|
||||
break;
|
||||
default:
|
||||
// By construction, we will not see these errors here:
|
||||
// - InvalidReceiverTypes
|
||||
// - WalletEncrypted
|
||||
//
|
||||
// If we see these, the user either has generated many addresses, or
|
||||
// was very unlucky with their mnemonic phrase generation:
|
||||
// - DiversifierSpaceExhausted
|
||||
strErr = tfm::format("Error: ran out of diversifier indices. Generate a new account with z_getnewaccount");
|
||||
case UnifiedAddressGenerationError::ShieldedReceiverNotFound:
|
||||
strErr = tfm::format(
|
||||
"Error: cannot generate an address containing no shielded receivers.");
|
||||
break;
|
||||
case UnifiedAddressGenerationError::ReceiverTypeNotAvailable:
|
||||
strErr = tfm::format(
|
||||
"Error: one or more of the requested receiver types does not have a corresponding spending key in this account.");
|
||||
break;
|
||||
case UnifiedAddressGenerationError::DiversifierSpaceExhausted:
|
||||
strErr = tfm::format(
|
||||
"Error: ran out of diversifier indices. Generate a new account with z_getnewaccount");
|
||||
break;
|
||||
}
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strErr);
|
||||
},
|
||||
|
@ -4010,9 +4023,15 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
|
|||
// Collect OutgoingViewingKeys for recovering output information
|
||||
std::set<uint256> ovks;
|
||||
{
|
||||
// Generate the common ovk for recovering t->z outputs.
|
||||
// Generate the old, pre-UA accounts OVK for recovering t->z outputs.
|
||||
HDSeed seed = pwalletMain->GetHDSeedForRPC();
|
||||
ovks.insert(ovkForShieldingFromTaddr(seed));
|
||||
|
||||
// Generate the OVKs for shielding from the legacy UA account
|
||||
auto legacyKey = pwalletMain->GetLegacyAccountKey().ToAccountPubKey();
|
||||
auto legacyAcctOVKs = legacyKey.GetOVKsForShielding();
|
||||
ovks.insert(legacyAcctOVKs.first);
|
||||
ovks.insert(legacyAcctOVKs.second);
|
||||
}
|
||||
|
||||
// Sapling spends
|
||||
|
@ -4044,7 +4063,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
|
|||
// If the note belongs to a Sapling address that is part of an account in the
|
||||
// wallet, show the corresponding Unified Address.
|
||||
std::string address;
|
||||
const auto ua = pwalletMain->GetUnifiedForReceiver(pa);
|
||||
const auto ua = pwalletMain->FindUnifiedAddressByReceiver(pa);
|
||||
if (ua.has_value()) {
|
||||
address = keyIO.EncodePaymentAddress(ua.value());
|
||||
} else {
|
||||
|
@ -4097,7 +4116,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
|
|||
// If the note belongs to a Sapling address that is part of an account in the
|
||||
// wallet, show the corresponding Unified Address.
|
||||
std::string address;
|
||||
const auto ua = pwalletMain->GetUnifiedForReceiver(pa);
|
||||
const auto ua = pwalletMain->FindUnifiedAddressByReceiver(pa);
|
||||
if (ua.has_value()) {
|
||||
address = keyIO.EncodePaymentAddress(ua.value());
|
||||
} else {
|
||||
|
@ -4377,6 +4396,24 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
"Invalid from address, no payment source found for address.");
|
||||
}
|
||||
|
||||
auto selectorAccount = pwalletMain->FindAccountForSelector(ztxoSelectorOpt.value());
|
||||
std::visit(match {
|
||||
[&](const libzcash::UnifiedAddress& ua) {
|
||||
if (!selectorAccount.has_value() || selectorAccount.value() == ZCASH_LEGACY_ACCOUNT) {
|
||||
throw JSONRPCError(
|
||||
RPC_INVALID_ADDRESS_OR_KEY,
|
||||
"Invalid from address, UA does not correspond to a known account.");
|
||||
}
|
||||
},
|
||||
[&](const auto& other) {
|
||||
if (selectorAccount.has_value() && selectorAccount.value() != ZCASH_LEGACY_ACCOUNT) {
|
||||
throw JSONRPCError(
|
||||
RPC_INVALID_ADDRESS_OR_KEY,
|
||||
"Invalid from address: is a bare receiver from a Unified Address in this wallet. Provide the UA as returned by z_getaddressforaccount instead.");
|
||||
}
|
||||
}
|
||||
}, decoded.value());
|
||||
|
||||
return ztxoSelectorOpt.value();
|
||||
}
|
||||
}();
|
||||
|
@ -4446,7 +4483,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive");
|
||||
}
|
||||
|
||||
recipients.push_back(SendManyRecipient(addr.value(), nAmount, memo) );
|
||||
recipients.push_back(SendManyRecipient(addr.value(), nAmount, memo));
|
||||
nTotalOut += nAmount;
|
||||
}
|
||||
if (recipients.empty()) {
|
||||
|
|
|
@ -134,7 +134,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet)
|
|||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
CPubKey demoPubkey = pwalletMain->GenerateNewKey();
|
||||
CPubKey demoPubkey = pwalletMain->GenerateNewKey(true);
|
||||
CTxDestination demoAddress(CTxDestination(demoPubkey.GetID()));
|
||||
UniValue retValue;
|
||||
string strPurpose = "receive";
|
||||
|
@ -143,7 +143,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet)
|
|||
pwalletMain->SetAddressBook(demoPubkey.GetID(), "", strPurpose);
|
||||
});
|
||||
|
||||
CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey();
|
||||
CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(true);
|
||||
CTxDestination setaccountDemoAddress(CTxDestination(setaccountDemoPubkey.GetID()));
|
||||
|
||||
/*********************************
|
||||
|
@ -1245,19 +1245,6 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
BOOST_CHECK( msg.find("Insufficient funds") != string::npos);
|
||||
}
|
||||
|
||||
// minconf cannot be zero when sending from zaddr
|
||||
{
|
||||
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
|
||||
try {
|
||||
auto selector = pwalletMain->ToZTXOSelector(zaddr1, true).value();
|
||||
std::vector<SendManyRecipient> recipients = {SendManyRecipient(taddr1, 100*COIN, "DEADBEEF")};
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 0));
|
||||
BOOST_CHECK(false); // Fail test if an exception is not thrown
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK(find_error(objError, "Minconf cannot be zero when sending from a shielded address"));
|
||||
}
|
||||
}
|
||||
|
||||
// there are no unspent notes to spend
|
||||
{
|
||||
auto selector = pwalletMain->ToZTXOSelector(zaddr1, true).value();
|
||||
|
@ -1338,7 +1325,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
|
|||
|
||||
KeyIO keyIO(Params());
|
||||
// add keys manually
|
||||
auto taddr = pwalletMain->GenerateNewKey().GetID();
|
||||
auto taddr = pwalletMain->GenerateNewKey(true).GetID();
|
||||
std::string taddr1 = keyIO.EncodeDestination(taddr);
|
||||
auto pa = pwalletMain->GenerateNewLegacySaplingZKey();
|
||||
|
||||
|
@ -1402,13 +1389,19 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
|
|||
tx.vShieldedOutput[0].cmu,
|
||||
tx.vShieldedOutput[0].ephemeralKey));
|
||||
|
||||
// We should be able to decrypt the outCiphertext with the ovk
|
||||
// generated for transparent addresses
|
||||
std::optional<MnemonicSeed> seed = pwalletMain->GetMnemonicSeed();
|
||||
BOOST_ASSERT(seed.has_value());
|
||||
auto accountKey = pwalletMain->GetLegacyAccountKey().ToAccountPubKey();
|
||||
auto ovks = accountKey.GetOVKsForShielding();
|
||||
// We should not be able to decrypt with the internal change OVK for shielding
|
||||
BOOST_CHECK(!AttemptSaplingOutDecryption(
|
||||
tx.vShieldedOutput[0].outCiphertext,
|
||||
ovks.first,
|
||||
tx.vShieldedOutput[0].cv,
|
||||
tx.vShieldedOutput[0].cmu,
|
||||
tx.vShieldedOutput[0].ephemeralKey));
|
||||
// We should be able to decrypt with the external OVK for shielding
|
||||
BOOST_CHECK(AttemptSaplingOutDecryption(
|
||||
tx.vShieldedOutput[0].outCiphertext,
|
||||
ovkForShieldingFromTaddr(seed.value()),
|
||||
ovks.second,
|
||||
tx.vShieldedOutput[0].cv,
|
||||
tx.vShieldedOutput[0].cmu,
|
||||
tx.vShieldedOutput[0].ephemeralKey));
|
||||
|
@ -1994,7 +1987,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_internals)
|
|||
void TestWTxStatus(const Consensus::Params consensusParams, const int delta) {
|
||||
|
||||
auto AddTrx = [&consensusParams]() {
|
||||
auto taddr = pwalletMain->GenerateNewKey().GetID();
|
||||
auto taddr = pwalletMain->GenerateNewKey(true).GetID();
|
||||
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(consensusParams, 1);
|
||||
CScript scriptPubKey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(taddr) << OP_EQUALVERIFY << OP_CHECKSIG;
|
||||
mtx.vout.push_back(CTxOut(5 * COIN, scriptPubKey));
|
||||
|
|
|
@ -129,13 +129,12 @@ SaplingPaymentAddress CWallet::GenerateNewLegacySaplingZKey() {
|
|||
|
||||
// loop until we find an unused address index
|
||||
while (true) {
|
||||
auto generatedKey = GenerateLegacySaplingZKey(hdChain.GetLegacySaplingKeyCounter());
|
||||
auto xfvk = generatedKey.first.ToXFVK();
|
||||
auto generated = GenerateLegacySaplingZKey(hdChain.GetLegacySaplingKeyCounter());
|
||||
|
||||
// advance the address index counter so that the next time we need to generate
|
||||
// a key we're pointing at a free index.
|
||||
hdChain.IncrementLegacySaplingKeyCounter();
|
||||
if (!generatedKey.second) {
|
||||
if (!generated.second) {
|
||||
// the key already existed, so try the next one
|
||||
continue;
|
||||
} else {
|
||||
|
@ -145,12 +144,12 @@ SaplingPaymentAddress CWallet::GenerateNewLegacySaplingZKey() {
|
|||
"CWallet::GenerateNewLegacySaplingZKey(): Writing HD chain model failed");
|
||||
}
|
||||
|
||||
return xfvk.DefaultAddress();
|
||||
return generated.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<SaplingExtendedSpendingKey, bool> CWallet::GenerateLegacySaplingZKey(uint32_t addrIndex) {
|
||||
std::pair<SaplingPaymentAddress, bool> CWallet::GenerateLegacySaplingZKey(uint32_t addrIndex) {
|
||||
auto seedOpt = GetMnemonicSeed();
|
||||
if (!seedOpt.has_value()) {
|
||||
throw std::runtime_error(
|
||||
|
@ -159,8 +158,9 @@ std::pair<SaplingExtendedSpendingKey, bool> CWallet::GenerateLegacySaplingZKey(u
|
|||
auto seed = seedOpt.value();
|
||||
|
||||
auto xsk = libzcash::SaplingExtendedSpendingKey::Legacy(seed, BIP44CoinType(), addrIndex);
|
||||
if (!HaveSaplingSpendingKey(xsk.first.ToXFVK())) {
|
||||
auto ivk = xsk.first.expsk.full_viewing_key().in_viewing_key();
|
||||
auto extfvk = xsk.first.ToXFVK();
|
||||
if (!HaveSaplingSpendingKey(extfvk)) {
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
CKeyMetadata keyMeta(GetTime());
|
||||
keyMeta.hdKeypath = xsk.second;
|
||||
keyMeta.seedFp = seed.Fingerprint();
|
||||
|
@ -169,9 +169,15 @@ std::pair<SaplingExtendedSpendingKey, bool> CWallet::GenerateLegacySaplingZKey(u
|
|||
if (!AddSaplingZKey(xsk.first)) {
|
||||
throw std::runtime_error("CWallet::GenerateLegacySaplingZKey(): AddSaplingZKey failed.");
|
||||
}
|
||||
return std::make_pair(xsk.first, true) ;
|
||||
|
||||
auto addr = extfvk.DefaultAddress();
|
||||
if (!AddSaplingPaymentAddress(ivk, addr)) {
|
||||
throw std::runtime_error("CWallet::GenerateLegacySaplingZKey(): AddSaplingPaymentAddress failed.");
|
||||
};
|
||||
|
||||
return std::make_pair(addr, true) ;
|
||||
} else {
|
||||
return std::make_pair(xsk.first, false);
|
||||
return std::make_pair(extfvk.DefaultAddress(), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,13 +218,13 @@ bool CWallet::AddSaplingFullViewingKey(const libzcash::SaplingExtendedFullViewin
|
|||
}
|
||||
|
||||
// Add payment address -> incoming viewing key map entry
|
||||
bool CWallet::AddSaplingIncomingViewingKey(
|
||||
bool CWallet::AddSaplingPaymentAddress(
|
||||
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
const libzcash::SaplingPaymentAddress &addr)
|
||||
{
|
||||
AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata
|
||||
|
||||
if (!CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr)) {
|
||||
if (!CCryptoKeyStore::AddSaplingPaymentAddress(ivk, addr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -226,11 +232,7 @@ bool CWallet::AddSaplingIncomingViewingKey(
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!IsCrypted()) {
|
||||
return CWalletDB(strWalletFile).WriteSaplingPaymentAddress(addr, ivk);
|
||||
}
|
||||
|
||||
return true;
|
||||
return CWalletDB(strWalletFile).WriteSaplingPaymentAddress(addr, ivk);
|
||||
}
|
||||
|
||||
|
||||
|
@ -256,59 +258,54 @@ bool CWallet::AddSproutZKey(const libzcash::SproutSpendingKey &key)
|
|||
return true;
|
||||
}
|
||||
|
||||
CPubKey CWallet::GenerateNewKey()
|
||||
CPubKey CWallet::GenerateNewKey(bool external)
|
||||
{
|
||||
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
||||
|
||||
auto seedOpt = GetMnemonicSeed();
|
||||
if (!seedOpt.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"CWallet::GenerateNewKey(): Wallet does not have a mnemonic seed.");
|
||||
}
|
||||
auto seed = seedOpt.value();
|
||||
|
||||
if (!mnemonicHDChain.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"CWallet::GenerateNewKey(): Wallet is missing mnemonic seed metadata.");
|
||||
}
|
||||
CHDChain& hdChain = mnemonicHDChain.value();
|
||||
|
||||
// All mnemonic seeds are checked at construction to ensure that we can obtain
|
||||
// a valid spending key for the account ZCASH_LEGACY_ACCOUNT;
|
||||
// therefore, the `value()` call here is safe.
|
||||
Bip44AccountChains accountChains = Bip44AccountChains::ForAccount(
|
||||
seed,
|
||||
BIP44CoinType(),
|
||||
ZCASH_LEGACY_ACCOUNT).value();
|
||||
|
||||
std::optional<std::pair<CKey, HDKeyPath>> extKey = std::nullopt;
|
||||
transparent::AccountKey accountKey = this->GetLegacyAccountKey();
|
||||
std::optional<CPubKey> pubkey = std::nullopt;
|
||||
do {
|
||||
extKey = accountChains.DeriveExternal(hdChain.GetLegacyTKeyCounter());
|
||||
hdChain.IncrementLegacyTKeyCounter();
|
||||
// if we did not successfully generate a key, try again.
|
||||
} while (!extKey.has_value());
|
||||
auto index = hdChain.GetLegacyTKeyCounter();
|
||||
auto key = external ?
|
||||
accountKey.DeriveExternalSpendingKey(index) :
|
||||
accountKey.DeriveInternalSpendingKey(index);
|
||||
|
||||
auto pubkey = AddTransparentSecretKey(seed.Fingerprint(), extKey.value());
|
||||
hdChain.IncrementLegacyTKeyCounter();
|
||||
if (key.has_value()) {
|
||||
pubkey = AddTransparentSecretKey(
|
||||
hdChain.GetSeedFingerprint(),
|
||||
key.value(),
|
||||
transparent::AccountKey::KeyPath(BIP44CoinType(), ZCASH_LEGACY_ACCOUNT, external, index)
|
||||
);
|
||||
}
|
||||
// if we did not successfully generate a key, try again.
|
||||
} while (!pubkey.has_value());
|
||||
|
||||
// Update the persisted chain information
|
||||
if (fFileBacked && !CWalletDB(strWalletFile).WriteMnemonicHDChain(hdChain)) {
|
||||
throw std::runtime_error("CWallet::GenerateNewKey(): Writing HD chain model failed");
|
||||
}
|
||||
|
||||
return pubkey;
|
||||
return pubkey.value();
|
||||
}
|
||||
|
||||
CPubKey CWallet::AddTransparentSecretKey(
|
||||
const uint256& seedFingerprint,
|
||||
const std::pair<CKey, HDKeyPath>& extSecret)
|
||||
const CKey& secret,
|
||||
const HDKeyPath& keyPath)
|
||||
{
|
||||
CKey secret = extSecret.first;
|
||||
CPubKey pubkey = secret.GetPubKey();
|
||||
assert(secret.VerifyPubKey(pubkey));
|
||||
|
||||
// Create new metadata
|
||||
CKeyMetadata keyMeta(GetTime());
|
||||
keyMeta.hdKeypath = extSecret.second;
|
||||
keyMeta.hdKeypath = keyPath;
|
||||
keyMeta.seedFp = seedFingerprint;
|
||||
mapKeyMetadata[pubkey.GetID()] = keyMeta;
|
||||
if (nTimeFirstKey == 0 || keyMeta.nCreateTime < nTimeFirstKey)
|
||||
|
@ -410,16 +407,34 @@ bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullVi
|
|||
if (pwalletdbEncryption) {
|
||||
return pwalletdbEncryption->WriteCryptedSaplingZKey(extfvk,
|
||||
vchCryptedSecret,
|
||||
mapSaplingZKeyMetadata[extfvk.fvk.in_viewing_key()]);
|
||||
mapSaplingZKeyMetadata[extfvk.ToIncomingViewingKey()]);
|
||||
} else {
|
||||
return CWalletDB(strWalletFile).WriteCryptedSaplingZKey(extfvk,
|
||||
vchCryptedSecret,
|
||||
mapSaplingZKeyMetadata[extfvk.fvk.in_viewing_key()]);
|
||||
mapSaplingZKeyMetadata[extfvk.ToIncomingViewingKey()]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
libzcash::transparent::AccountKey CWallet::GetLegacyAccountKey() const {
|
||||
auto seedOpt = GetMnemonicSeed();
|
||||
if (!seedOpt.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"CWallet::GenerateNewKey(): Wallet does not have a mnemonic seed.");
|
||||
}
|
||||
auto seed = seedOpt.value();
|
||||
|
||||
// All mnemonic seeds are checked at construction to ensure that we can obtain
|
||||
// a valid spending key for the account ZCASH_LEGACY_ACCOUNT;
|
||||
// therefore, the `value()` call here is safe.
|
||||
return transparent::AccountKey::ForAccount(
|
||||
seed,
|
||||
BIP44CoinType(),
|
||||
ZCASH_LEGACY_ACCOUNT).value();
|
||||
}
|
||||
|
||||
|
||||
std::pair<ZcashdUnifiedSpendingKey, libzcash::AccountId> CWallet::GenerateNewUnifiedSpendingKey() {
|
||||
AssertLockHeld(cs_wallet);
|
||||
|
||||
|
@ -471,7 +486,12 @@ std::optional<libzcash::ZcashdUnifiedSpendingKey>
|
|||
|
||||
// Set up the bidirectional maps between the account ID and the UFVK ID.
|
||||
auto metaKey = std::make_pair(skmeta.GetSeedFingerprint(), skmeta.GetAccountId());
|
||||
mapUnifiedAccountKeys.insert({metaKey, ufvkid});
|
||||
const auto [it, is_new_key] = mapUnifiedAccountKeys.insert({metaKey, skmeta.GetKeyID()});
|
||||
if (!is_new_key) {
|
||||
// key was already present, so just return the USK.
|
||||
return usk.value();
|
||||
}
|
||||
|
||||
// We set up the UFVKAddressMetadata with the correct account ID (so we identify
|
||||
// the UFVK as corresponding to this account) and empty receivers data (as we
|
||||
// haven't generated any addresses yet). We don't need to persist this directly,
|
||||
|
@ -479,26 +499,41 @@ std::optional<libzcash::ZcashdUnifiedSpendingKey>
|
|||
// LoadUnifiedAccountMetadata().
|
||||
mapUfvkAddressMetadata.insert({ufvkid, UFVKAddressMetadata(accountId)});
|
||||
|
||||
// Add Transparent component to the wallet
|
||||
AddTransparentSecretKey(
|
||||
skmeta.GetSeedFingerprint(),
|
||||
std::make_pair(
|
||||
usk.value().GetTransparentKey().key,
|
||||
libzcash::Bip44TransparentAccountKeyPath(BIP44CoinType(), accountId)
|
||||
)
|
||||
// We do not explicitly add any transparent component to the keystore;
|
||||
// the secret keys that we need to store are the child spending keys
|
||||
// that are produced whenever we create a transparent address.
|
||||
|
||||
// Create the function that we'll use to add Sapling keys
|
||||
// to the wallet.
|
||||
auto addSaplingKey = AddSpendingKeyToWallet(
|
||||
this, Params().GetConsensus(), GetTime(),
|
||||
libzcash::Zip32AccountKeyPath(BIP44CoinType(), accountId),
|
||||
skmeta.GetSeedFingerprint().GetHex(), true, false
|
||||
);
|
||||
|
||||
// Add Sapling component to the wallet
|
||||
// Add the Sapling spending key to the wallet
|
||||
auto saplingEsk = usk.value().GetSaplingExtendedSpendingKey();
|
||||
auto saplingKeyPath = libzcash::Zip32AccountKeyPath(BIP44CoinType(), accountId);
|
||||
auto addSpendingKey = AddSpendingKeyToWallet(
|
||||
this, Params().GetConsensus(), GetTime(),
|
||||
saplingKeyPath, skmeta.GetSeedFingerprint().GetHex(), true);
|
||||
if (addSpendingKey(saplingEsk) == KeyNotAdded) {
|
||||
if (addSaplingKey(saplingEsk) == KeyNotAdded) {
|
||||
// If adding the Sapling key to the wallet failed, abort the process.
|
||||
throw std::runtime_error("CWalletDB::GenerateUnifiedSpendingKeyForAccount(): Unable to add Sapling key component to the wallet.");
|
||||
throw std::runtime_error("CWalletDB::GenerateUnifiedSpendingKeyForAccount(): Unable to add Sapling spending key to the wallet.");
|
||||
}
|
||||
|
||||
// Add the Sapling change spending key to the wallet
|
||||
auto saplingChangeEsk = saplingEsk.DeriveInternalKey();
|
||||
if (addSaplingKey(saplingChangeEsk) == KeyNotAdded) {
|
||||
// If adding the Sapling change key to the wallet failed, abort the process.
|
||||
throw std::runtime_error("CWalletDB::GenerateUnifiedSpendingKeyForAccount(): Unable to add Sapling change key to the wallet.");
|
||||
}
|
||||
|
||||
// Associate the Sapling default change address with its IVK. We do this
|
||||
// here because there is only ever a single Sapling change receiver, and
|
||||
// it is never exposed to the user. External Sapling receivers are added
|
||||
// when the user calls z_getaddressforaccount.
|
||||
auto saplingXFVK = saplingEsk.ToXFVK();
|
||||
if (!AddSaplingPaymentAddress(saplingXFVK.GetChangeIVK(), saplingXFVK.GetChangeAddress())) {
|
||||
throw std::runtime_error("CWallet::GenerateUnifiedSpendingKeyForAccount(): Failed to add Sapling change address to the wallet.");
|
||||
};
|
||||
|
||||
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
|
||||
if (!CCryptoKeyStore::AddUnifiedFullViewingKey(zufvk)) {
|
||||
throw std::runtime_error("CWalletDB::GenerateUnifiedSpendingKeyForAccount(): Failed to add UFVK to the keystore.");
|
||||
|
@ -536,7 +571,7 @@ bool CWallet::AddUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &uf
|
|||
return CWalletDB(strWalletFile).WriteUnifiedFullViewingKey(ufvk);
|
||||
}
|
||||
|
||||
std::optional<ZcashdUnifiedFullViewingKey> CWallet::GetUnifiedFullViewingKeyByAccount(libzcash::AccountId accountId) {
|
||||
std::optional<ZcashdUnifiedFullViewingKey> CWallet::GetUnifiedFullViewingKeyByAccount(libzcash::AccountId accountId) const {
|
||||
if (!mnemonicHDChain.has_value()) {
|
||||
throw std::runtime_error(
|
||||
"CWallet::GetUnifiedFullViewingKeyByAccount(): Wallet is missing mnemonic seed metadata.");
|
||||
|
@ -551,14 +586,25 @@ std::optional<ZcashdUnifiedFullViewingKey> CWallet::GetUnifiedFullViewingKeyByAc
|
|||
}
|
||||
}
|
||||
|
||||
UAGenerationResult CWallet::GenerateUnifiedAddress(
|
||||
WalletUAGenerationResult ToWalletUAGenerationResult(UnifiedAddressGenerationResult result) {
|
||||
return std::visit(match {
|
||||
[](const UnifiedAddressGenerationError& err) {
|
||||
return WalletUAGenerationResult(err);
|
||||
},
|
||||
[](const std::pair<UnifiedAddress, diversifier_index_t>& addrPair) {
|
||||
return WalletUAGenerationResult(addrPair);
|
||||
}
|
||||
}, result);
|
||||
}
|
||||
|
||||
WalletUAGenerationResult CWallet::GenerateUnifiedAddress(
|
||||
const libzcash::AccountId& accountId,
|
||||
const std::set<libzcash::ReceiverType>& receiverTypes,
|
||||
std::optional<libzcash::diversifier_index_t> j)
|
||||
{
|
||||
bool searchDiversifiers = !j.has_value();
|
||||
if (!libzcash::HasShielded(receiverTypes)) {
|
||||
return AddressGenerationError::InvalidReceiverTypes;
|
||||
return UnifiedAddressGenerationError::ShieldedReceiverNotFound;
|
||||
}
|
||||
|
||||
// The wallet must be unlocked in order to generate new transparent UA
|
||||
|
@ -570,14 +616,13 @@ UAGenerationResult CWallet::GenerateUnifiedAddress(
|
|||
if (hasTransparent) {
|
||||
// A preemptive check to ensure that the user has not specified an
|
||||
// invalid transparent child index. If we search from a valid transparent
|
||||
// child index into invalid child index space, later checks will return
|
||||
// this error as `AddressGenerationError::DiversifierSpaceExhausted`
|
||||
// child index into invalid child index space.
|
||||
if (j.has_value() && !j.value().ToTransparentChildIndex().has_value()) {
|
||||
return AddressGenerationError::InvalidTransparentChildIndex;
|
||||
return UnifiedAddressGenerationError::InvalidTransparentChildIndex;
|
||||
}
|
||||
|
||||
if (IsCrypted() || !GetMnemonicSeed().has_value()) {
|
||||
return AddressGenerationError::WalletEncrypted;
|
||||
return WalletUAGenerationError::WalletEncrypted;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -599,9 +644,9 @@ UAGenerationResult CWallet::GenerateUnifiedAddress(
|
|||
// the same as the set of receiver types that was previously
|
||||
// generated. If they match, simply return that address.
|
||||
if (receivers.value() == receiverTypes) {
|
||||
return std::make_pair(ufvk.value().Address(j.value(), receiverTypes).value(), j.value());
|
||||
return ToWalletUAGenerationResult(ufvk.value().Address(j.value(), receiverTypes));
|
||||
} else {
|
||||
return AddressGenerationError::ExistingAddressMismatch;
|
||||
return WalletUAGenerationError::ExistingAddressMismatch;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -609,7 +654,7 @@ UAGenerationResult CWallet::GenerateUnifiedAddress(
|
|||
// diversifier
|
||||
j = metadata->second.GetNextDiversifierIndex();
|
||||
if (!j.has_value()) {
|
||||
return AddressGenerationError::DiversifierSpaceExhausted;
|
||||
return UnifiedAddressGenerationError::DiversifierSpaceExhausted;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -622,50 +667,71 @@ UAGenerationResult CWallet::GenerateUnifiedAddress(
|
|||
}
|
||||
|
||||
// Find a working diversifier and construct the associated address.
|
||||
// At this point, we know that `j` will contain a value. Optimistically
|
||||
// attempt to find an address at that diversifier; we'll search if we
|
||||
// don't find one.
|
||||
auto diversifierIndex = j.value();
|
||||
auto address = ufvk.value().Address(diversifierIndex, receiverTypes);
|
||||
if (!address.has_value() && searchDiversifiers) {
|
||||
auto found = ufvk.value().FindAddress(j.value(), receiverTypes);
|
||||
if (found.has_value()) {
|
||||
address = found.value().first;
|
||||
diversifierIndex = found.value().second;
|
||||
} else {
|
||||
return AddressGenerationError::DiversifierSpaceExhausted;
|
||||
// At this point, we know that `j` will contain a value.
|
||||
auto addressGenerationResult = searchDiversifiers ?
|
||||
ufvk.value().FindAddress(j.value(), receiverTypes) :
|
||||
ufvk.value().Address(j.value(), receiverTypes);
|
||||
|
||||
if (std::holds_alternative<UnifiedAddressGenerationError>(addressGenerationResult)) {
|
||||
return std::get<UnifiedAddressGenerationError>(addressGenerationResult);
|
||||
}
|
||||
|
||||
auto address = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(addressGenerationResult);
|
||||
|
||||
assert(mapUfvkAddressMetadata[ufvkid].SetReceivers(address.second, receiverTypes));
|
||||
if (hasTransparent) {
|
||||
// We must construct and add the transparent spending key associated
|
||||
// with the external transparent child address to the transparent
|
||||
// keystore.
|
||||
auto usk = GenerateUnifiedSpendingKeyForAccount(accountId).value();
|
||||
auto accountKey = usk.GetTransparentKey();
|
||||
// this .value is known to be safe from the earlier check
|
||||
auto childIndex = address.second.ToTransparentChildIndex().value();
|
||||
auto externalKey = accountKey.DeriveExternalSpendingKey(childIndex);
|
||||
|
||||
if (!externalKey.has_value()) {
|
||||
return UnifiedAddressGenerationError::NoAddressForDiversifier;
|
||||
}
|
||||
|
||||
AddTransparentSecretKey(
|
||||
mnemonicHDChain.value().GetSeedFingerprint(),
|
||||
externalKey.value(),
|
||||
transparent::AccountKey::KeyPath(BIP44CoinType(), accountId, true, childIndex)
|
||||
);
|
||||
|
||||
// We do not add the change address for the transparent key, because
|
||||
// we do not send transparent change when using unified accounts.
|
||||
|
||||
// Writing this data is handled by `CWalletDB::WriteUnifiedAddressMetadata` below.
|
||||
assert(
|
||||
CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(
|
||||
ufvkid, address.second, address.first
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Now that we're done searching, check that we actually got an address
|
||||
if (!address.has_value()) {
|
||||
return AddressGenerationError::NoAddressForDiversifier;
|
||||
}
|
||||
// If the address has a Sapling component, add an association between
|
||||
// that address and the Sapling IVK corresponding to the ufvk
|
||||
auto hasSapling = receiverTypes.find(ReceiverType::Sapling) != receiverTypes.end();
|
||||
if (hasSapling) {
|
||||
auto dfvk = ufvk.value().GetSaplingKey();
|
||||
auto saplingAddress = address.first.GetSaplingReceiver();
|
||||
assert (dfvk.has_value() && saplingAddress.has_value());
|
||||
|
||||
assert(mapUfvkAddressMetadata[ufvkid].SetReceivers(diversifierIndex, receiverTypes));
|
||||
// Writing this data is handled by `CWalletDB::WriteUnifiedAddressMetadata` below.
|
||||
assert(CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(ufvkid, diversifierIndex, address.value()));
|
||||
AddSaplingPaymentAddress(dfvk.value().ToIncomingViewingKey(), saplingAddress.value());
|
||||
}
|
||||
|
||||
// Save the metadata for the generated address so that we can re-derive
|
||||
// it in the future.
|
||||
ZcashdUnifiedAddressMetadata addrmeta(ufvkid, diversifierIndex, receiverTypes);
|
||||
ZcashdUnifiedAddressMetadata addrmeta(ufvkid, address.second, receiverTypes);
|
||||
if (fFileBacked && !CWalletDB(strWalletFile).WriteUnifiedAddressMetadata(addrmeta)) {
|
||||
throw std::runtime_error(
|
||||
"CWallet::AddUnifiedAddress(): Writing unified address metadata failed");
|
||||
}
|
||||
|
||||
if (hasTransparent) {
|
||||
// Regenerate the secret key for the transparent address and add it to
|
||||
// the wallet.
|
||||
auto seed = GetMnemonicSeed().value();
|
||||
auto b44 = libzcash::Bip44AccountChains::ForAccount(seed, BIP44CoinType(), accountId).value();
|
||||
auto key = b44.DeriveExternal(diversifierIndex.ToTransparentChildIndex().value()).value();
|
||||
AddTransparentSecretKey(seed.Fingerprint(), key);
|
||||
}
|
||||
|
||||
return std::make_pair(address.value(), diversifierIndex);
|
||||
return address;
|
||||
} else {
|
||||
return AddressGenerationError::NoSuchAccount;
|
||||
return WalletUAGenerationError::NoSuchAccount;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -698,10 +764,18 @@ bool CWallet::LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &k
|
|||
// restore unified addresses that have been previously generated to the
|
||||
// keystore
|
||||
for (const auto &[j, receiverTypes] : metadata->second.GetKnownReceiverSetsByDiversifierIndex()) {
|
||||
auto addr = zufvk.Address(j, receiverTypes).value();
|
||||
if (!CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(zufvk.GetKeyID(), j, addr)) {
|
||||
return false;
|
||||
}
|
||||
bool restored = std::visit(match {
|
||||
[&](const UnifiedAddressGenerationError& err) {
|
||||
return false;
|
||||
},
|
||||
[&](const std::pair<UnifiedAddress, diversifier_index_t>& addr) {
|
||||
return CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(
|
||||
zufvk.GetKeyID(), addr.second, addr.first);
|
||||
}
|
||||
}, zufvk.Address(j, receiverTypes));
|
||||
|
||||
// failure to restore the generated address is an error
|
||||
if (!restored) return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -729,8 +803,14 @@ bool CWallet::LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &add
|
|||
if (ufvk.has_value()) {
|
||||
// Regenerate the unified address and add it to the keystore.
|
||||
auto j = addrmeta.GetDiversifierIndex();
|
||||
auto addr = ufvk.value().Address(j, addrmeta.GetReceiverTypes()).value();
|
||||
return CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(addrmeta.GetKeyID(), j, addr);
|
||||
auto addr = ufvk.value().Address(j, addrmeta.GetReceiverTypes());
|
||||
auto addrPtr = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&addr);
|
||||
|
||||
if (addrPtr == nullptr) {
|
||||
return false;
|
||||
} else {
|
||||
return CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(addrmeta.GetKeyID(), j, addrPtr->first);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -788,7 +868,7 @@ bool CWallet::LoadSaplingPaymentAddress(
|
|||
const libzcash::SaplingPaymentAddress &addr,
|
||||
const libzcash::SaplingIncomingViewingKey &ivk)
|
||||
{
|
||||
return CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr);
|
||||
return CCryptoKeyStore::AddSaplingPaymentAddress(ivk, addr);
|
||||
}
|
||||
|
||||
bool CWallet::LoadZKey(const libzcash::SproutSpendingKey &key)
|
||||
|
@ -1546,6 +1626,32 @@ bool CWallet::SelectorMatchesAddress(
|
|||
}, selector.GetPattern());
|
||||
}
|
||||
|
||||
std::optional<RecipientAddress> CWallet::GenerateChangeAddressForAccount(
|
||||
libzcash::AccountId accountId,
|
||||
std::set<libzcash::ChangeType> changeOptions) {
|
||||
AssertLockHeld(cs_wallet);
|
||||
|
||||
// changeOptions is sorted in preference order, so return
|
||||
// the first (and therefore most preferred) change address that
|
||||
// we're able to generate.
|
||||
for (libzcash::ChangeType t : changeOptions) {
|
||||
if (t == libzcash::ChangeType::Transparent && accountId == ZCASH_LEGACY_ACCOUNT) {
|
||||
return GenerateNewKey(false).GetID();
|
||||
} else {
|
||||
auto ufvk = this->GetUnifiedFullViewingKeyByAccount(accountId);
|
||||
if (ufvk.has_value()) {
|
||||
// Default to Sapling shielded change (TODO ORCHARD: update this)
|
||||
auto changeAddr = ufvk.value().GetChangeAddress(SaplingChangeRequest());
|
||||
if (changeAddr.has_value()) {
|
||||
return changeAddr.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
SpendableInputs CWallet::FindSpendableInputs(
|
||||
ZTXOSelector selector,
|
||||
bool allowTransparentCoinbase,
|
||||
|
@ -2495,7 +2601,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
|
|||
auto saplingNoteData = saplingNoteDataAndAddressesToAdd.first;
|
||||
auto addressesToAdd = saplingNoteDataAndAddressesToAdd.second;
|
||||
for (const auto &addressToAdd : addressesToAdd) {
|
||||
if (!AddSaplingIncomingViewingKey(addressToAdd.second, addressToAdd.first)) {
|
||||
if (!AddSaplingPaymentAddress(addressToAdd.second, addressToAdd.first)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -4861,7 +4967,7 @@ bool CWallet::NewKeyPool()
|
|||
for (int i = 0; i < nKeys; i++)
|
||||
{
|
||||
int64_t nIndex = i+1;
|
||||
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
|
||||
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey(true)));
|
||||
setKeyPool.insert(nIndex);
|
||||
}
|
||||
LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys);
|
||||
|
@ -4891,7 +4997,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
|||
int64_t nEnd = 1;
|
||||
if (!setKeyPool.empty())
|
||||
nEnd = *(--setKeyPool.end()) + 1;
|
||||
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey())))
|
||||
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(true))))
|
||||
throw runtime_error("TopUpKeyPool(): writing generated key failed");
|
||||
setKeyPool.insert(nEnd);
|
||||
LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size());
|
||||
|
@ -4958,7 +5064,7 @@ std::optional<CPubKey> CWallet::GetKeyFromPool()
|
|||
if (nIndex == -1)
|
||||
{
|
||||
if (IsLocked()) return std::nullopt;
|
||||
return GenerateNewKey();
|
||||
return GenerateNewKey(true);
|
||||
}
|
||||
KeepKey(nIndex);
|
||||
return keypool.vchPubKey;
|
||||
|
@ -5980,8 +6086,12 @@ std::optional<libzcash::AccountId> CWallet::GetUnifiedAccountId(const libzcash::
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<UnifiedAddress> CWallet::GetUnifiedForReceiver(const Receiver& receiver) const {
|
||||
return std::visit(LookupUnifiedAddress(*this), receiver);
|
||||
std::optional<ZcashdUnifiedFullViewingKey> CWallet::FindUFVKByReceiver(const libzcash::Receiver& receiver) const {
|
||||
return std::visit(UFVKForReceiver(*this), receiver);
|
||||
}
|
||||
|
||||
std::optional<UnifiedAddress> CWallet::FindUnifiedAddressByReceiver(const Receiver& receiver) const {
|
||||
return std::visit(UnifiedAddressForReceiver(*this), receiver);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -6112,81 +6222,6 @@ std::optional<libzcash::ViewingKey> GetViewingKeyForPaymentAddress::operator()(
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
// GetSproutKeyForPaymentAddress
|
||||
|
||||
std::optional<libzcash::SproutSpendingKey> GetSproutKeyForPaymentAddress::operator()(
|
||||
const CKeyID &zaddr) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<libzcash::SproutSpendingKey> GetSproutKeyForPaymentAddress::operator()(
|
||||
const CScriptID &zaddr) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<libzcash::SproutSpendingKey> GetSproutKeyForPaymentAddress::operator()(
|
||||
const libzcash::SproutPaymentAddress &zaddr) const
|
||||
{
|
||||
libzcash::SproutSpendingKey k;
|
||||
if (m_wallet->GetSproutSpendingKey(zaddr, k)) {
|
||||
return k;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<libzcash::SproutSpendingKey> GetSproutKeyForPaymentAddress::operator()(
|
||||
const libzcash::SaplingPaymentAddress &zaddr) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<libzcash::SproutSpendingKey> GetSproutKeyForPaymentAddress::operator()(
|
||||
const libzcash::UnifiedAddress &uaddr) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// GetSaplingKeyForPaymentAddress
|
||||
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> GetSaplingKeyForPaymentAddress::operator()(
|
||||
const CKeyID &zaddr) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> GetSaplingKeyForPaymentAddress::operator()(
|
||||
const CScriptID &zaddr) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> GetSaplingKeyForPaymentAddress::operator()(
|
||||
const libzcash::SproutPaymentAddress &zaddr) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> GetSaplingKeyForPaymentAddress::operator()(
|
||||
const libzcash::SaplingPaymentAddress &zaddr) const
|
||||
{
|
||||
libzcash::SaplingExtendedSpendingKey extsk;
|
||||
if (m_wallet->GetSaplingExtendedSpendingKey(zaddr, extsk)) {
|
||||
return extsk;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> GetSaplingKeyForPaymentAddress::operator()(
|
||||
const libzcash::UnifiedAddress &uaddr) const
|
||||
{
|
||||
for (const libzcash::Receiver& receiver: uaddr) {
|
||||
auto saplingAddr = std::get_if<SaplingPaymentAddress>(&receiver);
|
||||
if (saplingAddr != nullptr) {
|
||||
libzcash::SaplingExtendedSpendingKey extsk;
|
||||
if (m_wallet->GetSaplingExtendedSpendingKey(*saplingAddr, extsk)) {
|
||||
return extsk;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// AddViewingKeyToWallet
|
||||
|
||||
KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::SproutViewingKey &vkey) const {
|
||||
|
@ -6205,15 +6240,16 @@ KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::SproutViewingKey
|
|||
KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::SaplingExtendedFullViewingKey &extfvk) const {
|
||||
if (m_wallet->HaveSaplingSpendingKey(extfvk)) {
|
||||
return SpendingKeyExists;
|
||||
} else if (m_wallet->HaveSaplingFullViewingKey(extfvk.fvk.in_viewing_key())) {
|
||||
} else if (m_wallet->HaveSaplingFullViewingKey(extfvk.ToIncomingViewingKey())) {
|
||||
return KeyAlreadyExists;
|
||||
} else if (m_wallet->AddSaplingFullViewingKey(extfvk)) {
|
||||
} else if (
|
||||
m_wallet->AddSaplingFullViewingKey(extfvk) &&
|
||||
(!addDefaultAddress || m_wallet->AddSaplingPaymentAddress(extfvk.ToIncomingViewingKey(), extfvk.DefaultAddress()))) {
|
||||
return KeyAdded;
|
||||
} else {
|
||||
return KeyNotAdded;
|
||||
}
|
||||
}
|
||||
|
||||
KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::UnifiedFullViewingKey& no) const {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unified full viewing key import is not yet supported.");
|
||||
}
|
||||
|
@ -6237,48 +6273,89 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SproutSpendingKe
|
|||
}
|
||||
KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingKey &sk) const {
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
auto addr = extfvk.DefaultAddress();
|
||||
KeyIO keyIO(Params());
|
||||
{
|
||||
if (log){
|
||||
LogPrint("zrpc", "Importing zaddr %s...\n", keyIO.EncodePaymentAddress(sk.ToXFVK().DefaultAddress()));
|
||||
if (log){
|
||||
LogPrint("zrpc", "Importing zaddr %s...\n", keyIO.EncodePaymentAddress(addr));
|
||||
}
|
||||
// Don't throw error in case a key is already there
|
||||
if (m_wallet->HaveSaplingSpendingKey(extfvk)) {
|
||||
return KeyAlreadyExists;
|
||||
} else {
|
||||
if (!(
|
||||
m_wallet->AddSaplingZKey(sk) &&
|
||||
(!addDefaultAddress || m_wallet->AddSaplingPaymentAddress(ivk, addr))
|
||||
)) {
|
||||
return KeyNotAdded;
|
||||
}
|
||||
// Don't throw error in case a key is already there
|
||||
if (m_wallet->HaveSaplingSpendingKey(extfvk)) {
|
||||
return KeyAlreadyExists;
|
||||
} else {
|
||||
if (!m_wallet->AddSaplingZKey(sk)) {
|
||||
return KeyNotAdded;
|
||||
}
|
||||
|
||||
// Sapling addresses can't have been used in transactions prior to activation.
|
||||
if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) {
|
||||
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = nTime;
|
||||
} else {
|
||||
// 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates
|
||||
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime);
|
||||
}
|
||||
if (hdKeypath.has_value()) {
|
||||
m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.value();
|
||||
}
|
||||
if (seedFpStr.has_value()) {
|
||||
uint256 seedFp;
|
||||
seedFp.SetHex(seedFpStr.value());
|
||||
m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp;
|
||||
}
|
||||
return KeyAdded;
|
||||
// Sapling addresses can't have been used in transactions prior to activation.
|
||||
if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) {
|
||||
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = nTime;
|
||||
} else {
|
||||
// 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates
|
||||
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime);
|
||||
}
|
||||
if (hdKeypath.has_value()) {
|
||||
m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.value();
|
||||
}
|
||||
if (seedFpStr.has_value()) {
|
||||
uint256 seedFp;
|
||||
seedFp.SetHex(seedFpStr.value());
|
||||
m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp;
|
||||
}
|
||||
return KeyAdded;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
|
||||
// UFVKForReceiver :: (CWallet&, Receiver) -> std::optional<ZcashdUnifiedFullViewingKey>
|
||||
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
|
||||
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
|
||||
if (ufvkPair.has_value()) {
|
||||
auto ufvkid = ufvkPair.value().first;
|
||||
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
|
||||
if (!(ufvk.has_value() && ufvk.value().GetSaplingKey().has_value())) {
|
||||
throw std::runtime_error("CWallet::LookupUnifiedAddress(): UFVK has no Sapling key part.");
|
||||
}
|
||||
// If we have UFVK metadata, `GetUnifiedFullViewingKey` should always
|
||||
// return a non-nullopt value, and since we obtained that metadata by
|
||||
// lookup from as Sapling address, it should have a Sapling key component.
|
||||
assert(ufvk.has_value() && ufvk.value().GetSaplingKey().has_value());
|
||||
return ufvk.value();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const CScriptID& scriptId) const {
|
||||
// We do not currently generate unified addresses containing P2SH components,
|
||||
// so there's nothing to look up here.
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const CKeyID& keyId) const {
|
||||
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(keyId);
|
||||
if (ufvkPair.has_value()) {
|
||||
auto ufvkid = ufvkPair.value().first;
|
||||
// transparent address UFVK metadata is always accompanied by the child
|
||||
// index at which the address was produced
|
||||
assert(ufvkPair.value().second.has_value());
|
||||
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
|
||||
assert(ufvk.has_value() && ufvk.value().GetTransparentKey().has_value());
|
||||
return ufvk.value();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::UnknownReceiver& receiver) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// UnifiedAddressForReceiver :: (CWallet&, Receiver) -> std::optional<UnifiedAddress>
|
||||
|
||||
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
|
||||
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
|
||||
if (ufvkPair.has_value()) {
|
||||
auto ufvkid = ufvkPair.value().first;
|
||||
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
|
||||
assert(ufvk.has_value() && ufvk.value().GetSaplingKey().has_value());
|
||||
|
||||
diversifier_index_t j;
|
||||
// If the wallet is missing metadata at this UFVK id, it is probably
|
||||
|
@ -6290,7 +6367,13 @@ std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const l
|
|||
j.begin());
|
||||
auto receivers = metadata.GetReceivers(j);
|
||||
if (receivers.has_value()) {
|
||||
return ufvk.value().Address(j, receivers.value());
|
||||
auto addr = ufvk.value().Address(j, receivers.value());
|
||||
auto addrPtr = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&addr);
|
||||
if (addrPtr == nullptr) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return addrPtr->first;
|
||||
}
|
||||
} else {
|
||||
// If we don't know the receiver types at which the address was originally
|
||||
// generated, we can't reconstruct the address.
|
||||
|
@ -6300,10 +6383,12 @@ std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const l
|
|||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const CScriptID& scriptId) const {
|
||||
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const CScriptID& scriptId) const {
|
||||
// We do not currently generate unified addresses containing P2SH components,
|
||||
// so there's nothing to look up here.
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const CKeyID& keyId) const {
|
||||
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const CKeyID& keyId) const {
|
||||
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(keyId);
|
||||
if (ufvkPair.has_value()) {
|
||||
auto ufvkid = ufvkPair.value().first;
|
||||
|
@ -6313,7 +6398,7 @@ std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const C
|
|||
diversifier_index_t j = ufvkPair.value().second.value();
|
||||
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
|
||||
if (!(ufvk.has_value() && ufvk.value().GetTransparentKey().has_value())) {
|
||||
throw std::runtime_error("CWallet::LookupUnifiedAddress(): UFVK has no P2PKH key part.");
|
||||
throw std::runtime_error("CWallet::UnifiedAddressForReceiver(): UFVK has no P2PKH key part.");
|
||||
}
|
||||
|
||||
// If the wallet is missing metadata at this UFVK id, it is probably
|
||||
|
@ -6325,15 +6410,17 @@ std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const C
|
|||
// diversifier, we cannot reconstruct the address.
|
||||
auto receivers = metadata.GetReceivers(j);
|
||||
if (receivers.has_value()) {
|
||||
return ufvk.value().Address(j, receivers.value());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
auto addr = ufvk.value().Address(j, receivers.value());
|
||||
auto addrPtr = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&addr);
|
||||
if (addrPtr != nullptr) {
|
||||
return addrPtr->first;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const libzcash::UnknownReceiver& receiver) const {
|
||||
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const libzcash::UnknownReceiver& receiver) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#include "wallet/crypter.h"
|
||||
#include "wallet/walletdb.h"
|
||||
#include "wallet/rpcwallet.h"
|
||||
#include "zcash/address/bip44.h"
|
||||
#include "zcash/address/unified.h"
|
||||
#include "zcash/address/mnemonic.h"
|
||||
#include "zcash/Address.hpp"
|
||||
|
@ -402,19 +401,16 @@ public:
|
|||
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
|
||||
};
|
||||
|
||||
enum class AddressGenerationError {
|
||||
enum class WalletUAGenerationError {
|
||||
NoSuchAccount,
|
||||
InvalidReceiverTypes,
|
||||
ExistingAddressMismatch,
|
||||
NoAddressForDiversifier,
|
||||
DiversifierSpaceExhausted,
|
||||
WalletEncrypted,
|
||||
InvalidTransparentChildIndex
|
||||
WalletEncrypted
|
||||
};
|
||||
|
||||
typedef std::variant<
|
||||
std::pair<libzcash::UnifiedAddress, libzcash::diversifier_index_t>,
|
||||
AddressGenerationError> UAGenerationResult;
|
||||
libzcash::UnifiedAddressGenerationError,
|
||||
WalletUAGenerationError> WalletUAGenerationResult;
|
||||
|
||||
/**
|
||||
* A transaction with a bunch of additional info that only the owner cares about.
|
||||
|
@ -1053,7 +1049,8 @@ private:
|
|||
/* Add a transparent secret key to the wallet. Internal use only. */
|
||||
CPubKey AddTransparentSecretKey(
|
||||
const uint256& seedFingerprint,
|
||||
const std::pair<CKey, HDKeyPath>& extSecret);
|
||||
const CKey& secret,
|
||||
const HDKeyPath& keyPath);
|
||||
|
||||
protected:
|
||||
bool UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx);
|
||||
|
@ -1256,6 +1253,23 @@ public:
|
|||
*/
|
||||
std::optional<libzcash::AccountId> FindAccountForSelector(const ZTXOSelector& paymentSource) const;
|
||||
|
||||
/**
|
||||
* Generate a change address for the specified account.
|
||||
*
|
||||
* If a shielded change address is requested, this will return the default
|
||||
* unified address for the internal unified full viewing key.
|
||||
*
|
||||
* If a transparent change address is requested, this will generate a fresh
|
||||
* diversified unified address from the internal unified full viewing key,
|
||||
* and return the associated transparent change address.
|
||||
*
|
||||
* Returns `std::nullopt` if the account does not have an internal spending
|
||||
* key matching the requested `ChangeType`.
|
||||
*/
|
||||
std::optional<libzcash::RecipientAddress> GenerateChangeAddressForAccount(
|
||||
libzcash::AccountId accountId,
|
||||
std::set<libzcash::ChangeType> changeOptions);
|
||||
|
||||
SpendableInputs FindSpendableInputs(
|
||||
ZTXOSelector paymentSource,
|
||||
bool allowTransparentCoinbase,
|
||||
|
@ -1291,7 +1305,7 @@ public:
|
|||
* keystore implementation
|
||||
* Generate a new key
|
||||
*/
|
||||
CPubKey GenerateNewKey();
|
||||
CPubKey GenerateNewKey(bool external);
|
||||
//! Adds a key to the store, and saves it to disk.
|
||||
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
|
||||
//! Adds a key to the store, without saving it to disk (used by LoadWallet)
|
||||
|
@ -1359,11 +1373,11 @@ public:
|
|||
//! Generates new Sapling key, stores the newly generated spending
|
||||
//! key to the wallet, and returns the default address for the newly generated key.
|
||||
libzcash::SaplingPaymentAddress GenerateNewLegacySaplingZKey();
|
||||
//! Generates Sapling key at the specified address index, and stores the newly generated
|
||||
//! spending key to the wallet if it has not alreay been persisted.
|
||||
//! Returns the newly created key, and a flag distinguishing
|
||||
//! whether or not the key was already known by the wallet.
|
||||
std::pair<libzcash::SaplingExtendedSpendingKey, bool> GenerateLegacySaplingZKey(uint32_t addrIndex);
|
||||
//! Generates Sapling key at the specified address index, and stores that
|
||||
//! key to the wallet if it has not already been persisted. Returns the
|
||||
//! default address for the key, and a flag that is true when the key
|
||||
//! was newly generated (not already in the wallet).
|
||||
std::pair<libzcash::SaplingPaymentAddress, bool> GenerateLegacySaplingZKey(uint32_t addrIndex);
|
||||
//! Adds Sapling spending key to the store, and saves it to disk
|
||||
bool AddSaplingZKey(const libzcash::SaplingExtendedSpendingKey &key);
|
||||
//! Add Sapling full viewing key to the wallet.
|
||||
|
@ -1374,7 +1388,7 @@ public:
|
|||
//! full viewing key to the keystore, to avoid this override.
|
||||
bool AddSaplingFullViewingKey(
|
||||
const libzcash::SaplingExtendedFullViewingKey &extfvk);
|
||||
bool AddSaplingIncomingViewingKey(
|
||||
bool AddSaplingPaymentAddress(
|
||||
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||
const libzcash::SaplingPaymentAddress &addr);
|
||||
bool AddCryptedSaplingSpendingKey(
|
||||
|
@ -1396,9 +1410,13 @@ public:
|
|||
const std::vector<unsigned char> &vchCryptedSecret);
|
||||
|
||||
//
|
||||
// Unified keys & addresses
|
||||
// Unified keys, addresses, and accounts
|
||||
//
|
||||
|
||||
//! Obtain the account key for the legacy account by deriving it from
|
||||
//! the wallet's mnemonic seed.
|
||||
libzcash::transparent::AccountKey GetLegacyAccountKey() const;
|
||||
|
||||
//! Generate the unified spending key from the wallet's mnemonic seed
|
||||
//! for the next unused account identifier.
|
||||
std::pair<libzcash::ZcashdUnifiedSpendingKey, libzcash::AccountId>
|
||||
|
@ -1413,14 +1431,14 @@ public:
|
|||
|
||||
//! Retrieves the UFVK derived from the wallet's mnemonic seed for the specified account.
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey>
|
||||
GetUnifiedFullViewingKeyByAccount(libzcash::AccountId account);
|
||||
GetUnifiedFullViewingKeyByAccount(libzcash::AccountId account) const;
|
||||
|
||||
//! Generate a new unified address for the specified account, diversifier, and
|
||||
//! set of receiver types.
|
||||
//!
|
||||
//! If no diversifier index is provided, the next unused diversifier index
|
||||
//! will be selected.
|
||||
UAGenerationResult GenerateUnifiedAddress(
|
||||
WalletUAGenerationResult GenerateUnifiedAddress(
|
||||
const libzcash::AccountId& accountId,
|
||||
const std::set<libzcash::ReceiverType>& receivers,
|
||||
std::optional<libzcash::diversifier_index_t> j = std::nullopt);
|
||||
|
@ -1433,7 +1451,9 @@ public:
|
|||
|
||||
std::optional<libzcash::UFVKId> FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const;
|
||||
std::optional<libzcash::AccountId> GetUnifiedAccountId(const libzcash::UFVKId& ufvkId) const;
|
||||
std::optional<libzcash::UnifiedAddress> GetUnifiedForReceiver(const libzcash::Receiver& receiver) const;
|
||||
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> FindUFVKByReceiver(const libzcash::Receiver& receiver) const;
|
||||
std::optional<libzcash::UnifiedAddress> FindUnifiedAddressByReceiver(const libzcash::Receiver& receiver) const;
|
||||
|
||||
/**
|
||||
* Increment the next transaction order id
|
||||
|
@ -1761,34 +1781,6 @@ public:
|
|||
std::optional<libzcash::ViewingKey> operator()(const libzcash::UnifiedAddress &uaddr) const;
|
||||
};
|
||||
|
||||
class GetSproutKeyForPaymentAddress
|
||||
{
|
||||
private:
|
||||
CWallet *m_wallet;
|
||||
public:
|
||||
GetSproutKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
|
||||
|
||||
std::optional<libzcash::SproutSpendingKey> operator()(const CKeyID &zaddr) const;
|
||||
std::optional<libzcash::SproutSpendingKey> operator()(const CScriptID &zaddr) const;
|
||||
std::optional<libzcash::SproutSpendingKey> operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
std::optional<libzcash::SproutSpendingKey> operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
std::optional<libzcash::SproutSpendingKey> operator()(const libzcash::UnifiedAddress &uaddr) const;
|
||||
};
|
||||
|
||||
class GetSaplingKeyForPaymentAddress
|
||||
{
|
||||
private:
|
||||
CWallet *m_wallet;
|
||||
public:
|
||||
GetSaplingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
|
||||
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> operator()(const CKeyID &zaddr) const;
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> operator()(const CScriptID &zaddr) const;
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
std::optional<libzcash::SaplingExtendedSpendingKey> operator()(const libzcash::UnifiedAddress &uaddr) const;
|
||||
};
|
||||
|
||||
enum PaymentAddressSource {
|
||||
Random,
|
||||
LegacyHDSeed,
|
||||
|
@ -1823,8 +1815,9 @@ class AddViewingKeyToWallet
|
|||
{
|
||||
private:
|
||||
CWallet *m_wallet;
|
||||
bool addDefaultAddress;
|
||||
public:
|
||||
AddViewingKeyToWallet(CWallet *wallet) : m_wallet(wallet) {}
|
||||
AddViewingKeyToWallet(CWallet *wallet, bool addDefaultAddressIn) : m_wallet(wallet), addDefaultAddress(addDefaultAddressIn) {}
|
||||
|
||||
KeyAddResult operator()(const libzcash::SproutViewingKey &sk) const;
|
||||
KeyAddResult operator()(const libzcash::SaplingExtendedFullViewingKey &sk) const;
|
||||
|
@ -1840,29 +1833,44 @@ private:
|
|||
std::optional<std::string> hdKeypath; // currently sapling only
|
||||
std::optional<std::string> seedFpStr; // currently sapling only
|
||||
bool log;
|
||||
bool addDefaultAddress;
|
||||
public:
|
||||
AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params ¶ms) :
|
||||
m_wallet(wallet), params(params), nTime(1), hdKeypath(std::nullopt), seedFpStr(std::nullopt), log(false) {}
|
||||
m_wallet(wallet), params(params), nTime(1), hdKeypath(std::nullopt), seedFpStr(std::nullopt), log(false), addDefaultAddress(true) {}
|
||||
AddSpendingKeyToWallet(
|
||||
CWallet *wallet,
|
||||
const Consensus::Params ¶ms,
|
||||
int64_t _nTime,
|
||||
std::optional<std::string> _hdKeypath,
|
||||
std::optional<std::string> _seedFp,
|
||||
bool _log
|
||||
) : m_wallet(wallet), params(params), nTime(_nTime), hdKeypath(_hdKeypath), seedFpStr(_seedFp), log(_log) {}
|
||||
bool _log,
|
||||
bool _addDefaultAddress
|
||||
) : m_wallet(wallet), params(params), nTime(_nTime), hdKeypath(_hdKeypath), seedFpStr(_seedFp), log(_log), addDefaultAddress(_addDefaultAddress) {}
|
||||
|
||||
|
||||
KeyAddResult operator()(const libzcash::SproutSpendingKey &sk) const;
|
||||
KeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const;
|
||||
};
|
||||
|
||||
class LookupUnifiedAddress {
|
||||
class UFVKForReceiver {
|
||||
private:
|
||||
const CWallet& wallet;
|
||||
|
||||
public:
|
||||
LookupUnifiedAddress(const CWallet& wallet): wallet(wallet) {}
|
||||
UFVKForReceiver(const CWallet& wallet): wallet(wallet) {}
|
||||
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const CScriptID& scriptId) const;
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const CKeyID& keyId) const;
|
||||
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const libzcash::UnknownReceiver& receiver) const;
|
||||
};
|
||||
|
||||
class UnifiedAddressForReceiver {
|
||||
private:
|
||||
const CWallet& wallet;
|
||||
|
||||
public:
|
||||
UnifiedAddressForReceiver(const CWallet& wallet): wallet(wallet) {}
|
||||
|
||||
std::optional<libzcash::UnifiedAddress> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
|
||||
std::optional<libzcash::UnifiedAddress> operator()(const CScriptID& scriptId) const;
|
||||
|
|
|
@ -138,7 +138,7 @@ bool CWalletDB::WriteCryptedSaplingZKey(
|
|||
{
|
||||
const bool fEraseUnencryptedKey = true;
|
||||
nWalletDBUpdateCounter++;
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto ivk = extfvk.ToIncomingViewingKey();
|
||||
|
||||
if (!Write(std::make_pair(std::string("sapzkeymeta"), ivk), keyMeta))
|
||||
return false;
|
||||
|
|
|
@ -115,13 +115,12 @@ std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(con
|
|||
return std::make_pair("sapling", xfvk.DefaultAddress());
|
||||
}
|
||||
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const UnifiedFullViewingKey &ufvk) const {
|
||||
return std::make_pair(
|
||||
"unified",
|
||||
ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(keyConstants, ufvk)
|
||||
.FindAddress(diversifier_index_t(0))
|
||||
.value() //safe because we're searching from 0
|
||||
.first
|
||||
);
|
||||
// using std::get here is safe because we're searching from 0
|
||||
auto addr = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(
|
||||
ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(keyConstants, ufvk)
|
||||
.FindAddress(diversifier_index_t(0))
|
||||
);
|
||||
return std::make_pair("unified", addr.first);
|
||||
}
|
||||
|
||||
} // namespace libzcash
|
||||
|
@ -195,10 +194,10 @@ std::optional<CChainablePubKey> libzcash::UnifiedFullViewingKey::GetTransparentK
|
|||
}
|
||||
}
|
||||
|
||||
bool libzcash::UnifiedFullViewingKeyBuilder::AddTransparentKey(const CChainablePubKey& key) {
|
||||
bool libzcash::UnifiedFullViewingKeyBuilder::AddTransparentKey(const transparent::AccountPubKey& key) {
|
||||
if (t_bytes.has_value()) return false;
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << key;
|
||||
ss << key.GetPubKey();
|
||||
assert(ss.size() == 65);
|
||||
std::vector<uint8_t> ss_bytes(ss.begin(), ss.end());
|
||||
t_bytes = ss_bytes;
|
||||
|
|
|
@ -23,39 +23,6 @@ const unsigned char ZCASH_UFVK_ID_PERSONAL[BLAKE2bPersonalBytes] =
|
|||
|
||||
namespace libzcash {
|
||||
|
||||
class UnknownReceiver {
|
||||
public:
|
||||
uint32_t typecode;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
UnknownReceiver(uint32_t typecode, std::vector<uint8_t> data) :
|
||||
typecode(typecode), data(data) {}
|
||||
|
||||
friend inline bool operator==(const UnknownReceiver& a, const UnknownReceiver& b) {
|
||||
return a.typecode == b.typecode && a.data == b.data;
|
||||
}
|
||||
friend inline bool operator<(const UnknownReceiver& a, const UnknownReceiver& b) {
|
||||
// We don't know for certain the preference order of unknown receivers, but it is
|
||||
// _likely_ that the higher typecode has higher preference. The exact sort order
|
||||
// doesn't really matter, as unknown receivers have lower preference than known
|
||||
// receivers.
|
||||
return (a.typecode > b.typecode ||
|
||||
(a.typecode == b.typecode && a.data < b.data));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Receivers that can appear in a Unified Address.
|
||||
*
|
||||
* These types are given in order of preference (as defined in ZIP 316), so that sorting
|
||||
* variants by `operator<` is equivalent to sorting by preference.
|
||||
*/
|
||||
typedef std::variant<
|
||||
SaplingPaymentAddress,
|
||||
CScriptID,
|
||||
CKeyID,
|
||||
UnknownReceiver> Receiver;
|
||||
|
||||
bool HasKnownReceiverType(const Receiver& receiver);
|
||||
|
||||
struct ReceiverIterator {
|
||||
|
@ -90,11 +57,6 @@ private:
|
|||
size_t cur;
|
||||
};
|
||||
|
||||
/** A recipient address to which a unified address can be resolved */
|
||||
typedef std::variant<
|
||||
CKeyID,
|
||||
CScriptID,
|
||||
libzcash::SaplingPaymentAddress> RecipientAddress;
|
||||
|
||||
class UnifiedAddress {
|
||||
std::vector<Receiver> receivers;
|
||||
|
@ -239,7 +201,7 @@ private:
|
|||
public:
|
||||
UnifiedFullViewingKeyBuilder(): t_bytes(std::nullopt), sapling_bytes(std::nullopt) {}
|
||||
|
||||
bool AddTransparentKey(const CChainablePubKey&);
|
||||
bool AddTransparentKey(const transparent::AccountPubKey&);
|
||||
bool AddSaplingKey(const SaplingDiversifiableFullViewingKey&);
|
||||
|
||||
std::optional<UnifiedFullViewingKey> build() const;
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
// 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 .
|
||||
|
||||
#include "bip44.h"
|
||||
|
||||
HDKeyPath libzcash::Bip44TransparentAccountKeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId) {
|
||||
return "m/44'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'";
|
||||
}
|
||||
|
||||
std::optional<std::pair<CExtKey, HDKeyPath>> libzcash::DeriveBip44TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId) {
|
||||
auto rawSeed = seed.RawSeed();
|
||||
auto m = CExtKey::Master(rawSeed.data(), rawSeed.size());
|
||||
|
||||
// We use a fixed keypath scheme of m/44'/coin_type'/account'
|
||||
// Derive m/44'
|
||||
auto m_44h = m.Derive(44 | HARDENED_KEY_LIMIT);
|
||||
if (!m_44h.has_value()) return std::nullopt;
|
||||
|
||||
// Derive m/44'/coin_type'
|
||||
auto m_44h_cth = m_44h.value().Derive(bip44CoinType | HARDENED_KEY_LIMIT);
|
||||
if (!m_44h_cth.has_value()) return std::nullopt;
|
||||
|
||||
// Derive m/44'/coin_type'/account_id'
|
||||
auto result = m_44h_cth.value().Derive(accountId | HARDENED_KEY_LIMIT);
|
||||
if (!result.has_value()) return std::nullopt;
|
||||
|
||||
auto hdKeypath = libzcash::Bip44TransparentAccountKeyPath(bip44CoinType, accountId);
|
||||
|
||||
return std::make_pair(result.value(), hdKeypath);
|
||||
}
|
||||
|
||||
std::optional<libzcash::Bip44AccountChains> libzcash::Bip44AccountChains::ForAccount(
|
||||
const HDSeed& seed,
|
||||
uint32_t bip44CoinType,
|
||||
libzcash::AccountId accountId) {
|
||||
auto accountKeyOpt = DeriveBip44TransparentAccountKey(seed, bip44CoinType, accountId);
|
||||
if (!accountKeyOpt.has_value()) return std::nullopt;
|
||||
|
||||
auto accountKey = accountKeyOpt.value();
|
||||
auto external = accountKey.first.Derive(0);
|
||||
auto internal = accountKey.first.Derive(1);
|
||||
|
||||
if (!(external.has_value() && internal.has_value())) return std::nullopt;
|
||||
|
||||
return Bip44AccountChains(seed.Fingerprint(), bip44CoinType, accountId, external.value(), internal.value());
|
||||
}
|
||||
|
||||
std::optional<std::pair<CKey, HDKeyPath>> libzcash::Bip44AccountChains::DeriveExternal(uint32_t addrIndex) {
|
||||
auto childKey = external.Derive(addrIndex);
|
||||
if (!childKey.has_value()) return std::nullopt;
|
||||
|
||||
auto hdKeypath = Bip44TransparentAccountKeyPath(bip44CoinType, accountId) + "/0/" + std::to_string(addrIndex);
|
||||
|
||||
return std::make_pair(childKey.value().key, hdKeypath);
|
||||
}
|
||||
|
||||
std::optional<std::pair<CKey, HDKeyPath>> libzcash::Bip44AccountChains::DeriveInternal(uint32_t addrIndex) {
|
||||
auto childKey = internal.Derive(addrIndex);
|
||||
if (!childKey.has_value()) return std::nullopt;
|
||||
|
||||
auto hdKeypath = Bip44TransparentAccountKeyPath(bip44CoinType, accountId) + "/1/" + std::to_string(addrIndex);
|
||||
|
||||
return std::make_pair(childKey.value().key, hdKeypath);
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// 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_ZCASH_ADDRESS_BIP44_H
|
||||
#define ZCASH_ZCASH_ADDRESS_BIP44_H
|
||||
|
||||
#include "zip32.h"
|
||||
|
||||
namespace libzcash {
|
||||
|
||||
HDKeyPath Bip44TransparentAccountKeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId);
|
||||
|
||||
/**
|
||||
* Derive a transparent extended key in compressed format for the specified
|
||||
* seed, bip44 coin type, and account ID.
|
||||
*/
|
||||
std::optional<std::pair<CExtKey, HDKeyPath>> DeriveBip44TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId);
|
||||
|
||||
class Bip44AccountChains {
|
||||
private:
|
||||
uint256 seedFp;
|
||||
libzcash::AccountId accountId;
|
||||
uint32_t bip44CoinType;
|
||||
CExtKey external;
|
||||
CExtKey internal;
|
||||
|
||||
Bip44AccountChains(uint256 seedFpIn, uint32_t bip44CoinTypeIn, libzcash::AccountId accountIdIn, CExtKey externalIn, CExtKey internalIn):
|
||||
seedFp(seedFpIn), accountId(accountIdIn), bip44CoinType(bip44CoinTypeIn), external(externalIn), internal(internalIn) {}
|
||||
public:
|
||||
static std::optional<Bip44AccountChains> ForAccount(
|
||||
const HDSeed& mnemonic,
|
||||
uint32_t bip44CoinType,
|
||||
libzcash::AccountId accountId);
|
||||
|
||||
/**
|
||||
* Generate the key corresponding to the specified index at the "external child"
|
||||
* level of the BIP44 path for the account.
|
||||
*/
|
||||
std::optional<std::pair<CKey, HDKeyPath>> DeriveExternal(uint32_t addrIndex);
|
||||
|
||||
/**
|
||||
* Generate the key corresponding to the specified index at the "internal child"
|
||||
* level of the BIP44 path for the account. This should probably only usually be
|
||||
* used at address index 0, but ordinarily it won't need to be used at all since
|
||||
* all change should be shielded by default.
|
||||
*/
|
||||
std::optional<std::pair<CKey, HDKeyPath>> DeriveInternal(uint32_t addrIndex = 0);
|
||||
};
|
||||
|
||||
} //namespace libzcash
|
||||
|
||||
#endif // ZCASH_ZCASH_ADDRESS_BIP44_H
|
|
@ -5,8 +5,6 @@
|
|||
#include "random.h"
|
||||
|
||||
#include "mnemonic.h"
|
||||
|
||||
#include "bip44.h"
|
||||
#include "unified.h"
|
||||
|
||||
using namespace libzcash;
|
||||
|
@ -33,7 +31,7 @@ MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, siz
|
|||
// for a valid diversifier; unlike in the unified spending key case, diversifier
|
||||
// indices don't need to line up with anything.
|
||||
if (ZcashdUnifiedSpendingKey::ForAccount(seed, bip44CoinType, 0).has_value() &&
|
||||
Bip44AccountChains::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_ACCOUNT).has_value()) {
|
||||
transparent::AccountKey::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_ACCOUNT).has_value()) {
|
||||
return seed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ public:
|
|||
SaplingExpandedSpendingKey expanded_spending_key() const;
|
||||
SaplingFullViewingKey full_viewing_key() const;
|
||||
|
||||
// Can derive Sapling addr from default diversifier
|
||||
// Can derive Sapling addr from default diversifier
|
||||
SaplingPaymentAddress default_address() const;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
// 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 .
|
||||
|
||||
#include <rust/unified_keys.h>
|
||||
|
||||
#include "streams.h"
|
||||
#include "transparent.h"
|
||||
|
||||
namespace libzcash {
|
||||
namespace transparent {
|
||||
|
||||
std::optional<CPubKey> AccountPubKey::DeriveExternal(uint32_t addrIndex) const {
|
||||
auto externalKey = pubkey.Derive(0);
|
||||
if (!externalKey.has_value()) return std::nullopt;
|
||||
auto childKey = externalKey.value().Derive(addrIndex);
|
||||
if (!childKey.has_value()) return std::nullopt;
|
||||
return childKey.value().GetPubKey();
|
||||
}
|
||||
|
||||
std::optional<CPubKey> AccountPubKey::DeriveInternal(uint32_t addrIndex) const {
|
||||
auto internalKey = pubkey.Derive(1);
|
||||
if (!internalKey.has_value()) return std::nullopt;
|
||||
auto childKey = internalKey.value().Derive(addrIndex);
|
||||
if (!childKey.has_value()) return std::nullopt;
|
||||
return childKey.value().GetPubKey();
|
||||
}
|
||||
|
||||
std::optional<CKeyID> AccountPubKey::GetChangeAddress(const diversifier_index_t& j) const {
|
||||
auto childIndex = j.ToTransparentChildIndex();
|
||||
if (!childIndex.has_value()) return std::nullopt;
|
||||
|
||||
auto changeKey = this->DeriveInternal(childIndex.value());
|
||||
if (!changeKey.has_value()) return std::nullopt;
|
||||
|
||||
return changeKey.value().GetID();
|
||||
}
|
||||
|
||||
std::pair<uint256, uint256> AccountPubKey::GetOVKsForShielding() const {
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << pubkey;
|
||||
assert(ss.size() == 65);
|
||||
CSerializeData ss_bytes(ss.begin(), ss.end());
|
||||
|
||||
uint256 internalOVK;
|
||||
uint256 externalOVK;
|
||||
|
||||
assert(transparent_key_ovks(
|
||||
reinterpret_cast<unsigned char*>(ss_bytes.data()),
|
||||
internalOVK.begin(),
|
||||
externalOVK.begin()));
|
||||
|
||||
return std::make_pair(internalOVK, externalOVK);
|
||||
}
|
||||
|
||||
std::optional<std::pair<CKeyID, diversifier_index_t>> AccountPubKey::FindChangeAddress(diversifier_index_t j) const {
|
||||
while (true) {
|
||||
auto childIndex = j.ToTransparentChildIndex();
|
||||
if (!childIndex.has_value()) return std::nullopt;
|
||||
|
||||
auto changeKey = this->DeriveInternal(childIndex.value());
|
||||
if (changeKey.has_value()) {
|
||||
return std::make_pair(changeKey.value().GetID(), j);
|
||||
} else {
|
||||
j.increment();
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<AccountKey> AccountKey::ForAccount(
|
||||
const HDSeed& seed,
|
||||
uint32_t bip44CoinType,
|
||||
AccountId accountId) {
|
||||
auto rawSeed = seed.RawSeed();
|
||||
auto m = CExtKey::Master(rawSeed.data(), rawSeed.size());
|
||||
|
||||
// We use a fixed keypath scheme of m/44'/coin_type'/account'
|
||||
// Derive m/44'
|
||||
auto m_44h = m.Derive(44 | HARDENED_KEY_LIMIT);
|
||||
if (!m_44h.has_value()) return std::nullopt;
|
||||
|
||||
// Derive m/44'/coin_type'
|
||||
auto m_44h_cth = m_44h.value().Derive(bip44CoinType | HARDENED_KEY_LIMIT);
|
||||
if (!m_44h_cth.has_value()) return std::nullopt;
|
||||
|
||||
// Derive m/44'/coin_type'/account_id'
|
||||
auto accountKeyOpt = m_44h_cth.value().Derive(accountId | HARDENED_KEY_LIMIT);
|
||||
if (!accountKeyOpt.has_value()) return std::nullopt;
|
||||
|
||||
auto accountKey = accountKeyOpt.value();
|
||||
auto external = accountKey.Derive(0);
|
||||
auto internal = accountKey.Derive(1);
|
||||
|
||||
if (!(external.has_value() && internal.has_value())) return std::nullopt;
|
||||
|
||||
return AccountKey(accountKey, external.value(), internal.value());
|
||||
}
|
||||
|
||||
std::optional<CKey> AccountKey::DeriveExternalSpendingKey(uint32_t addrIndex) const {
|
||||
auto childKey = external.Derive(addrIndex);
|
||||
if (!childKey.has_value()) return std::nullopt;
|
||||
|
||||
return childKey.value().key;
|
||||
}
|
||||
|
||||
std::optional<CKey> AccountKey::DeriveInternalSpendingKey(uint32_t addrIndex) const {
|
||||
auto childKey = internal.Derive(addrIndex);
|
||||
if (!childKey.has_value()) return std::nullopt;
|
||||
|
||||
return childKey.value().key;
|
||||
}
|
||||
|
||||
AccountPubKey AccountKey::ToAccountPubKey() const {
|
||||
// The .value() call is safe here because we never derive
|
||||
// non-compressed public keys.
|
||||
return accountKey.Neuter().ToChainablePubKey().value();
|
||||
}
|
||||
|
||||
} //namespace transparent
|
||||
} //namespace libzcash
|
|
@ -0,0 +1,112 @@
|
|||
// 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_ZCASH_ADDRESS_TRANSPARENT_H
|
||||
#define ZCASH_ZCASH_ADDRESS_TRANSPARENT_H
|
||||
|
||||
#include "zip32.h"
|
||||
|
||||
namespace libzcash {
|
||||
namespace transparent {
|
||||
|
||||
class AccountPubKey {
|
||||
private:
|
||||
CChainablePubKey pubkey;
|
||||
public:
|
||||
AccountPubKey(CChainablePubKey pubkey): pubkey(pubkey) {};
|
||||
|
||||
const CChainablePubKey& GetPubKey() const {
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
std::optional<CPubKey> DeriveExternal(uint32_t addrIndex) const;
|
||||
|
||||
std::optional<CPubKey> DeriveInternal(uint32_t addrIndex) const;
|
||||
|
||||
/**
|
||||
* Return the transparent change address for this key a the given diversifier
|
||||
* index, if that index is valid for a transparent address and if key
|
||||
* derivation is successful.
|
||||
*/
|
||||
std::optional<CKeyID> GetChangeAddress(const diversifier_index_t& j) const;
|
||||
|
||||
/**
|
||||
* Search the space of valid child indices starting at the given index, and
|
||||
* return the change address corresponding to the first index at which a
|
||||
* valid address is derived, along with that index. This will always return
|
||||
* a valid address unless the space of valid transparent child indices is
|
||||
* exhausted or the provided diversifier index exceeds the maximum allowed
|
||||
* non-hardened transparent child index.
|
||||
*/
|
||||
std::optional<std::pair<CKeyID, diversifier_index_t>> FindChangeAddress(diversifier_index_t j) const;
|
||||
|
||||
/**
|
||||
* Return the internal and external OVKs for shielding from transparent
|
||||
* addresses derived from this key.
|
||||
*/
|
||||
std::pair<uint256, uint256> GetOVKsForShielding() const;
|
||||
|
||||
friend bool operator==(const AccountPubKey& a, const AccountPubKey& b)
|
||||
{
|
||||
return a.pubkey == b.pubkey;
|
||||
}
|
||||
};
|
||||
|
||||
class AccountKey {
|
||||
private:
|
||||
CExtKey accountKey;
|
||||
CExtKey external;
|
||||
CExtKey internal;
|
||||
|
||||
AccountKey(CExtKey accountKeyIn, CExtKey externalIn, CExtKey internalIn):
|
||||
accountKey(accountKeyIn), external(externalIn), internal(internalIn) {}
|
||||
public:
|
||||
static std::optional<AccountKey> ForAccount(
|
||||
const HDSeed& mnemonic,
|
||||
uint32_t bip44CoinType,
|
||||
libzcash::AccountId accountId);
|
||||
|
||||
static HDKeyPath KeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId) {
|
||||
return
|
||||
"m/44'/" +
|
||||
std::to_string(bip44CoinType) + "'/" +
|
||||
std::to_string(accountId) + "'";
|
||||
}
|
||||
|
||||
static HDKeyPath KeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId, bool external, uint32_t childIndex) {
|
||||
return
|
||||
AccountKey::KeyPath(bip44CoinType, accountId) + "/" +
|
||||
(external ? "0/" : "1/") +
|
||||
std::to_string(childIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the key corresponding to the specified index at the "external child"
|
||||
* level of the TRANSPARENT path for the account.
|
||||
*/
|
||||
std::optional<CKey> DeriveExternalSpendingKey(uint32_t addrIndex) const;
|
||||
|
||||
/**
|
||||
* Generate the key corresponding to the specified index at the "internal child"
|
||||
* level of the TRANSPARENT path for the account. This should probably only usually be
|
||||
* used at address index 0, but ordinarily it won't need to be used at all since
|
||||
* all change should be shielded by default.
|
||||
*/
|
||||
std::optional<CKey> DeriveInternalSpendingKey(uint32_t addrIndex = 0) const;
|
||||
|
||||
/**
|
||||
* Return the public key associated with this spending key.
|
||||
*/
|
||||
AccountPubKey ToAccountPubKey() const;
|
||||
|
||||
friend bool operator==(const AccountKey& a, const AccountKey& b)
|
||||
{
|
||||
return a.accountKey == b.accountKey;
|
||||
}
|
||||
};
|
||||
|
||||
} //namespace transparent
|
||||
} //namespace libzcash
|
||||
|
||||
#endif // ZCASH_ZCASH_ADDRESS_TRANSPARENT_H
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
#include "zcash/Address.hpp"
|
||||
#include "unified.h"
|
||||
#include "bip44.h"
|
||||
|
||||
#include <rust/unified_keys.h>
|
||||
|
||||
using namespace libzcash;
|
||||
|
||||
|
@ -32,23 +33,19 @@ std::optional<ZcashdUnifiedSpendingKey> ZcashdUnifiedSpendingKey::ForAccount(
|
|||
const HDSeed& seed,
|
||||
uint32_t bip44CoinType,
|
||||
AccountId accountId) {
|
||||
ZcashdUnifiedSpendingKey usk;
|
||||
|
||||
auto transparentKey = DeriveBip44TransparentAccountKey(seed, bip44CoinType, accountId);
|
||||
auto transparentKey = transparent::AccountKey::ForAccount(seed, bip44CoinType, accountId);
|
||||
if (!transparentKey.has_value()) return std::nullopt;
|
||||
usk.transparentKey = transparentKey.value().first;
|
||||
|
||||
auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId);
|
||||
usk.saplingKey = saplingKey.first;
|
||||
|
||||
return usk;
|
||||
return ZcashdUnifiedSpendingKey(transparentKey.value(), saplingKey.first);
|
||||
}
|
||||
|
||||
UnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
|
||||
UnifiedFullViewingKeyBuilder builder;
|
||||
|
||||
auto extPubKey = transparentKey.Neuter();
|
||||
builder.AddTransparentKey(CChainablePubKey::FromParts(extPubKey.chaincode, extPubKey.pubkey).value());
|
||||
builder.AddTransparentKey(transparentKey.ToAccountPubKey());
|
||||
builder.AddSaplingKey(saplingKey.ToXFVK());
|
||||
|
||||
// This call to .value() is safe as ZcashdUnifiedSpendingKey values are always
|
||||
|
@ -75,62 +72,88 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingK
|
|||
return result;
|
||||
}
|
||||
|
||||
std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(
|
||||
UnifiedAddressGenerationResult ZcashdUnifiedFullViewingKey::Address(
|
||||
const diversifier_index_t& j,
|
||||
const std::set<ReceiverType>& receiverTypes) const
|
||||
{
|
||||
if (!HasShielded(receiverTypes)) {
|
||||
throw std::runtime_error("Unified addresses must include a shielded receiver.");
|
||||
return UnifiedAddressGenerationError::ShieldedReceiverNotFound;
|
||||
}
|
||||
|
||||
if (receiverTypes.count(ReceiverType::P2SH) > 0) {
|
||||
return UnifiedAddressGenerationError::ReceiverTypeNotAvailable;
|
||||
}
|
||||
|
||||
UnifiedAddress ua;
|
||||
if (saplingKey.has_value() && receiverTypes.count(ReceiverType::Sapling) > 0) {
|
||||
auto saplingAddress = saplingKey.value().Address(j);
|
||||
if (saplingAddress.has_value()) {
|
||||
ua.AddReceiver(saplingAddress.value());
|
||||
if (receiverTypes.count(ReceiverType::Sapling) > 0) {
|
||||
if (saplingKey.has_value()) {
|
||||
auto saplingAddress = saplingKey.value().Address(j);
|
||||
if (saplingAddress.has_value()) {
|
||||
ua.AddReceiver(saplingAddress.value());
|
||||
} else {
|
||||
return UnifiedAddressGenerationError::NoAddressForDiversifier;
|
||||
}
|
||||
} else {
|
||||
return std::nullopt;
|
||||
return UnifiedAddressGenerationError::ReceiverTypeNotAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
if (transparentKey.has_value() && receiverTypes.count(ReceiverType::P2PKH) > 0) {
|
||||
const auto& tkey = transparentKey.value();
|
||||
auto childIndex = j.ToTransparentChildIndex();
|
||||
if (!childIndex.has_value()) return std::nullopt;
|
||||
if (receiverTypes.count(ReceiverType::P2PKH) > 0) {
|
||||
if (transparentKey.has_value()) {
|
||||
const auto& tkey = transparentKey.value();
|
||||
|
||||
CPubKey externalKey;
|
||||
ChainCode externalChainCode;
|
||||
if (!tkey.GetPubKey().Derive(externalKey, externalChainCode, 0, tkey.GetChainCode())) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto childIndex = j.ToTransparentChildIndex();
|
||||
if (!childIndex.has_value()) return UnifiedAddressGenerationError::InvalidTransparentChildIndex;
|
||||
|
||||
CPubKey childKey;
|
||||
ChainCode childChainCode;
|
||||
if (externalKey.Derive(childKey, childChainCode, childIndex.value(), externalChainCode)) {
|
||||
ua.AddReceiver(childKey.GetID());
|
||||
auto externalPubkey = tkey.DeriveExternal(childIndex.value());
|
||||
if (!externalPubkey.has_value()) return UnifiedAddressGenerationError::NoAddressForDiversifier;
|
||||
|
||||
ua.AddReceiver(externalPubkey.value().GetID());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
return UnifiedAddressGenerationError::ReceiverTypeNotAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
return ua;
|
||||
return std::make_pair(ua, j);
|
||||
}
|
||||
|
||||
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> ZcashdUnifiedFullViewingKey::FindAddress(
|
||||
UnifiedAddressGenerationResult ZcashdUnifiedFullViewingKey::FindAddress(
|
||||
const diversifier_index_t& j,
|
||||
const std::set<ReceiverType>& receiverTypes) const {
|
||||
diversifier_index_t j0(j);
|
||||
bool hasTransparent = HasTransparent(receiverTypes);
|
||||
auto addr = Address(j0, receiverTypes);
|
||||
while (!addr.has_value()) {
|
||||
if (!j0.increment() || (hasTransparent && !j0.ToTransparentChildIndex().has_value()))
|
||||
return std::nullopt;
|
||||
addr = Address(j0, receiverTypes);
|
||||
}
|
||||
return std::make_pair(addr.value(), j0);
|
||||
do {
|
||||
auto addr = Address(j0, receiverTypes);
|
||||
if (addr != UnifiedAddressGenerationResult(UnifiedAddressGenerationError::NoAddressForDiversifier)) {
|
||||
return addr;
|
||||
}
|
||||
} while (j0.increment());
|
||||
|
||||
return UnifiedAddressGenerationError::DiversifierSpaceExhausted;
|
||||
}
|
||||
|
||||
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> ZcashdUnifiedFullViewingKey::FindAddress(
|
||||
UnifiedAddressGenerationResult ZcashdUnifiedFullViewingKey::FindAddress(
|
||||
const diversifier_index_t& j) const {
|
||||
return FindAddress(j, {ReceiverType::P2PKH, ReceiverType::Sapling});
|
||||
}
|
||||
|
||||
std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress(const ChangeRequest& req) const {
|
||||
std::optional<RecipientAddress> addr;
|
||||
std::visit(match {
|
||||
[&](const TransparentChangeRequest& req) {
|
||||
if (transparentKey.has_value()) {
|
||||
auto changeAddr = transparentKey.value().GetChangeAddress(req.GetIndex());
|
||||
if (changeAddr.has_value()) {
|
||||
addr = changeAddr.value();
|
||||
}
|
||||
}
|
||||
},
|
||||
[&](const SaplingChangeRequest& req) {
|
||||
// currently true by construction, as a UFVK must have a supported shielded component
|
||||
if (saplingKey.has_value()) {
|
||||
addr = saplingKey.value().GetChangeAddress();
|
||||
}
|
||||
}
|
||||
}, req);
|
||||
return addr;
|
||||
}
|
||||
|
|
|
@ -5,12 +5,18 @@
|
|||
#ifndef ZCASH_ZCASH_ADDRESS_UNIFIED_H
|
||||
#define ZCASH_ZCASH_ADDRESS_UNIFIED_H
|
||||
|
||||
#include "bip44.h"
|
||||
#include "transparent.h"
|
||||
#include "key_constants.h"
|
||||
#include "script/script.h"
|
||||
#include "zip32.h"
|
||||
|
||||
namespace libzcash {
|
||||
|
||||
// prototypes for the classes handling ZIP-316 encoding (in Address.hpp)
|
||||
// TODO: ZIP-316 encoding should probably be moved here
|
||||
class UnifiedAddress;
|
||||
class UnifiedFullViewingKey;
|
||||
|
||||
enum class ReceiverType: uint32_t {
|
||||
P2PKH = 0x00,
|
||||
P2SH = 0x01,
|
||||
|
@ -18,6 +24,51 @@ enum class ReceiverType: uint32_t {
|
|||
//Orchard = 0x03
|
||||
};
|
||||
|
||||
enum class UnifiedAddressGenerationError {
|
||||
ShieldedReceiverNotFound,
|
||||
ReceiverTypeNotAvailable,
|
||||
NoAddressForDiversifier,
|
||||
DiversifierSpaceExhausted,
|
||||
InvalidTransparentChildIndex
|
||||
};
|
||||
|
||||
typedef std::variant<
|
||||
std::pair<UnifiedAddress, diversifier_index_t>,
|
||||
UnifiedAddressGenerationError> UnifiedAddressGenerationResult;
|
||||
|
||||
/** A recipient address to which a unified address can be resolved */
|
||||
typedef std::variant<
|
||||
CKeyID,
|
||||
CScriptID,
|
||||
libzcash::SaplingPaymentAddress> RecipientAddress;
|
||||
|
||||
/**
|
||||
* An enumeration of the types of change that a transaction may produce. It is
|
||||
* sorted in descending preference order, so that when iterating over a set of
|
||||
* change types the most-preferred type is selected first.
|
||||
*/
|
||||
enum class ChangeType {
|
||||
Sapling,
|
||||
Transparent,
|
||||
};
|
||||
|
||||
class TransparentChangeRequest {
|
||||
private:
|
||||
const diversifier_index_t& index;
|
||||
public:
|
||||
TransparentChangeRequest(const diversifier_index_t& indexIn): index(indexIn) {}
|
||||
|
||||
const diversifier_index_t& GetIndex() const {
|
||||
return index;
|
||||
}
|
||||
};
|
||||
|
||||
class SaplingChangeRequest {};
|
||||
|
||||
typedef std::variant<
|
||||
TransparentChangeRequest,
|
||||
SaplingChangeRequest> ChangeRequest;
|
||||
|
||||
/**
|
||||
* Test whether the specified list of receiver types contains a
|
||||
* shielded receiver type
|
||||
|
@ -32,9 +83,38 @@ bool HasTransparent(const std::set<ReceiverType>& receiverTypes);
|
|||
|
||||
class ZcashdUnifiedSpendingKey;
|
||||
|
||||
// prototypes for the classes handling ZIP-316 encoding (in Address.hpp)
|
||||
class UnifiedAddress;
|
||||
class UnifiedFullViewingKey;
|
||||
class UnknownReceiver {
|
||||
public:
|
||||
uint32_t typecode;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
UnknownReceiver(uint32_t typecode, std::vector<uint8_t> data) :
|
||||
typecode(typecode), data(data) {}
|
||||
|
||||
friend inline bool operator==(const UnknownReceiver& a, const UnknownReceiver& b) {
|
||||
return a.typecode == b.typecode && a.data == b.data;
|
||||
}
|
||||
friend inline bool operator<(const UnknownReceiver& a, const UnknownReceiver& b) {
|
||||
// We don't know for certain the preference order of unknown receivers, but it is
|
||||
// _likely_ that the higher typecode has higher preference. The exact sort order
|
||||
// doesn't really matter, as unknown receivers have lower preference than known
|
||||
// receivers.
|
||||
return (a.typecode > b.typecode ||
|
||||
(a.typecode == b.typecode && a.data < b.data));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Receivers that can appear in a Unified Address.
|
||||
*
|
||||
* These types are given in order of preference (as defined in ZIP 316), so that sorting
|
||||
* variants by `operator<` is equivalent to sorting by preference.
|
||||
*/
|
||||
typedef std::variant<
|
||||
SaplingPaymentAddress,
|
||||
CScriptID,
|
||||
CKeyID,
|
||||
UnknownReceiver> Receiver;
|
||||
|
||||
/**
|
||||
* An internal identifier for a unified full viewing key, derived as a
|
||||
|
@ -55,7 +135,7 @@ public:
|
|||
class ZcashdUnifiedFullViewingKey {
|
||||
private:
|
||||
UFVKId keyId;
|
||||
std::optional<CChainablePubKey> transparentKey;
|
||||
std::optional<transparent::AccountPubKey> transparentKey;
|
||||
std::optional<SaplingDiversifiableFullViewingKey> saplingKey;
|
||||
|
||||
ZcashdUnifiedFullViewingKey() {}
|
||||
|
@ -74,7 +154,10 @@ public:
|
|||
return keyId;
|
||||
}
|
||||
|
||||
const std::optional<CChainablePubKey>& GetTransparentKey() const {
|
||||
/**
|
||||
* Return the transparent key at the account level;
|
||||
*/
|
||||
const std::optional<transparent::AccountPubKey>& GetTransparentKey() const {
|
||||
return transparentKey;
|
||||
}
|
||||
|
||||
|
@ -85,14 +168,17 @@ public:
|
|||
/**
|
||||
* Creates a new unified address having the specified receiver types, at the specified
|
||||
* diversifier index, unless the diversifer index would generate an invalid receiver.
|
||||
* Returns `std::nullopt` if the diversifier index does not produce a valid receiver
|
||||
* for one or more of the specified receiver types; under this circumstance, the caller
|
||||
* should usually try successive diversifier indices until the operation returns a
|
||||
* non-null value.
|
||||
* Returns UnifiedAddressGenerationError::NoAddressForDiversifier if the diversifier
|
||||
* index does not produce a valid receiver for one or more of the specified receiver
|
||||
* types; under this circumstance, the caller should usually try successive diversifier
|
||||
* indices until the operation returns a valid address. Returns
|
||||
* `UnifiedAddressGenerationError::InvalidTransparentChildIndex` if a transparent
|
||||
* receiver was requested but the specified diversifier was out of range.
|
||||
*
|
||||
* This method will throw if `receiverTypes` does not include a shielded receiver type.
|
||||
* If successful in deriving an address, this method returns a `UnifiedAddressGenerationResult`
|
||||
* holding a pair consisting of the newly derived address and the provided value `j`.
|
||||
*/
|
||||
std::optional<UnifiedAddress> Address(
|
||||
UnifiedAddressGenerationResult Address(
|
||||
const diversifier_index_t& j,
|
||||
const std::set<ReceiverType>& receiverTypes) const;
|
||||
|
||||
|
@ -100,21 +186,33 @@ public:
|
|||
* Find the smallest diversifier index >= `j` such that it generates a valid
|
||||
* unified address according to the conditions specified in the documentation
|
||||
* for the `Address` method above, and returns the newly created address along
|
||||
* with the diversifier index used to produce it. Returns `std::nullopt` if the
|
||||
* with the diversifier index used to produce it.
|
||||
*
|
||||
* Returns UnifiedAddressGenerationError::NoAddressForDiversifier if the
|
||||
* diversifier space is exhausted, or if the set of receiver types contains a
|
||||
* transparent receiver and the diversifier exceeds the maximum transparent
|
||||
* child index.
|
||||
*
|
||||
* This method will throw if `receiverTypes` does not include a shielded receiver type.
|
||||
*/
|
||||
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> FindAddress(
|
||||
UnifiedAddressGenerationResult FindAddress(
|
||||
const diversifier_index_t& j,
|
||||
const std::set<ReceiverType>& receiverTypes) const;
|
||||
|
||||
/**
|
||||
* Find the next available address that contains all supported receiver types.
|
||||
*/
|
||||
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> FindAddress(const diversifier_index_t& j) const;
|
||||
UnifiedAddressGenerationResult FindAddress(const diversifier_index_t& j) const;
|
||||
|
||||
/**
|
||||
* Return the change address for this UFVK, given the provided
|
||||
* set of receiver types for pools involved in this transaction.
|
||||
* If the provided set is empty, return the change address
|
||||
* corresponding to the most-preferred pool. Returns `std::nullopt`
|
||||
* if the request cannot be satisfied; for example, if a transparent
|
||||
* change address is requested but derivation fails for the requested
|
||||
* child index, or if the set of requested protocols does not intersect
|
||||
* with those supported by the this UFVKs constituent keys.
|
||||
*/
|
||||
std::optional<RecipientAddress> GetChangeAddress(const ChangeRequest& req) const;
|
||||
|
||||
friend bool operator==(const ZcashdUnifiedFullViewingKey& a, const ZcashdUnifiedFullViewingKey& b)
|
||||
{
|
||||
|
@ -127,17 +225,19 @@ public:
|
|||
*/
|
||||
class ZcashdUnifiedSpendingKey {
|
||||
private:
|
||||
CExtKey transparentKey;
|
||||
transparent::AccountKey transparentKey;
|
||||
SaplingExtendedSpendingKey saplingKey;
|
||||
|
||||
ZcashdUnifiedSpendingKey() {}
|
||||
ZcashdUnifiedSpendingKey(
|
||||
transparent::AccountKey tkey,
|
||||
SaplingExtendedSpendingKey skey): transparentKey(tkey), saplingKey(skey) {}
|
||||
public:
|
||||
static std::optional<ZcashdUnifiedSpendingKey> ForAccount(
|
||||
const HDSeed& seed,
|
||||
uint32_t bip44CoinType,
|
||||
libzcash::AccountId accountId);
|
||||
|
||||
const CExtKey& GetTransparentKey() const {
|
||||
const transparent::AccountKey& GetTransparentKey() const {
|
||||
return transparentKey;
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ std::optional<uint32_t> diversifier_index_t::ToTransparentChildIndex() const {
|
|||
}
|
||||
|
||||
//
|
||||
// Sapling
|
||||
// SaplingExtendedFullViewingKey
|
||||
//
|
||||
|
||||
std::optional<SaplingExtendedFullViewingKey> SaplingExtendedFullViewingKey::Derive(uint32_t i) const
|
||||
|
@ -80,6 +80,10 @@ std::optional<SaplingExtendedFullViewingKey> SaplingExtendedFullViewingKey::Deri
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SaplingDiversifiableFullViewingKey
|
||||
//
|
||||
|
||||
std::optional<libzcash::SaplingPaymentAddress>
|
||||
SaplingDiversifiableFullViewingKey::Address(diversifier_index_t j) const
|
||||
{
|
||||
|
@ -126,6 +130,42 @@ libzcash::SaplingPaymentAddress SaplingDiversifiableFullViewingKey::DefaultAddre
|
|||
}
|
||||
}
|
||||
|
||||
libzcash::SaplingDiversifiableFullViewingKey SaplingDiversifiableFullViewingKey::GetInternalDFVK() const {
|
||||
CDataStream ss_fvk(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss_fvk << fvk;
|
||||
CSerializeData fvk_bytes(ss_fvk.begin(), ss_fvk.end());
|
||||
|
||||
SaplingDiversifiableFullViewingKey internalDFVK;
|
||||
CSerializeData fvk_bytes_ret(libzcash::SerializedSaplingFullViewingKeySize);
|
||||
librustzcash_zip32_sapling_derive_internal_fvk(
|
||||
reinterpret_cast<unsigned char*>(fvk_bytes.data()),
|
||||
dk.begin(),
|
||||
reinterpret_cast<unsigned char*>(fvk_bytes_ret.data()),
|
||||
internalDFVK.dk.begin());
|
||||
|
||||
CDataStream ss_fvk_ret(fvk_bytes_ret, SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss_fvk_ret >> internalDFVK.fvk;
|
||||
return internalDFVK;
|
||||
}
|
||||
|
||||
libzcash::SaplingIncomingViewingKey SaplingDiversifiableFullViewingKey::GetChangeIVK() const {
|
||||
auto internalDFVK = this->GetInternalDFVK();
|
||||
return internalDFVK.fvk.in_viewing_key();
|
||||
}
|
||||
|
||||
libzcash::SaplingPaymentAddress SaplingDiversifiableFullViewingKey::GetChangeAddress() const {
|
||||
auto internalDFVK = this->GetInternalDFVK();
|
||||
return internalDFVK.DefaultAddress();
|
||||
}
|
||||
|
||||
std::pair<uint256, uint256> SaplingDiversifiableFullViewingKey::GetOVKs() const {
|
||||
return std::make_pair(this->GetInternalDFVK().fvk.ovk, fvk.ovk);
|
||||
}
|
||||
|
||||
//
|
||||
// SaplingExtendedSpendingKey
|
||||
//
|
||||
|
||||
SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Master(const HDSeed& seed)
|
||||
{
|
||||
auto rawSeed = seed.RawSeed();
|
||||
|
@ -208,6 +248,22 @@ SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
|
|||
return ret;
|
||||
}
|
||||
|
||||
SaplingExtendedSpendingKey SaplingExtendedSpendingKey::DeriveInternalKey() const {
|
||||
CDataStream ss_p(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss_p << *this;
|
||||
CSerializeData external_key_bytes(ss_p.begin(), ss_p.end());
|
||||
|
||||
CSerializeData internal_key_bytes(ZIP32_XSK_SIZE);
|
||||
librustzcash_zip32_xsk_derive_internal(
|
||||
reinterpret_cast<unsigned char*>(external_key_bytes.data()),
|
||||
reinterpret_cast<unsigned char*>(internal_key_bytes.data()));
|
||||
|
||||
CDataStream ss_i(internal_key_bytes, SER_NETWORK, PROTOCOL_VERSION);
|
||||
SaplingExtendedSpendingKey xsk_internal;
|
||||
ss_i >> xsk_internal;
|
||||
return xsk_internal;
|
||||
}
|
||||
|
||||
HDKeyPath Zip32AccountKeyPath(
|
||||
uint32_t bip44CoinType,
|
||||
libzcash::AccountId accountId,
|
||||
|
|
|
@ -131,6 +131,9 @@ public:
|
|||
const libzcash::diversifier_index_t MAX_TRANSPARENT_CHILD_IDX(0x7FFFFFFF);
|
||||
|
||||
class SaplingDiversifiableFullViewingKey {
|
||||
protected:
|
||||
SaplingDiversifiableFullViewingKey GetInternalDFVK() const;
|
||||
|
||||
public:
|
||||
libzcash::SaplingFullViewingKey fvk;
|
||||
uint256 dk;
|
||||
|
@ -153,8 +156,21 @@ public:
|
|||
return std::make_pair(addr.value(), j);
|
||||
}
|
||||
|
||||
libzcash::SaplingIncomingViewingKey ToIncomingViewingKey() const {
|
||||
return fvk.in_viewing_key();
|
||||
}
|
||||
|
||||
libzcash::SaplingPaymentAddress DefaultAddress() const;
|
||||
|
||||
libzcash::SaplingIncomingViewingKey GetChangeIVK() const;
|
||||
libzcash::SaplingPaymentAddress GetChangeAddress() const;
|
||||
|
||||
/**
|
||||
* Returns the (internal, external) OVKs for shielded spends
|
||||
* from the associated spend authority.
|
||||
*/
|
||||
std::pair<uint256, uint256> GetOVKs() const;
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
|
@ -237,11 +253,12 @@ struct SaplingExtendedSpendingKey {
|
|||
static std::pair<SaplingExtendedSpendingKey, HDKeyPath> ForAccount(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId);
|
||||
static std::pair<SaplingExtendedSpendingKey, HDKeyPath> Legacy(const HDSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex);
|
||||
|
||||
|
||||
SaplingExtendedSpendingKey Derive(uint32_t i) const;
|
||||
|
||||
SaplingExtendedFullViewingKey ToXFVK() const;
|
||||
|
||||
SaplingExtendedSpendingKey DeriveInternalKey() const;
|
||||
|
||||
friend bool operator==(const SaplingExtendedSpendingKey& a, const SaplingExtendedSpendingKey& b)
|
||||
{
|
||||
return a.depth == b.depth &&
|
||||
|
|
Loading…
Reference in New Issue