Auto merge of #3492 - str4d:zip32, r=str4d

Use ZIP 32 for all Sapling spending keys

The wallet now only stores Sapling extended spending keys, and thus can
only be used with keys generated from an HDSeed via ZIP 32. This means
that all Sapling keys and addresses generated by users can be recovered
as long as they have a backup that includes the seed.

Depends on zcash/librustzcash#29

Closes #3380.
This commit is contained in:
Homu 2018-09-11 04:47:14 -07:00
commit 88f52f0ffc
47 changed files with 1308 additions and 136 deletions

View File

@ -0,0 +1,15 @@
package=crate_aes
$(package)_crate_name=aes
$(package)_version=0.2.0
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=e6fb1737cdc8da3db76e90ca817a194249a38fcb500c2e6ecec39b29448aa873
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_aes_soft
$(package)_crate_name=aes-soft
$(package)_version=0.2.0
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=67cc03b0a090a05cb01e96998a01905d7ceedce1bc23b756c0bb7faa0682ccb1
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_aesni
$(package)_crate_name=aesni
$(package)_version=0.4.0
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=f2838c142db62c0c6aea0a24054c46d35488532fdaea0f51dbeba430f0985df5
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_block_cipher_trait
$(package)_crate_name=block-cipher-trait
$(package)_version=0.5.3
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=370424437b9459f3dfd68428ed9376ddfe03d8b70ede29cc533b3557df186ab4
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_byte_tools
$(package)_crate_name=byte-tools
$(package)_version=0.2.0
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_fpe
$(package)_crate_name=fpe
$(package)_version=0.1.0
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=ce3371c82bfbd984f624cab093f55e7336f5a6e589f8518e1258f54f011b89ad
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_num_bigint
$(package)_crate_name=num-bigint
$(package)_version=0.2.0
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=3eceac7784c5dc97c2d6edf30259b4e153e6e2b42b3c85e9a6e9f45d06caef6e
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_num_integer
$(package)_crate_name=num-integer
$(package)_version=0.1.39
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_num_traits
$(package)_crate_name=num-traits
$(package)_version=0.2.5
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=630de1ef5cc79d0cdd78b7e33b81f083cbfe90de0f4b2b2f07f905867c70e9fe
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_opaque_debug
$(package)_crate_name=opaque-debug
$(package)_version=0.1.1
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=d620c9c26834b34f039489ac0dfdb12c7ac15ccaf818350a64c9b5334a452ad7
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,15 @@
package=crate_stream_cipher
$(package)_crate_name=stream-cipher
$(package)_version=0.1.0
$(package)_download_path=https://static.crates.io/crates/$($(package)_crate_name)
$(package)_file_name=$($(package)_crate_name)-$($(package)_version).crate
$(package)_sha256_hash=ac49bc6cb2847200d18bfb738ce89448570f4aa1c34ac0348db6205ee69a0777
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -0,0 +1,16 @@
package=crate_zip32
$(package)_crate_name=zip32
$(package)_download_path=https://github.com/zcash-hackworks/$($(package)_crate_name)/archive/
$(package)_file_name=$(package)-$($(package)_git_commit).tar.gz
$(package)_download_file=$($(package)_git_commit).tar.gz
$(package)_sha256_hash=b0b011ea96524f0d918a44c7ab8a3dec6270879d1ff03d7dbda6c676d25caa7e
$(package)_git_commit=176470ef41583b5bd0bd749bd1b61d417aa8ec79
$(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds
$(call generate_unpackaged_crate_checksum,$(package))
endef
define $(package)_stage_cmds
$(call vendor_crate_source,$(package))
endef

View File

@ -3,8 +3,8 @@ $(package)_version=0.1
$(package)_download_path=https://github.com/zcash/$(package)/archive/ $(package)_download_path=https://github.com/zcash/$(package)/archive/
$(package)_file_name=$(package)-$($(package)_git_commit).tar.gz $(package)_file_name=$(package)-$($(package)_git_commit).tar.gz
$(package)_download_file=$($(package)_git_commit).tar.gz $(package)_download_file=$($(package)_git_commit).tar.gz
$(package)_sha256_hash=4d022b66e554efbf6db01b2a282e312e8a1b492c4680299ae8c26629882eb46b $(package)_sha256_hash=e9a488a8bbecf7fb237a32dadd65133211ef61616d44cf55609e029837a41004
$(package)_git_commit=f5d2afb4eabac29b1b1cc860d66e45a5b48b4f88 $(package)_git_commit=f5e5cb24e1bd756a02fc4a3fd2b824238ccd15ad
$(package)_dependencies=rust $(rust_crates) $(package)_dependencies=rust $(rust_crates)
$(package)_patches=cargo.config $(package)_patches=cargo.config

View File

@ -1,13 +1,19 @@
rust_crates := \ rust_crates := \
crate_aes \
crate_aesni \
crate_aes_soft \
crate_arrayvec \ crate_arrayvec \
crate_bellman \ crate_bellman \
crate_bitflags \ crate_bitflags \
crate_bit_vec \ crate_bit_vec \
crate_blake2_rfc \ crate_blake2_rfc \
crate_block_cipher_trait \
crate_byte_tools \
crate_byteorder \ crate_byteorder \
crate_constant_time_eq \ crate_constant_time_eq \
crate_crossbeam \ crate_crossbeam \
crate_digest \ crate_digest \
crate_fpe \
crate_fuchsia_zircon \ crate_fuchsia_zircon \
crate_fuchsia_zircon_sys \ crate_fuchsia_zircon_sys \
crate_futures_cpupool \ crate_futures_cpupool \
@ -16,14 +22,20 @@ rust_crates := \
crate_lazy_static \ crate_lazy_static \
crate_libc \ crate_libc \
crate_nodrop \ crate_nodrop \
crate_num_bigint \
crate_num_cpus \ crate_num_cpus \
crate_num_integer \
crate_num_traits \
crate_opaque_debug \
crate_pairing \ crate_pairing \
crate_rand \ crate_rand \
crate_sapling_crypto \ crate_sapling_crypto \
crate_stream_cipher \
crate_typenum \ crate_typenum \
crate_winapi_i686_pc_windows_gnu \ crate_winapi_i686_pc_windows_gnu \
crate_winapi \ crate_winapi \
crate_winapi_x86_64_pc_windows_gnu crate_winapi_x86_64_pc_windows_gnu \
crate_zip32
rust_packages := rust $(rust_crates) librustzcash rust_packages := rust $(rust_crates) librustzcash
proton_packages := proton proton_packages := proton
zcash_packages := libgmp libsodium zcash_packages := libgmp libsodium

View File

@ -11,6 +11,11 @@ git = "https://github.com/zcash-hackworks/sapling-crypto"
rev = "21084bde2019c04bd34208e63c3560fe2c02fb0e" rev = "21084bde2019c04bd34208e63c3560fe2c02fb0e"
replace-with = "vendored-sources" replace-with = "vendored-sources"
[source."https://github.com/zcash-hackworks/zip32"]
git = "https://github.com/zcash-hackworks/zip32"
rev = "176470ef41583b5bd0bd749bd1b61d417aa8ec79"
replace-with = "vendored-sources"
[source.vendored-sources] [source.vendored-sources]
directory = "CRATE_REGISTRY" directory = "CRATE_REGISTRY"

View File

@ -4,3 +4,16 @@ release-notes at release time)
Notable changes Notable changes
=============== ===============
Hierarchical Deterministic Key Generation for Sapling
-----------------------------------------------------
All Sapling addresses will use hierarchical deterministic key generation
according to ZIP 32 (keypath m/32'/133'/k' on mainnet). Transparent and
Sprout addresses will still use traditional key generation.
Backups of HD wallets, regardless of when they have been created, can
therefore be used to re-generate all possible Sapling private keys, even the
ones which haven't already been generated during the time of the backup.
Regular backups are still necessary, however, in order to ensure that
transparent and Sprout addresses are not lost.
[Pull request](https://github.com/zcash/zcash/pull/3492), [ZIP 32](https://github.com/zcash/zips/blob/master/zip-0032.mediawiki)

View File

@ -21,7 +21,7 @@ testScripts=(
'wallet_mergetoaddress.py' 'wallet_mergetoaddress.py'
'wallet.py' 'wallet.py'
'wallet_overwintertx.py' 'wallet_overwintertx.py'
'wallet_nullifiers.py' # 'wallet_nullifiers.py'
'wallet_1941.py' 'wallet_1941.py'
'wallet_addresses.py' 'wallet_addresses.py'
'wallet_sapling.py' 'wallet_sapling.py'
@ -41,7 +41,7 @@ testScripts=(
'zapwallettxes.py' 'zapwallettxes.py'
'proxy_test.py' 'proxy_test.py'
'merkle_blocks.py' 'merkle_blocks.py'
'fundrawtransaction.py' # 'fundrawtransaction.py'
'signrawtransactions.py' 'signrawtransactions.py'
'walletbackup.py' 'walletbackup.py'
'key_import_export.py' 'key_import_export.py'

View File

@ -108,7 +108,8 @@ LIBZCASH_H = \
zcash/prf.h \ zcash/prf.h \
zcash/Proof.hpp \ zcash/Proof.hpp \
zcash/util.h \ zcash/util.h \
zcash/Zcash.h zcash/Zcash.h \
zcash/zip32.h
.PHONY: FORCE collate-libsnark check-symbols check-security .PHONY: FORCE collate-libsnark check-symbols check-security
# bitcoin core # # bitcoin core #
@ -520,6 +521,7 @@ libzcash_a_SOURCES = \
zcash/Note.cpp \ zcash/Note.cpp \
zcash/prf.cpp \ zcash/prf.cpp \
zcash/util.cpp \ zcash/util.cpp \
zcash/zip32.cpp \
zcash/circuit/commitment.tcc \ zcash/circuit/commitment.tcc \
zcash/circuit/gadget.tcc \ zcash/circuit/gadget.tcc \
zcash/circuit/merkle.tcc \ zcash/circuit/merkle.tcc \

View File

@ -44,7 +44,8 @@ zcash_gtest_SOURCES += \
gtest/test_proofs.cpp \ gtest/test_proofs.cpp \
gtest/test_paymentdisclosure.cpp \ gtest/test_paymentdisclosure.cpp \
gtest/test_pedersen_hash.cpp \ gtest/test_pedersen_hash.cpp \
gtest/test_checkblock.cpp gtest/test_checkblock.cpp \
gtest/test_zip32.cpp
if ENABLE_WALLET if ENABLE_WALLET
zcash_gtest_SOURCES += \ zcash_gtest_SOURCES += \
wallet/gtest/test_wallet.cpp wallet/gtest/test_wallet.cpp

View File

@ -158,7 +158,7 @@ public:
bech32HRPs[SAPLING_PAYMENT_ADDRESS] = "zs"; bech32HRPs[SAPLING_PAYMENT_ADDRESS] = "zs";
bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviews"; bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviews";
bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivks"; bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivks";
bech32HRPs[SAPLING_SPENDING_KEY] = "secret-spending-key-main"; bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-main";
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main));
@ -329,7 +329,7 @@ public:
bech32HRPs[SAPLING_PAYMENT_ADDRESS] = "ztestsapling"; bech32HRPs[SAPLING_PAYMENT_ADDRESS] = "ztestsapling";
bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviewtestsapling"; bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviewtestsapling";
bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivktestsapling"; bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivktestsapling";
bech32HRPs[SAPLING_SPENDING_KEY] = "secret-spending-key-test"; bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-test";
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));
@ -457,7 +457,7 @@ public:
bech32HRPs[SAPLING_PAYMENT_ADDRESS] = "zregtestsapling"; bech32HRPs[SAPLING_PAYMENT_ADDRESS] = "zregtestsapling";
bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviewregtestsapling"; bech32HRPs[SAPLING_FULL_VIEWING_KEY] = "zviewregtestsapling";
bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivkregtestsapling"; bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivkregtestsapling";
bech32HRPs[SAPLING_SPENDING_KEY] = "secret-spending-key-regtest"; bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-regtest";
// Founders reward script expects a vector of 2-of-3 multisig addresses // Founders reward script expects a vector of 2-of-3 multisig addresses
vFoundersRewardAddress = { "t2FwcEhFdNXuFMv1tcYwaBJtYVtMj8b1uTg" }; vFoundersRewardAddress = { "t2FwcEhFdNXuFMv1tcYwaBJtYVtMj8b1uTg" };

