Skip to content

Commit

Permalink
descriptor: Allow extended keys in sp()
Browse files Browse the repository at this point in the history
  • Loading branch information
Eunovo committed Jul 22, 2024
1 parent af3927d commit 6d97c18
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 21 deletions.
108 changes: 87 additions & 21 deletions src/script/descriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -830,11 +830,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 +1437,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 +1495,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,12 +1535,7 @@ 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);
Expand All @@ -1537,7 +1547,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
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 +1566,58 @@ 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 : DeriveScanKey(extkey);
out.keys.emplace(scan_key.GetPubKey().GetID(), scan_key);
return std::make_unique<ConstPubkeyProvider>(key_exp_index, scan_key.GetPubKey(), false);
}

if (extkey.key.IsValid()) {
// Derive sp spend key from extkey
CKey spend_key = derivedKey.key.IsValid() ? derivedKey.key : DeriveSpendKey(extkey);
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 : DeriveSpendPubKey(extpubkey);
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,22 +1802,30 @@ 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 scanPubKey = scanKey->GetRootPubKey();
auto spendPubKey = spendKey->GetRootPubKey();
if (!scanPubKey.has_value()) {
error = "sp(): could not get scan pubkey";
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(spprv1qqqqqqqqqqqqqqqcw78khf9nvvgng9a0vsnzgz9hc29vqta5gwhwls59y60grpjpnvqryr9c920g3tru2cs3nazwqjdapvhx249ph95zkp3scsg957vzqag8ml0eq)",
"sp(sppub1qqqqqqqqqqqqqqqcw78khf9nvvgng9a0vsnzgz9hc29vqta5gwhwls59y60grpjpnvper07qpyg2fg4r32cfll4wytqcf9km7zzjaqd0aw69y2tdw0g2y2cklv80y)",
"sp(sppub1qqqqqqqqqqqqqqqcw78khf9nvvgng9a0vsnzgz9hc29vqta5gwhwls59y60grpjpnvper07qpyg2fg4r32cfll4wytqcf9km7zzjaqd0aw69y2tdw0g2y2cklv80y)");

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

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

CheckSilentPayments("sp(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/0h,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)",
"sp(spprv1qqqqqqqqqqqqqq8dkts5l8h805ndmya5ank735tw6syvu9ymdnvqkpc45tv3rg90agqryr9c920g3tru2cs3nazwqjdapvhx249ph95zkp3scsg957vzqaggc43p3)",
"sp(sppub1qqqqqqqqqqqqqq8dkts5l8h805ndmya5ank735tw6syvu9ymdnvqkpc45tv3rg90agper07qpyg2fg4r32cfll4wytqcf9km7zzjaqd0aw69y2tdw0g2y2ceuxeh4)",
"sp(sppub1qqqqqqqqqqqqqq8dkts5l8h805ndmya5ank735tw6syvu9ymdnvqkpc45tv3rg90agper07qpyg2fg4r32cfll4wytqcf9km7zzjaqd0aw69y2tdw0g2y2ceuxeh4)");

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

CheckSilentPayments("sp(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/0)",
"sp(sppub1qqqqqqqqqqqqqqqcw78khf9nvvgng9a0vsnzgz9hc29vqta5gwhwls59y60grpjpnvp8cjcfl7ucts5c4ln7tqfjvm9ledmcpdyq4s55kz6rmssl903az0qwte4qy)",
"sp(sppub1qqqqqqqqqqqqqqqcw78khf9nvvgng9a0vsnzgz9hc29vqta5gwhwls59y60grpjpnvp8cjcfl7ucts5c4ln7tqfjvm9ledmcpdyq4s55kz6rmssl903az0qwte4qy)",
"sp(sppub1qqqqqqqqqqqqqqqcw78khf9nvvgng9a0vsnzgz9hc29vqta5gwhwls59y60grpjpnvp8cjcfl7ucts5c4ln7tqfjvm9ledmcpdyq4s55kz6rmssl903az0qwte4qy)",
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()

0 comments on commit 6d97c18

Please sign in to comment.