Skip to content

Commit

Permalink
bitcoin-util: introduce evalscript subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
ajtowns committed Oct 8, 2024
1 parent 4f07a77 commit 6458e4e
Showing 1 changed file with 243 additions and 0 deletions.
243 changes: 243 additions & 0 deletions src/bitcoin-util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
#include <common/system.h>
#include <compat/compat.h>
#include <core_io.h>
#include <deploymentinfo.h>
#include <policy/policy.h>
#include <script/interpreter.h>
#include <streams.h>
#include <univalue.h>
#include <util/check.h>
#include <util/exception.h>
#include <util/strencodings.h>
#include <util/translation.h>
Expand All @@ -34,7 +39,15 @@ static void SetupBitcoinUtilArgs(ArgsManager &argsman)

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

// evalscript options
argsman.AddArg("-sigversion", "Specify a script sigversion (base, witness_v0, tapscript).", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-script_flags", "Specify SCRIPT_VERIFY flags.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-tx", "The tx (hex encoded)", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-input", "The index of the input being spent", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-spent_output", "The spent prevouts (hex encode TxOut, may be specified multiple times).", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);

argsman.AddCommand("grind", "Perform proof of work on hex header string");
argsman.AddCommand("evalscript", "Interpret a bitcoin script", {"-sigversion", "-script_flags", "-tx", "-input", "-spent_output"});

SetupChainParamsBaseOptions(argsman);
}
Expand Down Expand Up @@ -146,6 +159,234 @@ static int Grind(const std::vector<std::string>& args, std::string& strPrint)
return EXIT_SUCCESS;
}

static UniValue stack2uv(const std::vector<std::vector<unsigned char>>& stack)
{
UniValue result{UniValue::VARR};
for (const auto& v : stack) {
result.push_back(HexStr(v));
}
return result;
}

static std::string sigver2str(SigVersion sigver)
{
switch(sigver) {
case SigVersion::BASE: return "base";
case SigVersion::WITNESS_V0: return "witness_v0";
case SigVersion::TAPROOT: return "taproot";
case SigVersion::TAPSCRIPT: return "tapscript";
}
return "unknown";
}

static uint32_t parse_verify_flags(const std::string& strFlags)
{
if (strFlags.empty() || strFlags == "MANDATORY") return MANDATORY_SCRIPT_VERIFY_FLAGS;
if (strFlags == "STANDARD") return STANDARD_SCRIPT_VERIFY_FLAGS;
if (strFlags == "NONE") return 0;

unsigned int flags = 0;
std::vector<std::string> words = util::SplitString(strFlags, ',');

for (const std::string& word : words)
{
if (!g_verify_flag_names.count(word)) continue;
flags |= g_verify_flag_names.at(word);
}
return flags;
}

//! Public key to be used as internal key for dummy Taproot spends.
static const std::vector<unsigned char> NUMS_H{ParseHex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")};

namespace {
/** Dummy signature checker which accepts all signatures. */
class DummySignatureChecker final : public BaseSignatureChecker
{
public:
DummySignatureChecker() = default;
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; }
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const override { return sig.size() != 0; }
bool CheckLockTime(const CScriptNum& nLockTime) const override { return true; }
bool CheckSequence(const CScriptNum& nSequence) const override { return true; }
};
}

