Skip to content

Commit 4f473ea

Browse files
committed
script/sign: Miniscript support in Tapscript
We make the Satisfier a base in which to store the common methods between the Tapscript and P2WSH satisfier, and from which they both inherit. A field is added to SignatureData to be able to satisfy pkh() under Tapscript context (to get the pubkey hash preimage) without wallet data. For instance in `finalizepsbt` RPC. See also the next commits for a functional test that exercises this.
1 parent febe2ab commit 4f473ea

File tree

4 files changed

+97
-68
lines changed

4 files changed

+97
-68
lines changed

src/psbt.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const
132132
}
133133
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
134134
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
135+
sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey);
135136
}
136137
for (const auto& [hash, preimage] : ripemd160_preimages) {
137138
sigdata.ripemd160_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage);
@@ -246,6 +247,7 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
246247
}
247248
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
248249
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
250+
sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey);
249251
}
250252
}
251253

src/script/sign.cpp

Lines changed: 88 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,17 @@ static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigd
114114
pubkey = it->second.first;
115115
return true;
116116
}
117-
// Look for pubkey in pubkey list
117+
// Look for pubkey in pubkey lists
118118
const auto& pk_it = sigdata.misc_pubkeys.find(address);
119119
if (pk_it != sigdata.misc_pubkeys.end()) {
120120
pubkey = pk_it->second.first;
121121
return true;
122122
}
123+
const auto& tap_pk_it = sigdata.tap_pubkeys.find(address);
124+
if (tap_pk_it != sigdata.tap_pubkeys.end()) {
125+
pubkey = tap_pk_it->second.GetEvenCorrespondingCPubKey();
126+
return true;
127+
}
123128
// Query the underlying provider
124129
return provider.GetPubKey(address, pubkey);
125130
}
@@ -186,41 +191,35 @@ miniscript::Availability MsLookupHelper(const M& map, const K& key, V& value)
186191
* Context for solving a Miniscript.
187192
* If enough material (access to keys, hash preimages, ..) is given, produces a valid satisfaction.
188193
*/
194+
template<typename Pk>
189195
struct Satisfier {
190-
typedef CPubKey Key;
196+
using Key = Pk;
191197

192198
const SigningProvider& m_provider;
193199
SignatureData& m_sig_data;
194200
const BaseSignatureCreator& m_creator;
195201
const CScript& m_witness_script;
196-
//! For now Miniscript is only available under P2WSH.
197-
const miniscript::MiniscriptContext m_script_ctx{miniscript::MiniscriptContext::P2WSH};
202+
//! The context of the script we are satisfying (either P2WSH or Tapscript).
203+
const miniscript::MiniscriptContext m_script_ctx;
198204

199205
explicit Satisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND,
200206
const BaseSignatureCreator& creator LIFETIMEBOUND,
201-
const CScript& witscript LIFETIMEBOUND) : m_provider(provider),
202-
m_sig_data(sig_data),
203-
m_creator(creator),
204-
m_witness_script(witscript) {}
207+
const CScript& witscript LIFETIMEBOUND,
208+
miniscript::MiniscriptContext script_ctx) : m_provider(provider),
209+
m_sig_data(sig_data),
210+
m_creator(creator),
211+
m_witness_script(witscript),
212+
m_script_ctx(script_ctx) {}
205213

206214
static bool KeyCompare(const Key& a, const Key& b) {
207215
return a < b;
208216
}
209217

