Merge #14075: Import watch only pubkeys to the keypool if private keys are disabled
f4b00b70e
Import public keys in order (Andrew Chow)9e1551b9c
Test pubkey import to keypool (Andrew Chow)513719c5f
Add option to importmulti add an imported pubkey to the keypool (Andrew Chow)9b81fd19a
Fetch keys from keypool when private keys are disabled (Andrew Chow)99cccb900
Add a method to add a pubkey to the keypool (Andrew Chow) Pull request description: If the wallet has private keys disabled, allow importing public keys into the keypool. A `keypool` option has been added to `importmulti` in order to signal that the keys should be added to the keypool. Tree-SHA512: e88ea7bf726c13031aa739389a0c2662e6b22a4f9a4dc45b042418c692a950d98f170e0db80eb59e9c9063cda8765eaa85b2927d1790b9625744f7a87bad5fc8
This commit is contained in:
commit
c576979b78
6 changed files with 203 additions and 69 deletions
|
@ -967,7 +967,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data)
|
static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys)
|
||||||
{
|
{
|
||||||
UniValue warnings(UniValue::VARR);
|
UniValue warnings(UniValue::VARR);
|
||||||
|
|
||||||
|
@ -1038,6 +1038,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key");
|
||||||
}
|
}
|
||||||
pubkey_map.emplace(pubkey.GetID(), pubkey);
|
pubkey_map.emplace(pubkey.GetID(), pubkey);
|
||||||
|
ordered_pubkeys.push_back(pubkey.GetID());
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < keys.size(); ++i) {
|
for (size_t i = 0; i < keys.size(); ++i) {
|
||||||
const auto& str = keys[i].get_str();
|
const auto& str = keys[i].get_str();
|
||||||
|
@ -1110,7 +1111,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
|
||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data)
|
static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys)
|
||||||
{
|
{
|
||||||
UniValue warnings(UniValue::VARR);
|
UniValue warnings(UniValue::VARR);
|
||||||
|
|
||||||
|
@ -1144,14 +1145,15 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
|
||||||
|
|
||||||
const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
|
const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
|
||||||
|
|
||||||
FlatSigningProvider out_keys;
|
|
||||||
|
|
||||||
// Expand all descriptors to get public keys and scripts.
|
// Expand all descriptors to get public keys and scripts.
|
||||||
// TODO: get private keys from descriptors too
|
// TODO: get private keys from descriptors too
|
||||||
for (int i = range_start; i <= range_end; ++i) {
|
for (int i = range_start; i <= range_end; ++i) {
|
||||||
|
FlatSigningProvider out_keys;
|
||||||
std::vector<CScript> scripts_temp;
|
std::vector<CScript> scripts_temp;
|
||||||
parsed_desc->Expand(i, keys, scripts_temp, out_keys);
|
parsed_desc->Expand(i, keys, scripts_temp, out_keys);
|
||||||
std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end()));
|
std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end()));
|
||||||
|
for (const auto& key_pair : out_keys.pubkeys) {
|
||||||
|
ordered_pubkeys.push_back(key_pair.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& x : out_keys.scripts) {
|
for (const auto& x : out_keys.scripts) {
|
||||||
|
@ -1160,6 +1162,8 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
|
||||||
|
|
||||||
std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
|
std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
|
||||||
import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
|
import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < priv_keys.size(); ++i) {
|
for (size_t i = 0; i < priv_keys.size(); ++i) {
|
||||||
const auto& str = priv_keys[i].get_str();
|
const auto& str = priv_keys[i].get_str();
|
||||||
CKey key = DecodeSecret(str);
|
CKey key = DecodeSecret(str);
|
||||||
|
@ -1207,19 +1211,26 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
|
||||||
}
|
}
|
||||||
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
|
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
|
||||||
|
const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false;
|
||||||
|
|
||||||
|
// Add to keypool only works with privkeys disabled
|
||||||
|
if (add_keypool && !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled");
|
||||||
|
}
|
||||||
|
|
||||||
ImportData import_data;
|
ImportData import_data;
|
||||||
std::map<CKeyID, CPubKey> pubkey_map;
|
std::map<CKeyID, CPubKey> pubkey_map;
|
||||||
std::map<CKeyID, CKey> privkey_map;
|
std::map<CKeyID, CKey> privkey_map;
|
||||||
std::set<CScript> script_pub_keys;
|
std::set<CScript> script_pub_keys;
|
||||||
|
std::vector<CKeyID> ordered_pubkeys;
|
||||||
bool have_solving_data;
|
bool have_solving_data;
|
||||||
|
|
||||||
if (data.exists("scriptPubKey") && data.exists("desc")) {
|
if (data.exists("scriptPubKey") && data.exists("desc")) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided.");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided.");
|
||||||
} else if (data.exists("scriptPubKey")) {
|
} else if (data.exists("scriptPubKey")) {
|
||||||
warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
|
warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys);
|
||||||
} else if (data.exists("desc")) {
|
} else if (data.exists("desc")) {
|
||||||
warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
|
warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys);
|
||||||
} else {
|
} else {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided.");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided.");
|
||||||
}
|
}
|
||||||
|
@ -1255,9 +1266,12 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
|
||||||
}
|
}
|
||||||
pwallet->UpdateTimeFirstKey(timestamp);
|
pwallet->UpdateTimeFirstKey(timestamp);
|
||||||
}
|
}
|
||||||
for (const auto& entry : pubkey_map) {
|
for (const CKeyID& id : ordered_pubkeys) {
|
||||||
const CPubKey& pubkey = entry.second;
|
auto entry = pubkey_map.find(id);
|
||||||
const CKeyID& id = entry.first;
|
if (entry == pubkey_map.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const CPubKey& pubkey = entry->second;
|
||||||
CPubKey temp;
|
CPubKey temp;
|
||||||
if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) {
|
if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
||||||
|
@ -1267,6 +1281,11 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
|
||||||
pwallet->AddKeyOrigin(pubkey, key_orig_it->second);
|
pwallet->AddKeyOrigin(pubkey, key_orig_it->second);
|
||||||
}
|
}
|
||||||
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
|
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
|
||||||
|
|
||||||
|
// Add to keypool only works with pubkeys
|
||||||
|
if (add_keypool) {
|
||||||
|
pwallet->AddKeypoolPubkey(pubkey, internal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const CScript& script : script_pub_keys) {
|
for (const CScript& script : script_pub_keys) {
|
||||||
|
|
|
@ -173,18 +173,12 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
|
||||||
},
|
},
|
||||||
}.ToString());
|
}.ToString());
|
||||||
|
|
||||||
// Belt and suspenders check for disabled private keys
|
|
||||||
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
|
|
||||||
}
|
|
||||||
|
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
||||||
if (!pwallet->CanGetAddresses()) {
|
if (!pwallet->CanGetAddresses()) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Parse the label first so we don't generate a key if there's an error
|
// Parse the label first so we don't generate a key if there's an error
|
||||||
std::string label;
|
std::string label;
|
||||||
if (!request.params[0].isNull())
|
if (!request.params[0].isNull())
|
||||||
|
@ -240,11 +234,6 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
|
||||||
},
|
},
|
||||||
}.ToString());
|
}.ToString());
|
||||||
|
|
||||||
// Belt and suspenders check for disabled private keys
|
|
||||||
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
|
|
||||||
}
|
|
||||||
|
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
||||||
if (!pwallet->CanGetAddresses(true)) {
|
if (!pwallet->CanGetAddresses(true)) {
|
||||||
|
@ -2447,7 +2436,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
|
||||||
obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime());
|
obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime());
|
||||||
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
|
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
|
||||||
CKeyID seed_id = pwallet->GetHDChain().seed_id;
|
CKeyID seed_id = pwallet->GetHDChain().seed_id;
|
||||||
if (!seed_id.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
|
if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
|
||||||
obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
|
obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
|
||||||
}
|
}
|
||||||
if (pwallet->IsCrypted()) {
|
if (pwallet->IsCrypted()) {
|
||||||
|
|
|
@ -2833,8 +2833,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
||||||
// post-backup change.
|
// post-backup change.
|
||||||
|
|
||||||
// Reserve a new key pair from key pool
|
// Reserve a new key pair from key pool
|
||||||
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
if (!CanGetAddresses(true)) {
|
||||||
strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet.");
|
strFailReason = _("Can't generate a change-address key. No keys in the internal keypool and can't generate any keys.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
CPubKey vchPubKey;
|
CPubKey vchPubKey;
|
||||||
|
@ -3443,20 +3443,8 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
||||||
internal = true;
|
internal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys?
|
|
||||||
int64_t index = ++m_max_keypool_index;
|
|
||||||
|
|
||||||
CPubKey pubkey(GenerateNewKey(batch, internal));
|
CPubKey pubkey(GenerateNewKey(batch, internal));
|
||||||
if (!batch.WritePool(index, CKeyPool(pubkey, internal))) {
|
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
|
||||||
throw std::runtime_error(std::string(__func__) + ": writing generated key failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (internal) {
|
|
||||||
setInternalKeyPool.insert(index);
|
|
||||||
} else {
|
|
||||||
setExternalKeyPool.insert(index);
|
|
||||||
}
|
|
||||||
m_pool_key_to_index[pubkey.GetID()] = index;
|
|
||||||
}
|
}
|
||||||
if (missingInternal + missingExternal > 0) {
|
if (missingInternal + missingExternal > 0) {
|
||||||
WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size());
|
WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size());
|
||||||
|
@ -3466,6 +3454,29 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CWallet::AddKeypoolPubkey(const CPubKey& pubkey, const bool internal)
|
||||||
|
{
|
||||||
|
WalletBatch batch(*database);
|
||||||
|
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
|
||||||
|
NotifyCanGetAddressesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CWallet::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch)
|
||||||
|
{
|
||||||
|
LOCK(cs_wallet);
|
||||||
|
assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys?
|
||||||
|
int64_t index = ++m_max_keypool_index;
|
||||||
|
if (!batch.WritePool(index, CKeyPool(pubkey, internal))) {
|
||||||
|
throw std::runtime_error(std::string(__func__) + ": writing imported pubkey failed");
|
||||||
|
}
|
||||||
|
if (internal) {
|
||||||
|
setInternalKeyPool.insert(index);
|
||||||
|
} else {
|
||||||
|
setExternalKeyPool.insert(index);
|
||||||
|
}
|
||||||
|
m_pool_key_to_index[pubkey.GetID()] = index;
|
||||||
|
}
|
||||||
|
|
||||||
bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
|
bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
|
||||||
{
|
{
|
||||||
nIndex = -1;
|
nIndex = -1;
|
||||||
|
@ -3476,7 +3487,8 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe
|
||||||
if (!IsLocked())
|
if (!IsLocked())
|
||||||
TopUpKeyPool();
|
TopUpKeyPool();
|
||||||
|
|
||||||
bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal;
|
bool fReturningInternal = fRequestedInternal;
|
||||||
|
fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||||
bool use_split_keypool = set_pre_split_keypool.empty();
|
bool use_split_keypool = set_pre_split_keypool.empty();
|
||||||
std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool;
|
std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool;
|
||||||
|
|
||||||
|
@ -3493,7 +3505,8 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe
|
||||||
if (!batch.ReadPool(nIndex, keypool)) {
|
if (!batch.ReadPool(nIndex, keypool)) {
|
||||||
throw std::runtime_error(std::string(__func__) + ": read failed");
|
throw std::runtime_error(std::string(__func__) + ": read failed");
|
||||||
}
|
}
|
||||||
if (!HaveKey(keypool.vchPubKey.GetID())) {
|
CPubKey pk;
|
||||||
|
if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) {
|
||||||
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
|
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
|
||||||
}
|
}
|
||||||
// If the key was pre-split keypool, we don't care about what type it is
|
// If the key was pre-split keypool, we don't care about what type it is
|
||||||
|
@ -3547,7 +3560,7 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
int64_t nIndex;
|
int64_t nIndex;
|
||||||
if (!ReserveKeyFromKeyPool(nIndex, keypool, internal)) {
|
if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
if (IsLocked()) return false;
|
if (IsLocked()) return false;
|
||||||
WalletBatch batch(*database);
|
WalletBatch batch(*database);
|
||||||
result = GenerateNewKey(batch, internal);
|
result = GenerateNewKey(batch, internal);
|
||||||
|
|
|
@ -1001,6 +1001,8 @@ public:
|
||||||
bool NewKeyPool();
|
bool NewKeyPool();
|
||||||
size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
bool TopUpKeyPool(unsigned int kpSize = 0);
|
bool TopUpKeyPool(unsigned int kpSize = 0);
|
||||||
|
void AddKeypoolPubkey(const CPubKey& pubkey, const bool internal);
|
||||||
|
void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reserves a key from the keypool and sets nIndex to its index
|
* Reserves a key from the keypool and sets nIndex to its index
|
||||||
|
|
|
@ -31,8 +31,8 @@ class CreateWalletTest(BitcoinTestFramework):
|
||||||
self.log.info("Test disableprivatekeys creation.")
|
self.log.info("Test disableprivatekeys creation.")
|
||||||
self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True)
|
self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True)
|
||||||
w1 = node.get_wallet_rpc('w1')
|
w1 = node.get_wallet_rpc('w1')
|
||||||
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w1.getnewaddress)
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getnewaddress)
|
||||||
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w1.getrawchangeaddress)
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getrawchangeaddress)
|
||||||
w1.importpubkey(w0.getaddressinfo(address1)['pubkey'])
|
w1.importpubkey(w0.getaddressinfo(address1)['pubkey'])
|
||||||
|
|
||||||
self.log.info('Test that private keys cannot be imported')
|
self.log.info('Test that private keys cannot be imported')
|
||||||
|
@ -48,8 +48,8 @@ class CreateWalletTest(BitcoinTestFramework):
|
||||||
self.log.info("Test blank creation with private keys disabled.")
|
self.log.info("Test blank creation with private keys disabled.")
|
||||||
self.nodes[0].createwallet(wallet_name='w2', disable_private_keys=True, blank=True)
|
self.nodes[0].createwallet(wallet_name='w2', disable_private_keys=True, blank=True)
|
||||||
w2 = node.get_wallet_rpc('w2')
|
w2 = node.get_wallet_rpc('w2')
|
||||||
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w2.getnewaddress)
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getnewaddress)
|
||||||
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w2.getrawchangeaddress)
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getrawchangeaddress)
|
||||||
w2.importpubkey(w0.getaddressinfo(address1)['pubkey'])
|
w2.importpubkey(w0.getaddressinfo(address1)['pubkey'])
|
||||||
|
|
||||||
self.log.info("Test blank creation with private keys enabled.")
|
self.log.info("Test blank creation with private keys enabled.")
|
||||||
|
@ -89,12 +89,12 @@ class CreateWalletTest(BitcoinTestFramework):
|
||||||
self.nodes[0].createwallet(wallet_name='w5', disable_private_keys=True, blank=True)
|
self.nodes[0].createwallet(wallet_name='w5', disable_private_keys=True, blank=True)
|
||||||
w5 = node.get_wallet_rpc('w5')
|
w5 = node.get_wallet_rpc('w5')
|
||||||
assert_equal(w5.getwalletinfo()['keypoolsize'], 0)
|
assert_equal(w5.getwalletinfo()['keypoolsize'], 0)
|
||||||
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getnewaddress)
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
|
||||||
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress)
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress)
|
||||||
# Encrypt the wallet
|
# Encrypt the wallet
|
||||||
w5.encryptwallet('pass')
|
w5.encryptwallet('pass')
|
||||||
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getnewaddress)
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
|
||||||
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress)
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
CreateWalletTest().main()
|
CreateWalletTest().main()
|
||||||
|
|
|
@ -625,7 +625,6 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
ismine=False,
|
ismine=False,
|
||||||
iswatchonly=False)
|
iswatchonly=False)
|
||||||
|
|
||||||
|
|
||||||
# Import pubkeys with key origin info
|
# Import pubkeys with key origin info
|
||||||
self.log.info("Addresses should have hd keypath and master key id after import with key origin")
|
self.log.info("Addresses should have hd keypath and master key id after import with key origin")
|
||||||
pub_addr = self.nodes[1].getnewaddress()
|
pub_addr = self.nodes[1].getnewaddress()
|
||||||
|
@ -691,5 +690,117 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
assert 'hdmasterfingerprint' not in pub_import_info
|
assert 'hdmasterfingerprint' not in pub_import_info
|
||||||
assert 'hdkeypath' not in pub_import_info
|
assert 'hdkeypath' not in pub_import_info
|
||||||
|
|
||||||
|
# Import some public keys to the keypool of a no privkey wallet
|
||||||
|
self.log.info("Adding pubkey to keypool of disableprivkey wallet")
|
||||||
|
self.nodes[1].createwallet(wallet_name="noprivkeys", disable_private_keys=True)
|
||||||
|
wrpc = self.nodes[1].get_wallet_rpc("noprivkeys")
|
||||||
|
|
||||||
|
addr1 = self.nodes[0].getnewaddress()
|
||||||
|
addr2 = self.nodes[0].getnewaddress()
|
||||||
|
pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey']
|
||||||
|
pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey']
|
||||||
|
result = wrpc.importmulti(
|
||||||
|
[{
|
||||||
|
'desc': 'wpkh(' + pub1 + ')',
|
||||||
|
'keypool': True,
|
||||||
|
"timestamp": "now",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'desc': 'wpkh(' + pub2 + ')',
|
||||||
|
'keypool': True,
|
||||||
|
"timestamp": "now",
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
assert result[0]['success']
|
||||||
|
assert result[1]['success']
|
||||||
|
assert_equal(wrpc.getwalletinfo()["keypoolsize"], 2)
|
||||||
|
newaddr1 = wrpc.getnewaddress()
|
||||||
|
assert_equal(addr1, newaddr1)
|
||||||
|
newaddr2 = wrpc.getnewaddress()
|
||||||
|
assert_equal(addr2, newaddr2)
|
||||||
|
|
||||||
|
# Import some public keys to the internal keypool of a no privkey wallet
|
||||||
|
self.log.info("Adding pubkey to internal keypool of disableprivkey wallet")
|
||||||
|
addr1 = self.nodes[0].getnewaddress()
|
||||||
|
addr2 = self.nodes[0].getnewaddress()
|
||||||
|
pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey']
|
||||||
|
pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey']
|
||||||
|
result = wrpc.importmulti(
|
||||||
|
[{
|
||||||
|
'desc': 'wpkh(' + pub1 + ')',
|
||||||
|
'keypool': True,
|
||||||
|
'internal': True,
|
||||||
|
"timestamp": "now",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'desc': 'wpkh(' + pub2 + ')',
|
||||||
|
'keypool': True,
|
||||||
|
'internal': True,
|
||||||
|
"timestamp": "now",
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
assert result[0]['success']
|
||||||
|
assert result[1]['success']
|
||||||
|
assert_equal(wrpc.getwalletinfo()["keypoolsize_hd_internal"], 2)
|
||||||
|
newaddr1 = wrpc.getrawchangeaddress()
|
||||||
|
assert_equal(addr1, newaddr1)
|
||||||
|
newaddr2 = wrpc.getrawchangeaddress()
|
||||||
|
assert_equal(addr2, newaddr2)
|
||||||
|
|
||||||
|
# Import a multisig and make sure the keys don't go into the keypool
|
||||||
|
self.log.info('Imported scripts with pubkeys shoud not have their pubkeys go into the keypool')
|
||||||
|
addr1 = self.nodes[0].getnewaddress()
|
||||||
|
addr2 = self.nodes[0].getnewaddress()
|
||||||
|
pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey']
|
||||||
|
pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey']
|
||||||
|
result = wrpc.importmulti(
|
||||||
|
[{
|
||||||
|
'desc': 'wsh(multi(2,' + pub1 + ',' + pub2 + '))',
|
||||||
|
'keypool': True,
|
||||||
|
"timestamp": "now",
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
assert result[0]['success']
|
||||||
|
assert_equal(wrpc.getwalletinfo()["keypoolsize"], 0)
|
||||||
|
|
||||||
|
# Cannot import those pubkeys to keypool of wallet with privkeys
|
||||||
|
self.log.info("Pubkeys cannot be added to the keypool of a wallet with private keys")
|
||||||
|
wrpc = self.nodes[1].get_wallet_rpc("")
|
||||||
|
assert wrpc.getwalletinfo()['private_keys_enabled']
|
||||||
|
result = wrpc.importmulti(
|
||||||
|
[{
|
||||||
|
'desc': 'wpkh(' + pub1 + ')',
|
||||||
|
'keypool': True,
|
||||||
|
"timestamp": "now",
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
assert_equal(result[0]['error']['code'], -8)
|
||||||
|
assert_equal(result[0]['error']['message'], "Keys can only be imported to the keypool when private keys are disabled")
|
||||||
|
|
||||||
|
# Make sure ranged imports import keys in order
|
||||||
|
self.log.info('Key ranges should be imported in order')
|
||||||
|
wrpc = self.nodes[1].get_wallet_rpc("noprivkeys")
|
||||||
|
assert_equal(wrpc.getwalletinfo()["keypoolsize"], 0)
|
||||||
|
assert_equal(wrpc.getwalletinfo()["private_keys_enabled"], False)
|
||||||
|
xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY"
|
||||||
|
addresses = [
|
||||||
|
'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv', # m/0'/0'/0
|
||||||
|
'bcrt1q8vprchan07gzagd5e6v9wd7azyucksq2xc76k8', # m/0'/0'/1
|
||||||
|
'bcrt1qtuqdtha7zmqgcrr26n2rqxztv5y8rafjp9lulu', # m/0'/0'/2
|
||||||
|
'bcrt1qau64272ymawq26t90md6an0ps99qkrse58m640', # m/0'/0'/3
|
||||||
|
'bcrt1qsg97266hrh6cpmutqen8s4s962aryy77jp0fg0', # m/0'/0'/4
|
||||||
|
]
|
||||||
|
result = wrpc.importmulti(
|
||||||
|
[{
|
||||||
|
'desc': 'wpkh([80002067/0h/0h]' + xpub + '/*)',
|
||||||
|
'keypool': True,
|
||||||
|
'timestamp': 'now',
|
||||||
|
'range' : {'start': 0, 'end': 4}
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
for i in range(0, 5):
|
||||||
|
addr = wrpc.getnewaddress('', 'bech32')
|
||||||
|
assert_equal(addr, addresses[i])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ImportMultiTest().main()
|
ImportMultiTest().main()
|
||||||
|
|
Loading…
Reference in a new issue