Skip to content

Commit

Permalink
Merge #549: Make new actually working master blinding calls, test
Browse files Browse the repository at this point in the history
9acbd24 Make new actually working master blinding calls, test (Gregory Sanders)

Pull request description:

  resolves #547

  Previous API was improperly overloading the specific blinding key calls, and incorrectly on top of that.

  Instead implement `importmasterblindingkey` and `dumpmasterblindingkey` and check that `dumpwallet` details are enough to recover same blinded wallet addresses.

Tree-SHA512: d9d954d1cb787182d99b464cc6a80e8383d3ea71e432e6fd0e244b5f5a460b9adfcdc14e38fe841c5a5e2fef1345da78217d519a23a804d5066998828103efd1
  • Loading branch information
stevenroose committed Apr 1, 2019
2 parents c1c4757 + 9acbd24 commit a904f02
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 18 deletions.
1 change: 0 additions & 1 deletion src/rpc/client.cpp
Expand Up @@ -190,7 +190,6 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendmany", 8 , "output_assets" },
{ "sendmany", 9 , "ignoreblindfail" },
{ "sendtoaddress", 9 , "ignoreblindfail" },
{ "importblindingkey", 2, "key_is_master"},
{ "createrawtransaction", 4, "output_assets" },

};
Expand Down
90 changes: 74 additions & 16 deletions src/wallet/rpcdump.cpp
Expand Up @@ -764,7 +764,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
}
// ELEMENTS: Dump the master blinding key in hex as well
if (!pwallet->blinding_derivation_key.IsNull()) {
file << ("# Master private blinding key: " + pwallet->blinding_derivation_key.GetHex() + "\n\n");
file << ("# Master private blinding key: " + HexStr(pwallet->blinding_derivation_key) + "\n\n");
}
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
const CKeyID &keyid = it->second;
Expand Down Expand Up @@ -1343,14 +1343,13 @@ UniValue importblindingkey(const JSONRPCRequest& request)
return NullUniValue;
}

if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
if (request.fHelp || request.params.size() != 2)
throw std::runtime_error(
"importblindingkey \"address\" \"blindinghex\"\n"
"\nImports a private blinding key in hex for a CT address."
"\nArguments:\n"
"1. \"address\" (string, required) The CT address\n"
"2. \"hexkey\" (string, required) The blinding key in hex\n"
"3. \"key_is_master\" (bool, optional, default=false) If the `hexkey` is a master blinding key. Note: wallets can only have one master blinding key at a time. Funds could be permanently lost if user doesn't know what they are doing. Recommended use is only for wallet recovery using this in conjunction with `sethdseed`.\n"
"\nExample:\n"
+ HelpExampleCli("importblindingkey", "\"my blinded CT address\" <blindinghex>")
);
Expand All @@ -1373,11 +1372,6 @@ UniValue importblindingkey(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal key length");
}

bool key_is_master = false;
if (!request.params[2].isNull()) {
key_is_master = request.params[2].get_bool();
}

CKey key;
key.Set(keydata.begin(), keydata.end(), true);
if (!key.IsValid() || key.GetPubKey() != GetDestinationBlindingKey(dest)) {
Expand All @@ -1386,20 +1380,56 @@ UniValue importblindingkey(const JSONRPCRequest& request)

uint256 keyval;
memcpy(keyval.begin(), &keydata[0], 32);
if (key_is_master) {
if (pwallet->SetMasterBlindingKey(keyval)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import master blinding key");
}
} else {
if (!pwallet->AddSpecificBlindingKey(CScriptID(GetScriptForDestination(dest)), keyval)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import blinding key");
}
if (!pwallet->AddSpecificBlindingKey(CScriptID(GetScriptForDestination(dest)), keyval)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import blinding key");
}
pwallet->MarkDirty();

return NullUniValue;
}

UniValue importmasterblindingkey(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();

if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}

if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
"importblindingkey \"address\" \"blindinghex\"\n"
"\nImports a master private blinding key in hex for a CT address."
"Note: wallets can only have one master blinding key at a time. Funds could be permanently lost if user doesn't know what they are doing. Recommended use is only for wallet recovery using this in conjunction with `sethdseed`.\n"
"\nArguments:\n"
"1. \"hexkey\" (string, required) The blinding key in hex\n"
"\nExample:\n"
+ HelpExampleCli("importmasterblindingkey", "<hexkey>")
);

LOCK2(cs_main, pwallet->cs_wallet);

if (!IsHex(request.params[0].get_str())) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal for key");
}
std::vector<unsigned char> keydata = ParseHex(request.params[0].get_str());
if (keydata.size() != 32) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal key length");
}

uint256 keyval;
memcpy(keyval.begin(), &keydata[0], 32);

if (!pwallet->SetMasterBlindingKey(keyval)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import master blinding key");
}

pwallet->MarkDirty();

return NullUniValue;
}