210-
//! Conversion from a raw public key.
211-
template <typename I>
212-
std::optional<Key> FromPKBytes(I first, I last) const
213-
{
214-
Key pubkey{first, last};
215-
if (pubkey.IsValid()) return pubkey;
216-
return {};
217-
}
218-
219-
//! Conversion from a raw public key hash.
218+
//! Get a CPubKey from a key hash. Note the key hash may be of an xonly pubkey.
220219
template<typename I>
221-
std::optional<Key> FromPKHBytes(I first, I last) const {
220+
std::optional<CPubKey> CPubFromPKHBytes(I first, I last) const {
222221
assert(last - first == 20);
223-
Key pubkey;
222+
CPubKey pubkey;
224223
CKeyID key_id;
225224
std::copy(first, last, key_id.begin());
226225
if (GetPubKey(m_provider, m_sig_data, key_id, pubkey)) return pubkey;
@@ -229,21 +228,12 @@ struct Satisfier {
229228
}
230229

231230
//! Conversion to raw public key.
232-
std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; }
233-
234-
//! Satisfy a signature check.
235-
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
236-
if (CreateSig(m_creator, m_sig_data, m_provider, sig, key, m_witness_script, SigVersion::WITNESS_V0)) {
237-
return miniscript::Availability::YES;
238-
}
239-
return miniscript::Availability::NO;
240-
}
231+
std::vector<unsigned char> ToPKBytes(const Key& key) const { return {key.begin(), key.end()}; }
241232

242233
//! Time lock satisfactions.
243234
bool CheckAfter(uint32_t value) const { return m_creator.Checker().CheckLockTime(CScriptNum(value)); }
244235
bool CheckOlder(uint32_t value) const { return m_creator.Checker().CheckSequence(CScriptNum(value)); }
245236

246-
247237
//! Hash preimage satisfactions.
248238
miniscript::Availability SatSHA256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
249239
return MsLookupHelper(m_sig_data.sha256_preimages, hash, preimage);
@@ -263,49 +253,81 @@ struct Satisfier {
263253
}
264254
};
265255

266-
static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, Span<const unsigned char> script_bytes, std::vector<valtype>& result)
267-
{
268-
// Only BIP342 tapscript signing is supported for now.
269-
if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false;
270-
SigVersion sigversion = SigVersion::TAPSCRIPT;
256+
/** Miniscript satisfier specific to P2WSH context. */
257+
struct WshSatisfier: Satisfier<CPubKey> {
258+
explicit WshSatisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND,
259+
const BaseSignatureCreator& creator LIFETIMEBOUND, const CScript& witscript LIFETIMEBOUND)
260+
: Satisfier(provider, sig_data, creator, witscript, miniscript::MiniscriptContext::P2WSH) {}
271261

272-
uint256 leaf_hash = ComputeTapleafHash(leaf_version, script_bytes);
273-
CScript script = CScript(script_bytes.begin(), script_bytes.end());
262+
//! Conversion from a raw compressed public key.
263+
template <typename I>
264+
std::optional<CPubKey> FromPKBytes(I first, I last) const {
265+
CPubKey pubkey{first, last};
266+
if (pubkey.IsValid()) return pubkey;
267+
return {};
268+
}
274269

275-
// <xonly pubkey> OP_CHECKSIG
276-
if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) {
277-
XOnlyPubKey pubkey{Span{script}.subspan(1, 32)};
278-
std::vector<unsigned char> sig;
279-
if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) {
280-
result = Vector(std::move(sig));
281-
return true;
282-
}
283-
return false;
270+
//! Conversion from a raw compressed public key hash.
271+
template<typename I>
272+
std::optional<CPubKey> FromPKHBytes(I first, I last) const {
273+
return Satisfier::CPubFromPKHBytes(first, last);
284274
}
285275

