Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ext keys #3

Merged
merged 7 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/addresstype.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <attributes.h>
#include <pubkey.h>
#include <script/script.h>
#include <silentpaymentkey.h>
#include <uint256.h>
#include <util/hash_type.h>

Expand Down Expand Up @@ -121,6 +122,20 @@ struct V0SilentPaymentDestination
CPubKey m_scan_pubkey;
CPubKey m_spend_pubkey;

V0SilentPaymentDestination() = default;

V0SilentPaymentDestination(const CPubKey& scan_pubkey, const CPubKey& spend_pubkey) : m_scan_pubkey(scan_pubkey), m_spend_pubkey(spend_pubkey) {};

V0SilentPaymentDestination(const SpPubKey& sppubkey) {
m_scan_pubkey = sppubkey.scanKey.GetPubKey();
m_spend_pubkey = sppubkey.spendKey;
}

V0SilentPaymentDestination(const SpKey& spkey) {
m_scan_pubkey = spkey.scanKey.GetPubKey();
m_spend_pubkey = spkey.spendKey.GetPubKey();
}

friend bool operator==(const V0SilentPaymentDestination& a, const V0SilentPaymentDestination& b) {
if (a.m_scan_pubkey != b.m_scan_pubkey) return false;
if (a.m_spend_pubkey != b.m_spend_pubkey) return false;
Expand Down
4 changes: 4 additions & 0 deletions src/rpc/output_script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ static RPCHelpMan deriveaddresses()

addresses.push_back(EncodeDestination(dest));
}

for (const auto& sppubkey_pair: provider.sppubkeys) {
addresses.push_back(EncodeDestination(V0SilentPaymentDestination(sppubkey_pair.second)));
}
Comment on lines +289 to +291
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in 06281a3:

silent payments aren't ranged, so not clear to me how this line would be hit if calling deriveaddresses with an sp descriptor?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code always runs at least once. See line 268

}

// This should not be possible, but an assert seems overkill:
Expand Down
129 changes: 95 additions & 34 deletions src/script/descriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,10 @@ class SilentPubkeyProvider final : public PubkeyProvider
std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint);
pubkey = m_sppk.scanKey.GetPubKey();
}
void SetSpendPubKey(const CPubKey pubkey)
{
m_sppk.spendKey = pubkey;
}
};

/** Base class for all Descriptor implementations. */
Expand Down Expand Up @@ -830,11 +834,20 @@ class DescriptorImpl : public Descriptor
for (const auto& p : m_pubkey_args) {
try {
auto sppubkeyprovider = dynamic_cast<SilentPubkeyProvider&>(*p);
SpKey key;
sppubkeyprovider.GetSpKey(provider, key);
out.spkeys.emplace(key.Neuter().GetID(), key);
out.keys.emplace(key.spendKey.GetPubKey().GetID(), key.spendKey);
out.keys.emplace(key.scanKey.GetPubKey().GetID(), key.scanKey);
{
SpKey key;
if (sppubkeyprovider.GetSpKey(provider, key)) {
out.spkeys.emplace(key.Neuter().GetID(), key);
out.keys.emplace(key.spendKey.GetPubKey().GetID(), key.spendKey);
out.keys.emplace(key.scanKey.GetPubKey().GetID(), key.scanKey);
}
}
{
SpPubKey key;
if (sppubkeyprovider.GetSpPubKey(key)) {
out.keys.emplace(key.scanKey.GetPubKey().GetID(), key.scanKey);
}
}
continue;
} catch (const std::bad_cast&) {}

Expand Down Expand Up @@ -1428,7 +1441,9 @@ enum class ParseScriptContext {
P2WPKH, //!< Inside wpkh() (no script, pubkey only)
P2WSH, //!< Inside wsh() (script becomes v0 witness script)
P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf)
SP, //!< Inside sp() (spkeys are only valid under sp())
SP_ONLY, //!< Argument inside sp(<spkey>) variant (spkeys are only valid under sp())
SP_SCAN, //!< First argument inside sp()
SP_SPEND, //!< Second argument inside sp()
};

