merge bitcoin#17577: deduplicate the message sign/verify code

This commit is contained in:
Kittywhiskers Van Gogh 2022-09-19 02:19:22 +05:30
parent 0e58b340e6
commit d155f13c76
11 changed files with 332 additions and 89 deletions

View File

@ -303,6 +303,7 @@ BITCOIN_CORE_H = \
util/getuniquepath.h \
util/macros.h \
util/memory.h \
util/message.h \
util/moneystr.h \
util/ranges.h \
util/ref.h \
@ -702,13 +703,14 @@ libbitcoin_util_a_SOURCES = \
support/cleanse.cpp \
sync.cpp \
threadinterrupt.cpp \
util/asmap.cpp \
util/bip32.cpp \
util/bytevectorhash.cpp \
util/error.cpp \
util/fees.cpp \
util/getuniquepath.cpp \
util/system.cpp \
util/asmap.cpp \
util/message.cpp \
util/moneystr.cpp \
util/settings.cpp \
util/spanparsing.cpp \

View File

@ -4,7 +4,7 @@
#include <key_io.h>
#include <hash.h>
#include <util/validation.h> // For strMessageMagic
#include <util/message.h> // For MESSAGE_MAGIC
#include <messagesigner.h>
#include <tinyformat.h>
#include <util/strencodings.h>
@ -23,7 +23,7 @@ bool CMessageSigner::GetKeysFromSecret(const std::string& strSecret, CKey& keyRe
bool CMessageSigner::SignMessage(const std::string& strMessage, std::vector<unsigned char>& vchSigRet, const CKey& key)
{
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << MESSAGE_MAGIC;
ss << strMessage;
return CHashSigner::SignHash(ss.GetHash(), key, vchSigRet);
@ -37,7 +37,7 @@ bool CMessageSigner::VerifyMessage(const CPubKey& pubkey, const std::vector<unsi
bool CMessageSigner::VerifyMessage(const CKeyID& keyID, const std::vector<unsigned char>& vchSig, const std::string& strMessage, std::string& strErrorRet)
{
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << MESSAGE_MAGIC;
ss << strMessage;
return CHashSigner::VerifyHash(ss.GetHash(), keyID, vchSig, strErrorRet);

View File

@ -12,7 +12,7 @@
#include <key_io.h>
#include <util/strencodings.h>
#include <util/validation.h> // For strMessageMagic
#include <util/message.h> // For MessageSign(), MessageVerify()
#include <validation.h>
#include <vector>
@ -169,13 +169,10 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
return;
}
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << ui->messageIn_SM->document()->toPlainText().toStdString();
const std::string& message = ui->messageIn_SM->document()->toPlainText().toStdString();
std::string signature;
std::vector<unsigned char> vchSig;
if (!key.SignCompact(ss.GetHash(), vchSig))
{
if (!MessageSign(key, message, signature)) {
ui->statusLabel_SM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR));
ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signing failed.") + QString("</nobr>"));
return;
@ -184,7 +181,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
ui->statusLabel_SM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_SUCCESS));
ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signed.") + QString("</nobr>"));
ui->signatureOut_SM->setText(QString::fromStdString(EncodeBase64(vchSig)));
ui->signatureOut_SM->setText(QString::fromStdString(signature));
}
void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked()
@ -217,51 +214,57 @@ void SignVerifyMessageDialog::on_addressBookButton_VM_clicked()
void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked()
{
CTxDestination destination = DecodeDestination(ui->addressIn_VM->text().toStdString());
if (!IsValidDestination(destination)) {
const std::string& address = ui->addressIn_VM->text().toStdString();
const std::string& signature = ui->signatureIn_VM->text().toStdString();
const std::string& message = ui->messageIn_VM->document()->toPlainText().toStdString();
const auto result = MessageVerify(address, signature, message);
if (result == MessageVerificationResult::OK) {
ui->statusLabel_VM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_SUCCESS));
} else {
ui->statusLabel_VM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR));
ui->statusLabel_VM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again."));
return;
}
if (!boost::get<CKeyID>(&destination)) {
switch (result) {
case MessageVerificationResult::OK:
ui->statusLabel_VM->setText(
QString("<nobr>") + tr("Message verified.") + QString("</nobr>")
);
return;
case MessageVerificationResult::ERR_INVALID_ADDRESS:
ui->statusLabel_VM->setText(
tr("The entered address is invalid.") + QString(" ") +
tr("Please check the address and try again.")
);
return;
case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
ui->addressIn_VM->setValid(false);
ui->statusLabel_VM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR));
ui->statusLabel_VM->setText(tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again."));
ui->statusLabel_VM->setText(
tr("The entered address does not refer to a key.") + QString(" ") +
tr("Please check the address and try again.")
);
return;
}
bool fInvalid = false;
std::vector<unsigned char> vchSig = DecodeBase64(ui->signatureIn_VM->text().toStdString().c_str(), &fInvalid);
if (fInvalid)
{
case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
ui->signatureIn_VM->setValid(false);
ui->statusLabel_VM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR));
ui->statusLabel_VM->setText(tr("The signature could not be decoded.") + QString(" ") + tr("Please check the signature and try again."));
ui->statusLabel_VM->setText(
tr("The signature could not be decoded.") + QString(" ") +
tr("Please check the signature and try again.")
);
return;
}
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << ui->messageIn_VM->document()->toPlainText().toStdString();
CPubKey pubkey;
if (!pubkey.RecoverCompact(ss.GetHash(), vchSig))
{
case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
ui->signatureIn_VM->setValid(false);
ui->statusLabel_VM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR));
ui->statusLabel_VM->setText(tr("The signature did not match the message digest.") + QString(" ") + tr("Please check the signature and try again."));
ui->statusLabel_VM->setText(
tr("The signature did not match the message digest.") + QString(" ") +
tr("Please check the signature and try again.")
);
return;
case MessageVerificationResult::ERR_NOT_SIGNED:
ui->statusLabel_VM->setText(
QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>")
);
return;
}
if (!(CTxDestination(pubkey.GetID()) == destination)) {
ui->statusLabel_VM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR));
ui->statusLabel_VM->setText(QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>"));
return;
}
ui->statusLabel_VM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_SUCCESS));
ui->statusLabel_VM->setText(QString("<nobr>") + tr("Message verified.") + QString("</nobr>"));
}
void SignVerifyMessageDialog::on_clearButton_VM_clicked()