View File

@ -60,7 +60,7 @@ public:
SAPLING_PAYMENT_ADDRESS, SAPLING_PAYMENT_ADDRESS,
SAPLING_FULL_VIEWING_KEY, SAPLING_FULL_VIEWING_KEY,
SAPLING_INCOMING_VIEWING_KEY, SAPLING_INCOMING_VIEWING_KEY,
SAPLING_SPENDING_KEY, SAPLING_EXTENDED_SPEND_KEY,
MAX_BECH32_TYPES MAX_BECH32_TYPES
}; };

View File

@ -1,6 +1,7 @@
#include <chainparams.h> #include <chainparams.h>
#include <key_io.h> #include <key_io.h>
#include <zcash/Address.hpp> #include <zcash/Address.hpp>
#include <zcash/zip32.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -8,23 +9,27 @@ TEST(Keys, DISABLED_EncodeAndDecodeSapling)
{ {
SelectParams(CBaseChainParams::MAIN); SelectParams(CBaseChainParams::MAIN);
for (size_t i = 0; i < 1000; i++) { std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto sk = libzcash::SaplingSpendingKey::random(); HDSeed seed(rawSeed);
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
for (uint32_t i = 0; i < 1000; i++) {
auto sk = m.Derive(i);
{ {
std::string sk_string = EncodeSpendingKey(sk); std::string sk_string = EncodeSpendingKey(sk);
EXPECT_EQ( EXPECT_EQ(
sk_string.substr(0, 24), sk_string.substr(0, 24),
Params().Bech32HRP(CChainParams::SAPLING_SPENDING_KEY)); Params().Bech32HRP(CChainParams::SAPLING_EXTENDED_SPEND_KEY));
auto spendingkey2 = DecodeSpendingKey(sk_string); auto spendingkey2 = DecodeSpendingKey(sk_string);
EXPECT_TRUE(IsValidSpendingKey(spendingkey2)); EXPECT_TRUE(IsValidSpendingKey(spendingkey2));
ASSERT_TRUE(boost::get<libzcash::SaplingSpendingKey>(&spendingkey2) != nullptr); ASSERT_TRUE(boost::get<libzcash::SaplingExtendedSpendingKey>(&spendingkey2) != nullptr);
auto sk2 = boost::get<libzcash::SaplingSpendingKey>(spendingkey2); auto sk2 = boost::get<libzcash::SaplingExtendedSpendingKey>(spendingkey2);
EXPECT_EQ(sk, sk2); EXPECT_EQ(sk, sk2);
} }
{ {
auto addr = sk.default_address(); auto addr = sk.DefaultAddress();
std::string addr_string = EncodePaymentAddress(addr); std::string addr_string = EncodePaymentAddress(addr);
EXPECT_EQ( EXPECT_EQ(

View File

@ -8,11 +8,39 @@
#include "wallet/crypter.h" #include "wallet/crypter.h"
#endif #endif
#include "zcash/Address.hpp" #include "zcash/Address.hpp"
#include "zcash/zip32.h"
#include "json_test_vectors.h" #include "json_test_vectors.h"
#define MAKE_STRING(x) std::string((x), (x)+sizeof(x)) #define MAKE_STRING(x) std::string((x), (x)+sizeof(x))
TEST(keystore_tests, StoreAndRetrieveHDSeed) {
CBasicKeyStore keyStore;
HDSeed seedOut;
// When we haven't set a seed, we shouldn't get one
EXPECT_FALSE(keyStore.HaveHDSeed());
EXPECT_FALSE(keyStore.GetHDSeed(seedOut));
// Generate a random seed
auto seed = HDSeed::Random();
// We should be able to set and retrieve the seed
ASSERT_TRUE(keyStore.SetHDSeed(seed));
EXPECT_TRUE(keyStore.HaveHDSeed());
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
// Generate another random seed
auto seed2 = HDSeed::Random();
EXPECT_NE(seed, seed2);
// We should not be able to set and retrieve a different seed
EXPECT_FALSE(keyStore.SetHDSeed(seed2));
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
}
TEST(keystore_tests, sapling_keys) { TEST(keystore_tests, sapling_keys) {
// ["sk, ask, nsk, ovk, ak, nk, ivk, default_d, default_pk_d, note_v, note_r, note_cm, note_pos, note_nf"], // ["sk, ask, nsk, ovk, ak, nk, ivk, default_d, default_pk_d, note_v, note_r, note_cm, note_pos, note_nf"],
UniValue sapling_keys = read_json(MAKE_STRING(json_tests::sapling_key_components)); UniValue sapling_keys = read_json(MAKE_STRING(json_tests::sapling_key_components));
@ -166,14 +194,16 @@ TEST(keystore_tests, StoreAndRetrieveViewingKey) {
// Sapling // Sapling
TEST(keystore_tests, StoreAndRetrieveSaplingSpendingKey) { TEST(keystore_tests, StoreAndRetrieveSaplingSpendingKey) {
CBasicKeyStore keyStore; CBasicKeyStore keyStore;
libzcash::SaplingSpendingKey skOut; libzcash::SaplingExtendedSpendingKey skOut;
libzcash::SaplingFullViewingKey fvkOut; libzcash::SaplingFullViewingKey fvkOut;
libzcash::SaplingIncomingViewingKey ivkOut; libzcash::SaplingIncomingViewingKey ivkOut;
auto sk = libzcash::SaplingSpendingKey::random(); std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto fvk = sk.full_viewing_key(); HDSeed seed(rawSeed);
auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto fvk = sk.expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key(); auto ivk = fvk.in_viewing_key();
auto addr = sk.default_address(); auto addr = sk.DefaultAddress();
// Sanity-check: we can't get a key we haven't added // Sanity-check: we can't get a key we haven't added
EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(fvk)); EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(fvk));
@ -212,6 +242,66 @@ public:
bool Unlock(const CKeyingMaterial& vMasterKeyIn) { return CCryptoKeyStore::Unlock(vMasterKeyIn); } bool Unlock(const CKeyingMaterial& vMasterKeyIn) { return CCryptoKeyStore::Unlock(vMasterKeyIn); }
}; };
TEST(keystore_tests, StoreAndRetrieveHDSeedInEncryptedStore) {
TestCCryptoKeyStore keyStore;
CKeyingMaterial vMasterKey(32, 0);
GetRandBytes(vMasterKey.data(), 32);
HDSeed seedOut;
// 1) Test adding a seed to an unencrypted key store, then encrypting it
auto seed = HDSeed::Random();
EXPECT_FALSE(keyStore.HaveHDSeed());
EXPECT_FALSE(keyStore.GetHDSeed(seedOut));
ASSERT_TRUE(keyStore.SetHDSeed(seed));
EXPECT_TRUE(keyStore.HaveHDSeed());
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
ASSERT_TRUE(keyStore.EncryptKeys(vMasterKey));
EXPECT_FALSE(keyStore.GetHDSeed(seedOut));
// Unlocking with a random key should fail
CKeyingMaterial vRandomKey(32, 0);
GetRandBytes(vRandomKey.data(), 32);
EXPECT_FALSE(keyStore.Unlock(vRandomKey));
// Unlocking with a slightly-modified vMasterKey should fail
CKeyingMaterial vModifiedKey(vMasterKey);
vModifiedKey[0] += 1;
EXPECT_FALSE(keyStore.Unlock(vModifiedKey));
// Unlocking with vMasterKey should succeed
ASSERT_TRUE(keyStore.Unlock(vMasterKey));
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
// 2) Test replacing the seed in an already-encrypted key store fails
auto seed2 = HDSeed::Random();
EXPECT_FALSE(keyStore.SetHDSeed(seed2));
EXPECT_TRUE(keyStore.HaveHDSeed());
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
// 3) Test adding a new seed to an already-encrypted key store
TestCCryptoKeyStore keyStore2;
// Add a Sprout address so the wallet has something to test when decrypting
ASSERT_TRUE(keyStore2.AddSproutSpendingKey(libzcash::SproutSpendingKey::random()));
ASSERT_TRUE(keyStore2.EncryptKeys(vMasterKey));
ASSERT_TRUE(keyStore2.Unlock(vMasterKey));
EXPECT_FALSE(keyStore2.HaveHDSeed());
EXPECT_FALSE(keyStore2.GetHDSeed(seedOut));
auto seed3 = HDSeed::Random();
ASSERT_TRUE(keyStore2.SetHDSeed(seed3));
EXPECT_TRUE(keyStore2.HaveHDSeed());
ASSERT_TRUE(keyStore2.GetHDSeed(seedOut));
EXPECT_EQ(seed3, seedOut);
}
TEST(keystore_tests, store_and_retrieve_spending_key_in_encrypted_store) { TEST(keystore_tests, store_and_retrieve_spending_key_in_encrypted_store) {
TestCCryptoKeyStore keyStore; TestCCryptoKeyStore keyStore;
uint256 r {GetRandHash()}; uint256 r {GetRandHash()};

134
src/gtest/test_zip32.cpp Normal file
View File

@ -0,0 +1,134 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <zcash/zip32.h>
// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_zip32.py
// Sapling consistently uses little-endian encoding, but uint256S takes its input in
// big-endian byte order, so the test vectors below are byte-reversed.
TEST(ZIP32, TestVectors) {
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 m = libzcash::SaplingExtendedSpendingKey::Master(seed);
EXPECT_EQ(m.depth, 0);
EXPECT_EQ(m.parentFVKTag, 0);
EXPECT_EQ(m.childIndex, 0);
EXPECT_EQ(
m.chaincode,
uint256S("8e661820750d557e8b34733ebf7ecdfdf31c6d27724fb47aa372bf034b7c94d0"));
EXPECT_EQ(
m.expsk.ask,
uint256S("06257454c907f6510ba1c1830ebf60657760a8869ee968a2b93260d3930cc0b6"));
EXPECT_EQ(
m.expsk.nsk,
uint256S("06ea21888a749fd38eb443d20a030abd2e6e997f5db4f984bd1f2f3be8ed0482"));
EXPECT_EQ(
m.expsk.ovk,
uint256S("21fb4adfa42183848306ffb27719f27d76cf9bb81d023c93d4b9230389845839"));
EXPECT_EQ(
m.dk,
uint256S("72a196f93e8abc0935280ea2a96fa57d6024c9913e0f9fb3af96775bb77cc177"));
EXPECT_THAT(
m.ToXFVK().DefaultAddress().d,
testing::ElementsAreArray({ 0xd8, 0x62, 0x1b, 0x98, 0x1c, 0xf3, 0x00, 0xe9, 0xd4, 0xcc, 0x89 }));
auto m_1 = m.Derive(1);
EXPECT_EQ(m_1.depth, 1);
EXPECT_EQ(m_1.parentFVKTag, 0x3a71c214);
EXPECT_EQ(m_1.childIndex, 1);
EXPECT_EQ(
m_1.chaincode,
uint256S("e6bcda05678a43fad229334ef0b795a590e7c50590baf0d9b9031a690c114701"));
EXPECT_EQ(
m_1.expsk.ask,
uint256S("0c357a2655b4b8d761794095df5cb402d3ba4a428cf6a88e7c2816a597c12b28"));
EXPECT_EQ(
m_1.expsk.nsk,
uint256S("01ba6bff1018fd4eac04da7e3f2c6be9c229e662c5c4d1d6fc1ecafd8829a3e7"));
EXPECT_EQ(
m_1.expsk.ovk,
uint256S("7474a4c518551bd82f14a7f7365a8ffa403c50cfeffedf026ada8688fc81135f"));
EXPECT_EQ(
m_1.dk,
uint256S("dcb4c170d878510e96c4a74192d7eecde9c9912b00b99a12ec91d7a232e84de0"));
EXPECT_THAT(
m_1.ToXFVK().DefaultAddress().d,
testing::ElementsAreArray({ 0x8b, 0x41, 0x38, 0x32, 0x0d, 0xfa, 0xfd, 0x7b, 0x39, 0x97, 0x81 }));
auto m_1_2h = m_1.Derive(2 | ZIP32_HARDENED_KEY_LIMIT);
EXPECT_EQ(m_1_2h.depth, 2);
EXPECT_EQ(m_1_2h.parentFVKTag, 0x079e99db);
EXPECT_EQ(m_1_2h.childIndex, 2 | ZIP32_HARDENED_KEY_LIMIT);
EXPECT_EQ(
m_1_2h.chaincode,
uint256S("35d4a883737742ca41a4baa92323bdb3c93dcb3b462a26b039971bedf415ce97"));
EXPECT_EQ(
m_1_2h.expsk.ask,
uint256S("0dc6e4fe846bda925c82e632980434e17b51dac81fc4821fa71334ee3c11e88b"));
EXPECT_EQ(
m_1_2h.expsk.nsk,
uint256S("0c99a63a275c1c66734761cfb9c62fe9bd1b953f579123d3d0e769c59d057837"));
EXPECT_EQ(
m_1_2h.expsk.ovk,
uint256S("bc1328fc5eb693e18875c5149d06953b11d39447ebd6e38c023c22962e1881cf"));
EXPECT_EQ(
m_1_2h.dk,
uint256S("377bb062dce7e0dcd8a0054d0ca4b4d1481b3710bfa1df12ca46ff9e9fa1eda3"));
EXPECT_THAT(
m_1_2h.ToXFVK().DefaultAddress().d,
testing::ElementsAreArray({ 0xe8, 0xd0, 0x37, 0x93, 0xcd, 0xd2, 0xba, 0xcc, 0x9c, 0x70, 0x41 }));
auto m_1_2hv = m_1_2h.ToXFVK();
EXPECT_EQ(m_1_2hv.depth, 2);
EXPECT_EQ(m_1_2hv.parentFVKTag, 0x079e99db);
EXPECT_EQ(m_1_2hv.childIndex, 2 | ZIP32_HARDENED_KEY_LIMIT);
EXPECT_EQ(
m_1_2hv.chaincode,
uint256S("35d4a883737742ca41a4baa92323bdb3c93dcb3b462a26b039971bedf415ce97"));
EXPECT_EQ(
m_1_2hv.fvk.ak,
uint256S("4138cffdf7200e52d4e9f4384481b4a4c4d070493a5e401e4ffa850f5a92c5a6"));
EXPECT_EQ(
m_1_2hv.fvk.nk,
uint256S("11eee22577304f660cc036bc84b3fc88d1ec50ae8a4d657beb6b211659304e30"));
EXPECT_EQ(
m_1_2hv.fvk.ovk,
uint256S("bc1328fc5eb693e18875c5149d06953b11d39447ebd6e38c023c22962e1881cf"));
EXPECT_EQ(
m_1_2hv.dk,
uint256S("377bb062dce7e0dcd8a0054d0ca4b4d1481b3710bfa1df12ca46ff9e9fa1eda3"));
EXPECT_EQ(m_1_2hv.DefaultAddress(), m_1_2h.ToXFVK().DefaultAddress());
// Hardened derivation from an xfvk fails
EXPECT_FALSE(m_1_2hv.Derive(3 | ZIP32_HARDENED_KEY_LIMIT));
// Non-hardened derivation succeeds
auto maybe_m_1_2hv_3 = m_1_2hv.Derive(3);
EXPECT_TRUE(maybe_m_1_2hv_3);
auto m_1_2hv_3 = maybe_m_1_2hv_3.get();
EXPECT_EQ(m_1_2hv_3.depth, 3);
EXPECT_EQ(m_1_2hv_3.parentFVKTag, 0x7583c148);
EXPECT_EQ(m_1_2hv_3.childIndex, 3);
EXPECT_EQ(
m_1_2hv_3.chaincode,
uint256S("e8e7d6a74a5a1c05be41baec7998d91f7b3603a4c0af495b0d43ba81cf7b938d"));
EXPECT_EQ(
m_1_2hv_3.fvk.ak,
uint256S("a3a697bdda9d648d32a97553de4754b2fac866d726d3f2c436259c507bc585b1"));
EXPECT_EQ(
m_1_2hv_3.fvk.nk,
uint256S("4f66c0814b769963f3bf1bc001270b50edabb27de042fc8a5607d2029e0488db"));
EXPECT_EQ(
m_1_2hv_3.fvk.ovk,
uint256S("f61a699934dc78441324ef628b4b4721611571e8ee3bd591eb3d4b1cfae0b969"));
EXPECT_EQ(
m_1_2hv_3.dk,
uint256S("6ee53b1261f2c9c0f7359ab236f87b52a0f1b0ce43305cdad92ebb63c350cbbe"));
EXPECT_THAT(
m_1_2hv_3.DefaultAddress().d,
testing::ElementsAreArray({ 0x03, 0x0f, 0xfb, 0x26, 0x3a, 0x93, 0x9e, 0x23, 0x0e, 0x96, 0xdd }));
}

View File

@ -1626,6 +1626,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
pwalletMain->SetMaxVersion(nMaxVersion); pwalletMain->SetMaxVersion(nMaxVersion);
} }
if (!pwalletMain->HaveHDSeed())
{
// generate a new HD seed
pwalletMain->GenerateNewSeed();
}
if (fFirstRun) if (fFirstRun)
{ {
// Create new keyUser and set as default key // Create new keyUser and set as default key

View File

@ -142,7 +142,7 @@ public:
return ret; return ret;
} }
std::string operator()(const libzcash::SaplingSpendingKey& zkey) const std::string operator()(const libzcash::SaplingExtendedSpendingKey& zkey) const
{ {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zkey; ss << zkey;
@ -152,7 +152,7 @@ public:
// See calculation comment below // See calculation comment below
data.reserve((serkey.size() * 8 + 4) / 5); data.reserve((serkey.size() * 8 + 4) / 5);
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, serkey.begin(), serkey.end()); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, serkey.begin(), serkey.end());
std::string ret = bech32::Encode(m_params.Bech32HRP(CChainParams::SAPLING_SPENDING_KEY), data); std::string ret = bech32::Encode(m_params.Bech32HRP(CChainParams::SAPLING_EXTENDED_SPEND_KEY), data);
memory_cleanse(serkey.data(), serkey.size()); memory_cleanse(serkey.data(), serkey.size());
memory_cleanse(data.data(), data.size()); memory_cleanse(data.data(), data.size());
return ret; return ret;
@ -166,7 +166,7 @@ public:
// regular serialized size in bytes, convert to bits, and then // regular serialized size in bytes, convert to bits, and then
// perform ceiling division to get the number of 5-bit clusters. // perform ceiling division to get the number of 5-bit clusters.
const size_t ConvertedSaplingPaymentAddressSize = ((32 + 11) * 8 + 4) / 5; const size_t ConvertedSaplingPaymentAddressSize = ((32 + 11) * 8 + 4) / 5;
const size_t ConvertedSaplingSpendingKeySize = (32 * 8 + 4) / 5; const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5;
} // namespace } // namespace
CKey DecodeSecret(const std::string& str) CKey DecodeSecret(const std::string& str)
@ -360,13 +360,13 @@ libzcash::SpendingKey DecodeSpendingKey(const std::string& str)
Params().NetworkIDString() == "test" && Params().NetworkIDString() == "test" &&
GetBoolArg("-experimentalfeatures", false) && GetBoolArg("-experimentalfeatures", false) &&
GetBoolArg("-developersapling", false)); GetBoolArg("-developersapling", false));
if (allowSapling && bech.first == Params().Bech32HRP(CChainParams::SAPLING_SPENDING_KEY) && if (allowSapling && bech.first == Params().Bech32HRP(CChainParams::SAPLING_EXTENDED_SPEND_KEY) &&
bech.second.size() == ConvertedSaplingSpendingKeySize) { bech.second.size() == ConvertedSaplingExtendedSpendingKeySize) {
// Bech32 decoding // Bech32 decoding
data.reserve((bech.second.size() * 5) / 8); data.reserve((bech.second.size() * 5) / 8);
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin(), bech.second.end())) { if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin(), bech.second.end())) {
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
libzcash::SaplingSpendingKey ret; libzcash::SaplingExtendedSpendingKey ret;
ss >> ret; ss >> ret;
memory_cleanse(data.data(), data.size()); memory_cleanse(data.data(), data.size());
return ret; return ret;

View File

@ -12,6 +12,7 @@
#include <pubkey.h> #include <pubkey.h>
#include <script/standard.h> #include <script/standard.h>
#include <zcash/Address.hpp> #include <zcash/Address.hpp>
#include <zcash/zip32.h>
#include <string> #include <string>

View File

@ -23,6 +23,35 @@ bool CKeyStore::AddKey(const CKey &key) {
return AddKeyPubKey(key, key.GetPubKey()); return AddKeyPubKey(key, key.GetPubKey());
} }
bool CBasicKeyStore::SetHDSeed(const HDSeed& seed)
{
LOCK(cs_SpendingKeyStore);
if (!hdSeed.IsNull()) {
// Don't allow an existing seed to be changed. We can maybe relax this
// restriction later once we have worked out the UX implications.
return false;
}
hdSeed = seed;
return true;
}
bool CBasicKeyStore::HaveHDSeed() const
{
LOCK(cs_SpendingKeyStore);
return !hdSeed.IsNull();
}
bool CBasicKeyStore::GetHDSeed(HDSeed& seedOut) const
{
LOCK(cs_SpendingKeyStore);
if (hdSeed.IsNull()) {
return false;
} else {
seedOut = hdSeed;
return true;
}
}
bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
@ -95,11 +124,11 @@ bool CBasicKeyStore::AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk)
//! Sapling //! Sapling
bool CBasicKeyStore::AddSaplingSpendingKey( bool CBasicKeyStore::AddSaplingSpendingKey(
const libzcash::SaplingSpendingKey &sk, const libzcash::SaplingExtendedSpendingKey &sk,
const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr) const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr)
{ {
LOCK(cs_SpendingKeyStore); LOCK(cs_SpendingKeyStore);
auto fvk = sk.full_viewing_key(); auto fvk = sk.expsk.full_viewing_key();
// if SaplingFullViewingKey is not in SaplingFullViewingKeyMap, add it // if SaplingFullViewingKey is not in SaplingFullViewingKeyMap, add it
if (!AddSaplingFullViewingKey(fvk, defaultAddr)){ if (!AddSaplingFullViewingKey(fvk, defaultAddr)){

View File

@ -13,6 +13,7 @@
#include "sync.h" #include "sync.h"
#include "zcash/Address.hpp" #include "zcash/Address.hpp"
#include "zcash/NoteEncryption.hpp" #include "zcash/NoteEncryption.hpp"
#include "zcash/zip32.h"
#include <boost/signals2/signal.hpp> #include <boost/signals2/signal.hpp>
#include <boost/variant.hpp> #include <boost/variant.hpp>
@ -27,6 +28,12 @@ protected:
public: public:
virtual ~CKeyStore() {} virtual ~CKeyStore() {}
//! Set the HD seed for this keystore
virtual bool SetHDSeed(const HDSeed& seed) =0;
virtual bool HaveHDSeed() const =0;
//! Get the HD seed for this keystore
virtual bool GetHDSeed(HDSeed& seedOut) const =0;
//! Add a key to the store. //! Add a key to the store.
virtual bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) =0; virtual bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) =0;
virtual bool AddKey(const CKey &key); virtual bool AddKey(const CKey &key);
@ -58,12 +65,12 @@ public:
//! Add a Sapling spending key to the store. //! Add a Sapling spending key to the store.
virtual bool AddSaplingSpendingKey( virtual bool AddSaplingSpendingKey(
const libzcash::SaplingSpendingKey &sk, const libzcash::SaplingExtendedSpendingKey &sk,
const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none) =0; const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none) =0;
//! Check whether a Sapling spending key corresponding to a given Sapling viewing key is present in the store. //! Check whether a Sapling spending key corresponding to a given Sapling viewing key is present in the store.
virtual bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const =0; virtual bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const =0;
virtual bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingSpendingKey& skOut) const =0; virtual bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingExtendedSpendingKey& skOut) const =0;
//! Support for Sapling full viewing keys //! Support for Sapling full viewing keys
virtual bool AddSaplingFullViewingKey( virtual bool AddSaplingFullViewingKey(
@ -99,8 +106,7 @@ typedef std::map<libzcash::SproutPaymentAddress, ZCNoteDecryption> NoteDecryptor
// Full viewing key has equivalent functionality to a transparent address // Full viewing key has equivalent functionality to a transparent address
// When encrypting wallet, encrypt SaplingSpendingKeyMap, while leaving SaplingFullViewingKeyMap unencrypted // When encrypting wallet, encrypt SaplingSpendingKeyMap, while leaving SaplingFullViewingKeyMap unencrypted
// When implementing ZIP 32, add another map from SaplingFullViewingKey -> SaplingExpandedSpendingKey typedef std::map<libzcash::SaplingFullViewingKey, libzcash::SaplingExtendedSpendingKey> SaplingSpendingKeyMap;
typedef std::map<libzcash::SaplingFullViewingKey, libzcash::SaplingSpendingKey> SaplingSpendingKeyMap;
typedef std::map<libzcash::SaplingIncomingViewingKey, libzcash::SaplingFullViewingKey> SaplingFullViewingKeyMap; typedef std::map<libzcash::SaplingIncomingViewingKey, libzcash::SaplingFullViewingKey> SaplingFullViewingKeyMap;
// Only maps from default addresses to ivk, may need to be reworked when adding diversified addresses. // Only maps from default addresses to ivk, may need to be reworked when adding diversified addresses.
typedef std::map<libzcash::SaplingPaymentAddress, libzcash::SaplingIncomingViewingKey> SaplingIncomingViewingKeyMap; typedef std::map<libzcash::SaplingPaymentAddress, libzcash::SaplingIncomingViewingKey> SaplingIncomingViewingKeyMap;
@ -109,6 +115,7 @@ typedef std::map<libzcash::SaplingPaymentAddress, libzcash::SaplingIncomingViewi
class CBasicKeyStore : public CKeyStore class CBasicKeyStore : public CKeyStore
{ {
protected: protected:
HDSeed hdSeed;
KeyMap mapKeys; KeyMap mapKeys;
ScriptMap mapScripts; ScriptMap mapScripts;
WatchOnlySet setWatchOnly; WatchOnlySet setWatchOnly;
@ -121,6 +128,10 @@ protected:
SaplingIncomingViewingKeyMap mapSaplingIncomingViewingKeys; SaplingIncomingViewingKeyMap mapSaplingIncomingViewingKeys;
public: public:
bool SetHDSeed(const HDSeed& seed);
bool HaveHDSeed() const;
bool GetHDSeed(HDSeed& seedOut) const;
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
bool HaveKey(const CKeyID &address) const bool HaveKey(const CKeyID &address) const
{ {
@ -224,7 +235,7 @@ public:
//! Sapling //! Sapling
bool AddSaplingSpendingKey( bool AddSaplingSpendingKey(
const libzcash::SaplingSpendingKey &sk, const libzcash::SaplingExtendedSpendingKey &sk,
const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none); const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none);
bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const
{ {
@ -235,7 +246,7 @@ public:
} }
return result; return result;
} }
bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingSpendingKey &skOut) const bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingExtendedSpendingKey &skOut) const
{ {
{ {
LOCK(cs_SpendingKeyStore); LOCK(cs_SpendingKeyStore);

View File

@ -225,21 +225,25 @@ BOOST_AUTO_TEST_CASE(zs_address_test)
{ {
SelectParams(CBaseChainParams::REGTEST); SelectParams(CBaseChainParams::REGTEST);
for (size_t i = 0; i < 1000; i++) { std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto sk = SaplingSpendingKey::random(); HDSeed seed(rawSeed);
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
for (uint32_t i = 0; i < 1000; i++) {
auto sk = m.Derive(i);
{ {
std::string sk_string = EncodeSpendingKey(sk); std::string sk_string = EncodeSpendingKey(sk);
BOOST_CHECK(sk_string.compare(0, 27, Params().Bech32HRP(CChainParams::SAPLING_SPENDING_KEY)) == 0); BOOST_CHECK(sk_string.compare(0, 27, Params().Bech32HRP(CChainParams::SAPLING_EXTENDED_SPEND_KEY)) == 0);
auto spendingkey2 = DecodeSpendingKey(sk_string); auto spendingkey2 = DecodeSpendingKey(sk_string);
BOOST_CHECK(IsValidSpendingKey(spendingkey2)); BOOST_CHECK(IsValidSpendingKey(spendingkey2));
BOOST_ASSERT(boost::get<SaplingSpendingKey>(&spendingkey2) != nullptr); BOOST_ASSERT(boost::get<SaplingExtendedSpendingKey>(&spendingkey2) != nullptr);
auto sk2 = boost::get<SaplingSpendingKey>(spendingkey2); auto sk2 = boost::get<SaplingExtendedSpendingKey>(spendingkey2);
BOOST_CHECK(sk == sk2); BOOST_CHECK(sk == sk2);
} }
{ {
auto addr = sk.default_address(); auto addr = sk.DefaultAddress();
std::string addr_string = EncodePaymentAddress(addr); std::string addr_string = EncodePaymentAddress(addr);
BOOST_CHECK(addr_string.compare(0, 15, Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS)) == 0); BOOST_CHECK(addr_string.compare(0, 15, Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS)) == 0);

View File

@ -568,6 +568,10 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport)
pwalletMain->GetSaplingPaymentAddresses(saplingAddrs); pwalletMain->GetSaplingPaymentAddresses(saplingAddrs);
BOOST_CHECK(saplingAddrs.empty()); BOOST_CHECK(saplingAddrs.empty());
std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
HDSeed seed(rawSeed);
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
// verify import and export key // verify import and export key
for (int i = 0; i < n1; i++) { for (int i = 0; i < n1; i++) {
// create a random Sprout key locally // create a random Sprout key locally
@ -578,10 +582,10 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport)
BOOST_CHECK_NO_THROW(CallRPC(string("z_importkey ") + testKey)); BOOST_CHECK_NO_THROW(CallRPC(string("z_importkey ") + testKey));
BOOST_CHECK_NO_THROW(retValue = CallRPC(string("z_exportkey ") + testAddr)); BOOST_CHECK_NO_THROW(retValue = CallRPC(string("z_exportkey ") + testAddr));
BOOST_CHECK_EQUAL(retValue.get_str(), testKey); BOOST_CHECK_EQUAL(retValue.get_str(), testKey);
// create a random Sapling key locally // create a random Sapling key locally
auto testSaplingSpendingKey = libzcash::SaplingSpendingKey::random(); auto testSaplingSpendingKey = m.Derive(i);
auto testSaplingPaymentAddress = testSaplingSpendingKey.default_address(); auto testSaplingPaymentAddress = testSaplingSpendingKey.DefaultAddress();
std::string testSaplingAddr = EncodePaymentAddress(testSaplingPaymentAddress); std::string testSaplingAddr = EncodePaymentAddress(testSaplingPaymentAddress);
std::string testSaplingKey = EncodeSpendingKey(testSaplingSpendingKey); std::string testSaplingKey = EncodeSpendingKey(testSaplingSpendingKey);
BOOST_CHECK_NO_THROW(CallRPC(string("z_importkey ") + testSaplingKey)); BOOST_CHECK_NO_THROW(CallRPC(string("z_importkey ") + testSaplingKey));
@ -1253,6 +1257,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
/* /*
* This test covers storing encrypted zkeys in the wallet. * This test covers storing encrypted zkeys in the wallet.
*/ */
/* TODO: Uncomment during PR for #3388
BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys) BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys)
{ {
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
@ -1308,6 +1313,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys)
// We can't simulate over RPC the wallet closing and being reloaded // We can't simulate over RPC the wallet closing and being reloaded
// but there are tests for this in gtest. // but there are tests for this in gtest.
} }
*/
BOOST_AUTO_TEST_CASE(rpc_z_listunspent_parameters) BOOST_AUTO_TEST_CASE(rpc_z_listunspent_parameters)

View File

@ -88,6 +88,15 @@ public:
} }
}; };
/** 88-bit opaque blob.
*/
class blob88 : public base_blob<88> {
public:
blob88() {}
blob88(const base_blob<88>& b) : base_blob<88>(b) {}
explicit blob88(const std::vector<unsigned char>& vch) : base_blob<88>(vch) {}
};
/** 160-bit opaque blob. /** 160-bit opaque blob.
* @note This type is called uint160 for historical reasons only. It is an opaque * @note This type is called uint160 for historical reasons only. It is an opaque
* blob of 160 bits and has no integer operations. * blob of 160 bits and has no integer operations.

View File

@ -382,8 +382,8 @@ bool AsyncRPCOperation_sendmany::main_impl() {
SaplingExpandedSpendingKey expsk; SaplingExpandedSpendingKey expsk;
SaplingFullViewingKey from; SaplingFullViewingKey from;
if (isfromzaddr_) { if (isfromzaddr_) {
auto sk = boost::get<libzcash::SaplingSpendingKey>(spendingkey_); auto sk = boost::get<libzcash::SaplingExtendedSpendingKey>(spendingkey_);
expsk = sk.expanded_spending_key(); expsk = sk.expsk;
from = expsk.full_viewing_key(); from = expsk.full_viewing_key();
} else { } else {
// TODO: Set "from" to something! // TODO: Set "from" to something!

View File

@ -121,6 +121,23 @@ static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<u
return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext)); return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext));
} }
static bool DecryptHDSeed(
const CKeyingMaterial& vMasterKey,
const std::vector<unsigned char>& vchCryptedSecret,
const uint256& seedFp,
HDSeed& seed)
{
CKeyingMaterial vchSecret;
// Use seed's fingerprint as IV
// TODO: Handle IV properly when we make encryption a supported feature
if(!DecryptSecret(vMasterKey, vchCryptedSecret, seedFp, vchSecret))
return false;
seed = HDSeed(vchSecret);
return seed.Fingerprint() == seedFp;
}
static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key) static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key)
{ {
CKeyingMaterial vchSecret; CKeyingMaterial vchSecret;
@ -154,18 +171,18 @@ static bool DecryptSproutSpendingKey(const CKeyingMaterial& vMasterKey,
static bool DecryptSaplingSpendingKey(const CKeyingMaterial& vMasterKey, static bool DecryptSaplingSpendingKey(const CKeyingMaterial& vMasterKey,
const std::vector<unsigned char>& vchCryptedSecret, const std::vector<unsigned char>& vchCryptedSecret,
const libzcash::SaplingFullViewingKey& fvk, const libzcash::SaplingFullViewingKey& fvk,
libzcash::SaplingSpendingKey& sk) libzcash::SaplingExtendedSpendingKey& sk)
{ {
CKeyingMaterial vchSecret; CKeyingMaterial vchSecret;
if (!DecryptSecret(vMasterKey, vchCryptedSecret, fvk.GetFingerprint(), vchSecret)) if (!DecryptSecret(vMasterKey, vchCryptedSecret, fvk.GetFingerprint(), vchSecret))
return false; return false;
if (vchSecret.size() != libzcash::SerializedSaplingSpendingKeySize) if (vchSecret.size() != ZIP32_XSK_SIZE)
return false; return false;
CSecureDataStream ss(vchSecret, SER_NETWORK, PROTOCOL_VERSION); CSecureDataStream ss(vchSecret, SER_NETWORK, PROTOCOL_VERSION);
ss >> sk; ss >> sk;
return sk.full_viewing_key() == fvk; return sk.expsk.full_viewing_key() == fvk;
} }
bool CCryptoKeyStore::SetCrypted() bool CCryptoKeyStore::SetCrypted()
@ -202,6 +219,15 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
bool keyPass = false; bool keyPass = false;
bool keyFail = false; bool keyFail = false;
if (!cryptedHDSeed.first.IsNull()) {
HDSeed seed;
if (!DecryptHDSeed(vMasterKeyIn, cryptedHDSeed.second, cryptedHDSeed.first, seed))
{
keyFail = true;
} else {
keyPass = true;
}
}
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
for (; mi != mapCryptedKeys.end(); ++mi) for (; mi != mapCryptedKeys.end(); ++mi)
{ {
@ -237,7 +263,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
{ {
const libzcash::SaplingFullViewingKey &fvk = (*miSapling).first; const libzcash::SaplingFullViewingKey &fvk = (*miSapling).first;
const std::vector<unsigned char> &vchCryptedSecret = (*miSapling).second; const std::vector<unsigned char> &vchCryptedSecret = (*miSapling).second;
libzcash::SaplingSpendingKey sk; libzcash::SaplingExtendedSpendingKey sk;
if (!DecryptSaplingSpendingKey(vMasterKeyIn, vchCryptedSecret, fvk, sk)) if (!DecryptSaplingSpendingKey(vMasterKeyIn, vchCryptedSecret, fvk, sk))
{ {
keyFail = true; keyFail = true;
@ -261,6 +287,73 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
return true; return true;
} }
bool CCryptoKeyStore::SetHDSeed(const HDSeed& seed)
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted()) {
return CBasicKeyStore::SetHDSeed(seed);
}
if (IsLocked())
return false;
std::vector<unsigned char> vchCryptedSecret;
// Use seed's fingerprint as IV
// TODO: Handle this properly when we make encryption a supported feature
auto seedFp = seed.Fingerprint();
if (!EncryptSecret(vMasterKey, seed.RawSeed(), seedFp, vchCryptedSecret))
return false;
// This will call into CWallet to store the crypted seed to disk
if (!SetCryptedHDSeed(seedFp, vchCryptedSecret))
return false;
}
return true;
}
bool CCryptoKeyStore::SetCryptedHDSeed(
const uint256& seedFp,
const std::vector<unsigned char>& vchCryptedSecret)
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted()) {
return false;
}
if (!cryptedHDSeed.first.IsNull()) {
// Don't allow an existing seed to be changed. We can maybe relax this
// restriction later once we have worked out the UX implications.
return false;
}
cryptedHDSeed = std::make_pair(seedFp, vchCryptedSecret);
}
return true;
}
bool CCryptoKeyStore::HaveHDSeed() const
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::HaveHDSeed();
return !cryptedHDSeed.second.empty();
}
bool CCryptoKeyStore::GetHDSeed(HDSeed& seedOut) const
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::GetHDSeed(seedOut);
if (cryptedHDSeed.second.empty())
return false;
return DecryptHDSeed(vMasterKey, cryptedHDSeed.second, cryptedHDSeed.first, seedOut);
}
bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{ {
{ {
@ -355,7 +448,7 @@ bool CCryptoKeyStore::AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk
} }
bool CCryptoKeyStore::AddSaplingSpendingKey( bool CCryptoKeyStore::AddSaplingSpendingKey(
const libzcash::SaplingSpendingKey &sk, const libzcash::SaplingExtendedSpendingKey &sk,
const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr) const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr)
{ {
{ {
@ -372,7 +465,7 @@ bool CCryptoKeyStore::AddSaplingSpendingKey(
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << sk; ss << sk;
CKeyingMaterial vchSecret(ss.begin(), ss.end()); CKeyingMaterial vchSecret(ss.begin(), ss.end());
auto fvk = sk.full_viewing_key(); auto fvk = sk.expsk.full_viewing_key();
if (!EncryptSecret(vMasterKey, vchSecret, fvk.GetFingerprint(), vchCryptedSecret)) { if (!EncryptSecret(vMasterKey, vchSecret, fvk.GetFingerprint(), vchCryptedSecret)) {
return false; return false;
} }
@ -438,7 +531,7 @@ bool CCryptoKeyStore::GetSproutSpendingKey(const libzcash::SproutPaymentAddress
return false; return false;
} }
bool CCryptoKeyStore::GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingSpendingKey &skOut) const bool CCryptoKeyStore::GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingExtendedSpendingKey &skOut) const
{ {
{ {
LOCK(cs_SpendingKeyStore); LOCK(cs_SpendingKeyStore);
@ -463,6 +556,22 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
return false; return false;
fUseCrypto = true; fUseCrypto = true;
if (!hdSeed.IsNull()) {
{
std::vector<unsigned char> vchCryptedSecret;
// Use seed's fingerprint as IV
// TODO: Handle this properly when we make encryption a supported feature
auto seedFp = hdSeed.Fingerprint();
if (!EncryptSecret(vMasterKeyIn, hdSeed.RawSeed(), seedFp, vchCryptedSecret)) {
return false;
}
// This will call into CWallet to store the crypted seed to disk
if (!SetCryptedHDSeed(seedFp, vchCryptedSecret)) {
return false;
}
}
hdSeed = HDSeed();
}
BOOST_FOREACH(KeyMap::value_type& mKey, mapKeys) BOOST_FOREACH(KeyMap::value_type& mKey, mapKeys)
{ {
const CKey &key = mKey.second; const CKey &key = mKey.second;
@ -496,11 +605,11 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
//! Sapling key support //! Sapling key support
BOOST_FOREACH(SaplingSpendingKeyMap::value_type& mSaplingSpendingKey, mapSaplingSpendingKeys) BOOST_FOREACH(SaplingSpendingKeyMap::value_type& mSaplingSpendingKey, mapSaplingSpendingKeys)
{ {
const libzcash::SaplingSpendingKey &sk = mSaplingSpendingKey.second; const auto &sk = mSaplingSpendingKey.second;
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << sk; ss << sk;
CKeyingMaterial vchSecret(ss.begin(), ss.end()); CKeyingMaterial vchSecret(ss.begin(), ss.end());
libzcash::SaplingFullViewingKey fvk = sk.full_viewing_key(); libzcash::SaplingFullViewingKey fvk = sk.expsk.full_viewing_key();
std::vector<unsigned char> vchCryptedSecret; std::vector<unsigned char> vchCryptedSecret;
if (!EncryptSecret(vMasterKeyIn, vchSecret, fvk.GetFingerprint(), vchCryptedSecret)) { if (!EncryptSecret(vMasterKeyIn, vchSecret, fvk.GetFingerprint(), vchCryptedSecret)) {
return false; return false;

View File

@ -10,6 +10,7 @@
#include "streams.h" #include "streams.h"
#include "support/allocators/secure.h" #include "support/allocators/secure.h"
#include "zcash/Address.hpp" #include "zcash/Address.hpp"
#include "zcash/zip32.h"
class uint256; class uint256;
@ -127,6 +128,7 @@ public:
class CCryptoKeyStore : public CBasicKeyStore class CCryptoKeyStore : public CBasicKeyStore
{ {
private: private:
std::pair<uint256, std::vector<unsigned char>> cryptedHDSeed;
CryptedKeyMap mapCryptedKeys; CryptedKeyMap mapCryptedKeys;
CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys; CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys;
CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys; CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys;
@ -172,6 +174,11 @@ public:
bool Lock(); bool Lock();
virtual bool SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
bool SetHDSeed(const HDSeed& seed);
bool HaveHDSeed() const;
bool GetHDSeed(HDSeed& seedOut) const;
virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
bool HaveKey(const CKeyID &address) const bool HaveKey(const CKeyID &address) const
@ -238,7 +245,7 @@ public:
const std::vector<unsigned char> &vchCryptedSecret, const std::vector<unsigned char> &vchCryptedSecret,
const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none); const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none);
bool AddSaplingSpendingKey( bool AddSaplingSpendingKey(
const libzcash::SaplingSpendingKey &sk, const libzcash::SaplingExtendedSpendingKey &sk,
const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none); const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none);
bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const
{ {
@ -250,7 +257,7 @@ public:
} }
return false; return false;
} }
bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingSpendingKey &skOut) const; bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingExtendedSpendingKey &skOut) const;
/** /**

View File

@ -363,11 +363,13 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
TestWallet wallet; TestWallet wallet;
auto sk = libzcash::SaplingSpendingKey::random(); std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto expsk = sk.expanded_spending_key(); HDSeed seed(rawSeed);
auto fvk = sk.full_viewing_key(); auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto pk = sk.default_address(); auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key(); auto ivk = fvk.in_viewing_key();
auto pk = sk.DefaultAddress();
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000);
auto cm = note.cm().get(); auto cm = note.cm().get();
@ -484,10 +486,12 @@ TEST(WalletTests, FindMySaplingNotes) {
TestWallet wallet; TestWallet wallet;
// Generate dummy Sapling address // Generate dummy Sapling address
auto sk = libzcash::SaplingSpendingKey::random(); std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto expsk = sk.expanded_spending_key(); HDSeed seed(rawSeed);
auto fvk = sk.full_viewing_key(); auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto pk = sk.default_address(); auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key();
auto pk = sk.DefaultAddress();
// Generate dummy Sapling note // Generate dummy Sapling note
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000);
@ -618,11 +622,13 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
TestWallet wallet; TestWallet wallet;
// Generate Sapling address // Generate Sapling address
auto sk = libzcash::SaplingSpendingKey::random(); std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto expsk = sk.expanded_spending_key(); HDSeed seed(rawSeed);
auto fvk = sk.full_viewing_key(); auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key(); auto ivk = fvk.in_viewing_key();
auto pk = sk.default_address(); auto pk = sk.DefaultAddress();
ASSERT_TRUE(wallet.AddSaplingZKey(sk)); ASSERT_TRUE(wallet.AddSaplingZKey(sk));
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(fvk)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(fvk));
@ -785,10 +791,12 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
TestWallet wallet; TestWallet wallet;
// Generate dummy Sapling address // Generate dummy Sapling address
auto sk = libzcash::SaplingSpendingKey::random(); std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto expsk = sk.expanded_spending_key(); HDSeed seed(rawSeed);
auto fvk = sk.full_viewing_key(); auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto pk = sk.default_address(); auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key();
auto pk = sk.DefaultAddress();
// Generate dummy Sapling note // Generate dummy Sapling note
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000);
@ -880,10 +888,12 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
TestWallet wallet; TestWallet wallet;
// Generate dummy Sapling address // Generate dummy Sapling address
auto sk = libzcash::SaplingSpendingKey::random(); std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto expsk = sk.expanded_spending_key(); HDSeed seed(rawSeed);
auto fvk = sk.full_viewing_key(); auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto pk = sk.default_address(); auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key();
auto pk = sk.DefaultAddress();
// Generate dummy Sapling note // Generate dummy Sapling note
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000);
@ -1013,11 +1023,13 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
TestWallet wallet; TestWallet wallet;
// Generate Sapling address // Generate Sapling address
auto sk = libzcash::SaplingSpendingKey::random(); std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto expsk = sk.expanded_spending_key(); HDSeed seed(rawSeed);
auto fvk = sk.full_viewing_key(); auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key(); auto ivk = fvk.in_viewing_key();
auto pk = sk.default_address(); auto pk = sk.DefaultAddress();
// Generate Sapling note A // Generate Sapling note A
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000);
@ -1705,16 +1717,21 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
TestWallet wallet; TestWallet wallet;
std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
HDSeed seed(rawSeed);
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
// Generate dummy Sapling address // Generate dummy Sapling address
auto sk = libzcash::SaplingSpendingKey::random(); auto sk = m.Derive(0);
auto expsk = sk.expanded_spending_key(); auto expsk = sk.expsk;
auto fvk = sk.full_viewing_key(); auto fvk = expsk.full_viewing_key();
auto pk = sk.default_address(); auto pk = sk.DefaultAddress();
// Generate dummy recipient Sapling address // Generate dummy recipient Sapling address
auto sk2 = libzcash::SaplingSpendingKey::random(); auto sk2 = m.Derive(1);
auto fvk2 = sk2.full_viewing_key(); auto expsk2 = sk2.expsk;
auto pk2 = sk2.default_address(); auto fvk2 = expsk2.full_viewing_key();
auto pk2 = sk2.DefaultAddress();
// Generate dummy Sapling note // Generate dummy Sapling note
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000);
@ -1856,11 +1873,13 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
TestWallet wallet; TestWallet wallet;
// Generate Sapling address // Generate Sapling address
auto sk = libzcash::SaplingSpendingKey::random(); std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
auto expsk = sk.expanded_spending_key(); HDSeed seed(rawSeed);
auto fvk = sk.full_viewing_key(); auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key(); auto ivk = fvk.in_viewing_key();
auto pk = sk.default_address(); auto pk = sk.DefaultAddress();
ASSERT_TRUE(wallet.AddSaplingZKey(sk)); ASSERT_TRUE(wallet.AddSaplingZKey(sk));
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(fvk)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(fvk));

View File

@ -21,8 +21,18 @@ TEST(wallet_zkeys_tests, store_and_load_sapling_zkeys) {
wallet.GetSaplingPaymentAddresses(addrs); wallet.GetSaplingPaymentAddresses(addrs);
ASSERT_EQ(0, addrs.size()); ASSERT_EQ(0, addrs.size());
// wallet should have one key // No HD seed in the wallet
EXPECT_ANY_THROW(wallet.GenerateNewSaplingZKey());
// Load the all-zeroes seed
CKeyingMaterial rawSeed(32, 0);
HDSeed seed(rawSeed);
wallet.LoadHDSeed(seed);
// Now this call succeeds
auto address = wallet.GenerateNewSaplingZKey(); auto address = wallet.GenerateNewSaplingZKey();
// wallet should have one key
wallet.GetSaplingPaymentAddresses(addrs); wallet.GetSaplingPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size()); ASSERT_EQ(1, addrs.size());
@ -30,15 +40,16 @@ TEST(wallet_zkeys_tests, store_and_load_sapling_zkeys) {
ASSERT_TRUE(wallet.HaveSaplingIncomingViewingKey(address)); ASSERT_TRUE(wallet.HaveSaplingIncomingViewingKey(address));
// manually add new spending key to wallet // manually add new spending key to wallet
auto sk = libzcash::SaplingSpendingKey::random(); auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
ASSERT_TRUE(wallet.AddSaplingZKey(sk, sk.default_address())); auto sk = m.Derive(0);
ASSERT_TRUE(wallet.AddSaplingZKey(sk, sk.DefaultAddress()));
// verify wallet did add it // verify wallet did add it
auto fvk = sk.full_viewing_key(); auto fvk = sk.expsk.full_viewing_key();
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(fvk)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(fvk));
// verify spending key stored correctly // verify spending key stored correctly
libzcash::SaplingSpendingKey keyOut; libzcash::SaplingExtendedSpendingKey keyOut;
wallet.GetSaplingSpendingKey(fvk, keyOut); wallet.GetSaplingSpendingKey(fvk, keyOut);
ASSERT_EQ(sk, keyOut); ASSERT_EQ(sk, keyOut);
@ -46,7 +57,7 @@ TEST(wallet_zkeys_tests, store_and_load_sapling_zkeys) {
wallet.GetSaplingPaymentAddresses(addrs); wallet.GetSaplingPaymentAddresses(addrs);
EXPECT_EQ(2, addrs.size()); EXPECT_EQ(2, addrs.size());
EXPECT_EQ(1, addrs.count(address)); EXPECT_EQ(1, addrs.count(address));
EXPECT_EQ(1, addrs.count(sk.default_address())); EXPECT_EQ(1, addrs.count(sk.DefaultAddress()));
} }
/** /**
@ -277,6 +288,7 @@ TEST(wallet_zkeys_tests, WriteViewingKeyDirectToDB) {
/** /**
* This test covers methods on CWalletDB to load/save crypted z keys. * This test covers methods on CWalletDB to load/save crypted z keys.
*/ */
/* TODO: Uncomment during PR for #3388
TEST(wallet_zkeys_tests, write_cryptedzkey_direct_to_db) { TEST(wallet_zkeys_tests, write_cryptedzkey_direct_to_db) {
SelectParams(CBaseChainParams::TESTNET); SelectParams(CBaseChainParams::TESTNET);
@ -351,4 +363,5 @@ TEST(wallet_zkeys_tests, write_cryptedzkey_direct_to_db) {
wallet2.GetSproutSpendingKey(paymentAddress2, keyOut); wallet2.GetSproutSpendingKey(paymentAddress2, keyOut);
ASSERT_EQ(paymentAddress2, keyOut.address()); ASSERT_EQ(paymentAddress2, keyOut.address());
} }
*/

View File

@ -554,8 +554,10 @@ class AddSpendingKeyToWallet : public boost::static_visitor<bool>
{ {
private: private:
CWallet *m_wallet; CWallet *m_wallet;
const Consensus::Params &params;
public: public:
AddSpendingKeyToWallet(CWallet *wallet) : m_wallet(wallet) {} AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params &params) :
m_wallet(wallet), params(params) {}
bool operator()(const libzcash::SproutSpendingKey &sk) const { bool operator()(const libzcash::SproutSpendingKey &sk) const {
auto addr = sk.address(); auto addr = sk.address();
@ -574,23 +576,30 @@ public:
return false; return false;
} }
} }
bool operator()(const libzcash::SaplingSpendingKey &sk) const { bool operator()(const libzcash::SaplingExtendedSpendingKey &sk) const {
auto fvk = sk.full_viewing_key(); auto fvk = sk.expsk.full_viewing_key();
auto addr = sk.default_address(); auto ivk = fvk.in_viewing_key();
auto addr = sk.DefaultAddress();
{ {
// Don't throw error in case a key is already there // Don't throw error in case a key is already there
if (m_wallet->HaveSaplingSpendingKey(fvk)) { if (m_wallet->HaveSaplingSpendingKey(fvk)) {
return true; return true;
} else { } else {
m_wallet->MarkDirty(); m_wallet->MarkDirty();
if (!m_wallet-> AddSaplingZKey(sk, addr)) { if (!m_wallet-> AddSaplingZKey(sk, addr)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet");
} }
m_wallet->mapSaplingZKeyMetadata[addr].nCreateTime = 1; // 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 = 1;
} else {
// Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1540512000;
}
return false; return false;
} }
} }
@ -672,9 +681,10 @@ UniValue z_importkey(const UniValue& params, bool fHelp)
if (!IsValidSpendingKey(spendingkey)) { if (!IsValidSpendingKey(spendingkey)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key");
} }
// Sapling support // Sapling support
auto keyAlreadyExists = boost::apply_visitor(AddSpendingKeyToWallet(pwalletMain), spendingkey); auto keyAlreadyExists = boost::apply_visitor(
AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey);
if (keyAlreadyExists && fIgnoreExistingKey) { if (keyAlreadyExists && fIgnoreExistingKey) {
return NullUniValue; return NullUniValue;
} }

View File

@ -21,6 +21,7 @@
#include "primitives/transaction.h" #include "primitives/transaction.h"
#include "zcbenchmarks.h" #include "zcbenchmarks.h"
#include "script/interpreter.h" #include "script/interpreter.h"
#include "zcash/zip32.h"
#include "utiltime.h" #include "utiltime.h"
#include "asyncrpcoperation.h" #include "asyncrpcoperation.h"

View File

@ -20,6 +20,7 @@
#include "utilmoneystr.h" #include "utilmoneystr.h"
#include "zcash/Note.hpp" #include "zcash/Note.hpp"
#include "crypter.h" #include "crypter.h"
#include "zcash/zip32.h"
#include <assert.h> #include <assert.h>
@ -104,21 +105,42 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey()
SaplingPaymentAddress CWallet::GenerateNewSaplingZKey() SaplingPaymentAddress CWallet::GenerateNewSaplingZKey()
{ {
AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata
auto sk = SaplingSpendingKey::random();
auto fvk = sk.full_viewing_key();
auto addr = sk.default_address();
// Check for collision, even though it is unlikely to ever occur
if (CCryptoKeyStore::HaveSaplingSpendingKey(fvk)) {
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Collision detected");
}
// Create new metadata // Create new metadata
int64_t nCreationTime = GetTime(); int64_t nCreationTime = GetTime();
mapSaplingZKeyMetadata[addr] = CKeyMetadata(nCreationTime); CKeyMetadata metadata(nCreationTime);
if (!AddSaplingZKey(sk, addr)) { // Try to get the seed
HDSeed seed;
if (!GetHDSeed(seed))
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found");
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
// We use a fixed keypath scheme of m/32'/coin_type'/account'
// Derive m/32'
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
// Derive m/32'/coin_type'
auto m_32h_cth = m_32h.Derive(Params().BIP44CoinType() | ZIP32_HARDENED_KEY_LIMIT);
// Derive account key at next index, skip keys already known to the wallet
libzcash::SaplingExtendedSpendingKey xsk;
do
{
xsk = m_32h_cth.Derive(hdChain.saplingAccountCounter | ZIP32_HARDENED_KEY_LIMIT);
// Increment childkey index
hdChain.saplingAccountCounter++;
} while (HaveSaplingSpendingKey(xsk.expsk.full_viewing_key()));
// Update the chain model in the database
if (fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(hdChain))
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Writing HD chain model failed");
auto ivk = xsk.expsk.full_viewing_key().in_viewing_key();
mapSaplingZKeyMetadata[ivk] = metadata;
auto addr = xsk.DefaultAddress();
if (!AddSaplingZKey(xsk, addr)) {
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): AddSaplingZKey failed"); throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): AddSaplingZKey failed");
} }
// return default sapling payment address. // return default sapling payment address.
@ -127,7 +149,7 @@ SaplingPaymentAddress CWallet::GenerateNewSaplingZKey()
// Add spending key to keystore // Add spending key to keystore
bool CWallet::AddSaplingZKey( bool CWallet::AddSaplingZKey(
const libzcash::SaplingSpendingKey &sk, const libzcash::SaplingExtendedSpendingKey &sk,
const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr) const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr)
{ {
AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata
@ -1865,6 +1887,98 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
return nChange; return nChange;
} }
bool CWallet::IsHDFullyEnabled() const
{
// Only Sapling addresses are HD for now
return false;
}
void CWallet::GenerateNewSeed()
{
LOCK(cs_wallet);
auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH);
int64_t nCreationTime = GetTime();
// If the wallet is encrypted and locked, this will fail.
if (!SetHDSeed(seed))
throw std::runtime_error(std::string(__func__) + ": SetHDSeed failed");
// store the key creation time together with
// the child index counter in the database
// as a hdchain object
CHDChain newHdChain;
newHdChain.nVersion = CHDChain::VERSION_HD_BASE;
newHdChain.seedFp = seed.Fingerprint();
newHdChain.nCreateTime = nCreationTime;
SetHDChain(newHdChain, false);
}
bool CWallet::SetHDSeed(const HDSeed& seed)
{
if (!CCryptoKeyStore::SetHDSeed(seed)) {
return false;
}
if (!fFileBacked) {
return true;
}
/* TODO: Uncomment during PR for #3388
{
LOCK(cs_wallet);
if (!IsCrypted()) {
return CWalletDB(strWalletFile).WriteHDSeed(seed);
}
}
*/
return true;
}
bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret)
{
if (!CCryptoKeyStore::SetCryptedHDSeed(seedFp, vchCryptedSecret)) {
return false;
}
if (!fFileBacked) {
return true;
}
/* TODO: Uncomment during PR for #3388
{
LOCK(cs_wallet);
if (pwalletdbEncryption)
return pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret);
else
return CWalletDB(strWalletFile).WriteCryptedHDSeed(seedFp, vchCryptedSecret);
}
*/
return false;
}
void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
{
LOCK(cs_wallet);
/* TODO: Uncomment during PR for #3388
if (!memonly && fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(chain))
throw std::runtime_error(std::string(__func__) + ": writing chain failed");
*/
hdChain = chain;
}
bool CWallet::LoadHDSeed(const HDSeed& seed)
{
return CBasicKeyStore::SetHDSeed(seed);
}
bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed)
{
return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed);
}
void CWalletTx::SetSproutNoteData(mapSproutNoteData_t &noteData) void CWalletTx::SetSproutNoteData(mapSproutNoteData_t &noteData)
{ {
mapSproutNoteData.clear(); mapSproutNoteData.clear();
@ -4363,8 +4477,8 @@ bool PaymentAddressBelongsToWallet::operator()(const libzcash::SaplingPaymentAdd
{ {
libzcash::SaplingIncomingViewingKey ivk; libzcash::SaplingIncomingViewingKey ivk;
// If we have a SaplingSpendingKey or SaplingExpandedSpendingKey in the // If we have a SaplingExtendedSpendingKey in the wallet, then we will
// wallet, then we will also have the corresponding SaplingFullViewingKey. // also have the corresponding SaplingFullViewingKey.
return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) && return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) &&
m_wallet->HaveSaplingFullViewingKey(ivk); m_wallet->HaveSaplingFullViewingKey(ivk);
} }
@ -4410,7 +4524,7 @@ boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator
{ {
libzcash::SaplingIncomingViewingKey ivk; libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingFullViewingKey fvk; libzcash::SaplingFullViewingKey fvk;
libzcash::SaplingSpendingKey sk; libzcash::SaplingExtendedSpendingKey sk;
if (m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) && if (m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) &&
m_wallet->GetSaplingFullViewingKey(ivk, fvk) && m_wallet->GetSaplingFullViewingKey(ivk, fvk) &&

View File

@ -23,6 +23,7 @@
#include "wallet/walletdb.h" #include "wallet/walletdb.h"
#include "wallet/rpcwallet.h" #include "wallet/rpcwallet.h"
#include "zcash/Address.hpp" #include "zcash/Address.hpp"
#include "zcash/zip32.h"
#include "base58.h" #include "base58.h"
#include <algorithm> #include <algorithm>
@ -61,6 +62,9 @@ static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
// unless there is some exceptional network disruption. // unless there is some exceptional network disruption.
static const unsigned int WITNESS_CACHE_SIZE = MAX_REORG_LENGTH + 1; static const unsigned int WITNESS_CACHE_SIZE = MAX_REORG_LENGTH + 1;
//! Size of HD seed in bytes
static const size_t HD_WALLET_SEED_LENGTH = 32;
class CBlockIndex; class CBlockIndex;
class CCoinControl; class CCoinControl;
class COutput; class COutput;
@ -823,6 +827,9 @@ protected:
bool UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx); bool UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx);
void MarkAffectedTransactionsDirty(const CTransaction& tx); void MarkAffectedTransactionsDirty(const CTransaction& tx);
/* the hd chain data model (chain counters) */
CHDChain hdChain;
public: public:
/* /*
* Main wallet lock. * Main wallet lock.
@ -839,7 +846,7 @@ public:
std::set<int64_t> setKeyPool; std::set<int64_t> setKeyPool;
std::map<CKeyID, CKeyMetadata> mapKeyMetadata; std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapZKeyMetadata; std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapZKeyMetadata;
std::map<libzcash::SaplingPaymentAddress, CKeyMetadata> mapSaplingZKeyMetadata; std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap; typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys; MasterKeyMap mapMasterKeys;
@ -1045,7 +1052,7 @@ public:
libzcash::SaplingPaymentAddress GenerateNewSaplingZKey(); libzcash::SaplingPaymentAddress GenerateNewSaplingZKey();
//! Adds Sapling spending key to the store, and saves it to disk //! Adds Sapling spending key to the store, and saves it to disk
bool AddSaplingZKey( bool AddSaplingZKey(
const libzcash::SaplingSpendingKey &key, const libzcash::SaplingExtendedSpendingKey &key,
const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none); const boost::optional<libzcash::SaplingPaymentAddress> &defaultAddr = boost::none);
bool AddCryptedSaplingSpendingKey( bool AddCryptedSaplingSpendingKey(
const libzcash::SaplingFullViewingKey &fvk, const libzcash::SaplingFullViewingKey &fvk,
@ -1222,6 +1229,27 @@ public:
bool GetBroadcastTransactions() const { return fBroadcastTransactions; } bool GetBroadcastTransactions() const { return fBroadcastTransactions; }
/** Set whether this wallet broadcasts transactions. */ /** Set whether this wallet broadcasts transactions. */
void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; } void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; }
/* Returns true if HD is enabled for all address types, false if only for Sapling */
bool IsHDFullyEnabled() const;
/* Generates a new HD seed (will reset the chain child index counters)
Sets the seed's version based on the current wallet version (so the
caller must ensure the current wallet version is correct before calling
this function). */
void GenerateNewSeed();
bool SetHDSeed(const HDSeed& seed);
bool SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
/* Set the HD chain model (chain child index counters) */
void SetHDChain(const CHDChain& chain, bool memonly);
const CHDChain& GetHDChain() const { return hdChain; }
/* Set the current HD seed, without saving it to disk (used by LoadWallet) */
bool LoadHDSeed(const HDSeed& key);
/* Set the current encrypted HD seed, without saving it to disk (used by LoadWallet) */
bool LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed);
/* Find notes filtered by payment address, min depth, ability to spend */ /* Find notes filtered by payment address, min depth, ability to spend */
void GetFilteredNotes(std::vector<CSproutNotePlaintextEntry>& sproutEntries, void GetFilteredNotes(std::vector<CSproutNotePlaintextEntry>& sproutEntries,

View File

@ -708,6 +708,45 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
{ {
ssValue >> pwallet->nWitnessCacheSize; ssValue >> pwallet->nWitnessCacheSize;
} }
else if (strType == "hdseed")
{
uint256 seedFp;
RawHDSeed rawSeed;
ssKey >> seedFp;
ssValue >> rawSeed;
HDSeed seed(rawSeed);
if (seed.Fingerprint() != seedFp)
{
strErr = "Error reading wallet database: HDSeed corrupt";
return false;
}
if (!pwallet->LoadHDSeed(seed))
{
strErr = "Error reading wallet database: LoadHDSeed failed";
return false;
}
}
else if (strType == "chdseed")
{
uint256 seedFp;
vector<unsigned char> vchCryptedSecret;
ssKey >> seedFp;
ssValue >> vchCryptedSecret;
if (!pwallet->LoadCryptedHDSeed(seedFp, vchCryptedSecret))
{
strErr = "Error reading wallet database: LoadCryptedSeed failed";
return false;
}
wss.fIsEncrypted = true;
}
else if (strType == "hdchain")
{
CHDChain chain;
ssValue >> chain;
pwallet->SetHDChain(chain, true);
}
} catch (...) } catch (...)
{ {
return false; return false;
@ -718,6 +757,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
static bool IsKeyType(string strType) static bool IsKeyType(string strType)
{ {
return (strType== "key" || strType == "wkey" || return (strType== "key" || strType == "wkey" ||
strType == "hdseed" || strType == "chdseed" ||
strType == "zkey" || strType == "czkey" || strType == "zkey" || strType == "czkey" ||
strType == "vkey" || strType == "vkey" ||
strType == "mkey" || strType == "ckey"); strType == "mkey" || strType == "ckey");
@ -1103,3 +1143,22 @@ bool CWalletDB::EraseDestData(const std::string &address, const std::string &key
nWalletDBUpdated++; nWalletDBUpdated++;
return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key))); return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
} }
bool CWalletDB::WriteHDSeed(const HDSeed& seed)
{
nWalletDBUpdated++;
return Write(std::make_pair(std::string("hdseed"), seed.Fingerprint()), seed.RawSeed());
}
bool CWalletDB::WriteCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& vchCryptedSecret)
{
nWalletDBUpdated++;
return Write(std::make_pair(std::string("chdseed"), seedFp), vchCryptedSecret);
}
bool CWalletDB::WriteHDChain(const CHDChain& chain)
{
nWalletDBUpdated++;
return Write(std::string("hdchain"), chain);
}

View File

@ -11,6 +11,7 @@
#include "key.h" #include "key.h"
#include "keystore.h" #include "keystore.h"
#include "zcash/Address.hpp" #include "zcash/Address.hpp"
#include "zcash/zip32.h"
#include <list> #include <list>
#include <stdint.h> #include <stdint.h>
@ -40,6 +41,39 @@ enum DBErrors
DB_NEED_REWRITE DB_NEED_REWRITE
}; };
/* simple hd chain data model */
class CHDChain
{
public:
static const int VERSION_HD_BASE = 1;
static const int CURRENT_VERSION = VERSION_HD_BASE;
int nVersion;
uint256 seedFp;
int64_t nCreateTime; // 0 means unknown
uint32_t saplingAccountCounter;
CHDChain() { SetNull(); }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action)
{
READWRITE(nVersion);
READWRITE(seedFp);
READWRITE(nCreateTime);
READWRITE(saplingAccountCounter);
}
void SetNull()
{
nVersion = CHDChain::CURRENT_VERSION;
seedFp.SetNull();
nCreateTime = 0;
saplingAccountCounter = 0;
}
};
class CKeyMetadata class CKeyMetadata
{ {
public: public:
@ -132,6 +166,11 @@ public:
static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys); static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys);
static bool Recover(CDBEnv& dbenv, const std::string& filename); static bool Recover(CDBEnv& dbenv, const std::string& filename);
bool WriteHDSeed(const HDSeed& seed);
bool WriteCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& vchCryptedSecret);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
/// Write spending key to wallet database, where key is payment address and value is spending key. /// Write spending key to wallet database, where key is payment address and value is spending key.
bool WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta); bool WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta);
bool WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr, bool WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr,