static int EvalScript(const ArgsManager& argsman, const std::vector<std::string>& args, std::string& strPrint)
{
UniValue result{UniValue::VOBJ};

uint32_t flags{0};
PrecomputedTransactionData txdata;
ScriptExecutionData execdata;

std::unique_ptr<const CTransaction> txTo;
std::unique_ptr<BaseSignatureChecker> checker;

SigVersion sigversion = SigVersion::WITNESS_V0;

if (const auto verstr = argsman.GetArg("-sigversion"); verstr.has_value()) {
if (*verstr == "base") {
sigversion = SigVersion::BASE;
} else if (*verstr == "witness_v0") {
sigversion = SigVersion::WITNESS_V0;
} else if (*verstr == "tapscript") {
sigversion = SigVersion::TAPSCRIPT;
} else {
strPrint = strprintf("Unknown -sigversion=%s", *verstr);
return EXIT_FAILURE;
}
}

const auto verifystr = argsman.GetArg("-script_flags");
flags = parse_verify_flags(verifystr.value_or(""));

CScript script{};
std::vector<std::vector<unsigned char> > stack{};
if (args.size() > 0) {
if (IsHex(args[0])) {
auto h = ParseHex(args[0]);
script = CScript(h.begin(), h.end());
} else {
script = ParseScript(args[0]);
}

for (size_t i = 1; i < args.size(); ++i) {
if (args[i].size() == 0) {
stack.push_back({});
} else if (IsHex(args[i])) {
stack.push_back(ParseHex(args[i]));
} else {
strPrint = strprintf("Initial stack element not valid hex: %s", args[i]);
return EXIT_FAILURE;
}
}
}

if (const auto txhex = argsman.GetArg("-tx"); txhex.has_value()) {
const int input = argsman.GetIntArg("-input", 0);
const auto spent_outputs_hex = argsman.GetArgs("-spent_output");

CMutableTransaction mut_tx;
if (!DecodeHexTx(mut_tx, *txhex)) {
strPrint = "Could not decode transaction from -tx argument";
return EXIT_FAILURE;
}
txTo = std::make_unique<CTransaction>(mut_tx);

if (spent_outputs_hex.size() != txTo->vin.size()) {
strPrint = "When -tx is specified, must specify exactly one -spent_output for each input";
return EXIT_FAILURE;
}

std::vector<CTxOut> spent_outputs;
for (const auto& outhex : spent_outputs_hex) {
bool ok = false;
if (IsHex(outhex)) {
CTxOut txout;
std::vector<unsigned char> out(ParseHex(outhex));
DataStream ss(out);
try {
ss >> txout;
if (ss.empty()) {
spent_outputs.push_back(txout);
ok = true;
}
} catch (const std::exception&) {
// fall through
}
}
if (!ok) {
strPrint = strprintf("Could not parse -spent_output=%s", outhex);
return EXIT_FAILURE;
}
}

const bool input_in_range = input >= 0 && static_cast<size_t>(input) < spent_outputs.size();
CAmount amount = (input_in_range ? spent_outputs.at(input).nValue : 0);
txdata.Init(*txTo, std::move(spent_outputs), /*force=*/true);
checker = std::make_unique<TransactionSignatureChecker>(txTo.get(), input, amount, txdata, MissingDataBehavior::ASSERT_FAIL);

if (sigversion == SigVersion::TAPSCRIPT && input >= 0 && input_in_range) {
const CTxIn& txin = txTo->vin.at(input);
execdata.m_annex_present = false;
if (txin.scriptWitness.stack.size() <= 1) {
// either key path spend or no witness, so nothing to do here
} else {
const auto& top = txin.scriptWitness.stack.back();
if (top.size() >= 1 && top.at(0) == 0x50) {
execdata.m_annex_hash = (HashWriter{} << top).GetSHA256();
execdata.m_annex_present = true;
}
}
execdata.m_annex_init = true;
execdata.m_tapleaf_hash = ComputeTapleafHash(TAPROOT_LEAF_TAPSCRIPT & TAPROOT_LEAF_MASK, script);
execdata.m_tapleaf_hash_init = true;
execdata.m_validation_weight_left = ::GetSerializeSize(stack) + ::GetSerializeSize(script) + VALIDATION_WEIGHT_OFFSET;
execdata.m_validation_weight_left_init = true;
}
} else {
checker = std::make_unique<DummySignatureChecker>();
}

if (sigversion == SigVersion::TAPSCRIPT && !execdata.m_annex_init) {
execdata.m_annex_present = false;
execdata.m_annex_init = true;
execdata.m_tapleaf_hash = uint256::ZERO;
execdata.m_tapleaf_hash_init = true;
execdata.m_validation_weight_left = ::GetSerializeSize(stack) + ::GetSerializeSize(script) + VALIDATION_WEIGHT_OFFSET;
execdata.m_validation_weight_left_init = true;
}

ScriptError serror{};

UniValue uv_flags{UniValue::VARR};
for (const auto& el : GetScriptFlagNames(flags)) {
uv_flags.push_back(el);
}
UniValue uv_script{UniValue::VOBJ};
ScriptToUniv(script, uv_script);
result.pushKV("script", uv_script);
result.pushKV("sigversion", sigver2str(sigversion));
result.pushKV("script_flags", uv_flags);

std::optional<bool> opsuccess_check;
if (sigversion == SigVersion::TAPSCRIPT) {
opsuccess_check = CheckTapscriptOpSuccess(script, flags, &serror);
}

bool success = (opsuccess_check.has_value() ? *opsuccess_check : EvalScript(stack, script, flags, *Assert(checker), sigversion, execdata, &serror));
if (opsuccess_check.has_value()) {
result.pushKV("opsuccess_found", true);
} else if (success) {
if (stack.empty() || !CastToBool(stack.back())) {
success = false;
serror = SCRIPT_ERR_EVAL_FALSE;
} else if (stack.size() > 1) {
if (sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPSCRIPT) {
success = false;
serror = SCRIPT_ERR_CLEANSTACK;
} else if ((flags & SCRIPT_VERIFY_CLEANSTACK) != 0) {
success = false;
serror = SCRIPT_ERR_CLEANSTACK;
}
}
}

result.pushKV("stack-after", stack2uv(stack));

result.pushKV("sigop-count", (sigversion == SigVersion::TAPSCRIPT ? 0 : script.GetSigOpCount(true)));

result.pushKV("success", success);
if (!success) {
result.pushKV("error", ScriptErrorString(serror));
}

strPrint = result.write(2);

return EXIT_SUCCESS;
}

MAIN_FUNCTION
{
ArgsManager& args = gArgs;
Expand Down Expand Up @@ -175,6 +416,8 @@ MAIN_FUNCTION
try {
if (cmd->command == "grind") {
ret = Grind(cmd->args, strPrint);
} else if (cmd->command == "evalscript") {
ret = EvalScript(args, cmd->args, strPrint);
} else {
assert(false); // unknown command should be caught earlier
}
Expand Down

0 comments on commit 6458e4e

Please sign in to comment.