Skip to content

Commit a904f02

Browse files
committed
Merge #549: Make new actually working master blinding calls, test
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
2 parents c1c4757 + 9acbd24 commit a904f02

File tree

4 files changed

+136
-18
lines changed

4 files changed

+136
-18
lines changed

src/rpc/client.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ static const CRPCConvertParam vRPCConvertParams[] =
190190
{ "sendmany", 8 , "output_assets" },
191191
{ "sendmany", 9 , "ignoreblindfail" },
192192
{ "sendtoaddress", 9 , "ignoreblindfail" },
193-
{ "importblindingkey", 2, "key_is_master"},
194193
{ "createrawtransaction", 4, "output_assets" },
195194

196195
};

src/wallet/rpcdump.cpp

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
764764
}
765765
// ELEMENTS: Dump the master blinding key in hex as well
766766
if (!pwallet->blinding_derivation_key.IsNull()) {
767-
file << ("# Master private blinding key: " + pwallet->blinding_derivation_key.GetHex() + "\n\n");
767+
file << ("# Master private blinding key: " + HexStr(pwallet->blinding_derivation_key) + "\n\n");
768768
}
769769
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
770770
const CKeyID &keyid = it->second;
@@ -1343,14 +1343,13 @@ UniValue importblindingkey(const JSONRPCRequest& request)
13431343
return NullUniValue;
13441344
}
13451345

1346-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
1346+
if (request.fHelp || request.params.size() != 2)
13471347
throw std::runtime_error(
13481348
"importblindingkey \"address\" \"blindinghex\"\n"
13491349
"\nImports a private blinding key in hex for a CT address."
13501350
"\nArguments:\n"
13511351
"1. \"address\" (string, required) The CT address\n"
13521352
"2. \"hexkey\" (string, required) The blinding key in hex\n"
1353-
"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"
13541353
"\nExample:\n"
13551354
+ HelpExampleCli("importblindingkey", "\"my blinded CT address\" <blindinghex>")
13561355
);
@@ -1373,11 +1372,6 @@ UniValue importblindingkey(const JSONRPCRequest& request)
13731372
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal key length");
13741373
}
13751374

1376-
bool key_is_master = false;
1377-
if (!request.params[2].isNull()) {
1378-
key_is_master = request.params[2].get_bool();
1379-
}
1380-
13811375
CKey key;
13821376
key.Set(keydata.begin(), keydata.end(), true);
13831377
if (!key.IsValid() || key.GetPubKey() != GetDestinationBlindingKey(dest)) {
@@ -1386,20 +1380,56 @@ UniValue importblindingkey(const JSONRPCRequest& request)
13861380

13871381
uint256 keyval;
13881382
memcpy(keyval.begin(), &keydata[0], 32);
1389-
if (key_is_master) {
1390-
if (pwallet->SetMasterBlindingKey(keyval)) {
1391-
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import master blinding key");
1392-
}
1393-
} else {
1394-
if (!pwallet->AddSpecificBlindingKey(CScriptID(GetScriptForDestination(dest)), keyval)) {
1395-
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import blinding key");
1396-
}
1383+
if (!pwallet->AddSpecificBlindingKey(CScriptID(GetScriptForDestination(dest)), keyval)) {
1384+
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import blinding key");
13971385
}
13981386
pwallet->MarkDirty();
13991387

14001388
return NullUniValue;
14011389
}
14021390

1391+
UniValue importmasterblindingkey(const JSONRPCRequest& request)
1392+
{
1393+
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
1394+
CWallet* const pwallet = wallet.get();
1395+
1396+
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
1397+
return NullUniValue;
1398+
}
1399+
1400+
if (request.fHelp || request.params.size() != 1)
1401+
throw std::runtime_error(
1402+
"importblindingkey \"address\" \"blindinghex\"\n"
1403+
"\nImports a master private blinding key in hex for a CT address."
1404+
"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"
1405+
"\nArguments:\n"
1406+
"1. \"hexkey\" (string, required) The blinding key in hex\n"
1407+
"\nExample:\n"
1408+
+ HelpExampleCli("importmasterblindingkey", "<hexkey>")
1409+
);
1410+
1411+
LOCK2(cs_main, pwallet->cs_wallet);
1412+
1413+
if (!IsHex(request.params[0].get_str())) {
1414+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal for key");
1415+
}
1416+
std::vector<unsigned char> keydata = ParseHex(request.params[0].get_str());
1417+
if (keydata.size() != 32) {
1418+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal key length");
1419+
}
1420+
1421+
uint256 keyval;
1422+
memcpy(keyval.begin(), &keydata[0], 32);
1423+
1424+
if (!pwallet->SetMasterBlindingKey(keyval)) {
1425+
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import master blinding key");
1426+
}
1427+
1428+
pwallet->MarkDirty();
1429+
1430+
return NullUniValue;
1431+
}
1432+
14031433
UniValue importissuanceblindingkey(const JSONRPCRequest& request)
14041434
{
14051435
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
@@ -1519,6 +1549,34 @@ UniValue dumpblindingkey(const JSONRPCRequest& request)
15191549
throw JSONRPCError(RPC_WALLET_ERROR, "Blinding key for address is unknown");
15201550
}
15211551

