Skip to content

Commit 03f98b1

Browse files
committed
Merge #17577: refactor: deduplicate the message sign/verify code
e193a84 Refactor message hashing into a utility function (Jeffrey Czyz) f8f0d98 Deduplicate the message signing code (Vasil Dimov) 2ce3447 Deduplicate the message verifying code (Vasil Dimov) Pull request description: The message signing and verifying logic was replicated in a few places in the code. Consolidate in a newly introduced `MessageSign()` and `MessageVerify()` and add unit tests for them. ACKs for top commit: Sjors: re-ACK e193a84 achow101: ACK e193a84 instagibbs: utACK bitcoin/bitcoin@e193a84 meshcollider: utACK e193a84 Tree-SHA512: b0e02a7d4623a98c8f8c77627af1725e6df07700de4630c2f75da6beacdf55414c38ba147bc6d2a757491ab07c827dddf93e8632fe600478760e255714ddab88
2 parents a674e89 + e193a84 commit 03f98b1

File tree

9 files changed

+324
-81
lines changed

9 files changed

+324
-81
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ BITCOIN_CORE_H = \
220220
util/system.h \
221221
util/macros.h \
222222
util/memory.h \
223+
util/message.h \
223224
util/moneystr.h \
224225
util/rbf.h \
225226
util/settings.h \
@@ -517,6 +518,7 @@ libbitcoin_util_a_SOURCES = \
517518
util/error.cpp \
518519
util/fees.cpp \
519520
util/system.cpp \
521+
util/message.cpp \
520522
util/moneystr.cpp \
521523
util/rbf.cpp \
522524
util/settings.cpp \

src/qt/signverifymessagedialog.cpp

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#include <qt/walletmodel.h>
1212

1313
#include <key_io.h>
14-
#include <util/validation.h> // For strMessageMagic
14+
#include <util/message.h> // For MessageSign(), MessageVerify()
1515
#include <wallet/wallet.h>
1616

1717
#include <vector>
@@ -141,13 +141,10 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
141141
return;
142142
}
143143

144-
CHashWriter ss(SER_GETHASH, 0);
145-
ss << strMessageMagic;
146-
ss << ui->messageIn_SM->document()->toPlainText().toStdString();
144+
const std::string& message = ui->messageIn_SM->document()->toPlainText().toStdString();
145+
std::string signature;
147146

148-
std::vector<unsigned char> vchSig;
149-
if (!key.SignCompact(ss.GetHash(), vchSig))
150-
{
147+
if (!MessageSign(key, message, signature)) {
151148
ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }");
152149
ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signing failed.") + QString("</nobr>"));
153150
return;
@@ -156,7 +153,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
156153
ui->statusLabel_SM->setStyleSheet("QLabel { color: green; }");
157154
ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signed.") + QString("</nobr>"));
158155

159-
ui->signatureOut_SM->setText(QString::fromStdString(EncodeBase64(vchSig.data(), vchSig.size())));
156+
ui->signatureOut_SM->setText(QString::fromStdString(signature));
160157
}
161158

162159
void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked()
@@ -189,51 +186,57 @@ void SignVerifyMessageDialog::on_addressBookButton_VM_clicked()
189186

