Skip to content

Commit

Permalink
Implement OP_CHECKSIGFROMSTACK(VERIFY)
Browse files Browse the repository at this point in the history
Some code and ideas from Elements by stevenroose, and sanket1729
Porting help from moonsettler
  • Loading branch information
reardencode committed Dec 19, 2023
1 parent 9b7fc85 commit b76d390
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/pubkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,12 @@ bool XOnlyPubKey::IsFullyValid() const
return secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data());
}

bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const
bool XOnlyPubKey::VerifySchnorr(const Span<const unsigned char> msg, Span<const unsigned char> sigbytes) const
{
assert(sigbytes.size() == 64);
secp256k1_xonly_pubkey pubkey;
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data())) return false;
return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.begin(), 32, &pubkey);
return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.data(), msg.size(), &pubkey);
}

static const HashWriter HASHER_TAPTWEAK{TaggedHash("TapTweak")};
Expand Down
2 changes: 1 addition & 1 deletion src/pubkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ class XOnlyPubKey
*
* sigbytes must be exactly 64 bytes.
*/
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
bool VerifySchnorr(const Span<const unsigned char> msg, Span<const unsigned char> sigbytes) const;

/** Compute the Taproot tweak as specified in BIP341, with *this as internal
* key:
Expand Down
71 changes: 70 additions & 1 deletion src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
}
break;

case OP_NOP1: case OP_NOP5:
case OP_NOP1:
case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10:
{
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
Expand Down Expand Up @@ -1257,6 +1257,37 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
break;
}

case OP_CHECKSIGFROMSTACK:
case OP_CHECKSIGFROMSTACKVERIFY: {
// sig data pubkey
if (stack.size() < 3)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

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

std::vector<unsigned char> vchSigAll(vchSig.begin(), vchSig.begin() + vchSig.size());
// Push SIGHASH_ALL to pass checks - stripped by MessageSignatureChecker
vchSigAll.push_back(SIGHASH_ALL);

bool fSuccess = true;
MessageSignatureChecker checker{vchData};

if (!EvalChecksig(vchSigAll, vchPubKey, pbegincodehash, pend, execdata, flags, checker, sigversion, serror, fSuccess)) return false;

if (opcode == OP_CHECKSIGFROMSTACKVERIFY) {
if (!fSuccess) return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
break;
}

popstack(stack);
popstack(stack);
popstack(stack);
stack.push_back(fSuccess ? vchTrue : vchFalse);
break;
}

default:
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
}
Expand Down Expand Up @@ -1918,6 +1949,44 @@ bool GenericTransactionSignatureChecker<T>::CheckDefaultCheckTemplateVerifyHash(
template class GenericTransactionSignatureChecker<CTransaction>;
template class GenericTransactionSignatureChecker<CMutableTransaction>;

bool MessageSignatureChecker::CheckECDSASignature(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
{
valtype vchHash(CSHA256::OUTPUT_SIZE);
CSHA256().Write(this->msg.data(), this->msg.size()).Finalize(vchHash.data());

CPubKey pubkey(vchPubKey);
if (!pubkey.IsValid())
return false;

// Strip dummy SIGHASH_ALL added to pass format checks
std::vector<unsigned char> vchSig(vchSigIn);
if (vchSig.empty())
return false;
vchSig.pop_back();

if (!pubkey.Verify(uint256(vchHash), vchSig))
return false;

return true;
}

bool MessageSignatureChecker::CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey_in, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const
{
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
// Schnorr signatures have 32-byte public keys. The caller is responsible for enforcing this.
assert(pubkey_in.size() == 32);
// Note that in Tapscript evaluation, empty signatures are treated specially (invalid signature that does not
// abort script execution). This is implemented in EvalChecksigTapscript, which won't invoke
// CheckSchnorrSignature in that case. In other contexts, they are invalid like every other signature with
// size different from 64 or 65.
if (sig.size() != 64) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE);

XOnlyPubKey pubkey{pubkey_in};

if (!pubkey.VerifySchnorr(this->msg, sig)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
return true;
}

static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CScript& exec_script, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror)
{
std::vector<valtype> stack{stack_span.begin(), stack_span.end()};
Expand Down
14 changes: 14 additions & 0 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,20 @@ class BaseSignatureChecker
virtual ~BaseSignatureChecker() {}
};

/**
* Class for performing signature checking on an arbitrary length message
*/
class MessageSignatureChecker : public BaseSignatureChecker
{
private:
const Span<const unsigned char> msg;

public:
MessageSignatureChecker(const Span<const unsigned char> msgIn) : msg(msgIn) {}
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override;
};