View File

@ -20,11 +20,11 @@
#include <script/descriptor.h>
#include <txmempool.h>
#include <util/check.h>
#include <util/message.h> // For MessageSign(), MessageVerify()
#include <util/ref.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <validation.h>
#include <util/validation.h>
#include <masternode/sync.h>
#include <spork.h>
@ -443,31 +443,21 @@ static UniValue verifymessage(const JSONRPCRequest& request)
std::string strSign = request.params[1].get_str();
std::string strMessage = request.params[2].get_str();
CTxDestination destination = DecodeDestination(strAddress);
if (!IsValidDestination(destination)) {
switch (MessageVerify(strAddress, strSign, strMessage)) {
case MessageVerificationResult::ERR_INVALID_ADDRESS:
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
}
const CKeyID *keyID = boost::get<CKeyID>(&destination);
if (!keyID) {
case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding");
case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
case MessageVerificationResult::ERR_NOT_SIGNED:
return false;
case MessageVerificationResult::OK:
return true;
}
bool fInvalid = false;
std::vector<unsigned char> vchSig = DecodeBase64(strSign.c_str(), &fInvalid);
if (fInvalid)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding");
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << strMessage;
CPubKey pubkey;
if (!pubkey.RecoverCompact(ss.GetHash(), vchSig))
return false;
return (pubkey.GetID() == *keyID);
return false;
}
static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
@ -499,15 +489,13 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
}
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << strMessage;
std::string signature;
std::vector<unsigned char> vchSig;
if (!key.SignCompact(ss.GetHash(), vchSig))
if (!MessageSign(key, strMessage, signature)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
}
return EncodeBase64(vchSig);
return signature;
}
static UniValue setmocktime(const JSONRPCRequest& request)

