Merge 24058 via bip322-28+knots

This commit is contained in:
Luke Dashjr 2025-03-05 03:27:08 +00:00
commit c7dd00c6de
20 changed files with 494 additions and 52 deletions

View File

@ -51,6 +51,7 @@ BIPs that are implemented by Bitcoin Core:
* [`BIP 173`](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki): Bech32 addresses for native Segregated Witness outputs are supported as of **v0.16.0** ([PR 11167](https://github.com/bitcoin/bitcoin/pull/11167)). Bech32 addresses are generated by default as of **v0.20.0** ([PR 16884](https://github.com/bitcoin/bitcoin/pull/16884)).
* [`BIP 174`](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki): RPCs to operate on Partially Signed Bitcoin Transactions (PSBT) are present as of **v0.17.0** ([PR 13557](https://github.com/bitcoin/bitcoin/pull/13557)).
* [`BIP 176`](https://github.com/bitcoin/bips/blob/master/bip-0176.mediawiki): Bits Denomination [QT only] is supported as of **v0.16.0** ([PR 12035](https://github.com/bitcoin/bitcoin/pull/12035)).
* [`BIP 322`](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki): Signing and verifying signed messages proving the receiver agrees to a message are supported for Segwit and Taproot addresses as of **v28.1.knots20250301**.
* [`BIP 324`](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki): The v2 transport protocol specified by BIP324 and the associated `NODE_P2P_V2` service bit are supported as of **v26.0**, but off by default ([PR 28331](https://github.com/bitcoin/bitcoin/pull/28331)). On by default as of **v27.0** ([PR 29347](https://github.com/bitcoin/bitcoin/pull/29347)).
* [`BIP 325`](https://github.com/bitcoin/bips/blob/master/bip-0325.mediawiki): Signet test network is supported as of **v0.21.0** ([PR 18267](https://github.com/bitcoin/bitcoin/pull/18267)).
* [`BIP 339`](https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki): Relay of transactions by wtxid is supported as of **v0.21.0** ([PR 18044](https://github.com/bitcoin/bitcoin/pull/18044)).

View File

@ -4,11 +4,14 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <common/signmessage.h>
#include <core_io.h>
#include <hash.h>
#include <key.h>
#include <key_io.h>
#include <outputtype.h>
#include <pubkey.h>
#include <script/interpreter.h>
#include <streams.h>
#include <uint256.h>
#include <util/strencodings.h>
@ -24,11 +27,77 @@
*/
const std::string MESSAGE_MAGIC = "Bitcoin Signed Message:\n";
/**
* BIP-322 tagged hash
*/
static const HashWriter HASHER_BIP322{TaggedHash("BIP0322-signed-message")};
static constexpr unsigned int BIP322_REQUIRED_FLAGS =
SCRIPT_VERIFY_CONST_SCRIPTCODE // disallows OP_CODESEPARATOR and FindAndDelete
| SCRIPT_VERIFY_LOW_S
| SCRIPT_VERIFY_STRICTENC
| SCRIPT_VERIFY_NULLFAIL
| SCRIPT_VERIFY_MINIMALDATA
| SCRIPT_VERIFY_CLEANSTACK
| SCRIPT_VERIFY_P2SH
| SCRIPT_VERIFY_WITNESS
| SCRIPT_VERIFY_TAPROOT
| SCRIPT_VERIFY_MINIMALIF;
static constexpr unsigned int BIP322_INCONCLUSIVE_FLAGS =
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS
| SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS
| SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
| SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION
| SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM;
MessageVerificationResult MessageVerifyBIP322(
CTxDestination& destination,
std::vector<unsigned char>& signature,
const std::string& message,
MessageVerificationResult legacyError)
{
auto txs = BIP322Txs::Create(destination, message, legacyError, signature);
if (!txs) return legacyError;
const CTransaction& to_sign = txs->m_to_sign;
const CTransaction& to_spend = txs->m_to_spend;
const CScript scriptSig = to_sign.vin[0].scriptSig;
const CScriptWitness& witness = to_sign.vin[0].scriptWitness;
PrecomputedTransactionData txdata;
txdata.Init(to_sign, {to_spend.vout[0]});
TransactionSignatureChecker sigcheck(&to_sign, /* nInIn= */ 0, /* amountIn= */ to_spend.vout[0].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
sigcheck.m_require_sighash_all = true;
if (!VerifyScript(scriptSig, to_spend.vout[0].scriptPubKey, &witness, BIP322_REQUIRED_FLAGS, sigcheck)) {
return MessageVerificationResult::ERR_INVALID;
}
// inconclusive checks
if (to_sign.version != 0 && to_sign.version != 2) {
return MessageVerificationResult::INCONCLUSIVE;
}
if (!VerifyScript(scriptSig, to_spend.vout[0].scriptPubKey, &witness, BIP322_INCONCLUSIVE_FLAGS, sigcheck)) {
return MessageVerificationResult::INCONCLUSIVE;
}
return MessageVerificationResult::OK;
}
MessageVerificationResult MessageVerify(
const std::string& address,
const std::string& signature,
const std::string& message)
{
auto signature_bytes = DecodeBase64(signature);
if (!signature_bytes) {
return MessageVerificationResult::ERR_MALFORMED_SIGNATURE;
}
CTxDestination destination = DecodeDestination(address);
if (!IsValidDestination(destination)) {
return MessageVerificationResult::ERR_INVALID_ADDRESS;
@ -42,17 +111,12 @@ MessageVerificationResult MessageVerify(
} else if (std::holds_alternative<WitnessV0KeyHash>(destination)) {
signed_for_outputtype = OutputType::BECH32;
} else {
return MessageVerificationResult::ERR_ADDRESS_NO_KEY;
}
auto signature_bytes = DecodeBase64(signature);
if (!signature_bytes) {
return MessageVerificationResult::ERR_MALFORMED_SIGNATURE;
return MessageVerifyBIP322(destination, *signature_bytes, message, MessageVerificationResult::ERR_ADDRESS_NO_KEY);
}
uint8_t sigtype{(*signature_bytes)[0]};
if (sigtype < 27 || sigtype > 42) {
return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED;
return MessageVerifyBIP322(destination, *signature_bytes, message, MessageVerificationResult::ERR_MALFORMED_SIGNATURE);
}
sigtype = (sigtype - 27) >> 2;
if (sigtype == 3) {
@ -64,14 +128,14 @@ MessageVerificationResult MessageVerify(
}
CPubKey pubkey;
if (!pubkey.RecoverCompact(MessageHash(message), *signature_bytes)) {
return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED;
if (!pubkey.RecoverCompact(MessageHash(message, MessageSignatureFormat::LEGACY), *signature_bytes)) {
return MessageVerifyBIP322(destination, *signature_bytes, message, MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED);
}
CTxDestination recovered_dest = GetDestinationForKey(pubkey, signed_for_outputtype);
if (!(recovered_dest == destination)) {
return MessageVerificationResult::ERR_NOT_SIGNED;
return MessageVerifyBIP322(destination, *signature_bytes, message, MessageVerificationResult::ERR_NOT_SIGNED);
}
return MessageVerificationResult::OK;
@ -84,7 +148,7 @@ bool MessageSign(
{
std::vector<unsigned char> signature_bytes;
if (!privkey.SignCompact(MessageHash(message), signature_bytes)) {
if (!privkey.SignCompact(MessageHash(message, MessageSignatureFormat::LEGACY), signature_bytes)) {
return false;
}
@ -93,12 +157,28 @@ bool MessageSign(
return true;
}
uint256 MessageHash(const std::string& message)
uint256 MessageHash(const std::string& message, MessageSignatureFormat format)
{
switch (format) {
case MessageSignatureFormat::LEGACY:
{
HashWriter hasher{};
hasher << MESSAGE_MAGIC << message;
return hasher.GetHash();
}
case MessageSignatureFormat::SIMPLE:
case MessageSignatureFormat::FULL:
{
HashWriter hasher{HASHER_BIP322};
if (!message.empty()) {
hasher.write(AsBytes(Span{message.data(), message.size() * sizeof(char)}));
}
return hasher.GetSHA256();
}
}
assert(false);
}
std::string SigningResultString(const SigningResult res)
@ -114,3 +194,65 @@ std::string SigningResultString(const SigningResult res)
}
assert(false);
}
std::optional<BIP322Txs> BIP322Txs::Create(const CTxDestination& destination, const std::string& message, MessageVerificationResult& result, std::optional<const std::vector<unsigned char>> signature)
{
// attempt to get script pub key for destination
CScript message_challenge = GetScriptForDestination(destination);
if (message_challenge.size() == 0) {
// NoDestination; failure
// (use legacy result)
return std::nullopt;
}
// prepare message hash
uint256 message_hash = MessageHash(message, MessageSignatureFormat::SIMPLE);
std::vector<unsigned char> message_hash_vec(message_hash.begin(), message_hash.end());
// generate to_spend transaction
CMutableTransaction to_spend;
to_spend.version = 0;
to_spend.nLockTime = 0;
to_spend.vin.emplace_back(COutPoint(Txid::FromUint256(uint256::ZERO), 0xFFFFFFFF), (CScript() << OP_0 << message_hash_vec), 0);
to_spend.vout.emplace_back(0, message_challenge);
CMutableTransaction to_sign;
if (signature.has_value() && DecodeTx(to_sign, signature.value(), /* try_no_witness= */ true, /* try_witness= */ true)) {
// validate decoded transaction
// multiple inputs (proof of funds) are not supported as we do not have UTXO set access
if (to_sign.vin.size() > 1) {
result = MessageVerificationResult::ERR_POF;
return std::nullopt;
}
if ((to_sign.vin.size() == 0 || to_sign.vin[0].prevout.hash != to_spend.GetHash()) ||
(to_sign.vin[0].prevout.n != 0) ||
(to_sign.vout.size() != 1) ||
(to_sign.vout[0].nValue != 0) ||
(to_sign.vout[0].scriptPubKey != (CScript() << OP_RETURN))) {
result = MessageVerificationResult::ERR_INVALID;
return std::nullopt;
}
} else {
// signature is missing, or a witness stack only
to_sign.version = 0;
to_sign.nLockTime = 0;
to_sign.vin.emplace_back(COutPoint(to_spend.GetHash(), 0), CScript(), 0);
if (signature.has_value()) {
try {
DataStream ds(signature.value());
ds >> to_sign.vin[0].scriptWitness.stack;
if (!ds.empty()) {
result = MessageVerificationResult::ERR_INVALID;
return std::nullopt;
}
} catch (...) {
// not a script witness either; fall back to legacy error
// (use legacy result)
return std::nullopt;
}
}
to_sign.vout.emplace_back(0, CScript() << OP_RETURN);
}
return BIP322Txs{to_spend, to_sign};
}

View File

@ -6,14 +6,29 @@
#ifndef BITCOIN_COMMON_SIGNMESSAGE_H
#define BITCOIN_COMMON_SIGNMESSAGE_H
#include <addresstype.h>
#include <primitives/transaction.h>
#include <uint256.h>
#include <optional>
#include <string>
#include <vector>
class CKey;
extern const std::string MESSAGE_MAGIC;
enum class MessageSignatureFormat {
//! Legacy format, which only works on legacy addresses
LEGACY,
//! Simple BIP-322 format, i.e. the script witness stack only
SIMPLE,
//! Full BIP-322 format, i.e. the serialized to_sign transaction in full
FULL,
};
/** The result of a signed message verification.
* Message verification takes as an input:
* - address (with whose private key the message is supposed to have been signed)
@ -37,7 +52,20 @@ enum class MessageVerificationResult {
ERR_NOT_SIGNED,
//! The message verification was successful.
OK
OK,
//
// BIP-322 extensions
//
//! The validator was unable to check the scripts (BIP-322)
INCONCLUSIVE,
//! Some check failed (BIP-322)
ERR_INVALID,
//! Proof of funds require the wallet-enabled verifier (BIP-322)
ERR_POF,
};
enum class SigningResult {
@ -47,7 +75,7 @@ enum class SigningResult {
};
/** Verify a signed message.
* @param[in] address Signer's bitcoin address, it must refer to a public key.
* @param[in] address Signer's bitcoin address.
* @param[in] signature The signature in base64 format.
* @param[in] message The message that was signed.
* @return result code */
@ -56,7 +84,7 @@ MessageVerificationResult MessageVerify(
const std::string& signature,
const std::string& message);
/** Sign a message.
/** Sign a message using legacy format.
* @param[in] privkey Private key to sign with.
* @param[in] message The message to sign.
* @param[out] signature Signature, base64 encoded, only set if true is returned.
@ -70,8 +98,23 @@ bool MessageSign(
* Hashes a message for signing and verification in a manner that prevents
* inadvertently signing a transaction.
*/
uint256 MessageHash(const std::string& message);
uint256 MessageHash(const std::string& message, MessageSignatureFormat format);
std::string SigningResultString(const SigningResult res);
/**
* Generate the BIP-322 tx corresponding to the given challenge
*/
class BIP322Txs {
private:
template<class T1, class T2>
BIP322Txs(const T1& to_spend, const T2& to_sign) : m_to_spend{to_spend}, m_to_sign{to_sign} { }
public:
static std::optional<BIP322Txs> Create(const CTxDestination& destination, const std::string& message, MessageVerificationResult& result, std::optional<const std::vector<unsigned char>> = std::nullopt);
const CTransaction m_to_spend;
const CTransaction m_to_sign;
};
#endif // BITCOIN_COMMON_SIGNMESSAGE_H

View File

@ -34,6 +34,7 @@ enum class TxVerbosity {
// core_read.cpp
CScript ParseScript(const std::string& s);
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
[[nodiscard]] bool DecodeTx(CMutableTransaction& tx, const std::vector<unsigned char>& tx_data, bool try_no_witness, bool try_witness);
[[nodiscard]] bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no_witness = false, bool try_witness = true);
[[nodiscard]] bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);

View File

@ -122,7 +122,7 @@ static bool CheckTxScriptsSanity(const CMutableTransaction& tx)
return true;
}
static bool DecodeTx(CMutableTransaction& tx, const std::vector<unsigned char>& tx_data, bool try_no_witness, bool try_witness)
bool DecodeTx(CMutableTransaction& tx, const std::vector<unsigned char>& tx_data, bool try_no_witness, bool try_witness)
{
// General strategy:
// - Decode both with extended serialization (which interprets the 0x0001 tag as a marker for

View File

@ -109,7 +109,7 @@ public:
virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0;
//! Sign message
virtual SigningResult signMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) = 0;
virtual SigningResult signMessage(const MessageSignatureFormat format, const std::string& message, const CTxDestination& address, std::string& str_sig) = 0;
//! Return whether wallet has private key.
virtual bool isSpendable(const CTxDestination& dest) = 0;

View File

@ -120,12 +120,10 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
ui->statusLabel_SM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again."));
return;
}
MessageSignatureFormat sig_format{MessageSignatureFormat::LEGACY};
const PKHash* pkhash = std::get_if<PKHash>(&destination);
if (!pkhash) {
ui->addressIn_SM->setValid(false);
ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_SM->setText(tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again."));
return;
sig_format = MessageSignatureFormat::SIMPLE;
}
WalletModel::UnlockContext ctx(model->requestUnlock());
@ -138,7 +136,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
const std::string& message = ui->messageIn_SM->document()->toPlainText().toStdString();
std::string signature;
SigningResult res = model->wallet().signMessage(message, *pkhash, signature);
SigningResult res = model->wallet().signMessage(sig_format, message, destination, signature);
QString error;
switch (res) {
@ -214,6 +212,12 @@ void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked()
QString("<nobr>") + tr("Message verified.") + QString("</nobr>")
);
return;
case MessageVerificationResult::INCONCLUSIVE:
case MessageVerificationResult::ERR_POF:
ui->statusLabel_VM->setText(
QString("<nobr>") + tr("This version of %1 is unable to check this signature.").arg(PACKAGE_NAME) + QString("</nobr>")
);
return;
case MessageVerificationResult::ERR_INVALID_ADDRESS:
ui->statusLabel_VM->setText(
tr("The entered address is invalid.") + QString(" ") +
@ -221,12 +225,6 @@ void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked()
);
return;
case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
ui->addressIn_VM->setValid(false);
ui->statusLabel_VM->setText(
tr("The entered address does not refer to a key.") + QString(" ") +
tr("Please check the address and try again.")
);
return;
case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
ui->signatureIn_VM->setValid(false);
ui->statusLabel_VM->setText(
@ -241,6 +239,7 @@ void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked()
tr("Please check the signature and try again.")
);
return;
case MessageVerificationResult::ERR_INVALID:
case MessageVerificationResult::ERR_NOT_SIGNED:
ui->statusLabel_VM->setText(
QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>")

View File

@ -49,6 +49,10 @@ static RPCHelpMan verifymessage()
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding");
case MessageVerificationResult::ERR_POF:
case MessageVerificationResult::INCONCLUSIVE:
throw JSONRPCError(RPC_TYPE_ERROR, "This signature is not yet supported");
case MessageVerificationResult::ERR_INVALID:
case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
case MessageVerificationResult::ERR_NOT_SIGNED:
return false;

View File

@ -1658,6 +1658,10 @@ bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vecto
int nHashType = vchSig.back();
vchSig.pop_back();
if (m_require_sighash_all && nHashType != SIGHASH_ALL) {
return false;
}
// Witness sighashes need the amount.
if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return HandleMissingData(m_mdb);
@ -1686,6 +1690,9 @@ bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const uns
uint8_t hashtype = SIGHASH_DEFAULT;
if (sig.size() == 65) {
hashtype = SpanPopBack(sig);
if (m_require_sighash_all && hashtype != SIGHASH_ALL) {
return set_error(serror, SCRIPT_ERR_SIG_HASHTYPE);
}
if (hashtype == SIGHASH_DEFAULT) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
}
uint256 sighash;

View File

@ -301,6 +301,8 @@ public:
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
bool CheckSequence(const CScriptNum& nSequence) const override;
bool m_require_sighash_all{false};
};
using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;

View File

@ -38,7 +38,7 @@ FUZZ_TARGET(message, .init = initialize_message)
}
}
{
(void)MessageHash(random_message);
(void)MessageHash(random_message, MessageSignatureFormat::LEGACY);
(void)MessageVerify(fuzzed_data_provider.ConsumeRandomLengthString(1024), fuzzed_data_provider.ConsumeRandomLengthString(1024), random_message);
(void)SigningResultString(fuzzed_data_provider.PickValueInArray({SigningResult::OK, SigningResult::PRIVATE_KEY_NOT_AVAILABLE, SigningResult::SIGNING_FAILED}));
}

View File

@ -6,6 +6,8 @@
#include <common/signmessage.h> // For MessageSign(), MessageVerify(), MESSAGE_MAGIC
#include <hash.h> // For Hash()
#include <key.h> // For CKey
#include <key_io.h> // EncodeDestination
#include <outputtype.h> // For BIP-322 tests
#include <script/parsing.h>
#include <sync.h>
#include <test/util/random.h>
@ -1673,6 +1675,38 @@ BOOST_AUTO_TEST_CASE(message_sign)
"Sign with a valid private key");
BOOST_CHECK_EQUAL(expected_signature, generated_signature);
// BIP-322 tests
// (no signing done here, as we need a wallet to do so)
auto pubkey = privkey.GetPubKey();
MessageVerificationResult mvr{MessageVerificationResult::OK};
// LEGACY pubkey type
auto dest_legacy = GetDestinationForKey(pubkey, OutputType::LEGACY);
BOOST_CHECK_EQUAL("15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs", EncodeDestination(dest_legacy));
auto txs_legacy = BIP322Txs::Create(dest_legacy, message, mvr);
if (!txs_legacy || mvr != MessageVerificationResult::OK) {
BOOST_FAIL("Failed to create BIP-322 txs for legacy address");
}
// P2SH_SEGWIT pubkey type
auto dest_p2sh_segwit = GetDestinationForKey(pubkey, OutputType::P2SH_SEGWIT);
BOOST_CHECK_EQUAL("35uijJkf4rcCnGzEZsn12YJenTHToDKpr2", EncodeDestination(dest_p2sh_segwit));
auto txs_p2sh_segwit = BIP322Txs::Create(dest_p2sh_segwit, message, mvr);
if (!txs_p2sh_segwit || mvr != MessageVerificationResult::OK) {
BOOST_FAIL("Failed to create BIP-322 txs for p2sh-segwit address");
}
// BECH32
auto dest_bech32 = GetDestinationForKey(pubkey, OutputType::BECH32);
BOOST_CHECK_EQUAL("bc1q9cy7s7nmzah0m6mt2ftmu6x723esjxqkkl4wsw", EncodeDestination(dest_bech32));
auto txs_bech32 = BIP322Txs::Create(dest_bech32, message, mvr);
if (!txs_bech32 || mvr != MessageVerificationResult::OK) {
BOOST_FAIL("Failed to create BIP-322 txs for bech32 address");
}
// TODO: BECH32M
}
BOOST_AUTO_TEST_CASE(message_verify)
@ -1680,16 +1714,16 @@ BOOST_AUTO_TEST_CASE(message_verify)
BOOST_CHECK_EQUAL(
MessageVerify(
"invalid address",
"signature should be irrelevant",
"AA==",
"message too"),
MessageVerificationResult::ERR_INVALID_ADDRESS);
BOOST_CHECK_EQUAL(
MessageVerify(
"3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV",
"signature should be irrelevant",
"AA==",
"message too"),
MessageVerificationResult::ERR_ADDRESS_NO_KEY);
MessageVerificationResult::ERR_INVALID /* ERR_ADDRESS_NO_KEY */);
BOOST_CHECK_EQUAL(
MessageVerify(
@ -1703,7 +1737,7 @@ BOOST_AUTO_TEST_CASE(message_verify)
"1KqbBpLy5FARmTPD4VZnDDpYjkUvkr82Pm",
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"message should be irrelevant"),
MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED);
MessageVerificationResult::ERR_INVALID /* ERR_PUBKEY_NOT_RECOVERED */);
BOOST_CHECK_EQUAL(
MessageVerify(
@ -1725,6 +1759,124 @@ BOOST_AUTO_TEST_CASE(message_verify)
"IIcaIENoYW5jZWxsb3Igb24gYnJpbmsgb2Ygc2Vjb25kIGJhaWxvdXQgZm9yIGJhbmtzIAaHRtbCeDZINyavx14=",
"Trust me"),
MessageVerificationResult::OK);
// BIP-322 tests
// privkey: L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
""),
MessageVerificationResult::OK);
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
"Hello World"),
MessageVerificationResult::OK);
// BIP322 signature created using buidl-python library with same parameters as test on line 2596
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy",
"Hello World"),
MessageVerificationResult::OK);
// 2-of-3 p2sh multisig BIP322 signature (created with the buidl-python library)
// Keys are defined as (HDRootWIF, bip322_path)
// Key1 (L4DksdGZ4KQJfcLHD5Dv25fu8Rxyv7hHi2RjZR4TYzr8c6h9VNrp, m/45'/0/0/1)
// Key2 (KzSRqnCVwjzY8id2X5oHEJWXkSHwKUYaAXusjwgkES8BuQPJnPNu, m/45'/0/0/3)
// Key3 (L1zt9Rw7HrU7jaguMbVzhiX8ffuVkmMis5wLHddXYuHWYf8u8uRj, m/45'/0/0/6)
// BIP322 includes signs from Key2 and Key3
BOOST_CHECK_EQUAL(
MessageVerify(
"3LnYoUkFrhyYP3V7rq3mhpwALz1XbCY9Uq",
"AAAAAAHNcfHaNfl8f/+ZC2gTr8aF+0KgppYjKM94egaNm/u1ZAAAAAD8AEcwRAIhAJ6hdj61vLDP+aFa30qUZQmrbBfE0kiOObYvt5nqPSxsAh9IrOKFwflfPRUcQ/5e0REkdFHVP2GGdUsMgDet+sNlAUcwRAIgH3eW/VyFDoXvCasd8qxgwj5NDVo0weXvM6qyGXLCR5YCIEwjbEV6fS6RWP6QsKOcMwvlGr1/SgdCC6pW4eH87/YgAUxpUiECKJfGy28imLcuAeNBLHCNv3NRP5jnJwFDNRXCYNY/vJ4hAv1RQtaZs7+vKqQeWl2rb/jd/gMxkEjUnjZdDGPDZkMLIQL65cH2X5O7LujjTLDL2l8Pxy0Y2UUR99u1qCfjdz7dklOuAAAAAAEAAAAAAAAAAAFqAAAAAA==",
"This will be a p2sh 2-of-3 multisig BIP 322 signed message"),
MessageVerificationResult::OK);
// 3-of-3 p2wsh multisig BIP322 signature (created with the buidl-python library)
// Keys are defined as (HDRootWIF, bip322_path)
// Key1 (L4DksdGZ4KQJfcLHD5Dv25fu8Rxyv7hHi2RjZR4TYzr8c6h9VNrp, m/45'/0/0/6)
// Key2 (KzSRqnCVwjzY8id2X5oHEJWXkSHwKUYaAXusjwgkES8BuQPJnPNu, m/45'/0/0/9)
// Key3 (L1zt9Rw7HrU7jaguMbVzhiX8ffuVkmMis5wLHddXYuHWYf8u8uRj, m/45'/0/0/11)
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1qlqtuzpmazp2xmcutlwv0qvggdvem8vahkc333usey4gskug8nutsz53msw", "BQBIMEUCIQDQoXvGKLH58exuujBOta+7+GN7vi0lKwiQxzBpuNuXuAIgIE0XYQlFDOfxbegGYYzlf+tqegleAKE6SXYIa1U+uCcBRzBEAiATegywVl6GWrG9jJuPpNwtgHKyVYCX2yfuSSDRFATAaQIgTLlU6reLQsSIrQSF21z3PtUO2yAUseUWGZqRUIE7VKoBSDBFAiEAgxtpidsU0Z4u/+5RB9cyeQtoCW5NcreLJmWXZ8kXCZMCIBR1sXoEinhZE4CF9P9STGIcMvCuZjY6F5F0XTVLj9SjAWlTIQP3dyWvTZjUENWJowMWBsQrrXCUs20Gu5YF79CG5Ga0XSEDwqI5GVBOuFkFzQOGH5eTExSAj2Z/LDV/hbcvAPQdlJMhA17FuuJd+4wGuj+ZbVxEsFapTKAOwyhfw9qpch52JKxbU64=",
"This will be a p2wsh 3-of-3 multisig BIP 322 signed message"),
MessageVerificationResult::OK);
// Single key p2tr BIP322 signature (created with the buidl-python library)
// PrivateKeyWIF L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==",
"Hello World"),
MessageVerificationResult::OK);
// Same p2tr BIP322 signature as above (created with the buidl-python library)
// Signature should not verify against the message
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==",
"Hello World - This should fail"),
MessageVerificationResult::ERR_INVALID);
// wrong address
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1qkecg9ly2xwxqgdy9egpuy87qc9x26smpts562s",
"AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
""),
MessageVerificationResult::ERR_INVALID);
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1qkecg9ly2xwxqgdy9egpuy87qc9x26smpts562s",
"AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
"Hello World"),
MessageVerificationResult::ERR_INVALID);
// wrong signature / message (signatures swapped)
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
""),
MessageVerificationResult::ERR_INVALID);
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
"Hello World"),
MessageVerificationResult::ERR_INVALID);
// invalid address
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx1l",
"AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
""),
MessageVerificationResult::ERR_INVALID_ADDRESS);
// malformed signature
BOOST_CHECK_EQUAL(
MessageVerify(
"bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"AkcwRAIgClVQ8S9yX1h8YThlGElD9lOrQbOwbFDjkYb0ebfiq+oCIDHgb/X9WNalNNtqTXb465ufbv9JuLxcJf8qi7DP6yOXASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI",
""),
MessageVerificationResult::ERR_MALFORMED_SIGNATURE);
}
BOOST_AUTO_TEST_CASE(message_hash)
@ -1738,10 +1890,20 @@ BOOST_AUTO_TEST_CASE(message_hash)
const uint256 signature_hash = Hash(unsigned_tx);
const uint256 message_hash1 = Hash(prefixed_message);
const uint256 message_hash2 = MessageHash(unsigned_tx);
const uint256 message_hash2 = MessageHash(unsigned_tx, MessageSignatureFormat::LEGACY);
BOOST_CHECK_EQUAL(message_hash1, message_hash2);
BOOST_CHECK_NE(message_hash1, signature_hash);
// BIP-322 tests
const uint256 signature_hash_0x = MessageHash("", MessageSignatureFormat::FULL);
const uint256 signature_hash_Hello_World = MessageHash("Hello World", MessageSignatureFormat::FULL);
std::vector<unsigned char> vec(signature_hash_0x.begin(), signature_hash_0x.end());
BOOST_CHECK_EQUAL("c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1", HexStr(vec));
vec = std::vector<unsigned char>(signature_hash_Hello_World.begin(), signature_hash_Hello_World.end());
BOOST_CHECK_EQUAL("f0eb03b1a75ac6d9847f55c624a99169b5dccba2a31f5b23bea77ba270de0a7a", HexStr(vec));
}
BOOST_AUTO_TEST_CASE(remove_prefix)