/** Enum to specify what *TransactionSignatureChecker's behavior should be
* when dealing with missing transaction data.
*/
Expand Down
9 changes: 6 additions & 3 deletions src/script/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ std::string GetOpName(opcodetype opcode)
case OP_CHECKLOCKTIMEVERIFY : return "OP_CHECKLOCKTIMEVERIFY";
case OP_CHECKSEQUENCEVERIFY : return "OP_CHECKSEQUENCEVERIFY";
case OP_CHECKTEMPLATEVERIFY : return "OP_CHECKTEMPLATEVERIFY";
case OP_NOP5 : return "OP_NOP5";
case OP_CHECKSIGFROMSTACKVERIFY: return "OP_CHECKSIGFROMSTACKVERIFY";
case OP_NOP6 : return "OP_NOP6";
case OP_NOP7 : return "OP_NOP7";
case OP_NOP8 : return "OP_NOP8";
Expand All @@ -149,7 +149,9 @@ std::string GetOpName(opcodetype opcode)
// Opcode added by BIP 342 (Tapscript)
case OP_CHECKSIGADD : return "OP_CHECKSIGADD";

// Tapscript expansion
case OP_INTERNALKEY : return "OP_INTERNALKEY";
case OP_CHECKSIGFROMSTACK : return "OP_CHECKSIGFROMSTACK";

case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";

Expand All @@ -168,7 +170,8 @@ unsigned int CScript::GetSigOpCount(bool fAccurate) const
opcodetype opcode;
if (!GetOp(pc, opcode))
break;
if (opcode == OP_CHECKSIG || opcode == OP_CHECKSIGVERIFY)
if (opcode == OP_CHECKSIG || opcode == OP_CHECKSIGVERIFY ||
opcode == OP_CHECKSIGFROMSTACK || opcode == OP_CHECKSIGFROMSTACKVERIFY)
n++;
else if (opcode == OP_CHECKMULTISIG || opcode == OP_CHECKMULTISIGVERIFY)
{
Expand Down Expand Up @@ -352,7 +355,7 @@ bool IsOpSuccess(const opcodetype& opcode)
return opcode == 80 || opcode == 98 || (opcode >= 126 && opcode <= 129) ||
(opcode >= 131 && opcode <= 134) || (opcode >= 137 && opcode <= 138) ||
(opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) ||
(opcode >= 188 && opcode <= 254);
(opcode >= 189 && opcode <= 254);
}

bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode) {
Expand Down
6 changes: 5 additions & 1 deletion src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ enum opcodetype
OP_NOP3 = OP_CHECKSEQUENCEVERIFY,
OP_CHECKTEMPLATEVERIFY = 0xb3,
OP_NOP4 = OP_CHECKTEMPLATEVERIFY,
OP_NOP5 = 0xb4,
OP_CHECKSIGFROMSTACKVERIFY = 0xb4,
OP_NOP5 = OP_CHECKSIGFROMSTACKVERIFY,
OP_NOP6 = 0xb5,
OP_NOP7 = 0xb6,
OP_NOP8 = 0xb7,
Expand All @@ -208,7 +209,10 @@ enum opcodetype

// Opcode added by BIP 342 (Tapscript)
OP_CHECKSIGADD = 0xba,

// Tapscript expansion
OP_INTERNALKEY = 0xbb,
OP_CHECKSIGFROMSTACK = 0xbc,

OP_INVALIDOPCODE = 0xff,
};
Expand Down
2 changes: 1 addition & 1 deletion test/functional/test_framework/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -924,4 +924,4 @@ def taproot_construct(pubkey, scripts=None, treat_internal_as_infinity=False):
return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves, h, tweaked)

def is_op_success(o):
return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbc and o <= 0xfe)
return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbd and o <= 0xfe)

0 comments on commit b76d390

Please sign in to comment.