View File

@ -16,8 +16,8 @@
#include <protocol.h>
#include <script/standard.h>
#include <timedata.h>
#include <util/message.h> // for MESSAGE_MAGIC
#include <util/ranges.h>
#include <util/validation.h> // for strMessageMagic
#include <validation.h>
#include <string>
@ -422,7 +422,7 @@ bool CSporkMessage::GetSignerKeyID(CKeyID& retKeyidSporkSigner) const
} else {
std::string strMessage = std::to_string(nSporkID) + std::to_string(nValue) + std::to_string(nTimeSigned);
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << MESSAGE_MAGIC;
ss << strMessage;
if (!pubkeyFromSig.RecoverCompact(ss.GetHash(), vchSig)) {
return false;

View File

@ -5,18 +5,23 @@
#include <util/system.h>
#include <clientversion.h>
#include <hash.h> // For Hash()
#include <key.h> // For CKey
#include <sync.h>
#include <test/util/logging.h>
#include <test/util.h>
#include <uint256.h>
#include <util/getuniquepath.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/message.h> // For MessageSign(), MessageVerify(), MESSAGE_MAGIC
#include <util/moneystr.h>
#include <util/time.h>
#include <test/util/setup_common.h>
#include <util/vector.h>
#include <util/spanparsing.h>
#include <array>
#include <stdint.h>
#include <thread>
#include <univalue.h>
@ -1911,4 +1916,109 @@ BOOST_AUTO_TEST_CASE(test_LogEscapeMessage)
BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage(NUL), R"(O\x00O)");
}
BOOST_AUTO_TEST_CASE(message_sign)
{
const std::array<unsigned char, 32> privkey_bytes = {
// just some random data
// derived address from this private key: XetGnWHsPXV9VSkWzB6Wn2KhZLD24gqa5j
0xD9, 0x7F, 0x51, 0x08, 0xF1, 0x1C, 0xDA, 0x6E,
0xEE, 0xBA, 0xAA, 0x42, 0x0F, 0xEF, 0x07, 0x26,
0xB1, 0xF8, 0x98, 0x06, 0x0B, 0x98, 0x48, 0x9F,
0xA3, 0x09, 0x84, 0x63, 0xC0, 0x03, 0x28, 0x66
};
const std::string message = "Trust no one";
const std::string expected_signature =
"IIOzMDkvw3GtLWXkeEYRRRH53MOLHM44sJ428Nu4NNacTPJTGcKesMJ+3s3OadYK34tpSQIhu922EviNNWTsiQg=";
CKey privkey;
std::string generated_signature;
BOOST_REQUIRE_MESSAGE(!privkey.IsValid(),
"Confirm the private key is invalid");
BOOST_CHECK_MESSAGE(!MessageSign(privkey, message, generated_signature),
"Sign with an invalid private key");
privkey.Set(privkey_bytes.begin(), privkey_bytes.end(), true);
BOOST_REQUIRE_MESSAGE(privkey.IsValid(),
"Confirm the private key is valid");
BOOST_CHECK_MESSAGE(MessageSign(privkey, message, generated_signature),
"Sign with a valid private key");
BOOST_CHECK_EQUAL(expected_signature, generated_signature);
}
BOOST_AUTO_TEST_CASE(message_verify)
{
BOOST_CHECK_EQUAL(
MessageVerify(
"invalid address",
"signature should be irrelevant",
"message too"),
MessageVerificationResult::ERR_INVALID_ADDRESS);
BOOST_CHECK_EQUAL(
MessageVerify(
"7iRPy8FEHBzbChrktsG85YbDZ3SuiCxsNq",
"signature should be irrelevant",
"message too"),
MessageVerificationResult::ERR_ADDRESS_NO_KEY);
BOOST_CHECK_EQUAL(
MessageVerify(
"XuXS24zs2xP1vPynvNt14kWLa64csH8Mur",
"invalid signature, not in base64 encoding",
"message should be irrelevant"),
MessageVerificationResult::ERR_MALFORMED_SIGNATURE);
BOOST_CHECK_EQUAL(
MessageVerify(
"XuXS24zs2xP1vPynvNt14kWLa64csH8Mur",
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"message should be irrelevant"),
MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED);
BOOST_CHECK_EQUAL(
MessageVerify(
"XetGnWHsPXV9VSkWzB6Wn2KhZLD24gqa5j",
"IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk=",
"I never signed this"),
MessageVerificationResult::ERR_NOT_SIGNED);
BOOST_CHECK_EQUAL(
MessageVerify(
"XetGnWHsPXV9VSkWzB6Wn2KhZLD24gqa5j",
"IIOzMDkvw3GtLWXkeEYRRRH53MOLHM44sJ428Nu4NNacTPJTGcKesMJ+3s3OadYK34tpSQIhu922EviNNWTsiQg=",
"Trust no one"),
MessageVerificationResult::OK);
BOOST_CHECK_EQUAL(
MessageVerify(
"XenV77v8QQ3rwjyCb3j2fCwfvgbkC4Vwaj",
"IOACalWiJTLJ2U7wTKICx5mQ2tOAJ3to8dko8FMb2XSYbmvL+yMWedyfSfaK6V8jwoociyYx628nkXXnrOhPFIY=",
"Trust me"),
MessageVerificationResult::OK);
}
BOOST_AUTO_TEST_CASE(message_hash)
{
const std::string unsigned_tx = "...";
const std::string prefixed_message =
std::string(1, (char)MESSAGE_MAGIC.length()) +
MESSAGE_MAGIC +
std::string(1, (char)unsigned_tx.length()) +
unsigned_tx;
const uint256 signature_hash = Hash(unsigned_tx.begin(), unsigned_tx.end());
const uint256 message_hash1 = Hash(prefixed_message.begin(), prefixed_message.end());
const uint256 message_hash2 = MessageHash(unsigned_tx);
BOOST_CHECK_EQUAL(message_hash1, message_hash2);
BOOST_CHECK_NE(message_hash1, signature_hash);
}
BOOST_AUTO_TEST_SUITE_END()