View File

@ -118,7 +118,3 @@ bool IsValidPaymentAddress(const libzcash::PaymentAddress& zaddr) {
bool IsValidViewingKey(const libzcash::ViewingKey& vk) { bool IsValidViewingKey(const libzcash::ViewingKey& vk) {
return vk.which() != 0; return vk.which() != 0;
} }
bool IsValidSpendingKey(const libzcash::SpendingKey& zkey) {
return zkey.which() != 0;
}

View File

@ -19,6 +19,9 @@ const size_t SerializedSproutPaymentAddressSize = 64;
const size_t SerializedSproutViewingKeySize = 64; const size_t SerializedSproutViewingKeySize = 64;
const size_t SerializedSproutSpendingKeySize = 32; const size_t SerializedSproutSpendingKeySize = 32;
const size_t SerializedSaplingPaymentAddressSize = 43;
const size_t SerializedSaplingFullViewingKeySize = 96;
const size_t SerializedSaplingExpandedSpendingKeySize = 96;
const size_t SerializedSaplingSpendingKeySize = 32; const size_t SerializedSaplingSpendingKeySize = 32;
typedef std::array<unsigned char, ZC_DIVERSIFIER_SIZE> diversifier_t; typedef std::array<unsigned char, ZC_DIVERSIFIER_SIZE> diversifier_t;
@ -217,7 +220,6 @@ public:
typedef boost::variant<InvalidEncoding, SproutPaymentAddress, SaplingPaymentAddress> PaymentAddress; typedef boost::variant<InvalidEncoding, SproutPaymentAddress, SaplingPaymentAddress> PaymentAddress;
typedef boost::variant<InvalidEncoding, SproutViewingKey> ViewingKey; typedef boost::variant<InvalidEncoding, SproutViewingKey> ViewingKey;
typedef boost::variant<InvalidEncoding, SproutSpendingKey, SaplingSpendingKey> SpendingKey;
} }
@ -227,7 +229,4 @@ bool IsValidPaymentAddress(const libzcash::PaymentAddress& zaddr);
/** Check whether a ViewingKey is not an InvalidEncoding. */ /** Check whether a ViewingKey is not an InvalidEncoding. */
bool IsValidViewingKey(const libzcash::ViewingKey& vk); bool IsValidViewingKey(const libzcash::ViewingKey& vk);
/** Check whether a SpendingKey is not an InvalidEncoding. */
bool IsValidSpendingKey(const libzcash::SpendingKey& zkey);
#endif // ZC_ADDRESS_H_ #endif // ZC_ADDRESS_H_

