Skip to content

Commit

Permalink
OP_DATASIGVERIFY reference implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
gandrewstone committed Feb 12, 2018
1 parent c780801 commit 1bf5330
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 11 deletions.
2 changes: 0 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,6 @@ static void CheckBlockIndex(const Consensus::Params &consensusParams);
/** Constant stuff for coinbase transactions we create: */
CScript COINBASE_FLAGS;

const std::string strMessageMagic = "Bitcoin Signed Message:\n";

extern CCriticalSection cs_LastBlockFile;
extern CCriticalSection cs_nBlockSequenceId;

Expand Down
1 change: 0 additions & 1 deletion src/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ typedef boost::unordered_map<uint256, CBlockIndex *, BlockHasher> BlockMap;
extern BlockMap mapBlockIndex;
extern uint64_t nLastBlockTx;
extern uint64_t nLastBlockSize;
extern const std::string strMessageMagic;
extern CWaitableCriticalSection csBestBlock;
extern CConditionVariable cvBlockChange;
extern bool fImporting;
Expand Down
27 changes: 27 additions & 0 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "script/script.h"
#include "uint256.h"

const std::string strMessageMagic = "Bitcoin Signed Message:\n";

using namespace std;

typedef vector<unsigned char> valtype;
Expand Down Expand Up @@ -965,6 +967,31 @@ bool EvalScript(vector<vector<unsigned char> > &stack,
}
break;

case OP_DATASIGVERIFY:
{
if (stack.size() < 3)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

valtype &data = stacktop(-3);
valtype &vchSig = stacktop(-2);
valtype &vchAddr = stacktop(-1);

if (vchAddr.size() != 20)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic << data;

CPubKey pubkey;
if (!pubkey.RecoverCompact(ss.GetHash(), vchSig))
return set_error(serror, SCRIPT_ERR_VERIFY);
CKeyID id = pubkey.GetID();
if (id != uint160(vchAddr))
return set_error(serror, SCRIPT_ERR_VERIFY);
popstack(stack);
popstack(stack);
}
break;
case OP_CHECKSIG:
case OP_CHECKSIGVERIFY:
{
Expand Down
3 changes: 3 additions & 0 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,7 @@ bool VerifyScript(const CScript &scriptSig,
ScriptError *error = NULL,
unsigned char *sighashtype = NULL);

// string prefixed to data when validating signed messages either via DATASIGVERIFY or RPC call. This ensures
// that the signature was intended for use on this blockchain.
extern const std::string strMessageMagic;
#endif // BITCOIN_SCRIPT_INTERPRETER_H
1 change: 1 addition & 0 deletions src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ enum opcodetype
OP_CHECKSIGVERIFY = 0xad,
OP_CHECKMULTISIG = 0xae,
OP_CHECKMULTISIGVERIFY = 0xaf,
OP_DATASIGVERIFY = 0xbb,

// expansion
OP_NOP1 = 0xb0,
Expand Down
29 changes: 29 additions & 0 deletions src/script/sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,32 @@ bool DummySignatureCreator::CreateSig(std::vector<unsigned char> &vchSig,
vchSig[6 + 33 + 32] = SIGHASH_ALL;
return true;
}


std::vector<unsigned char> signmessage(const std::vector<unsigned char> &data, const CKey &key)
{
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic << data;

vector<unsigned char> vchSig;
if (!key.SignCompact(ss.GetHash(), vchSig)) // signing will only fail if the key is bogus
{
DbgAssert(!"bad key", );
return std::vector<unsigned char>();
}
return vchSig;
}

std::vector<unsigned char> signmessage(const std::string &data, const CKey &key)
{
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic << data;

vector<unsigned char> vchSig;
if (!key.SignCompact(ss.GetHash(), vchSig)) // signing will only fail if the key is bogus
{
DbgAssert(!"bad key", );
return std::vector<unsigned char>();
}
return vchSig;
}
6 changes: 6 additions & 0 deletions src/script/sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "script/interpreter.h"

class CKey;
class CKeyID;
class CKeyStore;
class CScript;
Expand Down Expand Up @@ -84,4 +85,9 @@ CScript CombineSignatures(const CScript &scriptPubKey,
const CScript &scriptSig1,
const CScript &scriptSig2);

/** sign arbitrary data using the same algorithm as the signmessage/verifymessage RPCs and OP_DATASIGVERIFY */
std::vector<unsigned char> signmessage(const std::vector<unsigned char> &data, const CKey &key);
std::vector<unsigned char> signmessage(const std::string &data, const CKey &key);


#endif // BITCOIN_SCRIPT_SIGN_H
2 changes: 0 additions & 2 deletions src/test/data/script_tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@
"Discouraged NOPs are allowed if not executed"],

