Skip to content

Commit

Permalink
Implement OP_CHECKSIG* for taproot
Browse files Browse the repository at this point in the history
  • Loading branch information
jl2012 committed Apr 9, 2019
1 parent 96e5460 commit bf1560a
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 11 deletions.
95 changes: 91 additions & 4 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
return set_error(serror, SCRIPT_ERR_SCRIPT_SIZE);
int nOpCount = 0;
bool fRequireMinimal = (flags & SCRIPT_VERIFY_MINIMALDATA) != 0;
int dls_passed = 0;
int16_t opcode_pos = -1;
TapscriptData tapscript_data;
tapscript_data.m_codeseparator_pos = opcode_pos;
if (sigversion == SigVersion::TAPSCRIPT)
CSHA256().Write(&script[0], script.size()).Finalize(tapscript_data.m_tapscript_hash.begin());

try
{
Expand All @@ -314,6 +320,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
if (vchPushValue.size() > MAX_SCRIPT_ELEMENT_SIZE)
return set_error(serror, SCRIPT_ERR_PUSH_SIZE);
++opcode_pos;

// Note how OP_RESERVED does not count towards the opcode limit.
if (opcode > OP_16 && ++nOpCount > MAX_OPS_PER_SCRIPT)
Expand Down Expand Up @@ -908,19 +915,39 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&

// Hash starts after the code separator
pbegincodehash = pc;
tapscript_data.m_codeseparator_pos = opcode_pos;
}
break;

case OP_CHECKSIG:
case OP_CHECKSIGVERIFY:
case OP_CHECKSIGADD:
{
if (opcode == OP_CHECKSIGADD) {
if (sigversion <= SigVersion::WITNESS_V0)
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);

// (sig x pubkey -- out)
// OP_CHECKDLSADD is a shorthand for OP_ROT OP_SWAP OP_CHECKDLS OP_ADD
if (stack.size() < 3)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
swap(stacktop(-3), stacktop(-2)); // OP_ROT followed by OP_SWAP
}

// (sig pubkey -- bool)
if (stack.size() < 2)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

valtype& vchSig = stacktop(-2);
valtype& vchPubKey = stacktop(-1);

bool fSuccess = !vchSig.empty();

switch (sigversion)
{
case SigVersion::BASE:
case SigVersion::WITNESS_V0:
{
// Subset of script starting at the most recent codeseparator
CScript scriptCode(pbegincodehash, pend);

Expand All @@ -935,10 +962,41 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
//serror is set
return false;
}
bool fSuccess = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion);
fSuccess = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion);

if (!fSuccess && (flags & SCRIPT_VERIFY_NULLFAIL) && vchSig.size())
return set_error(serror, SCRIPT_ERR_SIG_NULLFAIL);
}
break;

case SigVersion::TAPSCRIPT:
{
/*
* The following validation sequence is consensus critical. Please note how --
* upgradable public key versions precede other rules;
* the script execution fails when using empty signature with invalid public key;
* the script execution fails when using non-empty invalid signature.
*/
if (vchPubKey.empty() || vchPubKey[0] == 4 || vchPubKey[0] == 6 || vchPubKey[0] == 7)
return set_error(serror, SCRIPT_ERR_WITNESS_PUBKEYTYPE);
if (vchPubKey[0] != 2 && vchPubKey[0] != 3) {
if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0)
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE);
}
else if (!IsCompressedPubKey(vchPubKey))
return set_error(serror, SCRIPT_ERR_WITNESS_PUBKEYTYPE);
else if (!vchSig.empty() && !checker.CheckSig(vchSig, vchPubKey, {}, sigversion, &tapscript_data))
return set_error(serror, SCRIPT_ERR_SIG_NULLFAIL);

// Note how the first passing signature is not counted.
// Passing with upgradable public key version is also counted.
if (fSuccess && !checker.CheckValidationWeight(dls_passed++ * VALIDATION_WEIGHT_PER_DLS_PASSED))
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
}
break;

default: assert(false);
}

popstack(stack);
popstack(stack);
Expand All @@ -950,12 +1008,25 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
else
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
}
else if (opcode == OP_CHECKSIGADD)
{
// OP_ADD
CScriptNum bn1(stacktop(-2), fRequireMinimal);
CScriptNum bn2(stacktop(-1), fRequireMinimal);
CScriptNum bn = bn1 + bn2;
popstack(stack);
popstack(stack);
stack.push_back(bn.getvch());
}
}
break;

case OP_CHECKMULTISIG:
case OP_CHECKMULTISIGVERIFY:
{
if (sigversion >= SigVersion::TAPSCRIPT)
return set_error(serror, SCRIPT_ERR_DISABLED_OPCODE);

// ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool)

int i = 1;
Expand Down Expand Up @@ -1266,7 +1337,7 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CTransacti
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);

template <class T>
bool SignatureHashTap(uint256& hash_out, const T& tx_to, unsigned int in_pos, int hashtype, SigVersion sigversion, const PrecomputedTransactionData& cache)
bool SignatureHashTap(uint256& hash_out, const T& tx_to, unsigned int in_pos, int hashtype, SigVersion sigversion, const PrecomputedTransactionData& cache, const TapscriptData* tapscript_data)
{
assert(in_pos < tx_to.vin.size());

Expand Down Expand Up @@ -1308,6 +1379,8 @@ bool SignatureHashTap(uint256& hash_out, const T& tx_to, unsigned int in_pos, in
if (witstack && witstack->size() > 1 && witstack->back().size() > 0 && witstack->back()[0] == 0xff) {
spend_type |= 2;
}
if (sigversion == SigVersion::TAPSCRIPT)
spend_type |= 4;

ss << spend_type;
ss << scriptPubKey;
Expand All @@ -1334,6 +1407,13 @@ bool SignatureHashTap(uint256& hash_out, const T& tx_to, unsigned int in_pos, in
ss << sha_single_output.GetSHA256();
}

// Additional data for tapscript
if (sigversion == SigVersion::TAPSCRIPT) {
assert(tapscript_data);
ss << tapscript_data->m_tapscript_hash;
ss << tapscript_data->m_codeseparator_pos;
}

hash_out = ss.GetSHA256();
return true;
}
Expand Down Expand Up @@ -1422,7 +1502,7 @@ bool GenericTransactionSignatureChecker<T>::VerifySignature(const std::vector<un
}

template <class T>
bool GenericTransactionSignatureChecker<T>::CheckSig(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
bool GenericTransactionSignatureChecker<T>::CheckSig(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, const TapscriptData* tapscript_data) const
{
CPubKey pubkey(vchPubKey);
if (!pubkey.IsValid())
Expand All @@ -1442,6 +1522,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSig(const std::vector<unsigned
return VerifySignature(vchSig, pubkey, sighash, SignatureType::ECDSA);
}
case SigVersion::TAPROOT:
case SigVersion::TAPSCRIPT:
{
int hashtype = 0;
if (vchSig.size() == 65) {
Expand All @@ -1450,7 +1531,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSig(const std::vector<unsigned
}
if (vchSig.size() != 64) return false;
uint256 sighash;
bool ret = SignatureHashTap(sighash, *txTo, nIn, hashtype, sigversion, *this->txdata);
bool ret = SignatureHashTap(sighash, *txTo, nIn, hashtype, sigversion, *this->txdata, tapscript_data);
if (!ret) return false;
return VerifySignature(vchSig, pubkey, sighash, SignatureType::SCHNORR);
}
Expand Down Expand Up @@ -1543,6 +1624,12 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
return true;
}

template <class T>
bool GenericTransactionSignatureChecker<T>::CheckValidationWeight(const size_t& weight) const
{
return weight <= ::GetSerializeSize(txTo->vin[nIn].scriptWitness.stack, PROTOCOL_VERSION);
}

// explicit instantiation
template class GenericTransactionSignatureChecker<CTransaction>;
template class GenericTransactionSignatureChecker<CMutableTransaction>;
Expand Down
21 changes: 18 additions & 3 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ enum

// Making unknown OP_SUCCESS non-standard
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS = (1U << 20),

// Making unknown public key versions in tapscript non-standard
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1U << 21),
};

bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned int flags, ScriptError* serror);
Expand All @@ -153,6 +156,12 @@ struct PrecomputedTransactionData
explicit PrecomputedTransactionData(const T& tx);
};

struct TapscriptData
{
uint256 m_tapscript_hash;
int16_t m_codeseparator_pos;
};

enum class SigVersion
{
BASE = 0,
Expand All @@ -169,12 +178,12 @@ template <class T>
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);

template <class T>
bool SignatureHashTap(uint256& hash_out, const T& tx_to, unsigned int in_pos, int hashtype, SigVersion sigversion, const PrecomputedTransactionData& cache);
bool SignatureHashTap(uint256& hash_out, const T& tx_to, unsigned int in_pos, int hashtype, SigVersion sigversion, const PrecomputedTransactionData& cache, const TapscriptData* tapscript_data = nullptr);