/**
Expand Down Expand Up @@ -1484,6 +1499,10 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
error = "Hybrid public keys are not allowed";
return nullptr;
}
if (pubkey.IsValid() && ctx == ParseScriptContext::SP_SCAN) {
error = "Scan key must be a private key or extended private key";
return nullptr;
}
if (pubkey.IsFullyValid()) {
if (permit_uncompressed || pubkey.IsCompressed()) {
return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, false);
Expand Down Expand Up @@ -1520,24 +1539,17 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
CExtKey extkey = DecodeExtKey(str);
CExtPubKey extpubkey = DecodeExtPubKey(str);

if (ctx == ParseScriptContext::SP) {
if (extkey.key.IsValid() || extpubkey.pubkey.IsValid()) {
error = "extended keys are not allowed";
return nullptr;
}

if (ctx == ParseScriptContext::SP_ONLY) {
if (spkey.IsValid()) {
out.keys.emplace(spkey.scanKey.GetPubKey().GetID(), spkey.scanKey);
out.keys.emplace(spkey.spendKey.GetPubKey().GetID(), spkey.spendKey);
return std::make_unique<SilentPubkeyProvider>(key_exp_index, spkey.Neuter());
}

if (sppubkey.IsValid()) {
out.keys.emplace(sppubkey.scanKey.GetPubKey().GetID(), sppubkey.scanKey);
return std::make_unique<SilentPubkeyProvider>(key_exp_index, sppubkey);
}

error = "provided key is not a valid silent payment key";
error = strprintf("key '%s' is not a valid sp key", str);
return nullptr;
}

Expand All @@ -1556,10 +1568,57 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
type = DeriveType::HARDENED;
}
if (!ParseKeyPath(split, path, apostrophe, error)) return nullptr;

if (ctx == ParseScriptContext::SP_SCAN || ctx == ParseScriptContext::SP_SPEND) {
CExtKey derivedKey;
CExtPubKey derivedPubKey;
for (const auto& p : path) {
derivedKey = extkey;
derivedPubKey = extpubkey;

if (derivedKey.key.IsValid()) {
if (!derivedKey.Derive(derivedKey, p)) {
error = "Failed to derive key";
return nullptr;
}
}
if (derivedPubKey.pubkey.IsValid()) {
if (!derivedPubKey.Derive(derivedPubKey, p)) {
error = "Failed to derive key";
return nullptr;
}
}
}

if (ctx == ParseScriptContext::SP_SCAN) {
if (!extkey.key.IsValid()) {
error = "Scan key must be a private key or extended private key";
return nullptr;
}

// Derive sp scan key from extkey
CKey scan_key = derivedKey.key.IsValid() ? derivedKey.key : extkey.key;
return std::make_unique<SilentPubkeyProvider>(key_exp_index, SpPubKey(scan_key));
}

if (extkey.key.IsValid()) {
// Derive sp spend key from extkey
CKey spend_key = derivedKey.key.IsValid() ? derivedKey.key : extkey.key;
out.keys.emplace(spend_key.GetPubKey().GetID(), spend_key);
return std::make_unique<ConstPubkeyProvider>(key_exp_index, spend_key.GetPubKey(), false);
}
if (extpubkey.pubkey.IsValid()) {
// Derive sp spend key from extpubkey
CPubKey spend_key = derivedPubKey.pubkey.IsValid() ? derivedPubKey.pubkey : extpubkey.pubkey;
return std::make_unique<ConstPubkeyProvider>(key_exp_index, spend_key, false);
}
}

if (extkey.key.IsValid()) {
extpubkey = extkey.Neuter();
out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key);
}

return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type, apostrophe);
}

Expand Down Expand Up @@ -1744,39 +1803,41 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const

if (ctx == ParseScriptContext::TOP && Func("sp", expr)) {
auto arg1 = Expr(expr);
auto firstKey = ParsePubkey(key_exp_index, arg1, ParseScriptContext::SP, out, error);
if (!firstKey) {
if (!Const(",", expr)) {
auto sppKey = ParsePubkey(key_exp_index, arg1, ParseScriptContext::SP_ONLY, out, error);
if (!sppKey) {
error = strprintf("sp(): %s", error);
return nullptr;
}
++key_exp_index;
return std::make_unique<SpDescriptor>(std::move(sppKey));
}
auto arg2 = Expr(expr);

auto scanKey = ParsePubkey(key_exp_index, arg1, ParseScriptContext::SP_SCAN, out, error);
if (!scanKey) {
error = strprintf("sp(): %s", error);
return nullptr;
}
++key_exp_index;
if (!Const(",", expr)) {
return std::make_unique<SpDescriptor>(std::move(firstKey));
}
auto arg2 = Expr(expr);
auto spendKey = ParsePubkey(key_exp_index, arg2, ParseScriptContext::SP, out, error);

auto spendKey = ParsePubkey(key_exp_index, arg2, ParseScriptContext::SP_SPEND, out, error);
if (!spendKey) {
error = strprintf("sp(): %s", error);
return nullptr;
}
auto scanPubKey = firstKey->GetRootPubKey();
auto spendPubKey = spendKey->GetRootPubKey();
if (!scanPubKey.has_value()) {
error = "sp(): could not get scan pubkey";
return nullptr;
}
if (!spendPubKey.has_value()) {
error = "sp(): could not get spend pubkey";
return nullptr;
}
auto it = out.keys.find(scanPubKey->GetID());
if (it == out.keys.end()) {
error = "sp(): requires the scan priv key";
return nullptr;
}
auto sppk = std::make_unique<SilentPubkeyProvider>(key_exp_index, SpPubKey(it->second, *spendPubKey));

SilentPubkeyProvider* sppubKey = dynamic_cast<SilentPubkeyProvider*>(scanKey.get());
assert(sppubKey != nullptr);
sppubKey->SetSpendPubKey(*spendPubKey);

++key_exp_index;
return std::make_unique<SpDescriptor>(std::move(sppk));
return std::make_unique<SpDescriptor>(std::move(scanKey));
} else if (Func("sp", expr)) {
error = "Can only have sp() at top level";
return nullptr;
Expand Down
12 changes: 12 additions & 0 deletions src/silentpaymentkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ struct SpPubKey {

SpPubKey() = default;

/**
* Creates an incomplete SpPubKey.
*/
SpPubKey(CKey scan_key) : scanKey(scan_key)
{
memset(version, 0, sizeof(version));
memset(vchFingerprint, 0, sizeof(vchFingerprint));
maximumNumberOfLabels = 0;
CPubKey dummySpendPubKey;
spendKey = dummySpendPubKey;
}

