Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor: deduplicate the message sign/verify code #17577

Open
wants to merge 3 commits into
base: master
from
Open
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -219,6 +219,7 @@ BITCOIN_CORE_H = \
util/system.h \
util/macros.h \
util/memory.h \
util/message.h \
util/moneystr.h \
util/rbf.h \
util/settings.h \
@@ -515,6 +516,7 @@ libbitcoin_util_a_SOURCES = \
util/error.cpp \
util/fees.cpp \
util/system.cpp \
util/message.cpp \
util/moneystr.cpp \
util/rbf.cpp \
util/settings.cpp \
@@ -11,7 +11,7 @@
#include <qt/walletmodel.h>

#include <key_io.h>
#include <util/validation.h> // For strMessageMagic
#include <util/message.h> // For MessageSign(), MessageVerify()
#include <wallet/wallet.h>

#include <vector>
@@ -141,13 +141,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("QLabel { color: red; }");
ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signing failed.") + QString("</nobr>"));
return;
@@ -156,7 +153,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
ui->statusLabel_SM->setStyleSheet("QLabel { color: green; }");
ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signed.") + QString("</nobr>"));

ui->signatureOut_SM->setText(QString::fromStdString(EncodeBase64(vchSig.data(), vchSig.size())));
ui->signatureOut_SM->setText(QString::fromStdString(signature));
}

void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked()
@@ -189,51 +186,55 @@ void SignVerifyMessageDialog::on_addressBookButton_VM_clicked()

void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked()
{
CTxDestination destination = DecodeDestination(ui->addressIn_VM->text().toStdString());
if (!IsValidDestination(destination)) {
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_VM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again."));
return;
}
if (!boost::get<PKHash>(&destination)) {
ui->addressIn_VM->setValid(false);
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
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)
{
ui->signatureIn_VM->setValid(false);
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_VM->setText(tr("The signature could not be decoded.") + QString(" ") + tr("Please check the signature and try again."));
return;
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();

switch (MessageVerify(address, signature, message)) {
case MessageVerificationResult::ERR_INVALID_ADDRESS:
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
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("QLabel { color: red; }");
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->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_VM->setText(
tr("The signature could not be decoded.") + QString(" ") +
tr("Please check the signature and try again.")
);
return;
case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
ui->signatureIn_VM->setValid(false);
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
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->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_VM->setText(
QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>")
);
return;
case MessageVerificationResult::OK:
ui->statusLabel_VM->setStyleSheet("QLabel { color: green; }");
ui->statusLabel_VM->setText(
QString("<nobr>") + tr("Message verified.") + QString("</nobr>")
);
return;
}

CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << ui->messageIn_VM->document()->toPlainText().toStdString();

CPubKey pubkey;
if (!pubkey.RecoverCompact(ss.GetHash(), vchSig))
{
ui->signatureIn_VM->setValid(false);
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_VM->setText(tr("The signature did not match the message digest.") + QString(" ") + tr("Please check the signature and try again."));
return;
}

if (!(CTxDestination(PKHash(pubkey)) == destination)) {
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_VM->setText(QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>"));
return;
}

ui->statusLabel_VM->setStyleSheet("QLabel { color: green; }");
ui->statusLabel_VM->setText(QString("<nobr>") + tr("Message verified.") + QString("</nobr>"));
}

void SignVerifyMessageDialog::on_clearButton_VM_clicked()
@@ -11,9 +11,9 @@
#include <rpc/util.h>
#include <script/descriptor.h>
#include <util/check.h>
#include <util/message.h> // For MessageSign(), MessageVerify()
#include <util/strencodings.h>
#include <util/system.h>
#include <util/validation.h>

#include <stdint.h>
#include <tuple>
@@ -271,31 +271,19 @@ 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)) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
switch (MessageVerify(strAddress, strSign, strMessage)) {
case MessageVerificationResult::ERR_INVALID_ADDRESS:
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
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;
}

const PKHash *pkhash = boost::get<PKHash>(&destination);
if (!pkhash) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
}

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() == *pkhash);
}

static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
@@ -327,15 +315,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.data(), vchSig.size());
return signature;
}

static UniValue setmocktime(const JSONRPCRequest& request)
@@ -5,17 +5,22 @@
#include <util/system.h>

#include <clientversion.h>
#include <hash.h> // For Hash()
#include <key.h> // For CKey
#include <optional.h>
#include <sync.h>
#include <test/util/setup_common.h>
#include <test/util/str.h>
#include <uint256.h>
#include <util/message.h> // For MessageSign(), MessageVerify()
#include <util/moneystr.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/time.h>
#include <util/spanparsing.h>
#include <util/vector.h>

#include <array>
#include <stdint.h>
#include <thread>
#include <univalue.h>
@@ -2003,4 +2008,101 @@ BOOST_AUTO_TEST_CASE(test_tracked_vector)
BOOST_CHECK_EQUAL(v8[2].copies, 0);
}

BOOST_AUTO_TEST_CASE(message_sign)
{
const std::array<unsigned char, 32> privkey_bytes = {
// just some random data
// derived address from this private key: 15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs
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 =
"IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk=";

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(
"3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV",
"signature should be irrelevant",
"message too"),
MessageVerificationResult::ERR_ADDRESS_NO_KEY);

BOOST_CHECK_EQUAL(
MessageVerify(
"1KqbBpLy5FARmTPD4VZnDDpYjkUvkr82Pm",
"invalid signature, not in base64 encoding",
"message should be irrelevant"),
MessageVerificationResult::ERR_MALFORMED_SIGNATURE);

BOOST_CHECK_EQUAL(
MessageVerify(
"1KqbBpLy5FARmTPD4VZnDDpYjkUvkr82Pm",
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"message should be irrelevant"),
MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED);

BOOST_CHECK_EQUAL(
MessageVerify(
"15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs",
"IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk=",
"I never signed this"),
MessageVerificationResult::ERR_NOT_SIGNED);

BOOST_CHECK_EQUAL(
MessageVerify(
"15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs",
"IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk=",
"Trust no one"),
MessageVerificationResult::OK);

BOOST_CHECK_EQUAL(
MessageVerify(
"11canuhp9X2NocwCq7xNrQYTmUgZAnLK3",
"IIcaIENoYW5jZWxsb3Igb24gYnJpbmsgb2Ygc2Vjb25kIGJhaWxvdXQgZm9yIGJhbmtzIAaHRtbCeDZINyavx14=",
"Trust me"),
MessageVerificationResult::OK);
}

BOOST_AUTO_TEST_CASE(message_hash)
{
const std::string unsigned_tx = "...";
const uint256 signature_hash = Hash(unsigned_tx.begin(), unsigned_tx.end());

const uint256 message_hash = MessageHash(unsigned_tx);
BOOST_CHECK_NE(message_hash, signature_hash);
}

BOOST_AUTO_TEST_SUITE_END()
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.