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

Merged
merged 3 commits into from Feb 25, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -220,6 +220,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 \
@@ -517,6 +518,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,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("QLabel { color: green; }");
} else {
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)) {

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("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."));
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("QLabel { color: red; }");
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("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."));
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>"));
case MessageVerificationResult::ERR_NOT_SIGNED:
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>
@@ -276,31 +276,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 PKHash *pkhash = boost::get<PKHash>(&destination);
if (!pkhash) {
case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
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)
case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
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))
case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
case MessageVerificationResult::ERR_NOT_SIGNED:
return false;
case MessageVerificationResult::OK:
return true;
}

return (pubkey.GetID() == *pkhash);
return false;
}

static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
@@ -332,15 +322,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(), MESSAGE_MAGIC
#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>
@@ -2025,4 +2030,109 @@ 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 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()