SpPubKey(CKey scan_key, CPubKey spend_key) : scanKey(scan_key), spendKey(spend_key)
{
memset(version, 0, sizeof(version));
Expand Down
68 changes: 68 additions & 0 deletions src/test/descriptor_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,27 @@ void CheckInferDescriptor(const std::string& script_hex, const std::string& expe
BOOST_CHECK_EQUAL(desc->ToString(), expected_desc + "#" + checksum);
}

void CheckSilentPayments(const std::string& desc, const std::string& expected_private_string, const std::string& expected_public_string, const std::string& expected_norm_string, int flags = 0)
{
FlatSigningProvider keys;
std::string error;
auto parsed_desc = Parse(desc, keys, error, false);
BOOST_CHECK_MESSAGE(parsed_desc, error);

if (~flags & MISSING_PRIVKEYS) {
std::string private_string;
BOOST_CHECK(parsed_desc->ToPrivateString(keys, private_string));
BOOST_CHECK_MESSAGE(EqualDescriptor(private_string, expected_private_string), "Private: " + private_string + " Expected: " + expected_private_string);
}

std::string public_string = parsed_desc->ToString();
std::string norm_string;
BOOST_CHECK(parsed_desc->ToNormalizedString(keys, norm_string));

BOOST_CHECK_MESSAGE(EqualDescriptor(public_string, expected_public_string), "Public: " + public_string + " Expected: " + expected_public_string);
BOOST_CHECK_MESSAGE(EqualDescriptor(norm_string, expected_norm_string), "Normalized: " + norm_string + " Expected: " + expected_norm_string);
}

}

BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup)
Expand Down Expand Up @@ -657,6 +678,53 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
CheckInferDescriptor("76a914a31725c74421fadc50d35520ab8751ed120af80588ac", "pkh(04c56fe4a92d401bcbf1b3dfbe4ac3dac5602ca155a3681497f02c1b9a733b92d704e2da6ec4162e4846af9236ef4171069ac8b7f8234a8405b6cadd96f34f5a31)", {}, {{"04c56fe4a92d401bcbf1b3dfbe4ac3dac5602ca155a3681497f02c1b9a733b92d704e2da6ec4162e4846af9236ef4171069ac8b7f8234a8405b6cadd96f34f5a31", ""}});
// Infer pk() from p2pk with uncompressed key
CheckInferDescriptor("4104032540df1d3c7070a8ab3a9cdd304dfc7fd1e6541369c53c4c3310b2537d91059afc8b8e7673eb812a32978dabb78c40f2e423f7757dca61d11838c7aeeb5220ac", "pk(04032540df1d3c7070a8ab3a9cdd304dfc7fd1e6541369c53c4c3310b2537d91059afc8b8e7673eb812a32978dabb78c40f2e423f7757dca61d11838c7aeeb5220)", {}, {{"04032540df1d3c7070a8ab3a9cdd304dfc7fd1e6541369c53c4c3310b2537d91059afc8b8e7673eb812a32978dabb78c40f2e423f7757dca61d11838c7aeeb5220", ""}});