View File

@ -191,9 +191,9 @@ public:
}
return false;
}
SigningResult signMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) override
SigningResult signMessage(const MessageSignatureFormat format, const std::string& message, const CTxDestination& address, std::string& str_sig) override
{
return m_wallet->SignMessage(message, pkhash, str_sig);
return m_wallet->SignMessage(format, message, address, str_sig);
}
bool isSpendable(const CTxDestination& dest) override
{

View File

@ -51,12 +51,14 @@ RPCHelpMan signmessage()
}
const PKHash* pkhash = std::get_if<PKHash>(&dest);
MessageSignatureFormat sig_format{MessageSignatureFormat::LEGACY};
// TODO: Make sig_format choosable
if (!pkhash) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
sig_format = MessageSignatureFormat::SIMPLE;
}
std::string signature;
SigningResult err = pwallet->SignMessage(strMessage, *pkhash, signature);
SigningResult err = pwallet->SignMessage(sig_format, strMessage, dest, signature);
if (err == SigningResult::SIGNING_FAILED) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, SigningResultString(err));
} else if (err != SigningResult::OK) {

View File

@ -218,6 +218,47 @@ IsMineResult IsMineInner(const LegacyDataSPKM& keystore, const CScript& scriptPu
} // namespace
SigningResult ScriptPubKeyMan::SignMessageBIP322(MessageSignatureFormat format, const SigningProvider* keystore, const std::string& message, const CTxDestination& address, std::string& str_sig) const
{
assert(format != MessageSignatureFormat::LEGACY);
MessageVerificationResult result; // unused
auto txs = BIP322Txs::Create(address, message, result);
assert(txs);
const CTransaction& to_spend = txs->m_to_spend;
CMutableTransaction to_sign(txs->m_to_sign);
// Create the "unspent output" map, consisting of the to_spend output
std::map<COutPoint, Coin> coins;
coins[to_sign.vin[0].prevout] = Coin(to_spend.vout[0], 1, false);
// Sign the transaction
std::map<int, bilingual_str> errors;
if (!::SignTransaction(to_sign, keystore, coins, SIGHASH_ALL, errors)) {
// TODO: this may be a multisig which successfully signed but needed additional signatures
return SigningResult::SIGNING_FAILED;
}
// We force the format to FULL, if this turned out to be a legacy format (p2pkh) signature
if (to_sign.vin[0].scriptSig.size() > 0 || to_sign.vin[0].scriptWitness.IsNull()) {
format = MessageSignatureFormat::FULL;
}
DataStream ds;
if (format == MessageSignatureFormat::SIMPLE) {
// Simple format output
ds << to_sign.vin[0].scriptWitness.stack;
} else {
// Full format output
ds << TX_WITH_WITNESS(to_sign);
}
str_sig = EncodeBase64(ds);
return SigningResult::OK;
}
isminetype LegacyDataSPKM::IsMine(const CScript& script) const
{
switch (IsMineInner(*this, script, IsMineSigVersion::TOP)) {
@ -619,16 +660,26 @@ bool LegacyScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::
return ::SignTransaction(tx, this, coins, sighash, input_errors, inputs_amount_sum);
}
SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const
SigningResult LegacyScriptPubKeyMan::SignMessage(const MessageSignatureFormat format, const std::string& message, const CTxDestination& address, std::string& str_sig) const
{
if (format != MessageSignatureFormat::LEGACY) {
return SignMessageBIP322(format, this, message, address, str_sig);
}
const PKHash* pkhash = std::get_if<PKHash>(&address);
if (!pkhash) {
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
CKey key;
if (!GetKey(ToKeyID(pkhash), key)) {
if (!GetKey(ToKeyID(*pkhash), key)) {
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
if (MessageSign(key, message, str_sig)) {
return SigningResult::OK;
}
return SigningResult::SIGNING_FAILED;
}
@ -2490,21 +2541,31 @@ bool DescriptorScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const s
return ::SignTransaction(tx, keys.get(), coins, sighash, input_errors, inputs_amount_sum);
}
SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const
SigningResult DescriptorScriptPubKeyMan::SignMessage(const MessageSignatureFormat format, const std::string& message, const CTxDestination& address, std::string& str_sig) const
{
std::unique_ptr<FlatSigningProvider> keys = GetSigningProvider(GetScriptForDestination(pkhash), true);
std::unique_ptr<FlatSigningProvider> keys = GetSigningProvider(GetScriptForDestination(address), true);
if (!keys) {
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
if (format != MessageSignatureFormat::LEGACY) {
return SignMessageBIP322(format, keys.get(), message, address, str_sig);
}
const PKHash* pkhash = std::get_if<PKHash>(&address);
if (!pkhash) {
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
CKey key;
if (!keys->GetKey(ToKeyID(pkhash), key)) {
if (!keys->GetKey(ToKeyID(*pkhash), key)) {
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
if (!MessageSign(key, message, str_sig)) {
return SigningResult::SIGNING_FAILED;
}
return SigningResult::OK;
}

View File

@ -176,6 +176,8 @@ class ScriptPubKeyMan
protected:
WalletStorage& m_storage;
SigningResult SignMessageBIP322(MessageSignatureFormat format, const SigningProvider* keystore, const std::string& message, const CTxDestination& address, std::string& str_sig) const;
public:
explicit ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {}
virtual ~ScriptPubKeyMan() = default;
@ -243,7 +245,7 @@ public:
/** Creates new signatures and adds them to the transaction. Returns whether all inputs were signed */
virtual bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors, std::optional<CAmount>* inputs_amount_sum = nullptr) const { return false; }
/** Sign a message with the given script */
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
virtual SigningResult SignMessage(const MessageSignatureFormat format, const std::string& message, const CTxDestination& address, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
/** Adds script and derivation path information to a PSBT, and optionally signs it. */
virtual std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const { return common::PSBTError::UNSUPPORTED; }
@ -488,7 +490,7 @@ public:
bool CanProvide(const CScript& script, SignatureData& sigdata) override;
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors, std::optional<CAmount>* inputs_amount_sum = nullptr) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
SigningResult SignMessage(const MessageSignatureFormat format, const std::string& message, const CTxDestination& address, std::string& str_sig) const override;
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
uint256 GetID() const override;
@ -674,7 +676,7 @@ public:
bool CanProvide(const CScript& script, SignatureData& sigdata) override;
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors, std::optional<CAmount>* inputs_amount_sum = nullptr) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
SigningResult SignMessage(const MessageSignatureFormat format, const std::string& message, const CTxDestination& address, std::string& str_sig) const override;
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
uint256 GetID() const override;

View File

@ -137,7 +137,7 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
*std::get_if<PKHash>(&dest) :
PKHash{ConsumeUInt160(fuzzed_data_provider)}};
std::string str_sig;
(void)spk_manager->SignMessage(msg, pk_hash, str_sig);
(void)spk_manager->SignMessage(MessageSignatureFormat::LEGACY, msg, pk_hash, str_sig);
(void)spk_manager->GetMetadata(dest);
}
}

View File

@ -2318,14 +2318,14 @@ std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bo
return {};
}
SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const
SigningResult CWallet::SignMessage(const MessageSignatureFormat format, const std::string& message, const CTxDestination& address, std::string& str_sig) const
{
SignatureData sigdata;
CScript script_pub_key = GetScriptForDestination(pkhash);
CScript script_pub_key = GetScriptForDestination(address);
for (const auto& spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script_pub_key, sigdata)) {
LOCK(cs_wallet); // DescriptorScriptPubKeyMan calls IsLocked which can lock cs_wallet in a deadlocking order
return spk_man_pair.second->SignMessage(message, pkhash, str_sig);
return spk_man_pair.second->SignMessage(format, message, address, str_sig);
}
}
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;

View File

@ -664,7 +664,7 @@ public:
bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Sign the tx given the input coins and sighash. */
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors, std::optional<CAmount>* inputs_amount_sum = nullptr) const;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const;
SigningResult SignMessage(const MessageSignatureFormat format, const std::string& message, const CTxDestination& address, std::string& str_sig) const;
/**
* Fills out a PSBT with information from the wallet. Fills in UTXOs if we have

View File

@ -63,6 +63,22 @@ class SignMessagesWithPrivTest(BitcoinTestFramework):
signature = 'IBR+8bubsBxBFFE3CO6pggzNSRyg/23HRMNXyWUIIEXmTe3P0apzd5izyR/d80nVRE883I58gijFKIevBLtcPRI='
assert self.nodes[0].verifymessage('bcrt1qa0mscp9epevt07rscyjsre5fdlxjp3tlcchs4x', signature, message)
self.log.info('test that verifying Sparrow p2wpkh ("Electrum") succeeds')
signature = 'H036ky29d5FwS0K46di8ssfP+UbVEghDoexR2GGv+WX+WQHVnTWiUTOSjazS3+aIx92qsnE0m/WK2uflxI47BhQ='
assert self.nodes[0].verifymessage('bcrt1q00cc7f4m04f5mjdcm9g6c5y2a3wnvfvflljety', signature, message)
self.log.info('test that verifying Sparrow p2wpkh ("BIP137/Trezor") succeeds')
signature = 'J036ky29d5FwS0K46di8ssfP+UbVEghDoexR2GGv+WX+WQHVnTWiUTOSjazS3+aIx92qsnE0m/WK2uflxI47BhQ='
assert self.nodes[0].verifymessage('bcrt1q00cc7f4m04f5mjdcm9g6c5y2a3wnvfvflljety', signature, message)
self.log.info('test that verifying Sparrow p2wpkh ("BIP322 Simple") succeeds')
signature = 'AkcwRAIgCu7IrN3jCvBdp5myPZHCKiOW5o3EToYG2xgPbjiw6JsCIFwaZMcYQj9FmWcNtZk3qTp2UDBm77cbFmxQ3WVnVxrCASEDonFDI6fuQ05yUfeQIsOs599XHZnpaTxaqD13g4sXYRY='
assert self.nodes[0].verifymessage('bcrt1q00cc7f4m04f5mjdcm9g6c5y2a3wnvfvflljety', signature, message)
self.log.info('test that verifying Sparrow p2tr ("BIP322 Simple") succeeds')
signature = 'AUFdYU3dZCSmTxWnl5ja/Jo096VAaKtdaYs8b4ikF2iuQ2fiy7YSFHBWcD40a1oBKTVWUrXqOC0pXoDjTFKqM/ObAQ=='
assert self.nodes[0].verifymessage('bcrt1p5t6gmtfkd4q8jfgz40auxqgtll895n85amlgvgsz0jwxvfw9qltss6wfve', signature, message)
self.log.info('test parameter validity and error codes')
# signmessagewithprivkey has two required parameters
for num_params in [0, 1, 3, 4, 5]: