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:
str4d 2022-02-08 23:42:59 +00:00 committed by GitHub
commit 9439c79bab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1746 additions and 846 deletions

109
Cargo.lock generated
View File

@ -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",

View File

@ -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" }

View File

@ -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.

View File

@ -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()

View File

@ -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()

View File

@ -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 \

View File

@ -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());

View File

@ -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);

View File

@ -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());
}

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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
}
}
}

View File

@ -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()

View File

@ -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();

View File

@ -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

View File

@ -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() {

View File

@ -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;
}

View File

@ -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:

View File

@ -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);

View File

@ -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);
}

View File

@ -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();

View File

@ -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,

View File

@ -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()) {

View File

@ -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));

View File

@ -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;
}

View File

@ -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 &params) :
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 &params,
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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
};

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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 &&