286-
// multi_a scripts (<key> OP_CHECKSIG <key> OP_CHECKSIGADD <key> OP_CHECKSIGADD <k> OP_NUMEQUAL)
287-
if (auto match = MatchMultiA(script)) {
288-
std::vector<std::vector<unsigned char>> sigs;
289-
int good_sigs = 0;
290-
for (size_t i = 0; i < match->second.size(); ++i) {
291-
XOnlyPubKey pubkey{*(match->second.rbegin() + i)};
292-
std::vector<unsigned char> sig;
293-
bool good_sig = CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion);
294-
if (good_sig && good_sigs < match->first) {
295-
++good_sigs;
296-
sigs.push_back(std::move(sig));
297-
} else {
298-
sigs.emplace_back();
299-
}
276+
//! Satisfy an ECDSA signature check.
277+
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
278+
if (CreateSig(m_creator, m_sig_data, m_provider, sig, key, m_witness_script, SigVersion::WITNESS_V0)) {
279+
return miniscript::Availability::YES;
300280
}
301-
if (good_sigs == match->first) {
302-
result = std::move(sigs);
303-
return true;
281+
return miniscript::Availability::NO;
282+
}
283+
};
284+
285+
/** Miniscript satisfier specific to Tapscript context. */
286+
struct TapSatisfier: Satisfier<XOnlyPubKey> {
287+
const uint256& m_leaf_hash;
288+
289+
explicit TapSatisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND,
290+
const BaseSignatureCreator& creator LIFETIMEBOUND, const CScript& script LIFETIMEBOUND,
291+
const uint256& leaf_hash LIFETIMEBOUND)
292+
: Satisfier(provider, sig_data, creator, script, miniscript::MiniscriptContext::TAPSCRIPT),
293+
m_leaf_hash(leaf_hash) {}
294+
295+
//! Conversion from a raw xonly public key.
296+
template <typename I>
297+
std::optional<XOnlyPubKey> FromPKBytes(I first, I last) const {
298+
CHECK_NONFATAL(last - first == 32);
299+
XOnlyPubKey pubkey;
300+
std::copy(first, last, pubkey.begin());
301+
return pubkey;
302+
}
303+
304+
//! Conversion from a raw xonly public key hash.
305+
template<typename I>
306+
std::optional<XOnlyPubKey> FromPKHBytes(I first, I last) const {
307+
if (auto pubkey = Satisfier::CPubFromPKHBytes(first, last)) return XOnlyPubKey{*pubkey};
308+
return {};
309+
}
310+
311+
//! Satisfy a BIP340 signature check.
312+
miniscript::Availability Sign(const XOnlyPubKey& key, std::vector<unsigned char>& sig) const {
313+
if (CreateTaprootScriptSig(m_creator, m_sig_data, m_provider, sig, key, m_leaf_hash, SigVersion::TAPSCRIPT)) {
314+
return miniscript::Availability::YES;
304315
}
305-
return false;
316+
return miniscript::Availability::NO;
306317
}
318+
};
307319

308-
return false;
320+
static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, Span<const unsigned char> script_bytes, std::vector<valtype>& result)
321+
{
322+
// Only BIP342 tapscript signing is supported for now.
323+
if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false;
324+
325+
uint256 leaf_hash = ComputeTapleafHash(leaf_version, script_bytes);
326+
CScript script = CScript(script_bytes.begin(), script_bytes.end());
327+
328+
TapSatisfier ms_satisfier{provider, sigdata, creator, script, leaf_hash};
329+
const auto ms = miniscript::FromScript(script, ms_satisfier);
330+
return ms && ms->Satisfy(ms_satisfier, result) == miniscript::Availability::YES;
309331
}
310332

311333
static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result)
@@ -518,7 +540,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
518540
// isn't fully solved. For instance the CHECKMULTISIG satisfaction in SignStep() pushes partial signatures
519541
// and the extractor relies on this behaviour to combine witnesses.
520542
if (!solved && result.empty()) {
521-
Satisfier ms_satisfier{provider, sigdata, creator, witnessscript};
543+
WshSatisfier ms_satisfier{provider, sigdata, creator, witnessscript};
522544
const auto ms = miniscript::FromScript(witnessscript, ms_satisfier);
523545
solved = ms && ms->Satisfy(ms_satisfier, result) == miniscript::Availability::YES;
524546
}

src/script/sign.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ struct SignatureData {
7979
std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending
8080
std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash.
8181
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal key (may have no leaf script hashes).
82+
std::map<CKeyID, XOnlyPubKey> tap_pubkeys; ///< Misc Taproot pubkeys involved in this input, by hash. (Equivalent of misc_pubkeys but for Taproot.)
8283
std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found
8384
std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found
8485
uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any)

0 commit comments

Comments
 (0)