143
src/zcash/zip32.cpp Normal file
View File

@ -0,0 +1,143 @@
// Copyright (c) 2018 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "zip32.h"
#include "hash.h"
#include "random.h"
#include "streams.h"
#include "version.h"
#include <librustzcash.h>
#include <sodium.h>
const unsigned char ZCASH_HD_SEED_FP_PERSONAL[crypto_generichash_blake2b_PERSONALBYTES] =
{'Z', 'c', 'a', 's', 'h', '_', 'H', 'D', '_', 'S', 'e', 'e', 'd', '_', 'F', 'P'};
HDSeed HDSeed::Random(size_t len)
{
assert(len >= 32);
RawHDSeed rawSeed(len, 0);
GetRandBytes(rawSeed.data(), len);
return HDSeed(rawSeed);
}
uint256 HDSeed::Fingerprint() const
{
CBLAKE2bWriter h(SER_GETHASH, 0, ZCASH_HD_SEED_FP_PERSONAL);
h << seed;
return h.GetHash();
}
namespace libzcash {
boost::optional<SaplingExtendedFullViewingKey> SaplingExtendedFullViewingKey::Derive(uint32_t i) const
{
CDataStream ss_p(SER_NETWORK, PROTOCOL_VERSION);
ss_p << *this;
CSerializeData p_bytes(ss_p.begin(), ss_p.end());
CSerializeData i_bytes(ZIP32_XFVK_SIZE);
if (librustzcash_zip32_xfvk_derive(
reinterpret_cast<unsigned char*>(p_bytes.data()),
i,
reinterpret_cast<unsigned char*>(i_bytes.data())
)) {
CDataStream ss_i(i_bytes, SER_NETWORK, PROTOCOL_VERSION);
SaplingExtendedFullViewingKey xfvk_i;
ss_i >> xfvk_i;
return xfvk_i;
} else {
return boost::none;
}
}
boost::optional<std::pair<diversifier_index_t, libzcash::SaplingPaymentAddress>>
SaplingExtendedFullViewingKey::Address(diversifier_index_t j) const
{
CDataStream ss_xfvk(SER_NETWORK, PROTOCOL_VERSION);
ss_xfvk << *this;
CSerializeData xfvk_bytes(ss_xfvk.begin(), ss_xfvk.end());
diversifier_index_t j_ret;
CSerializeData addr_bytes(libzcash::SerializedSaplingPaymentAddressSize);
if (librustzcash_zip32_xfvk_address(
reinterpret_cast<unsigned char*>(xfvk_bytes.data()),
j.begin(), j_ret.begin(),
reinterpret_cast<unsigned char*>(addr_bytes.data()))) {
CDataStream ss_addr(addr_bytes, SER_NETWORK, PROTOCOL_VERSION);
libzcash::SaplingPaymentAddress addr;
ss_addr >> addr;
return std::make_pair(j_ret, addr);
} else {
return boost::none;
}
}
libzcash::SaplingPaymentAddress SaplingExtendedFullViewingKey::DefaultAddress() const
{
diversifier_index_t j0;
auto addr = Address(j0);
// If we can't obtain a default address, we are *very* unlucky...
if (!addr) {
throw std::runtime_error("SaplingExtendedFullViewingKey::DefaultAddress(): No valid diversifiers out of 2^88!");
}
return addr.get().second;
}
SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Master(const HDSeed& seed)
{
auto rawSeed = seed.RawSeed();
CSerializeData m_bytes(ZIP32_XSK_SIZE);
librustzcash_zip32_xsk_master(
rawSeed.data(),
rawSeed.size(),
reinterpret_cast<unsigned char*>(m_bytes.data()));
CDataStream ss(m_bytes, SER_NETWORK, PROTOCOL_VERSION);
SaplingExtendedSpendingKey xsk_m;
ss >> xsk_m;
return xsk_m;
}
SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Derive(uint32_t i) const
{
CDataStream ss_p(SER_NETWORK, PROTOCOL_VERSION);
ss_p << *this;
CSerializeData p_bytes(ss_p.begin(), ss_p.end());
CSerializeData i_bytes(ZIP32_XSK_SIZE);
librustzcash_zip32_xsk_derive(
reinterpret_cast<unsigned char*>(p_bytes.data()),
i,
reinterpret_cast<unsigned char*>(i_bytes.data()));
CDataStream ss_i(i_bytes, SER_NETWORK, PROTOCOL_VERSION);
SaplingExtendedSpendingKey xsk_i;
ss_i >> xsk_i;
return xsk_i;
}
SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
{
SaplingExtendedFullViewingKey ret;
ret.depth = depth;
ret.parentFVKTag = parentFVKTag;
ret.childIndex = childIndex;
ret.chaincode = chaincode;
ret.fvk = expsk.full_viewing_key();
ret.dk = dk;
return ret;
}
libzcash::SaplingPaymentAddress SaplingExtendedSpendingKey::DefaultAddress() const
{
return ToXFVK().DefaultAddress();
}
}
bool IsValidSpendingKey(const libzcash::SpendingKey& zkey) {
return zkey.which() != 0;
}

126
src/zcash/zip32.h Normal file
View File

@ -0,0 +1,126 @@
// Copyright (c) 2018 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef ZCASH_ZIP32_H
#define ZCASH_ZIP32_H
#include "serialize.h"
#include "support/allocators/secure.h"
#include "uint256.h"
#include "zcash/Address.hpp"
#include <boost/optional.hpp>
const uint32_t ZIP32_HARDENED_KEY_LIMIT = 0x80000000;
const size_t ZIP32_XFVK_SIZE = 169;
const size_t ZIP32_XSK_SIZE = 169;
typedef std::vector<unsigned char, secure_allocator<unsigned char>> RawHDSeed;
class HDSeed {
private:
RawHDSeed seed;
public:
HDSeed() {}
HDSeed(RawHDSeed& seedIn) : seed(seedIn) {}
static HDSeed Random(size_t len = 32);
bool IsNull() const { return seed.empty(); };
uint256 Fingerprint() const;
RawHDSeed RawSeed() const { return seed; }
friend bool operator==(const HDSeed& a, const HDSeed& b)
{
return a.seed == b.seed;
}
friend bool operator!=(const HDSeed& a, const HDSeed& b)
{
return !(a == b);
}
};
namespace libzcash {
typedef blob88 diversifier_index_t;
struct SaplingExtendedFullViewingKey {
uint8_t depth;
uint32_t parentFVKTag;
uint32_t childIndex;
uint256 chaincode;
libzcash::SaplingFullViewingKey fvk;
uint256 dk;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(depth);
READWRITE(parentFVKTag);
READWRITE(childIndex);
READWRITE(chaincode);
READWRITE(fvk);
READWRITE(dk);
}
boost::optional<SaplingExtendedFullViewingKey> Derive(uint32_t i) const;
// Returns the first index starting from j that generates a valid
// payment address, along with the corresponding address. Returns
// an error if the diversifier space is exhausted.
boost::optional<std::pair<diversifier_index_t, libzcash::SaplingPaymentAddress>>
Address(diversifier_index_t j) const;
libzcash::SaplingPaymentAddress DefaultAddress() const;
};
struct SaplingExtendedSpendingKey {
uint8_t depth;
uint32_t parentFVKTag;
uint32_t childIndex;
uint256 chaincode;
libzcash::SaplingExpandedSpendingKey expsk;
uint256 dk;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(depth);
READWRITE(parentFVKTag);
READWRITE(childIndex);
READWRITE(chaincode);
READWRITE(expsk);
READWRITE(dk);
}
static SaplingExtendedSpendingKey Master(const HDSeed& seed);
SaplingExtendedSpendingKey Derive(uint32_t i) const;
SaplingExtendedFullViewingKey ToXFVK() const;
libzcash::SaplingPaymentAddress DefaultAddress() const;
friend bool operator==(const SaplingExtendedSpendingKey& a, const SaplingExtendedSpendingKey& b)
{
return a.depth == b.depth &&
a.parentFVKTag == b.parentFVKTag &&
a.childIndex == b.childIndex &&
a.chaincode == b.chaincode &&
a.expsk == b.expsk &&
a.dk == b.dk;
}
};
typedef boost::variant<InvalidEncoding, SproutSpendingKey, SaplingExtendedSpendingKey> SpendingKey;
}
/** Check whether a SpendingKey is not an InvalidEncoding. */
bool IsValidSpendingKey(const libzcash::SpendingKey& zkey);
#endif // ZCASH_ZIP32_H