mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-12 19:20:42 +02:00
Merge 24058 via bip322-28+knots
This commit is contained in:
commit
c7dd00c6de
@ -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)).
|
||||
|
@ -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};
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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>")
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>;
|
||||
|
@ -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}));
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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]:
|
||||
|
Loading…
Reference in New Issue
Block a user