190187
void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked()
191188
{
192-
CTxDestination destination = DecodeDestination(ui->addressIn_VM->text().toStdString());
193-
if (!IsValidDestination(destination)) {
189+
const std::string& address = ui->addressIn_VM->text().toStdString();
190+
const std::string& signature = ui->signatureIn_VM->text().toStdString();
191+
const std::string& message = ui->messageIn_VM->document()->toPlainText().toStdString();
192+
193+
const auto result = MessageVerify(address, signature, message);
194+
195+
if (result == MessageVerificationResult::OK) {
196+
ui->statusLabel_VM->setStyleSheet("QLabel { color: green; }");
197+
} else {
194198
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
195-
ui->statusLabel_VM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again."));
196-
return;
197199
}
198-
if (!boost::get<PKHash>(&destination)) {
200+
201+
switch (result) {
202+
case MessageVerificationResult::OK:
203+
ui->statusLabel_VM->setText(
204+
QString("<nobr>") + tr("Message verified.") + QString("</nobr>")
205+
);
206+
return;
207+
case MessageVerificationResult::ERR_INVALID_ADDRESS:
208+
ui->statusLabel_VM->setText(
209+
tr("The entered address is invalid.") + QString(" ") +
210+
tr("Please check the address and try again.")
211+
);
212+
return;
213+
case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
199214
ui->addressIn_VM->setValid(false);
200-
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
201-
ui->statusLabel_VM->setText(tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again."));
215+
ui->statusLabel_VM->setText(
216+
tr("The entered address does not refer to a key.") + QString(" ") +
217+
tr("Please check the address and try again.")
218+
);
202219
return;
203-
}
204-
205-
bool fInvalid = false;
206-
std::vector<unsigned char> vchSig = DecodeBase64(ui->signatureIn_VM->text().toStdString().c_str(), &fInvalid);
207-
208-
if (fInvalid)
209-
{
220+
case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
210221
ui->signatureIn_VM->setValid(false);
211-
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
212-
ui->statusLabel_VM->setText(tr("The signature could not be decoded.") + QString(" ") + tr("Please check the signature and try again."));
222+
ui->statusLabel_VM->setText(
223+
tr("The signature could not be decoded.") + QString(" ") +
224+
tr("Please check the signature and try again.")
225+
);
213226
return;
214-
}
215-
216-
CHashWriter ss(SER_GETHASH, 0);
217-
ss << strMessageMagic;
218-
ss << ui->messageIn_VM->document()->toPlainText().toStdString();
219-
220-
CPubKey pubkey;
221-
if (!pubkey.RecoverCompact(ss.GetHash(), vchSig))
222-
{
227+
case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
223228
ui->signatureIn_VM->setValid(false);
224-
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
225-
ui->statusLabel_VM->setText(tr("The signature did not match the message digest.") + QString(" ") + tr("Please check the signature and try again."));
229+
ui->statusLabel_VM->setText(
230+
tr("The signature did not match the message digest.") + QString(" ") +
231+
tr("Please check the signature and try again.")
232+
);
226233
return;
227-
}
228-
229-
if (!(CTxDestination(PKHash(pubkey)) == destination)) {
230-
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
231-
ui->statusLabel_VM->setText(QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>"));
234+
case MessageVerificationResult::ERR_NOT_SIGNED:
235+
ui->statusLabel_VM->setText(
236+
QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>")
237+
);
232238
return;
233239
}
234-
235-
ui->statusLabel_VM->setStyleSheet("QLabel { color: green; }");
236-
ui->statusLabel_VM->setText(QString("<nobr>") + tr("Message verified.") + QString("</nobr>"));
237240
}
238241

239242
void SignVerifyMessageDialog::on_clearButton_VM_clicked()

src/rpc/misc.cpp

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
#include <scheduler.h>
1414
#include <script/descriptor.h>
1515
#include <util/check.h>
16+
#include <util/message.h> // For MessageSign(), MessageVerify()
1617
#include <util/strencodings.h>
1718
#include <util/system.h>
18-
#include <util/validation.h>
1919

2020
#include <stdint.h>
2121
#include <tuple>
@@ -278,31 +278,21 @@ static UniValue verifymessage(const JSONRPCRequest& request)
278278
std::string strSign = request.params[1].get_str();
279279
std::string strMessage = request.params[2].get_str();
280280

281-
CTxDestination destination = DecodeDestination(strAddress);
282-
if (!IsValidDestination(destination)) {
281+
switch (MessageVerify(strAddress, strSign, strMessage)) {
282+
case MessageVerificationResult::ERR_INVALID_ADDRESS:
283283
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
284-
}
285-
286-
const PKHash *pkhash = boost::get<PKHash>(&destination);
287-
if (!pkhash) {
284+
case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
288285
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
289-
}
290-
291-
bool fInvalid = false;
292-
std::vector<unsigned char> vchSig = DecodeBase64(strSign.c_str(), &fInvalid);
293-
294-
if (fInvalid)
286+
case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
295287
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding");
296-
297-
CHashWriter ss(SER_GETHASH, 0);
298-
ss << strMessageMagic;
299-
ss << strMessage;
300-
301-
CPubKey pubkey;
302-
if (!pubkey.RecoverCompact(ss.GetHash(), vchSig))
288+
case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
289+
case MessageVerificationResult::ERR_NOT_SIGNED:
303290
return false;
291+
case MessageVerificationResult::OK:
292+
return true;
293+
}
304294

305-
return (pubkey.GetID() == *pkhash);
295+
return false;
306296
}
307297

308298
static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
@@ -334,15 +324,13 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
334324
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
335325
}
336326

337-
CHashWriter ss(SER_GETHASH, 0);
338-
ss << strMessageMagic;
339-
ss << strMessage;
327+
std::string signature;
340328

341-
std::vector<unsigned char> vchSig;
342-
if (!key.SignCompact(ss.GetHash(), vchSig))
329+
if (!MessageSign(key, strMessage, signature)) {
343330
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
331+
}
344332

345-
return EncodeBase64(vchSig.data(), vchSig.size());
333+
return signature;
346334
}
347335

348336
static UniValue setmocktime(const JSONRPCRequest& request)

src/test/util_tests.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@
55
#include <util/system.h>
66

77
#include <clientversion.h>
8+
#include <hash.h> // For Hash()
9+
#include <key.h> // For CKey
810
#include <optional.h>
911
#include <sync.h>
1012
#include <test/util/setup_common.h>
1113
#include <test/util/str.h>
14+
#include <uint256.h>
15+
#include <util/message.h> // For MessageSign(), MessageVerify(), MESSAGE_MAGIC
1216
#include <util/moneystr.h>
1317
#include <util/strencodings.h>
1418
#include <util/string.h>
1519
#include <util/time.h>
1620
#include <util/spanparsing.h>
1721
#include <util/vector.h>
1822

23+
#include <array>
1924
#include <stdint.h>
2025
#include <thread>
2126
#include <univalue.h>
@@ -2025,4 +2030,109 @@ BOOST_AUTO_TEST_CASE(test_tracked_vector)
20252030
BOOST_CHECK_EQUAL(v8[2].copies, 0);
20262031
}
20272032

