Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ BITCOIN_CORE_H = \
scheduler.h \
script/descriptor.h \
script/keyorigin.h \
script/proof.h \
script/sigcache.h \
script/sign.h \
script/signingprovider.h \
Expand Down Expand Up @@ -507,6 +508,7 @@ libbitcoin_util_a_SOURCES = \
random.cpp \
randomenv.cpp \
rpc/request.cpp \
script/proof.cpp \
support/cleanse.cpp \
sync.cpp \
threadinterrupt.cpp \
Expand Down
6 changes: 6 additions & 0 deletions src/interfaces/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <policy/fees.h>
#include <primitives/transaction.h>
#include <script/standard.h>
#include <script/proof.h>
#include <support/allocators/secure.h>
#include <sync.h>
#include <ui_interface.h>
Expand Down Expand Up @@ -477,6 +478,11 @@ class WalletImpl : public Wallet
{
RemoveWallet(m_wallet);
}
void signMessage(const std::string& message, const CTxDestination& destination, std::vector<uint8_t>& signature_out) override
{
auto provider = m_wallet->GetSigningProvider(GetScriptForDestination(destination));
proof::SignMessageWithSigningProvider(std::move(provider), message, destination, signature_out);
}
std::unique_ptr<Handler> handleUnload(UnloadFn fn) override
{
return MakeHandler(m_wallet->NotifyUnload.connect(fn));
Expand Down
5 changes: 5 additions & 0 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,11 @@ class Wallet
// Remove wallet.
virtual void remove() = 0;

/**
* Attempt to sign a message with the given destination.
*/
virtual void signMessage(const std::string& message, const CTxDestination& destination, std::vector<uint8_t>& signature_out) = 0;

//! Register handler for unload message.
using UnloadFn = std::function<void()>;
virtual std::unique_ptr<Handler> handleUnload(UnloadFn fn) = 0;
Expand Down
31 changes: 12 additions & 19 deletions src/qt/signverifymessagedialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,6 @@ 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;
}
const PKHash* pkhash = boost::get<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;
}

WalletModel::UnlockContext ctx(model->requestUnlock());
if (!ctx.isValid())
Expand All @@ -133,27 +126,27 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
return;
}

CKey key;
if (!model->wallet().getPrivKey(GetScriptForDestination(destination), CKeyID(*pkhash), key))
{
ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_SM->setText(tr("Private key for the entered address is not available."));
return;
}
auto message = ui->messageIn_SM->document()->toPlainText().toStdString();
std::vector<uint8_t> vchSig;

const std::string& message = ui->messageIn_SM->document()->toPlainText().toStdString();
std::string signature;
std::string failure = "";

try {
model->wallet().signMessage(message, destination, vchSig);
} catch (const std::runtime_error& err) {
failure = err.what();
}

if (!MessageSign(key, message, signature)) {
if (failure != "") {
ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signing failed.") + QString("</nobr>"));
ui->statusLabel_SM->setText(tr(failure.c_str()));
return;
}

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

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

void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked()
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "importmulti", 1, "options" },
{ "verifychain", 0, "checklevel" },
{ "verifychain", 1, "nblocks" },
{ "verifymessage", 3, "verbosity" },
{ "getblockstats", 0, "hash_or_height" },
{ "getblockstats", 1, "stats" },
{ "pruneblockchain", 0, "height" },
Expand Down
41 changes: 21 additions & 20 deletions src/rpc/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,15 @@ static UniValue verifymessage(const JSONRPCRequest& request)
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
{"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."},
{"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."},
{"verbosity", RPCArg::Type::NUM, "0", "0 to return boolean true/false, 1 to return BIP-322 result strings."}
},
RPCResult{
RPCResult::Type::BOOL, "", "If the signature is verified or not."
{
RPCResult{"if verbosity is set to 0",
RPCResult::Type::BOOL, "", "If the proof is valid or not."
},
RPCResult{"if verbosity is set to 1",
RPCResult::Type::STR, "", "Error string",
},
},
RPCExamples{
"\nUnlock the wallet for 30 seconds\n"
Expand All @@ -283,22 +289,11 @@ static UniValue verifymessage(const JSONRPCRequest& request)
std::string strAddress = request.params[0].get_str();
std::string strSign = request.params[1].get_str();
std::string strMessage = request.params[2].get_str();
int verbosity = request.params[3].isNull() ? 0 : request.params[3].get_int();

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;
}

return false;
auto rv = MessageVerify(strAddress, strSign, strMessage);
if (verbosity > 0) return MessageVerificationResultString(rv);
return rv == MessageVerificationResult::OK;
}