// Silent Payments
// Check that /* uses default derivation path for SP
CheckSilentPayments("sp(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/*)",
"sp(spprv1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5qw3uewwg77eaq9rth6er3vj0yutvs5xyup0ndsrg2ffwgheppkkdg3tdld9)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5pnngmqzvcpt976aaqlhevn5qkv2y7sk42j0mpd7yzsut507jwgtssknx4ck)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5pnngmqzvcpt976aaqlhevn5qkv2y7sk42j0mpd7yzsut507jwgtssknx4ck)");

// Check that no path uses default derivation path for SP
CheckSilentPayments("sp(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)",
"sp(spprv1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5qw3uewwg77eaq9rth6er3vj0yutvs5xyup0ndsrg2ffwgheppkkdg3tdld9)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5pnngmqzvcpt976aaqlhevn5qkv2y7sk42j0mpd7yzsut507jwgtssknx4ck)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5pnngmqzvcpt976aaqlhevn5qkv2y7sk42j0mpd7yzsut507jwgtssknx4ck)");

// Check that provided path is used instead of the default derivation path for SP
CheckSilentPayments("sp(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/0h)",
"sp(spprv1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5qwmvhpf70wwlfxmkfmfm8dargka4qgec2fkmxcpvr3tgkezxs2l6sf4t658)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5p457zxv2j2yzn9ha42hxhf3fkqdz5pc5hykqevp765qrrsdn7vc4skd7hrr)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5p457zxv2j2yzn9ha42hxhf3fkqdz5pc5hykqevp765qrrsdn7vc4skd7hrr)");

CheckSilentPayments("sp(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/0h,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)",
"sp(spprv1qqqqqqqqqqqqqq8dkts5l8h805ndmya5ank735tw6syvu9ymdnvqkpc45tv3rg90agqw3uewwg77eaq9rth6er3vj0yutvs5xyup0ndsrg2ffwgheppkkdgnemptj)",
"sp(sppub1qqqqqqqqqqqqqq8dkts5l8h805ndmya5ank735tw6syvu9ymdnvqkpc45tv3rg90agpnngmqzvcpt976aaqlhevn5qkv2y7sk42j0mpd7yzsut507jwgtss5pst7p)",
"sp(sppub1qqqqqqqqqqqqqq8dkts5l8h805ndmya5ank735tw6syvu9ymdnvqkpc45tv3rg90agpnngmqzvcpt976aaqlhevn5qkv2y7sk42j0mpd7yzsut507jwgtss5pst7p)");

// Check that xpubs are accepted for spend key
CheckSilentPayments("sp(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5pnngmqzvcpt976aaqlhevn5qkv2y7sk42j0mpd7yzsut507jwgtssknx4ck)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5pnngmqzvcpt976aaqlhevn5qkv2y7sk42j0mpd7yzsut507jwgtssknx4ck)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5pnngmqzvcpt976aaqlhevn5qkv2y7sk42j0mpd7yzsut507jwgtssknx4ck)",
MISSING_PRIVKEYS);

CheckSilentPayments("sp(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/0)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5p8cjcfl7ucts5c4ln7tqfjvm9ledmcpdyq4s55kz6rmssl903az0qr6947z)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5p8cjcfl7ucts5c4ln7tqfjvm9ledmcpdyq4s55kz6rmssl903az0qr6947z)",
"sp(sppub1qqqqqqqqqqqqqq8g7vh8y00v7sz34mav3ckf8jw9kg2rzwqhekcp59y5hytussmtx5p8cjcfl7ucts5c4ln7tqfjvm9ledmcpdyq4s55kz6rmssl903az0qr6947z)",
MISSING_PRIVKEYS);

CheckUnparsable("sp(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)",
"sp(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)",
"sp(): key 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8' is not a valid sp key");
CheckUnparsable("sp(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)",
"sp(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)",
"sp(): Scan key must be a private key or extended private key");
CheckUnparsable("sp(spprv1qqqqqqqqqqqqqqqcw78khf9nvvgng9a0vsnzgz9hc29vqta5gwhwls59y60grpjpnvqryr9c920g3tru2cs3nazwqjdapvhx249ph95zkp3scsg957vzqag8ml0eq,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)",
"sp(sppub1qqqqqqqqqqqqqqqcw78khf9nvvgng9a0vsnzgz9hc29vqta5gwhwls59y60grpjpnvper07qpyg2fg4r32cfll4wytqcf9km7zzjaqd0aw69y2tdw0g2y2cklv80y,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)",
"sp(): key 'sppub1qqqqqqqqqqqqqqqcw78khf9nvvgng9a0vsnzgz9hc29vqta5gwhwls59y60grpjpnvper07qpyg2fg4r32cfll4wytqcf9km7zzjaqd0aw69y2tdw0g2y2cklv80y' is not valid");
}

BOOST_AUTO_TEST_SUITE_END()
Loading
Loading