class BaseSignatureChecker
{
public:
virtual bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
virtual bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, const TapscriptData* tapscript_data = nullptr) const
{
return false;
}
Expand All @@ -189,6 +198,11 @@ class BaseSignatureChecker
return false;
}

virtual bool CheckValidationWeight(const size_t& weight) const
{
return false;
}

virtual ~BaseSignatureChecker() {}
};

Expand All @@ -212,9 +226,10 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker
public:
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, const TapscriptData* tapscript_data = nullptr) const override;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
bool CheckSequence(const CScriptNum& nSequence) const override;
bool CheckValidationWeight(const size_t& weight) const override;
};

using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;
Expand Down
3 changes: 3 additions & 0 deletions src/script/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ const char* GetOpName(opcodetype opcode)
case OP_NOP9 : return "OP_NOP9";
case OP_NOP10 : return "OP_NOP10";

// taproot
case OP_CHECKSIGADD : return "OP_CHECKSIGADD";

case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";

default:
Expand Down
7 changes: 7 additions & 0 deletions src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20
// SEQUENCE_FINAL).
static const uint32_t LOCKTIME_MAX = 0xFFFFFFFFU;

// Validation weight per passing Schnorr (DLS) signature
// The first passing signature is not counted
static const size_t VALIDATION_WEIGHT_PER_DLS_PASSED = 50;

template <typename T>
std::vector<unsigned char> ToByteVector(const T& in)
{
Expand Down Expand Up @@ -187,6 +191,9 @@ enum opcodetype
OP_NOP9 = 0xb8,
OP_NOP10 = 0xb9,

// taproot
OP_CHECKSIGADD = 0xba,

OP_INVALIDOPCODE = 0xff,
};

Expand Down
4 changes: 4 additions & 0 deletions src/script/script_error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ const char* ScriptErrorString(const ScriptError serror)
return "Unknown input annex reserved for soft-fork upgrades";
case SCRIPT_ERR_DISCOURAGE_OP_SUCCESS:
return "SUCCESSx reserved for soft-fork upgrades";
case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE:
return "Public key version reserved for soft-fork upgrades";
case SCRIPT_ERR_PUBKEYTYPE:
return "Public key is neither compressed or uncompressed";
case SCRIPT_ERR_CLEANSTACK:
Expand All @@ -99,6 +101,8 @@ const char* ScriptErrorString(const ScriptError serror)
return "Invalid signature for taproot key path spending";
case SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE:
return "Invalid taproot control block size";
case SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT:
return "Too much signature validation relative to witness weight";
case SCRIPT_ERR_OP_CODESEPARATOR:
return "Using OP_CODESEPARATOR in non-witness script";
case SCRIPT_ERR_SIG_FINDANDDELETE:
Expand Down
2 changes: 2 additions & 0 deletions src/script/script_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ typedef enum ScriptError_t
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION,
SCRIPT_ERR_DISCOURAGE_UNKNOWN_ANNEX,
SCRIPT_ERR_DISCOURAGE_OP_SUCCESS,
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE,

/* segregated witness */
SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH,
Expand All @@ -70,6 +71,7 @@ typedef enum ScriptError_t
/* Taproot */
SCRIPT_ERR_TAPROOT_INVALID_SIG,
SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE,
SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT,

/* Constant scriptCode */
SCRIPT_ERR_OP_CODESEPARATOR,
Expand Down
8 changes: 4 additions & 4 deletions src/script/sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,12 @@ class SignatureExtractorChecker final : public BaseSignatureChecker

public:
SignatureExtractorChecker(SignatureData& sigdata, BaseSignatureChecker& checker) : sigdata(sigdata), checker(checker) {}
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, const TapscriptData* tapscript_data = nullptr) const override;
};

bool SignatureExtractorChecker::CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
bool SignatureExtractorChecker::CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, const TapscriptData* tapscript_data) const
{
if (checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion)) {
if (checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion, tapscript_data)) {
CPubKey pubkey(vchPubKey);
sigdata.signatures.emplace(pubkey.GetID(), SigPair(pubkey, scriptSig));
return true;
Expand Down Expand Up @@ -395,7 +395,7 @@ class DummySignatureChecker final : public BaseSignatureChecker
{
public:
DummySignatureChecker() {}
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; }
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, const TapscriptData* tapscript_data = nullptr) const override { return true; }
};
const DummySignatureChecker DUMMY_CHECKER;

Expand Down

0 comments on commit bf1560a

Please sign in to comment.