static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
Expand All @@ -308,6 +303,7 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
{
{"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."},
{"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."},
{"address_type", RPCArg::Type::STR, "legacy", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
},
RPCResult{
RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64"
Expand All @@ -325,14 +321,19 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
std::string strPrivkey = request.params[0].get_str();
std::string strMessage = request.params[1].get_str();

OutputType address_type = OutputType::LEGACY;
if (!request.params[2].isNull() && !ParseOutputType(request.params[2].get_str(), address_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
}

CKey key = DecodeSecret(strPrivkey);
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
}

std::string signature;

if (!MessageSign(key, strMessage, signature)) {
if (!MessageSign(key, strMessage, address_type, signature)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
}

Expand Down Expand Up @@ -599,8 +600,8 @@ static const CRPCCommand commands[] =
{ "util", "createmultisig", &createmultisig, {"nrequired","keys","address_type"} },
{ "util", "deriveaddresses", &deriveaddresses, {"descriptor", "range"} },
{ "util", "getdescriptorinfo", &getdescriptorinfo, {"descriptor"} },
{ "util", "verifymessage", &verifymessage, {"address","signature","message"} },
{ "util", "signmessagewithprivkey", &signmessagewithprivkey, {"privkey","message"} },
{ "util", "verifymessage", &verifymessage, {"address","signature","message","verbosity"} },
{ "util", "signmessagewithprivkey", &signmessagewithprivkey, {"privkey","message","address_type"} },

/* Not shown in help */
{ "hidden", "setmocktime", &setmocktime, {"timestamp"}},
Expand Down
14 changes: 14 additions & 0 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,20 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
template class GenericTransactionSignatureChecker<CTransaction>;
template class GenericTransactionSignatureChecker<CMutableTransaction>;

bool SimpleSignatureChecker::CheckSig(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
{
CPubKey pubkey(vchPubKey);
if (!pubkey.IsValid()) return false;

// Hash type is one byte tacked on to the end of the signature
std::vector<unsigned char> vchSig(vchSigIn);
if (vchSig.empty()) return false;
// int nHashType = vchSig.back();
vchSig.pop_back();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this make signatures trivially malleable? Also, maybe SIGHASH_ALL is somewhat abused here. Do we need a flag at all?

Copy link
Contributor Author

@kallewoof kallewoof Sep 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a problem to have malleable signature in this case, but perhaps this should be bolted down to always say SIGHASH_ALL.

We do not need the flag, in all honesty, but removing it means special-casing stuff in various places, so it felt simpler to just keep it in there.


return pubkey.Verify(hash, vchSig);
}

static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
{
std::vector<std::vector<unsigned char> > stack;
Expand Down
12 changes: 12 additions & 0 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@ class BaseSignatureChecker
virtual ~BaseSignatureChecker() {}
};

/** A general purpose signature checker. */
class SimpleSignatureChecker : public BaseSignatureChecker
{
private:
uint256 hash;

public:
const uint256& GetHash() const { return hash; }
explicit SimpleSignatureChecker(const uint256& hash_in) : hash(hash_in) {}
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
};

template <class T>
class GenericTransactionSignatureChecker : public BaseSignatureChecker
{
Expand Down
102 changes: 102 additions & 0 deletions src/script/proof.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) 2019-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 <script/proof.h>

/**
* Text used to signify that a signed message follows and to prevent
* inadvertently signing a transaction.
*/
const std::string MESSAGE_MAGIC = "Bitcoin Signed Message:\n";

namespace proof
{

Result SignMessage::Prepare(const std::string& message, std::set<CScript>& inputs_out, uint256& sighash_out, CScript& spk_out) const {
Result rv = Purpose::Prepare(m_scriptpubkey, inputs_out);
if (rv != Result::Valid) return rv;
CHashWriter hw(SER_DISK, 0);
std::string s = MESSAGE_MAGIC + message;
hw << m_scriptpubkey << s;
sighash_out = hw.GetHash();
spk_out = m_scriptpubkey;
return Result::Valid;
}

void SignMessageWithSigningProvider(std::unique_ptr<SigningProvider> sp, const std::string& message, const CTxDestination& destination, std::vector<uint8_t>& signature_out)
{
signature_out.clear();

// if this is a P2PKH, use the legacy approach
const PKHash *pkhash = boost::get<PKHash>(&destination);
if (pkhash) {
CKey key;
if (!sp->GetKey(CKeyID(*pkhash), key)) {
throw privkey_unavailable_error();
}

CHashWriter ss(SER_GETHASH, 0);
ss << MESSAGE_MAGIC << message;

if (!key.SignCompact(ss.GetHash(), signature_out)) {
throw signing_error();
}
} else {
SignMessageWorkspace p;

p.AppendDestinationChallenge(destination);

p.Prove(message, std::move(sp));

CVectorWriter w(SER_DISK, PROTOCOL_VERSION, signature_out, 0);
w << p.m_proof;
}
}

void SignMessageWithPrivateKey(const CKey& key, OutputType address_type, const std::string& message, std::vector<uint8_t>& signature_out)
{
if (address_type == OutputType::LEGACY) {
CHashWriter ss(SER_GETHASH, 0);
ss << MESSAGE_MAGIC << message;

if (!key.SignCompact(ss.GetHash(), signature_out)) {
throw signing_error();
}
} else {
SignMessageWorkspace p;

p.AppendPrivKeyChallenge(key, address_type);

p.Prove(message);

CVectorWriter w(SER_DISK, PROTOCOL_VERSION, signature_out, 0);
w << p.m_proof;
}
}

Result VerifySignature(const std::string& message, const CTxDestination& destination, const std::vector<uint8_t>& signature)
{
// if this is a P2PKH, use the legacy approach
const PKHash* pkhash = boost::get<PKHash>(&destination);
if (pkhash) {
CHashWriter ss(SER_GETHASH, 0);
ss << MESSAGE_MAGIC << message;
CPubKey pubkey;
return ResultFromBool(pubkey.RecoverCompact(ss.GetHash(), signature) && pubkey.GetID() == *pkhash);
}

SignMessageWorkspace p;

p.AppendDestinationChallenge(destination);

CDataStream stream(signature, SER_DISK, PROTOCOL_VERSION);
try {
stream >> p.m_proof;
return p.Verify(message);
} catch (const std::runtime_error&) {
return Result::Error;
}
}

} // namespace proof
Loading