78
src/util/message.cpp Normal file
View File

@ -0,0 +1,78 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <hash.h> // For CHashWriter
#include <key.h> // For CKey
#include <key_io.h> // For DecodeDestination()
#include <pubkey.h> // For CPubKey
#include <script/standard.h> // For CTxDestination, IsValidDestination(), CKeyID
#include <serialize.h> // For SER_GETHASH
#include <util/message.h>
#include <util/strencodings.h> // For DecodeBase64()
#include <string>
#include <vector>
/**
* Text used to signify that a signed message follows and to prevent
* inadvertently signing a transaction.
*/
const std::string MESSAGE_MAGIC = "DarkCoin Signed Message:\n";
MessageVerificationResult MessageVerify(
const std::string& address,
const std::string& signature,
const std::string& message)
{
CTxDestination destination = DecodeDestination(address);
if (!IsValidDestination(destination)) {
return MessageVerificationResult::ERR_INVALID_ADDRESS;
}
if (boost::get<CKeyID>(&destination) == nullptr) {
return MessageVerificationResult::ERR_ADDRESS_NO_KEY;
}
bool invalid = false;
std::vector<unsigned char> signature_bytes = DecodeBase64(signature.c_str(), &invalid);
if (invalid) {
return MessageVerificationResult::ERR_MALFORMED_SIGNATURE;
}
CPubKey pubkey;
if (!pubkey.RecoverCompact(MessageHash(message), signature_bytes)) {
return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED;
}
if (!(CTxDestination(pubkey.GetID()) == destination)) {
return MessageVerificationResult::ERR_NOT_SIGNED;
}
return MessageVerificationResult::OK;
}
bool MessageSign(
const CKey& privkey,
const std::string& message,
std::string& signature)
{
std::vector<unsigned char> signature_bytes;
if (!privkey.SignCompact(MessageHash(message), signature_bytes)) {
return false;
}
signature = EncodeBase64(signature_bytes);
return true;
}
uint256 MessageHash(const std::string& message)
{
CHashWriter hasher(SER_GETHASH, 0);
hasher << MESSAGE_MAGIC << message;
return hasher.GetHash();
}