UniValue importissuanceblindingkey(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
Expand Down Expand Up @@ -1519,6 +1549,34 @@ UniValue dumpblindingkey(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Blinding key for address is unknown");
}

UniValue dumpmasterblindingkey(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();

if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}

if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
"dumpmasterblindingkey\n"
"\nDumps the master private blinding key in hex."
"\nResult:\n"
"\"blindingkey\" (string) The master blinding key\n"
"\nExample:\n"
+ HelpExampleCli("dumpmasterblindingkey", "")
);

LOCK2(cs_main, pwallet->cs_wallet);

if (!pwallet->blinding_derivation_key.IsNull()) {
return HexStr(pwallet->blinding_derivation_key);
} else {
throw JSONRPCError(RPC_WALLET_ERROR, "Master blinding key is uninitialized.");
}
}

UniValue dumpissuanceblindingkey(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
Expand Down
6 changes: 5 additions & 1 deletion src/wallet/rpcwallet.cpp
Expand Up @@ -6203,8 +6203,10 @@ UniValue getpegoutkeys(const JSONRPCRequest& request)
UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue importblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue importmasterblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue importissuanceblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue dumpblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue dumpmasterblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue dumpissuanceblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue importprivkey(const JSONRPCRequest& request);
UniValue importaddress(const JSONRPCRequest& request);
Expand Down Expand Up @@ -6285,9 +6287,11 @@ static const CRPCCommand commands[] =
{ "wallet", "sendtomainchain", &sendtomainchain, {"address", "amount", "subtractfeefromamount"} },
{ "wallet", "initpegoutwallet", &initpegoutwallet, {"bitcoin_descriptor", "bip32_counter", "liquid_pak"} },
{ "wallet", "getwalletpakinfo", &getwalletpakinfo, {} },
{ "wallet", "importblindingkey", &importblindingkey, {"address", "hexkey", "key_is_master"}},
{ "wallet", "importblindingkey", &importblindingkey, {"address", "hexkey"}},
{ "wallet", "importmasterblindingkey", &importmasterblindingkey, {"hexkey"}},
{ "wallet", "importissuanceblindingkey", &importissuanceblindingkey, {"txid", "vin", "blindingkey"}},
{ "wallet", "dumpblindingkey", &dumpblindingkey, {"address"}},
{ "wallet", "dumpmasterblindingkey", &dumpmasterblindingkey, {}},
{ "wallet", "dumpissuanceblindingkey", &dumpissuanceblindingkey, {"txid", "vin"}},
{ "wallet", "signblock", &signblock, {"blockhex"}},
{ "wallet", "listissuances", &listissuances, {"asset"}},
Expand Down
57 changes: 57 additions & 0 deletions test/functional/feature_confidential_transactions.py
Expand Up @@ -7,6 +7,8 @@
from test_framework.util import connect_nodes_bi, assert_equal
from test_framework.authproxy import JSONRPCException
from decimal import Decimal
import os
import re

class CTTest (BitcoinTestFramework):

Expand All @@ -28,8 +30,63 @@ def setup_network(self, split=False):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def test_wallet_recovery(self):
file_path = "/tmp/blind_details"
try:
os.remove(file_path)
except OSError:
pass

# Wallet recovery requires more than just seed, but also master blinding key
# which currently is not derived from seed, see
# https://github.com/ElementsProject/elements/pull/232
blind_addr = self.nodes[0].getnewaddress()

self.nodes[0].dumpwallet(file_path)

found_seed = False
found_blind = False
with open(file_path, encoding="utf8") as f:
for line in f:
if "hdseed=1" in line:
split = re.split(" ", line)
found_seed = split[0]
split = re.split("Master private blinding key: ", line)
if len(split) == 2:
assert_equal(len(split[1].rstrip()), 64)
found_blind = split[1].rstrip()

assert_equal(found_blind, self.nodes[0].dumpmasterblindingkey())

# Create new wallet
self.nodes[0].createwallet("recover")
rec = self.nodes[0].get_wallet_rpc("recover")
wrong_info = rec.getaddressinfo(blind_addr)
assert("pubkey" not in wrong_info)
assert_equal(wrong_info["ismine"], False)

# Setting seed should get us more info, still not "ours" until blinding key
rec.generatetoaddress(1, rec.getnewaddress()) # get out of IBD
rec.sethdseed(True, found_seed)

wrong_blind_info = rec.getaddressinfo(blind_addr)
assert("pubkey" in wrong_blind_info)
assert_equal(wrong_blind_info["ismine"], False)

# Now import master blinding key
rec.importmasterblindingkey(found_blind)
assert_equal(rec.dumpmasterblindingkey(), found_blind)
blind_info = rec.getaddressinfo(blind_addr)
assert("pubkey" in blind_info)
assert_equal(blind_info["ismine"], True)
assert_equal(rec.getaddressinfo(blind_info["unconfidential"])["confidential"], blind_addr)
self.nodes[0].unloadwallet("recover")

def run_test(self):

print("Testing wallet secret recovery")
self.test_wallet_recovery()

print("General Confidential tests")
# Running balances
node0 = self.nodes[0].getbalance()["bitcoin"]
Expand Down

0 comments on commit a904f02

Please sign in to comment.