Skip to content

Commit dc9a67a

Browse files
committed
bitcoin-util: introduce evalscript subcommand
1 parent 10ddffe commit dc9a67a

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed

src/bitcoin-util.cpp

+243
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
#include <common/system.h>
1414
#include <compat/compat.h>
1515
#include <core_io.h>
16+
#include <deploymentinfo.h>
17+
#include <policy/policy.h>
18+
#include <script/interpreter.h>
1619
#include <streams.h>
20+
#include <univalue.h>
21+
#include <util/check.h>
1722
#include <util/exception.h>
1823
#include <util/strencodings.h>
1924
#include <util/translation.h>
@@ -34,7 +39,15 @@ static void SetupBitcoinUtilArgs(ArgsManager &argsman)
3439

3540
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
3641

42+
// evalscript options
43+
argsman.AddArg("-sigversion", "Specify a script sigversion (base, witness_v0, tapscript).", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
44+
argsman.AddArg("-script_flags", "Specify SCRIPT_VERIFY flags.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
45+
argsman.AddArg("-tx", "The tx (hex encoded)", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
46+
argsman.AddArg("-input", "The index of the input being spent", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
47+
argsman.AddArg("-spent_output", "The spent prevouts (hex encode TxOut, may be specified multiple times).", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
48+
3749
argsman.AddCommand("grind", "Perform proof of work on hex header string");
50+
argsman.AddCommand("evalscript", "Interpret a bitcoin script", {"-sigversion", "-script_flags", "-tx", "-input", "-spent_output"});
3851

3952
SetupChainParamsBaseOptions(argsman);
4053
}
@@ -146,6 +159,234 @@ static int Grind(const std::vector<std::string>& args, std::string& strPrint)
146159
return EXIT_SUCCESS;
147160
}
148161

162+
static UniValue stack2uv(const std::vector<std::vector<unsigned char>>& stack)
163+
{
164+
UniValue result{UniValue::VARR};
165+
for (const auto& v : stack) {
166+
result.push_back(HexStr(v));
167+
}
168+
return result;
169+
}
170+
171+
static std::string sigver2str(SigVersion sigver)
172+
{
173+
switch(sigver) {
174+
case SigVersion::BASE: return "base";
175+
case SigVersion::WITNESS_V0: return "witness_v0";
176+
case SigVersion::TAPROOT: return "taproot";
177+
case SigVersion::TAPSCRIPT: return "tapscript";
178+
}
179+
return "unknown";
180+
}
181+
182+
static uint32_t parse_verify_flags(const std::string& strFlags)
183+
{
184+
if (strFlags.empty() || strFlags == "MANDATORY") return MANDATORY_SCRIPT_VERIFY_FLAGS;
185+
if (strFlags == "STANDARD") return STANDARD_SCRIPT_VERIFY_FLAGS;
186+
if (strFlags == "NONE") return 0;
187+
188+
unsigned int flags = 0;
189+
std::vector<std::string> words = util::SplitString(strFlags, ',');
190+
191+
for (const std::string& word : words)
192+
{
193+
if (!g_verify_flag_names.count(word)) continue;
194+
flags |= g_verify_flag_names.at(word);
195+
}
196+
return flags;
197+
}
198+
199+
//! Public key to be used as internal key for dummy Taproot spends.
200+
static const std::vector<unsigned char> NUMS_H{ParseHex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")};
201+
202+
namespace {
203+
/** Dummy signature checker which accepts all signatures. */
204+
class DummySignatureChecker final : public BaseSignatureChecker
205+
{
206+
public:
207+
DummySignatureChecker() = default;
208+
bool CheckECDSASignature(const std::vector<unsigned char>& sig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return sig.size() != 0; }
209+
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const override { return sig.size() != 0; }
210+
bool CheckLockTime(const CScriptNum& nLockTime) const override { return true; }
211+
bool CheckSequence(const CScriptNum& nSequence) const override { return true; }
212+
};
213+
}
214+
215+
static int EvalScript(const ArgsManager& argsman, const std::vector<std::string>& args, std::string& strPrint)
216+
{
217+
UniValue result{UniValue::VOBJ};
218+
219+
uint32_t flags{0};
220+
PrecomputedTransactionData txdata;
221+
ScriptExecutionData execdata;
222+
223+
std::unique_ptr<const CTransaction> txTo;
224+
std::unique_ptr<BaseSignatureChecker> checker;
225+
226+
SigVersion sigversion = SigVersion::WITNESS_V0;
227+
228+
if (const auto verstr = argsman.GetArg("-sigversion"); verstr.has_value()) {
229+
if (*verstr == "base") {
230+
sigversion = SigVersion::BASE;
231+
} else if (*verstr == "witness_v0") {
232+
sigversion = SigVersion::WITNESS_V0;
233+
} else if (*verstr == "tapscript") {
234+
sigversion = SigVersion::TAPSCRIPT;
235+
} else {
236+
strPrint = strprintf("Unknown -sigversion=%s", *verstr);
237+
return EXIT_FAILURE;
238+
}
239+
}
240+
241+
const auto verifystr = argsman.GetArg("-script_flags");
242+
flags = parse_verify_flags(verifystr.value_or(""));
243+
244+
CScript script{};
245+
std::vector<std::vector<unsigned char> > stack{};
246+
if (args.size() > 0) {
247+
if (IsHex(args[0])) {
248+
auto h = ParseHex(args[0]);
249+
script = CScript(h.begin(), h.end());
250+
} else {
251+
script = ParseScript(args[0]);
252+
}
253+
254+
for (size_t i = 1; i < args.size(); ++i) {
255+
if (args[i].size() == 0) {
256+
stack.push_back({});
257+
} else if (IsHex(args[i])) {
258+
stack.push_back(ParseHex(args[i]));
259+
} else {
260+
strPrint = strprintf("Initial stack element not valid hex: %s", args[i]);
261+
return EXIT_FAILURE;
262+
}
263+
}
264+
}
265+
266+
if (const auto txhex = argsman.GetArg("-tx"); txhex.has_value()) {
267+
const int input = argsman.GetIntArg("-input", 0);
268+
const auto spent_outputs_hex = argsman.GetArgs("-spent_output");
269+
270+
CMutableTransaction mut_tx;
271+
if (!DecodeHexTx(mut_tx, *txhex)) {
272+
strPrint = "Could not decode transaction from -tx argument";
273+
return EXIT_FAILURE;
274+
}
275+
txTo = std::make_unique<CTransaction>(mut_tx);
276+
277+
if (spent_outputs_hex.size() != txTo->vin.size()) {
278+
strPrint = "When -tx is specified, must specify exactly one -spent_output for each input";
279+
return EXIT_FAILURE;
280+
}
281+
282+
std::vector<CTxOut> spent_outputs;
283+
for (const auto& outhex : spent_outputs_hex) {
284+
bool ok = false;
285+
if (IsHex(outhex)) {
286+
CTxOut txout;
287+
std::vector<unsigned char> out(ParseHex(outhex));
288+
DataStream ss(out);
289+
try {
290+
ss >> txout;
291+
if (ss.empty()) {
292+
spent_outputs.push_back(txout);
293+
ok = true;
294+
}
295+
} catch (const std::exception&) {
296+
// fall through
297+
}
298+
}
299+
if (!ok) {
300+
strPrint = strprintf("Could not parse -spent_output=%s", outhex);
301+
return EXIT_FAILURE;
302+
}
303+
}
304+
305+
const bool input_in_range = input >= 0 && static_cast<size_t>(input) < spent_outputs.size();
306+
CAmount amount = (input_in_range ? spent_outputs.at(input).nValue : 0);
307+
txdata.Init(*txTo, std::move(spent_outputs), /*force=*/true);
308+
checker = std::make_unique<TransactionSignatureChecker>(txTo.get(), input, amount, txdata, MissingDataBehavior::ASSERT_FAIL);
309+
310+
if (sigversion == SigVersion::TAPSCRIPT && input >= 0 && input_in_range) {
311+
const CTxIn& txin = txTo->vin.at(input);
312+
execdata.m_annex_present = false;
313+
if (txin.scriptWitness.stack.size() <= 1) {
314+
// either key path spend or no witness, so nothing to do here
315+
} else {
316+
const auto& top = txin.scriptWitness.stack.back();
317+
if (top.size() >= 1 && top.at(0) == 0x50) {
318+
execdata.m_annex_hash = (HashWriter{} << top).GetSHA256();
319+
execdata.m_annex_present = true;
320+
}
321+
}
322+
execdata.m_annex_init = true;
323+
execdata.m_tapleaf_hash = ComputeTapleafHash(TAPROOT_LEAF_TAPSCRIPT & TAPROOT_LEAF_MASK, script);
324+
execdata.m_tapleaf_hash_init = true;
325+
execdata.m_validation_weight_left = ::GetSerializeSize(stack) + ::GetSerializeSize(script) + VALIDATION_WEIGHT_OFFSET;
326+
execdata.m_validation_weight_left_init = true;
327+
}
328+
} else {
329+
checker = std::make_unique<DummySignatureChecker>();
330+
}
331+
332+
if (sigversion == SigVersion::TAPSCRIPT && !execdata.m_annex_init) {
333+
execdata.m_annex_present = false;
334+
execdata.m_annex_init = true;
335+
execdata.m_tapleaf_hash = uint256::ZERO;
336+
execdata.m_tapleaf_hash_init = true;
337+
execdata.m_validation_weight_left = ::GetSerializeSize(stack) + ::GetSerializeSize(script) + VALIDATION_WEIGHT_OFFSET;
338+
execdata.m_validation_weight_left_init = true;
339+
}
340+
341+
ScriptError serror{};
342+
343+
UniValue uv_flags{UniValue::VARR};
344+
for (const auto& el : GetScriptFlagNames(flags)) {
345+
uv_flags.push_back(el);
346+
}
347+
UniValue uv_script{UniValue::VOBJ};
348+
ScriptToUniv(script, uv_script);
349+
result.pushKV("script", uv_script);
350+
result.pushKV("sigversion", sigver2str(sigversion));
351+
result.pushKV("script_flags", uv_flags);
352+
353+
std::optional<bool> opsuccess_check;
354+
if (sigversion == SigVersion::TAPSCRIPT) {
355+
opsuccess_check = CheckTapscriptOpSuccess(script, flags, &serror);
356+
}
357+
358+
bool success = (opsuccess_check.has_value() ? *opsuccess_check : EvalScript(stack, script, flags, *Assert(checker), sigversion, execdata, &serror));
359+
if (opsuccess_check.has_value()) {
360+
result.pushKV("opsuccess_found", true);
361+
} else if (success) {
362+
if (stack.empty() || !CastToBool(stack.back())) {
363+
success = false;
364+
serror = SCRIPT_ERR_EVAL_FALSE;
365+
} else if (stack.size() > 1) {
366+
if (sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPSCRIPT) {
367+
success = false;
368+
serror = SCRIPT_ERR_CLEANSTACK;
369+
} else if ((flags & SCRIPT_VERIFY_CLEANSTACK) != 0) {
370+
success = false;
371+
serror = SCRIPT_ERR_CLEANSTACK;
372+
}
373+
}
374+
}
375+
376+
result.pushKV("stack-after", stack2uv(stack));
377+
378+
result.pushKV("sigop-count", (sigversion == SigVersion::TAPSCRIPT ? 0 : script.GetSigOpCount(true)));
379+
380+
result.pushKV("success", success);
381+
if (!success) {
382+
result.pushKV("error", ScriptErrorString(serror));
383+
}
384+
385+
strPrint = result.write(2);
386+
387+
return EXIT_SUCCESS;
388+
}
389+
149390
MAIN_FUNCTION
150391
{
151392
ArgsManager& args = gArgs;
@@ -175,6 +416,8 @@ MAIN_FUNCTION
175416
try {
176417
if (cmd->command == "grind") {
177418
ret = Grind(cmd->args, strPrint);
419+
} else if (cmd->command == "evalscript") {
420+
ret = EvalScript(args, cmd->args, strPrint);
178421
} else {
179422
assert(false); // unknown command should be caught earlier
180423
}

0 commit comments

Comments
 (0)