1552+
UniValue dumpmasterblindingkey(const JSONRPCRequest& request)
1553+
{
1554+
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
1555+
CWallet* const pwallet = wallet.get();
1556+
1557+
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
1558+
return NullUniValue;
1559+
}
1560+
1561+
if (request.fHelp || request.params.size() != 0)
1562+
throw std::runtime_error(
1563+
"dumpmasterblindingkey\n"
1564+
"\nDumps the master private blinding key in hex."
1565+
"\nResult:\n"
1566+
"\"blindingkey\" (string) The master blinding key\n"
1567+
"\nExample:\n"
1568+
+ HelpExampleCli("dumpmasterblindingkey", "")
1569+
);
1570+
1571+
LOCK2(cs_main, pwallet->cs_wallet);
1572+
1573+
if (!pwallet->blinding_derivation_key.IsNull()) {
1574+
return HexStr(pwallet->blinding_derivation_key);
1575+
} else {
1576+
throw JSONRPCError(RPC_WALLET_ERROR, "Master blinding key is uninitialized.");
1577+
}
1578+
}
1579+
15221580
UniValue dumpissuanceblindingkey(const JSONRPCRequest& request)
15231581
{
15241582
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);

src/wallet/rpcwallet.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6203,8 +6203,10 @@ UniValue getpegoutkeys(const JSONRPCRequest& request)
62036203
UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
62046204
UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
62056205
UniValue importblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
6206+
UniValue importmasterblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
62066207
UniValue importissuanceblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
62076208
UniValue dumpblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
6209+
UniValue dumpmasterblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
62086210
UniValue dumpissuanceblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
62096211
UniValue importprivkey(const JSONRPCRequest& request);
62106212
UniValue importaddress(const JSONRPCRequest& request);
@@ -6285,9 +6287,11 @@ static const CRPCCommand commands[] =
62856287
{ "wallet", "sendtomainchain", &sendtomainchain, {"address", "amount", "subtractfeefromamount"} },
62866288
{ "wallet", "initpegoutwallet", &initpegoutwallet, {"bitcoin_descriptor", "bip32_counter", "liquid_pak"} },
62876289
{ "wallet", "getwalletpakinfo", &getwalletpakinfo, {} },
6288-
{ "wallet", "importblindingkey", &importblindingkey, {"address", "hexkey", "key_is_master"}},
6290+
{ "wallet", "importblindingkey", &importblindingkey, {"address", "hexkey"}},
6291+
{ "wallet", "importmasterblindingkey", &importmasterblindingkey, {"hexkey"}},
62896292
{ "wallet", "importissuanceblindingkey", &importissuanceblindingkey, {"txid", "vin", "blindingkey"}},
62906293
{ "wallet", "dumpblindingkey", &dumpblindingkey, {"address"}},
6294+
{ "wallet", "dumpmasterblindingkey", &dumpmasterblindingkey, {}},
62916295
{ "wallet", "dumpissuanceblindingkey", &dumpissuanceblindingkey, {"txid", "vin"}},
62926296
{ "wallet", "signblock", &signblock, {"blockhex"}},
62936297
{ "wallet", "listissuances", &listissuances, {"asset"}},

test/functional/feature_confidential_transactions.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from test_framework.util import connect_nodes_bi, assert_equal
88
from test_framework.authproxy import JSONRPCException
99
from decimal import Decimal
10+
import os
11+
import re
1012

1113
class CTTest (BitcoinTestFramework):
1214

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

33+
def test_wallet_recovery(self):
34+
file_path = "/tmp/blind_details"
35+
try:
36+
os.remove(file_path)
37+
except OSError:
38+
pass
39+
40+
# Wallet recovery requires more than just seed, but also master blinding key
41+
# which currently is not derived from seed, see
42+
# https://github.com/ElementsProject/elements/pull/232
43+
blind_addr = self.nodes[0].getnewaddress()
44+
45+
self.nodes[0].dumpwallet(file_path)
46+
47+
found_seed = False
48+
found_blind = False
49+
with open(file_path, encoding="utf8") as f:
50+
for line in f:
51+
if "hdseed=1" in line:
52+
split = re.split(" ", line)
53+
found_seed = split[0]
54+
split = re.split("Master private blinding key: ", line)
55+
if len(split) == 2:
56+
assert_equal(len(split[1].rstrip()), 64)
57+
found_blind = split[1].rstrip()
58+
59+
assert_equal(found_blind, self.nodes[0].dumpmasterblindingkey())
60+
61+
# Create new wallet
62+
self.nodes[0].createwallet("recover")
63+
rec = self.nodes[0].get_wallet_rpc("recover")
64+
wrong_info = rec.getaddressinfo(blind_addr)
65+
assert("pubkey" not in wrong_info)
66+
assert_equal(wrong_info["ismine"], False)
67+
68+
# Setting seed should get us more info, still not "ours" until blinding key
69+
rec.generatetoaddress(1, rec.getnewaddress()) # get out of IBD
70+
rec.sethdseed(True, found_seed)
71+
72+
wrong_blind_info = rec.getaddressinfo(blind_addr)
73+
assert("pubkey" in wrong_blind_info)
74+
assert_equal(wrong_blind_info["ismine"], False)
75+
76+
# Now import master blinding key
77+
rec.importmasterblindingkey(found_blind)
78+
assert_equal(rec.dumpmasterblindingkey(), found_blind)
79+
blind_info = rec.getaddressinfo(blind_addr)
80+
assert("pubkey" in blind_info)
81+
assert_equal(blind_info["ismine"], True)
82+
assert_equal(rec.getaddressinfo(blind_info["unconfidential"])["confidential"], blind_addr)
83+
self.nodes[0].unloadwallet("recover")
84+
3185
def run_test(self):
3286

87+
print("Testing wallet secret recovery")
88+
self.test_wallet_recovery()
89+
3390
print("General Confidential tests")
3491
# Running balances
3592
node0 = self.nodes[0].getbalance()["bitcoin"]

0 commit comments

Comments
 (0)