68
src/util/message.h Normal file
View File

@ -0,0 +1,68 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_UTIL_MESSAGE_H
#define BITCOIN_UTIL_MESSAGE_H
#include <key.h> // For CKey
#include <uint256.h>
#include <string>
extern const std::string MESSAGE_MAGIC;
/** 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)
* - signature
* - message
*/
enum class MessageVerificationResult {
//! The provided address is invalid.
ERR_INVALID_ADDRESS,
//! The provided address is valid but does not refer to a public key.
ERR_ADDRESS_NO_KEY,
//! The provided signature couldn't be parsed (maybe invalid base64).
ERR_MALFORMED_SIGNATURE,
//! A public key could not be recovered from the provided signature and message.
ERR_PUBKEY_NOT_RECOVERED,
//! The message was not signed with the private key of the provided address.
ERR_NOT_SIGNED,
//! The message verification was successful.
OK
};
/** Verify a signed message.
* @param[in] address Signer's bitcoin address, it must refer to a public key.
* @param[in] signature The signature in base64 format.
* @param[in] message The message that was signed.
* @return result code */
MessageVerificationResult MessageVerify(
const std::string& address,
const std::string& signature,
const std::string& message);
/** Sign a message.
* @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.
* @return true if signing was successful. */
bool MessageSign(
const CKey& privkey,
const std::string& message,
std::string& signature);
/**
* Hashes a message for signing and verification in a manner that prevents
* inadvertently signing a transaction.
*/
uint256 MessageHash(const std::string& message);
#endif // BITCOIN_UTIL_MESSAGE_H

View File

@ -16,5 +16,3 @@ std::string FormatStateMessage(const CValidationState &state)
state.GetDebugMessage().empty() ? "" : ", "+state.GetDebugMessage(),
state.GetRejectCode());
}
const std::string strMessageMagic = "DarkCoin Signed Message:\n";

View File

@ -13,6 +13,4 @@ class CValidationState;
/** Convert CValidationState to a human-readable message for logging */
std::string FormatStateMessage(const CValidationState &state);
extern const std::string strMessageMagic;
#endif // BITCOIN_UTIL_VALIDATION_H

View File

@ -19,13 +19,13 @@
#include <script/descriptor.h>
#include <util/bip32.h>
#include <util/fees.h>
#include <util/message.h> // For MessageSign()
#include <util/system.h>
#include <util/moneystr.h>
#include <util/ref.h>
#include <util/string.h>
#include <util/translation.h>
#include <util/url.h>
#include <util/validation.h>
#include <util/vector.h>
#include <validation.h>
#include <wallet/coincontrol.h>
@ -596,15 +596,13 @@ static UniValue signmessage(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");
}
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << strMessage;
std::string signature;
std::vector<unsigned char> vchSig;
if (!key.SignCompact(ss.GetHash(), vchSig))
if (!MessageSign(key, strMessage, signature)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
}
return EncodeBase64(vchSig);
return signature;
}
static UniValue getreceivedbyaddress(const JSONRPCRequest& request)