mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-19 13:27:35 +02:00
Descriptor support in PAK infrastructure and tests
This commit is contained in:
parent
6366734477
commit
1cb252e01d
7 changed files with 310 additions and 86 deletions
|
|
@ -48,6 +48,8 @@
|
|||
#include <functional>
|
||||
|
||||
#include <script/generic.hpp> // signblock
|
||||
#include <script/descriptor.h> // initpegoutwallet
|
||||
#include <span.h> // sendtomainchain_pak
|
||||
|
||||
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
|
||||
|
||||
|
|
@ -4936,38 +4938,6 @@ bool DerivePubTweak(const std::vector<uint32_t>& vPath, const CPubKey& keyMaster
|
|||
return true;
|
||||
}
|
||||
|
||||
CTxDestination DeriveBitcoinOfflineAddress(const CExtPubKey& xpub, const uint32_t counter)
|
||||
{
|
||||
std::vector<uint32_t> vPath;
|
||||
vPath.push_back(0);
|
||||
vPath.push_back(counter);
|
||||
|
||||
std::vector<unsigned char> tweakSum;
|
||||
if (!DerivePubTweak(vPath, xpub.pubkey, xpub.chaincode, tweakSum)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not derive the pubkey tweak for given counter and xpub.");
|
||||
}
|
||||
|
||||
secp256k1_pubkey masterpub_secp;
|
||||
int ret = secp256k1_ec_pubkey_parse(secp256k1_ctx, &masterpub_secp, xpub.pubkey.begin(), xpub.pubkey.size());
|
||||
if (ret != 1) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Master pubkey could not be parsed.");
|
||||
}
|
||||
|
||||
ret = secp256k1_ec_pubkey_tweak_add(secp256k1_ctx, &masterpub_secp, tweakSum.data());
|
||||
assert(ret);
|
||||
|
||||
std::vector<unsigned char> btcpubkeybytes;
|
||||
btcpubkeybytes.resize(33);
|
||||
size_t btclen = 33;
|
||||
ret = secp256k1_ec_pubkey_serialize(secp256k1_ctx, &btcpubkeybytes[0], &btclen, &masterpub_secp, SECP256K1_EC_COMPRESSED);
|
||||
assert(ret == 1);
|
||||
assert(btclen == 33);
|
||||
assert(btcpubkeybytes.size() == 33);
|
||||
|
||||
CPubKey btcpub(btcpubkeybytes);
|
||||
return CTxDestination(btcpub.GetID());
|
||||
}
|
||||
|
||||
UniValue initpegoutwallet(const JSONRPCRequest& request)
|
||||
{
|
||||
|
||||
|
|
@ -4979,11 +4949,11 @@ UniValue initpegoutwallet(const JSONRPCRequest& request)
|
|||
|
||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
|
||||
throw std::runtime_error(
|
||||
"initpegoutwallet bitcoin_xpub ( bip32_counter liquid_pak )\n"
|
||||
"\nThis call is for Liquid network initialization on the Liquid wallet. The wallet generates a new Liquid pegout authorization key (PAK) and stores it in the Liquid wallet. It then combines this with the `bitcoin_xpub` to finally create a PAK entry for the network. This allows the user to send Liquid coins directly to a secure offline Bitcoin wallet at the `/0/k` non-hardened derivation path from the bitcoin_xpub using the `sendtomainchain` command. Losing the Liquid PAK or offline Bitcoin root key will result in the inability to pegout funds, so immediate backup upon initialization is required.\n"
|
||||
"initpegoutwallet bitcoin_descriptor ( bip32_counter liquid_pak )\n"
|
||||
"\nThis call is for Liquid network initialization on the Liquid wallet. The wallet generates a new Liquid pegout authorization key (PAK) and stores it in the Liquid wallet. It then combines this with the `bitcoin_descriptor` to finally create a PAK entry for the network. This allows the user to send Liquid coins directly to a secure offline Bitcoin wallet at the derived path from the bitcoin_descriptor using the `sendtomainchain` command. Losing the Liquid PAK or offline Bitcoin root key will result in the inability to pegout funds, so immediate backup upon initialization is required.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"bitcoin_xpub\" (string, required) The Bitcoin extended pubkey to be used as the root for the Bitcoin destination wallet. The derivation path from this key will be `0/k`.\n"
|
||||
"2. \"bip32_counter\" (numeric, default=0) The `k` in `0/k` to be set as the next address to derive from the `bitcoin_xpub`. This will be stored in the wallet and incremented on each successful `sendtomainchain` invocation.\n"
|
||||
"1. \"bitcoin_descriptor\" (string, required) The Bitcoin descriptor that includes a single extended pubkey. Must be one of the following: pkh(<xpub>), sh(wpkh(<xpub>)), or wpkh(<xpub>). This is used as the root for the Bitcoin destination wallet. The derivation path from the xpub will be `0/k`, reflecting the external chain of the wallet. DEPRECATED: If a plain xpub is given, pkh(<xpub>) is assumed. See link for more details on script descriptors: https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md\n"
|
||||
"2. \"bip32_counter\" (numeric, default=0) The `k` in `0/k` to be set as the next address to derive from the `bitcoin_descriptor`. This will be stored in the wallet and incremented on each successful `sendtomainchain` invocation.\n"
|
||||
"3. \"liquid_pak\" (string, optional) The Liquid wallet pubkey in hex to be used as the Liquid PAK for pegout authorization. The private key must be in the wallet if argument is given. If this argument is not provided one will be generated and stored in the wallet automatically and returned.\n"
|
||||
+ HelpRequiringPassphrase(pwallet) +
|
||||
"\nResult:\n"
|
||||
|
|
@ -4993,8 +4963,8 @@ UniValue initpegoutwallet(const JSONRPCRequest& request)
|
|||
"\"liquid_pak_address\" (string) The corresponding address for `liquid_pak`. Useful for `dumpprivkey` for wallet backup or transfer.\n"
|
||||
"\"address_lookahead\"(array) The three next Bitcoin addresses the wallet will use for `sendtomainchain` based on `bip32_counter`.\n"
|
||||
"}\n"
|
||||
+ HelpExampleCli("initpegoutwallet", "tpubDAY5hwtonH4NE8zY46ZMFf6B6F3fqMis7cwfNihXXpAg6XzBZNoHAdAzAZx2peoU8nTWFqvUncXwJ9qgE5VxcnUKxdut8F6mptVmKjfiwDQ")
|
||||
+ HelpExampleRpc("initpegoutwallet", "tpubDAY5hwtonH4NE8zY46ZMFf6B6F3fqMis7cwfNihXXpAg6XzBZNoHAdAzAZx2peoU8nTWFqvUncXwJ9qgE5VxcnUKxdut8F6mptVmKjfiwDQ")
|
||||
+ HelpExampleCli("initpegoutwallet", "sh(wpkh(tpubDAY5hwtonH4NE8zY46ZMFf6B6F3fqMis7cwfNihXXpAg6XzBZNoHAdAzAZx2peoU8nTWFqvUncXwJ9qgE5VxcnUKxdut8F6mptVmKjfiwDQ/0/*))")
|
||||
+ HelpExampleRpc("initpegoutwallet", "sh(wpkh(tpubDAY5hwtonH4NE8zY46ZMFf6B6F3fqMis7cwfNihXXpAg6XzBZNoHAdAzAZx2peoU8nTWFqvUncXwJ9qgE5VxcnUKxdut8F6mptVmKjfiwDQ/0/*))")
|
||||
);
|
||||
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
|
|
@ -5037,11 +5007,49 @@ UniValue initpegoutwallet(const JSONRPCRequest& request)
|
|||
}
|
||||
}
|
||||
|
||||
//offline_xpub
|
||||
CExtPubKey xpub = DecodeExtPubKey(request.params[0].get_str());
|
||||
std::string bitcoin_desc = request.params[0].get_str();
|
||||
std::string xpub_str = "";
|
||||
|
||||
// First check for naked xpub, and impute it as pkh(<xpub>/0/*) for backwards compat
|
||||
CExtPubKey xpub = DecodeExtPubKey(bitcoin_desc);
|
||||
if (xpub.pubkey.IsFullyValid()) {
|
||||
bitcoin_desc = "pkh(" + bitcoin_desc + "/0/*)";
|
||||
}
|
||||
|
||||
FlatSigningProvider provider;
|
||||
auto desc = Parse(bitcoin_desc, provider);
|
||||
if (!desc) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "bitcoin_descriptor is not a valid descriptor string.");
|
||||
} else if (!desc->IsRange()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "bitcoin_descriptor must be a ranged descriptor.");
|
||||
}
|
||||
|
||||
// Three acceptable descriptors:
|
||||
if (bitcoin_desc.substr(0, 8) == "sh(wpkh("
|
||||
&& bitcoin_desc.substr(bitcoin_desc.size()-2, 2) == "))") {
|
||||
xpub_str = bitcoin_desc.substr(8, bitcoin_desc.size()-2);
|
||||
} else if (bitcoin_desc.substr(0, 5) == "wpkh("
|
||||
&& bitcoin_desc.substr(bitcoin_desc.size()-1, 1) == ")") {
|
||||
xpub_str = bitcoin_desc.substr(5, bitcoin_desc.size()-1);
|
||||
} else if (bitcoin_desc.substr(0, 4) == "pkh("
|
||||
&& bitcoin_desc.substr(bitcoin_desc.size()-1, 1) == ")") {
|
||||
xpub_str = bitcoin_desc.substr(4, bitcoin_desc.size()-1);
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "bitcoin_descriptor is not of any type supported: pkh(<xpub>), sh(wpkh(<xpub>)), wpkh(<xpub>), or <xpub>.");
|
||||
}
|
||||
|
||||
// Strip off leading key origin
|
||||
if (xpub_str.find("]") != std::string::npos) {
|
||||
xpub_str = xpub_str.substr(xpub_str.find("]"), std::string::npos);
|
||||
}
|
||||
|
||||
// Strip off following range
|
||||
xpub_str = xpub_str.substr(0, xpub_str.find("/"));
|
||||
|
||||
xpub = DecodeExtPubKey(xpub_str);
|
||||
|
||||
if (!xpub.pubkey.IsFullyValid()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "bitcoin_xpub is invalid for this network.");
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "bitcoin_descriptor does not contain a valid extended pubkey for this network.");
|
||||
}
|
||||
|
||||
// Parse master pubkey
|
||||
|
|
@ -5049,13 +5057,14 @@ UniValue initpegoutwallet(const JSONRPCRequest& request)
|
|||
secp256k1_pubkey masterpub_secp;
|
||||
int ret = secp256k1_ec_pubkey_parse(secp256k1_ctx, &masterpub_secp, masterpub.begin(), masterpub.size());
|
||||
if (ret != 1) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "bitcoin_xpub could not be parsed.");
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "bitcoin_descriptor could not be parsed.");
|
||||
}
|
||||
|
||||
// Store the keys and metadata
|
||||
if (!pwallet->SetOnlinePubKey(online_pubkey) ||
|
||||
!pwallet->SetOfflineXPubKey(xpub) ||
|
||||
!pwallet->SetOfflineCounter(counter)) {
|
||||
!pwallet->SetOfflineCounter(counter) ||
|
||||
!pwallet->SetOfflineDescriptor(bitcoin_desc)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Failure to initialize pegout wallet.");
|
||||
}
|
||||
|
||||
|
|
@ -5071,8 +5080,14 @@ UniValue initpegoutwallet(const JSONRPCRequest& request)
|
|||
assert(negatedpubkeybytes.size() == 33);
|
||||
|
||||
UniValue address_list(UniValue::VARR);
|
||||
for (unsigned int i = 0; i < 3; i++) {
|
||||
address_list.push_back(EncodeParentDestination(DeriveBitcoinOfflineAddress(xpub, counter+i)));
|
||||
for (int i = counter; i < counter+3; i++) {
|
||||
std::vector<CScript> scripts;
|
||||
if (!desc->Expand(i, provider, scripts, provider)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not generate lookahead addresses with descriptor. This is a bug.");
|
||||
}
|
||||
CTxDestination destination;
|
||||
ExtractDestination(scripts[0], destination);
|
||||
address_list.push_back(EncodeParentDestination(destination));
|
||||
}
|
||||
UniValue pak(UniValue::VOBJ);
|
||||
pak.push_back(Pair("pakentry", "pak=" + HexStr(negatedpubkeybytes) + ":" + HexStr(online_pubkey)));
|
||||
|
|
@ -5124,14 +5139,14 @@ UniValue sendtomainchain_base(const JSONRPCRequest& request)
|
|||
}
|
||||
|
||||
// Parse Bitcoin address for destination, embed script
|
||||
CScript scriptPubKeyMainchain(GetScriptForDestination(parent_address));
|
||||
CScript mainchain_script(GetScriptForDestination(parent_address));
|
||||
|
||||
uint256 genesisBlockHash = Params().ParentGenesisBlockHash();
|
||||
|
||||
// Asset type is implicit, no need to add to script
|
||||
NullData nulldata;
|
||||
nulldata << std::vector<unsigned char>(genesisBlockHash.begin(), genesisBlockHash.end());
|
||||
nulldata << std::vector<unsigned char>(scriptPubKeyMainchain.begin(), scriptPubKeyMainchain.end());
|
||||
nulldata << std::vector<unsigned char>(mainchain_script.begin(), mainchain_script.end());
|
||||
CTxDestination address(nulldata);
|
||||
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
|
@ -5148,6 +5163,46 @@ UniValue sendtomainchain_base(const JSONRPCRequest& request)
|
|||
|
||||
}
|
||||
|
||||
// ELEMENTS: Copied from script/descriptor.cpp
|
||||
|
||||
typedef std::vector<uint32_t> KeyPath;
|
||||
|
||||
/** Split a string on every instance of sep, returning a vector. */
|
||||
std::vector<Span<const char>> Split(const Span<const char>& sp, char sep)
|
||||
{
|
||||
std::vector<Span<const char>> ret;
|
||||
auto it = sp.begin();
|
||||
auto start = it;
|
||||
while (it != sp.end()) {
|
||||
if (*it == sep) {
|
||||
ret.emplace_back(start, it);
|
||||
start = it + 1;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
ret.emplace_back(start, it);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Parse a key path, being passed a split list of elements (the first element is ignored). */
|
||||
bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out)
|
||||
{
|
||||
for (size_t i = 1; i < split.size(); ++i) {
|
||||
Span<const char> elem = split[i];
|
||||
bool hardened = false;
|
||||
if (elem.size() > 0 && (elem[elem.size() - 1] == '\'' || elem[elem.size() - 1] == 'h')) {
|
||||
elem = elem.first(elem.size() - 1);
|
||||
hardened = true;
|
||||
}
|
||||
uint32_t p;
|
||||
if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p) || p > 0x7FFFFFFFUL) return false;
|
||||
out.push_back(p | (((uint32_t)hardened) << 31));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
|
||||
UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
|
|
@ -5159,7 +5214,7 @@ UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
|||
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
|
||||
throw std::runtime_error(
|
||||
"sendtomainchain "" amount ( subtractfeefromamount ) \n"
|
||||
"\nSends Liquid funds to the Bitcoin mainchain, through the federated withdraw mechanism. The wallet internally generates the returned `bitcoin_address` via `bitcoin_xpub` and `bip32_counter` previously set in `initpegoutwallet`. The counter will be incremented upon successful send, avoiding address re-use.\n"
|
||||
"\nSends Liquid funds to the Bitcoin mainchain, through the federated withdraw mechanism. The wallet internally generates the returned `bitcoin_address` via `bitcoin_descriptor` and `bip32_counter` previously set in `initpegoutwallet`. The counter will be incremented upon successful send, avoiding address re-use.\n"
|
||||
+ HelpRequiringPassphrase(pwallet) +
|
||||
"\nArguments:\n"
|
||||
"1. \"address\" (string, required) Must be \"\". Only for non-PAK `sendtomainchain` compatibility.\n"
|
||||
|
|
@ -5170,8 +5225,8 @@ UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
|||
"{\n"
|
||||
"\"bitcoin_address\" (string) The destination address on Bitcoin mainchain."
|
||||
"\"txid\" (string) Transaction ID of the resulting Liquid transaction\n"
|
||||
"\"bitcoin_xpub\" (string) The xpubkey of the child destination address.\n"
|
||||
"\"derivation_path\" (string) The derivation path in text that leads to `bitcoin_address` from the `bitcoin_xpub`.\n"
|
||||
"\"bitcoin_descriptor\" (string) The xpubkey of the child destination address.\n"
|
||||
"\"bip32_counter\" (string) The derivation counter for the `bitcoin_descriptor`.\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("sendtomainchain", "\"\" 0.1")
|
||||
|
|
@ -5213,9 +5268,34 @@ UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
|||
throw JSONRPCError(RPC_WALLET_ERROR, "Pegout authorization for this wallet has not been set. Please call `initpegoutwallet` with the appropriate arguments first.");
|
||||
}
|
||||
|
||||
std::vector<uint32_t> vPath;
|
||||
vPath.push_back(0);
|
||||
vPath.push_back((uint32_t)counter);
|
||||
FlatSigningProvider provider;
|
||||
const auto descriptor = Parse(pwallet->offline_desc, provider);
|
||||
|
||||
// If descriptor not previously set, generate it
|
||||
if (!descriptor) {
|
||||
std::string offline_desc = "pkh(" + EncodeExtPubKey(xpub) + "0/*)";
|
||||
if (!pwallet->SetOfflineDescriptor(offline_desc)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Couldn't set wallet descriptor for peg-outs.");
|
||||
}
|
||||
}
|
||||
|
||||
std::string desc_str = pwallet->offline_desc;
|
||||
std::string xpub_str = EncodeExtPubKey(xpub);
|
||||
|
||||
// TODO: More properly expose key parsing functionality
|
||||
|
||||
// Strip last parenths(up to 2) and "/*" to let ParseKeyPath do its thing
|
||||
desc_str.erase(std::remove(desc_str.begin(), desc_str.end(), ')'), desc_str.end());
|
||||
desc_str = desc_str.substr(0, desc_str.size()-2);
|
||||
|
||||
// Since we know there are no key origin data, directly call inner parsing functions
|
||||
Span<const char> span(desc_str.data(), desc_str.size());
|
||||
auto split = Split(span, '/');
|
||||
KeyPath key_path;
|
||||
if (!ParseKeyPath(split, key_path)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Stored keypath in descriptor cannot be parsed.");
|
||||
}
|
||||
key_path.push_back(counter);
|
||||
|
||||
secp256k1_pubkey onlinepubkey_secp;
|
||||
if (secp256k1_ec_pubkey_parse(secp256k1_ctx, &onlinepubkey_secp, onlinepubkey.begin(), onlinepubkey.size()) != 1) {
|
||||
|
|
@ -5249,7 +5329,7 @@ UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
|||
|
||||
// Make sure negated master pubkey is in PAK list at same index as online_pubkey
|
||||
if (memcmp((void *)&paklist.OfflineKeys()[whitelistindex], (void *)&masterpub_secp, sizeof(secp256k1_pubkey)) != 0) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Given bitcoin_xpub cannot be found in same entry as known liquid_pak");
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Given bitcoin_descriptor cannot be found in same entry as known liquid_pak");
|
||||
}
|
||||
|
||||
// Get online PAK
|
||||
|
|
@ -5259,7 +5339,7 @@ UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
|||
|
||||
// Tweak offline pubkey by tweakSum aka sumkey to get bitcoin key
|
||||
std::vector<unsigned char> tweakSum;
|
||||
if (!DerivePubTweak(vPath, xpub.pubkey, xpub.chaincode, tweakSum)) {
|
||||
if (!DerivePubTweak(key_path, xpub.pubkey, xpub.chaincode, tweakSum)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not create xpub tweak to generate proof.");
|
||||
}
|
||||
ret = secp256k1_ec_pubkey_tweak_add(secp256k1_ctx, &btcpub_secp, tweakSum.data());
|
||||
|
|
@ -5292,14 +5372,20 @@ UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
|||
assert(outlen == expectedOutputSize);
|
||||
std::vector<unsigned char> whitelistproof(output, output + expectedOutputSize / sizeof(unsigned char));
|
||||
|
||||
// Bitcoin address
|
||||
CTxDestination bitcoin_address = DeriveBitcoinOfflineAddress(xpub, counter);
|
||||
CScript scriptPubKeyMainchain(GetScriptForDestination(bitcoin_address));
|
||||
// Derive the end address in mainchain
|
||||
std::vector<CScript> scripts;
|
||||
if (!descriptor->Expand(counter, provider, scripts, provider)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not generate mainchain destination with descriptor. This is a bug.");
|
||||
}
|
||||
assert(scripts.size() == 1);
|
||||
CScript mainchain_script = scripts[0];
|
||||
CTxDestination bitcoin_address;
|
||||
ExtractDestination(mainchain_script, bitcoin_address);
|
||||
|
||||
uint256 genesisBlockHash = Params().ParentGenesisBlockHash();
|
||||
NullData nulldata;
|
||||
nulldata << std::vector<unsigned char>(genesisBlockHash.begin(), genesisBlockHash.end());
|
||||
nulldata << std::vector<unsigned char>(scriptPubKeyMainchain.begin(), scriptPubKeyMainchain.end());
|
||||
nulldata << std::vector<unsigned char>(mainchain_script.begin(), mainchain_script.end());
|
||||
nulldata << btcpubkeybytes;
|
||||
nulldata << whitelistproof;
|
||||
CTxDestination address(nulldata);
|
||||
|
|
@ -5321,9 +5407,9 @@ UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
|||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("txid", tx->GetHash().GetHex()));
|
||||
obj.push_back(Pair("bitcoin_address", EncodeDestination(bitcoin_address)));
|
||||
obj.push_back(Pair("derivation_path", "/0/"+ss.str()));
|
||||
obj.push_back(Pair("bitcoin_xpub", EncodeExtPubKey(xpub)));
|
||||
obj.push_back(Pair("bitcoin_address", EncodeParentDestination(bitcoin_address)));
|
||||
obj.push_back(Pair("bip32_counter", ss.str()));
|
||||
obj.push_back(Pair("bitcoin_descriptor", pwallet->offline_desc));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
|
@ -5708,7 +5794,7 @@ static const CRPCCommand commands[] =
|
|||
{ "wallet", "claimpegin", &claimpegin, {"bitcoin_tx", "txoutproof", "claim_script"} },
|
||||
{ "wallet", "createrawpegin", &createrawpegin, {"bitcoin_tx", "txoutproof", "claim_script"} },
|
||||
{ "wallet", "sendtomainchain", &sendtomainchain, {"address", "amount", "subtractfeefromamount"} },
|
||||
{ "wallet", "initpegoutwallet", &initpegoutwallet, {"bitcoin_xpub", "bip32_counter", "liquid_pak"} },
|
||||
{ "wallet", "initpegoutwallet", &initpegoutwallet, {"bitcoin_descriptor", "bip32_counter", "liquid_pak"} },
|
||||
{ "wallet", "getwalletpakinfo", &getwalletpakinfo, {} },
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue