diff --git a/src/psbt.cpp b/src/psbt.cpp index 7ec9b9c136..76a2fd8241 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -132,6 +132,7 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const } for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey); } for (const auto& [hash, preimage] : ripemd160_preimages) { sigdata.ripemd160_preimages.emplace(std::vector(hash.begin(), hash.end()), preimage); @@ -246,6 +247,7 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const } for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey); } } diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 05808e4c22..11e1b4abb5 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -204,6 +204,13 @@ std::vector XOnlyPubKey::GetKeyIDs() const return out; } +CPubKey XOnlyPubKey::GetEvenCorrespondingCPubKey() const +{ + unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02}; + std::copy(begin(), end(), full_key + 1); + return CPubKey{full_key}; +} + bool XOnlyPubKey::IsFullyValid() const { secp256k1_xonly_pubkey pubkey; diff --git a/src/pubkey.h b/src/pubkey.h index 4b34fd829b..2b655c3f73 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -282,6 +282,8 @@ public: */ std::vector GetKeyIDs() const; + CPubKey GetEvenCorrespondingCPubKey() const; + const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); } const unsigned char* data() const { return m_keydata.begin(); } static constexpr size_t size() { return decltype(m_keydata)::size(); } diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 896fb0b5b3..ba2334df49 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -1114,16 +1114,33 @@ public: class ScriptMaker { //! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args). const std::vector& m_keys; + //! The script context we're operating within (Tapscript or P2WSH). + const miniscript::MiniscriptContext m_script_ctx; + + //! Get the ripemd160(sha256()) hash of this key. + //! Any key that is valid in a descriptor serializes as 32 bytes within a Tapscript context. So we + //! must not hash the sign-bit byte in this case. + uint160 GetHash160(uint32_t key) const { + if (miniscript::IsTapscript(m_script_ctx)) { + return Hash160(XOnlyPubKey{m_keys[key]}); + } + return m_keys[key].GetID(); + } public: - ScriptMaker(const std::vector& keys LIFETIMEBOUND) : m_keys(keys) {} + ScriptMaker(const std::vector& keys LIFETIMEBOUND, const miniscript::MiniscriptContext script_ctx) : m_keys(keys), m_script_ctx{script_ctx} {} std::vector ToPKBytes(uint32_t key) const { - return {m_keys[key].begin(), m_keys[key].end()}; + // In Tapscript keys always serialize as x-only, whether an x-only key was used in the descriptor or not. + if (!miniscript::IsTapscript(m_script_ctx)) { + return {m_keys[key].begin(), m_keys[key].end()}; + } + const XOnlyPubKey xonly_pubkey{m_keys[key]}; + return {xonly_pubkey.begin(), xonly_pubkey.end()}; } std::vector ToPKHBytes(uint32_t key) const { - auto id = m_keys[key].GetID(); + auto id = GetHash160(key); return {id.begin(), id.end()}; } }; @@ -1164,8 +1181,15 @@ protected: std::vector MakeScripts(const std::vector& keys, Span scripts, FlatSigningProvider& provider) const override { - for (const auto& key : keys) provider.pubkeys.emplace(key.GetID(), key); - return Vector(m_node->ToScript(ScriptMaker(keys))); + const auto script_ctx{m_node->GetMsCtx()}; + for (const auto& key : keys) { + if (miniscript::IsTapscript(script_ctx)) { + provider.pubkeys.emplace(Hash160(XOnlyPubKey{key}), key); + } else { + provider.pubkeys.emplace(key.GetID(), key); + } + } + return Vector(m_node->ToScript(ScriptMaker(keys, script_ctx))); } public: @@ -1401,9 +1425,7 @@ std::unique_ptr InferPubkey(const CPubKey& pubkey, ParseScriptCo std::unique_ptr InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider) { - unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02}; - std::copy(xkey.begin(), xkey.end(), full_key + 1); - CPubKey pubkey(full_key); + CPubKey pubkey{xkey.GetEvenCorrespondingCPubKey()}; std::unique_ptr key_provider = std::make_unique(0, pubkey, true); KeyOriginInfo info; if (provider.GetKeyOriginByXOnly(xkey, info)) { @@ -1426,18 +1448,32 @@ struct KeyParser { mutable std::vector> m_keys; //! Used to detect key parsing errors within a Miniscript. mutable std::string m_key_parsing_error; + //! The script context we're operating within (Tapscript or P2WSH). + const miniscript::MiniscriptContext m_script_ctx; + //! The number of keys that were parsed before starting to parse this Miniscript descriptor. + uint32_t m_offset; - KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND) : m_out(out), m_in(in) {} + KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND, + miniscript::MiniscriptContext ctx, uint32_t offset = 0) + : m_out(out), m_in(in), m_script_ctx(ctx), m_offset(offset) {} bool KeyCompare(const Key& a, const Key& b) const { return *m_keys.at(a) < *m_keys.at(b); } + ParseScriptContext ParseContext() const { + switch (m_script_ctx) { + case miniscript::MiniscriptContext::P2WSH: return ParseScriptContext::P2WSH; + case miniscript::MiniscriptContext::TAPSCRIPT: return ParseScriptContext::P2TR; + } + assert(false); + } + template std::optional FromString(I begin, I end) const { assert(m_out); Key key = m_keys.size(); - auto pk = ParsePubkey(key, {&*begin, &*end}, ParseScriptContext::P2WSH, *m_out, m_key_parsing_error); + auto pk = ParsePubkey(m_offset + key, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error); if (!pk) return {}; m_keys.push_back(std::move(pk)); return key; @@ -1451,11 +1487,18 @@ struct KeyParser { template std::optional FromPKBytes(I begin, I end) const { assert(m_in); - CPubKey pubkey(begin, end); - if (pubkey.IsValidNonHybrid()) { - Key key = m_keys.size(); - m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in)); + Key key = m_keys.size(); + if (miniscript::IsTapscript(m_script_ctx) && end - begin == 32) { + XOnlyPubKey pubkey; + std::copy(begin, end, pubkey.begin()); + m_keys.push_back(InferPubkey(pubkey.GetEvenCorrespondingCPubKey(), ParseContext(), *m_in)); return key; + } else if (!miniscript::IsTapscript(m_script_ctx)) { + CPubKey pubkey{begin, end}; + if (pubkey.IsValidNonHybrid()) { + m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in)); + return key; + } } return {}; } @@ -1470,11 +1513,15 @@ struct KeyParser { CPubKey pubkey; if (m_in->GetPubKey(keyid, pubkey)) { Key key = m_keys.size(); - m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in)); + m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in)); return key; } return {}; } + + miniscript::MiniscriptContext MsContext() const { + return m_script_ctx; + } }; /** Parse a script in a particular context. */ @@ -1500,8 +1547,9 @@ std::unique_ptr ParseScript(uint32_t& key_exp_index, Span(std::move(pubkey)); - } else if (Func("pkh", expr)) { - error = "Can only have pkh at top level, in sh(), or in wsh()"; + } else if (ctx != ParseScriptContext::P2TR && Func("pkh", expr)) { + // Under Taproot, always the Miniscript parser deal with it. + error = "Can only have pkh at top level, in sh(), wsh(), or in tr()"; return nullptr; } if (ctx == ParseScriptContext::TOP && Func("combo", expr)) { @@ -1714,11 +1762,12 @@ std::unique_ptr ParseScript(uint32_t& key_exp_index, Span ParseScript(uint32_t& key_exp_index, Span(std::move(parser.m_keys), std::move(node)); } } @@ -1886,8 +1936,9 @@ std::unique_ptr InferScript(const CScript& script, ParseScriptCo } } - if (ctx == ParseScriptContext::P2WSH) { - KeyParser parser(nullptr, &provider); + if (ctx == ParseScriptContext::P2WSH || ctx == ParseScriptContext::P2TR) { + const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT}; + KeyParser parser(/* out = */nullptr, /* in = */&provider, /* ctx = */script_ctx); auto node = miniscript::FromScript(script, parser); if (node && node->IsSane()) { return std::make_unique(std::move(parser.m_keys), std::move(node)); diff --git a/src/script/miniscript.cpp b/src/script/miniscript.cpp index 19556a9775..344a81bdf0 100644 --- a/src/script/miniscript.cpp +++ b/src/script/miniscript.cpp @@ -6,6 +6,7 @@ #include #include