["0", "IF 0xba ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "opcodes above NOP10 invalid if executed"],
["0", "IF 0xbb ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"],
["0", "IF 0xbc ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"],
["0", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"],
["0", "IF 0xbe ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"],
Expand Down Expand Up @@ -879,7 +878,6 @@

["0x50","1", "P2SH,STRICTENC", "BAD_OPCODE", "opcode 0x50 is reserved"],
["1", "IF 0xba ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE", "opcodes above NOP10 invalid if executed"],
["1", "IF 0xbb ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"],
["1", "IF 0xbc ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"],
["1", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"],
["1", "IF 0xbe ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"],
Expand Down
174 changes: 174 additions & 0 deletions src/test/script_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,180 @@ BOOST_AUTO_TEST_CASE(script_GetScriptAsm)
derSig + "83 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "83")) << vchPubKey));
}


class QuickAddress
{
public:
QuickAddress()
{
secret.MakeNewKey(true);
pubkey = secret.GetPubKey();
addr = pubkey.GetID();
}
QuickAddress(const CKey &k)
{
secret = k;
pubkey = secret.GetPubKey();
addr = pubkey.GetID();
}
QuickAddress(unsigned char key) // make a very simple key for testing only
{
secret.MakeNewKey(true);
unsigned char *c = (unsigned char *)secret.begin();
*c = key;
c++;
for (int i = 1; i < 32; i++, c++)
{
*c = 0;
}
pubkey = secret.GetPubKey();
addr = pubkey.GetID();
}

CKey secret;
CPubKey pubkey;
CKeyID addr; // 160 bit normal address
};

CTransaction tx1x1(const COutPoint &utxo,
const CScript &txo,
CAmount amt,
const CKey &key,
const CScript &prevOutScript,
bool p2pkh = true)
{
CMutableTransaction tx;
tx.vin.resize(1);
tx.vin[0].prevout = utxo;
tx.vout.resize(1);
tx.vout[0].scriptPubKey = txo;
tx.vout[0].nValue = amt;
tx.vin[0].scriptSig = CScript();
tx.nLockTime = 0;

unsigned int sighashType = SIGHASH_ALL | SIGHASH_FORKID;
std::vector<unsigned char> vchSig;
uint256 hash = SignatureHash(prevOutScript, tx, 0, sighashType, amt, 0);
if (!key.Sign(hash, vchSig))
{
assert(0);
}
vchSig.push_back((unsigned char)sighashType);
tx.vin[0].scriptSig << vchSig;
if (p2pkh)
{
tx.vin[0].scriptSig << ToByteVector(key.GetPubKey());
}

return tx;
}


// I want to create a script test that includes some CHECKSIGVERIFY instructions,
// however I don't have a transaction to verify. So instead of signing the hash of the
// tx, I sign the hash of the public key.
class SigPubkeyHashChecker : public BaseSignatureChecker
{
public:
virtual bool CheckSig(const std::vector<unsigned char> &scriptSig,
const std::vector<unsigned char> &vchPubKey,
const CScript &scriptCode) const
{
CPubKey pub = CPubKey(vchPubKey);
uint256 hash = pub.GetHash();
if (!pub.Verify(hash, scriptSig))
return false;
return true;
}

virtual bool CheckLockTime(const CScriptNum &nLockTime) const { return false; }
virtual bool CheckSequence(const CScriptNum &nSequence) const { return false; }
virtual ~SigPubkeyHashChecker() {}
};


BOOST_AUTO_TEST_CASE(script_datasigverify)
{
QuickAddress dataSigner;

std::vector<unsigned char> data(1);
data[0] = 123;

std::vector<unsigned char> sig = signmessage(data, dataSigner.secret);

CScript proveScript = CScript() << data << sig;

CScript condScript = CScript() << ToByteVector(dataSigner.addr) << OP_DATASIGVERIFY;

vector<vector<unsigned char> > stack;
ScriptError serror;
BaseSignatureChecker sigChecker;
if (!EvalScript(stack, proveScript, 0, sigChecker, &serror, nullptr))
{
BOOST_CHECK(0);
return;
}
if (!EvalScript(stack, condScript, 0, sigChecker, &serror, nullptr))
{
BOOST_CHECK(0);
return;
}

sig[1] ^= 1; // Screw up the signature
proveScript = CScript() << data << sig;
BOOST_CHECK(EvalScript(stack, proveScript, 0, sigChecker, &serror, nullptr));
BOOST_CHECK(!EvalScript(stack, condScript, 0, sigChecker, &serror, nullptr));
sig[1] ^= 1; // back to correct sig

QuickAddress u2;

// Now try a more realistic script
// Here I am validating that "dataSigner" signed a piece of data that is equal a
// particular value, and that that transaction is signed by the txo address in the
// normal p2pkh fashion. In a real transaction, you would likely use string instructions
// to break the data into pieces, such as <ticker>, <date>, and <price> if importing information
// about a security. However, these string instructions are not yet enabled.
condScript = CScript() << ToByteVector(dataSigner.addr) << OP_DATASIGVERIFY << data << OP_EQUALVERIFY << OP_DUP
<< OP_HASH160 << ToByteVector(u2.addr) << OP_EQUALVERIFY << OP_CHECKSIG;

unsigned int sighashType = SIGHASH_ALL | SIGHASH_FORKID;
std::vector<unsigned char> txoSig;
// Since I don't have a tx, I'm going to use a fake tx hash, which is just the hash
// of the public key.
// uint256 hash = SignatureHash(prevtx.vout[prevout].scriptPubKey, tx, 0, sighashType, prevtx.vout[prevout].nValue,
// 0);
uint256 hash = u2.pubkey.GetHash();
if (!u2.secret.Sign(hash, txoSig))
{
assert(0);
}
txoSig.push_back((unsigned char)sighashType);
SigPubkeyHashChecker pkhChecker;

proveScript = CScript() << txoSig << ToByteVector(u2.secret.GetPubKey()) << data << sig;

if (!EvalScript(stack, proveScript, 0, pkhChecker, &serror, nullptr))
{
BOOST_CHECK(0);
return;
}
if (!EvalScript(stack, condScript, 0, pkhChecker, &serror, nullptr))
{
BOOST_CHECK(0);
return;
}

sig[1] ^= 1; // Screw up the data signature
proveScript = CScript() << data << sig << ToByteVector(u2.pubkey);
BOOST_CHECK(EvalScript(stack, proveScript, 0, pkhChecker, &serror, nullptr));
BOOST_CHECK(!EvalScript(stack, condScript, 0, pkhChecker, &serror, nullptr));
sig[1] ^= 1; // back to correct data sig
// provide the wrong utxo pubkey
proveScript = CScript() << data << sig << ToByteVector(dataSigner.pubkey);
BOOST_CHECK(EvalScript(stack, proveScript, 0, pkhChecker, &serror, nullptr));
BOOST_CHECK(!EvalScript(stack, condScript, 0, pkhChecker, &serror, nullptr));
}

static CScript ScriptFromHex(const char *hex)
{
std::vector<unsigned char> data = ParseHex(hex);
Expand Down
9 changes: 3 additions & 6 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "main.h"
#include "net.h"
#include "rpc/server.h"
#include "script/sign.h"
#include "timedata.h"
#include "util.h"
#include "utilmoneystr.h"
Expand Down Expand Up @@ -545,12 +546,8 @@ UniValue signmessage(const UniValue &params, bool fHelp)
if (!pwalletMain->GetKey(*keyID, key))
throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");

CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << strMessage;

vector<unsigned char> vchSig;
if (!key.SignCompact(ss.GetHash(), vchSig))
vector<unsigned char> vchSig = signmessage(strMessage, key);
if (vchSig.empty())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");

return EncodeBase64(&vchSig[0], vchSig.size());
Expand Down

0 comments on commit 1bf5330

Please sign in to comment.