2033+
BOOST_AUTO_TEST_CASE(message_sign)
2034+
{
2035+
const std::array<unsigned char, 32> privkey_bytes = {
2036+
// just some random data
2037+
// derived address from this private key: 15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs
2038+
0xD9, 0x7F, 0x51, 0x08, 0xF1, 0x1C, 0xDA, 0x6E,
2039+
0xEE, 0xBA, 0xAA, 0x42, 0x0F, 0xEF, 0x07, 0x26,
2040+
0xB1, 0xF8, 0x98, 0x06, 0x0B, 0x98, 0x48, 0x9F,
2041+
0xA3, 0x09, 0x84, 0x63, 0xC0, 0x03, 0x28, 0x66
2042+
};
2043+
2044+
const std::string message = "Trust no one";
2045+
2046+
const std::string expected_signature =
2047+
"IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk=";
2048+
2049+
CKey privkey;
2050+
std::string generated_signature;
2051+
2052+
BOOST_REQUIRE_MESSAGE(!privkey.IsValid(),
2053+
"Confirm the private key is invalid");
2054+
2055+
BOOST_CHECK_MESSAGE(!MessageSign(privkey, message, generated_signature),
2056+
"Sign with an invalid private key");
2057+
2058+
privkey.Set(privkey_bytes.begin(), privkey_bytes.end(), true);
2059+
2060+
BOOST_REQUIRE_MESSAGE(privkey.IsValid(),
2061+
"Confirm the private key is valid");
2062+
2063+
BOOST_CHECK_MESSAGE(MessageSign(privkey, message, generated_signature),
2064+
"Sign with a valid private key");
2065+
2066+
BOOST_CHECK_EQUAL(expected_signature, generated_signature);
2067+
}
2068+
2069+
BOOST_AUTO_TEST_CASE(message_verify)
2070+
{
2071+
BOOST_CHECK_EQUAL(
2072+
MessageVerify(
2073+
"invalid address",
2074+
"signature should be irrelevant",
2075+
"message too"),
2076+
MessageVerificationResult::ERR_INVALID_ADDRESS);
2077+
2078+
BOOST_CHECK_EQUAL(
2079+
MessageVerify(
2080+
"3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV",
2081+
"signature should be irrelevant",
2082+
"message too"),
2083+
MessageVerificationResult::ERR_ADDRESS_NO_KEY);
2084+
2085+
BOOST_CHECK_EQUAL(
2086+
MessageVerify(
2087+
"1KqbBpLy5FARmTPD4VZnDDpYjkUvkr82Pm",
2088+
"invalid signature, not in base64 encoding",
2089+
"message should be irrelevant"),
2090+
MessageVerificationResult::ERR_MALFORMED_SIGNATURE);
2091+
2092+
BOOST_CHECK_EQUAL(
2093+
MessageVerify(
2094+
"1KqbBpLy5FARmTPD4VZnDDpYjkUvkr82Pm",
2095+
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
2096+
"message should be irrelevant"),
2097+
MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED);
2098+
2099+
BOOST_CHECK_EQUAL(
2100+
MessageVerify(
2101+
"15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs",
2102+
"IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk=",
2103+
"I never signed this"),
2104+
MessageVerificationResult::ERR_NOT_SIGNED);
2105+
2106+
BOOST_CHECK_EQUAL(
2107+
MessageVerify(
2108+
"15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs",
2109+
"IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk=",
2110+
"Trust no one"),
2111+
MessageVerificationResult::OK);
2112+
2113+
BOOST_CHECK_EQUAL(
2114+
MessageVerify(
2115+
"11canuhp9X2NocwCq7xNrQYTmUgZAnLK3",
2116+
"IIcaIENoYW5jZWxsb3Igb24gYnJpbmsgb2Ygc2Vjb25kIGJhaWxvdXQgZm9yIGJhbmtzIAaHRtbCeDZINyavx14=",
2117+
"Trust me"),
2118+
MessageVerificationResult::OK);
2119+
}
2120+
2121+
BOOST_AUTO_TEST_CASE(message_hash)
2122+
{
2123+
const std::string unsigned_tx = "...";
2124+
const std::string prefixed_message =
2125+
std::string(1, (char)MESSAGE_MAGIC.length()) +
2126+
MESSAGE_MAGIC +
2127+
std::string(1, (char)unsigned_tx.length()) +
2128+
unsigned_tx;
2129+
2130+
const uint256 signature_hash = Hash(unsigned_tx.begin(), unsigned_tx.end());
2131+
const uint256 message_hash1 = Hash(prefixed_message.begin(), prefixed_message.end());
2132+
const uint256 message_hash2 = MessageHash(unsigned_tx);
2133+
2134+
BOOST_CHECK_EQUAL(message_hash1, message_hash2);
2135+
BOOST_CHECK_NE(message_hash1, signature_hash);
2136+
}
2137+
20282138
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)