Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forward port bitcoin-util evalscript and getdeploymentinfo script flags #64

Merged
merged 9 commits into from
Oct 8, 2024
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
14 changes: 7 additions & 7 deletions src/bitcoin-wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
argsman.AddArg("-dumpfile=<file name>", "When used with 'dump', writes out the records to this file. When used with 'createfromdump', loads the records into a new wallet.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
argsman.AddArg("-dumpfile=<file name>", "When used with 'dump', writes out the records to this file. When used with 'createfromdump', loads the records into a new wallet.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-descriptors", "Create descriptors wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-legacy", "Create legacy wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-format=<format>", "The format of the wallet file to create. Either \"bdb\" or \"sqlite\". Only used with 'createfromdump'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-descriptors", "Create descriptors wallet.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-legacy", "Create legacy wallet.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-format=<format>", "The format of the wallet file to create. Either \"bdb\" or \"sqlite\".", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-withinternalbdb", "Use the internal Berkeley DB parser when dumping a Berkeley DB wallet file (default: false)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);

argsman.AddCommand("info", "Get wallet info");
argsman.AddCommand("create", "Create new wallet file");
argsman.AddCommand("create", "Create new wallet file", {"-legacy", "-descriptors"});
argsman.AddCommand("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.");
argsman.AddCommand("dump", "Print out all of the wallet key-value records");
argsman.AddCommand("createfromdump", "Create new wallet file from dumped records");
argsman.AddCommand("dump", "Print out all of the wallet key-value records", {"-dumpfile"});
argsman.AddCommand("createfromdump", "Create new wallet file from dumped records", {"-dumpfile", "-format"});
}

static std::optional<int> WalletAppInit(ArgsManager& args, int argc, char* argv[])
Expand Down
Loading
Loading