mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-20 13:37:28 +02:00
Add crypto opcodes
This commit is contained in:
parent
3e7b8c6aed
commit
7193636d09
7 changed files with 170 additions and 33 deletions
|
|
@ -173,12 +173,12 @@ XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
|
|||
std::copy(bytes.begin(), bytes.end(), m_keydata.begin());
|
||||
}
|
||||
|
||||
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_verify, &pubkey, m_keydata.data())) return false;
|
||||
return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), 32, &pubkey);
|
||||
return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.data(), msg.size(), &pubkey);
|
||||
}
|
||||
|
||||
bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const
|
||||
|
|
@ -188,6 +188,18 @@ bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& has
|
|||
return secp256k1_xonly_pubkey_tweak_add_check(secp256k1_context_verify, m_keydata.begin(), parity, &base_point, hash.begin());
|
||||
}
|
||||
|
||||
bool CPubKey::TweakMulVerify(const CPubKey& untweaked, const uint256& tweak) const
|
||||
{
|
||||
assert(this->IsCompressed());
|
||||
secp256k1_pubkey pk;
|
||||
if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pk, untweaked.data(), untweaked.size())) return false;
|
||||
if (!secp256k1_ec_pubkey_tweak_mul(secp256k1_context_verify, &pk, tweak.data())) return false;
|
||||
unsigned char out_pk[CPubKey::COMPRESSED_SIZE];
|
||||
size_t out_len = CPubKey::COMPRESSED_SIZE;
|
||||
if (!secp256k1_ec_pubkey_serialize(secp256k1_context_verify, out_pk, &out_len, &pk, SECP256K1_EC_COMPRESSED)) return false;
|
||||
return *this == CPubKey(out_pk, out_pk + out_len);
|
||||
}
|
||||
|
||||
bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
|
||||
if (!IsValid())
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -206,6 +206,9 @@ public:
|
|||
|
||||
//! Derive BIP32 child pubkey.
|
||||
bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc, std::vector<unsigned char>* tweak = nullptr /* ELEMENTS: vector of key tweak values that are filled out if non-null */) const;
|
||||
|
||||
//! Verify that when this public key is tweaked with tweak, the result is res
|
||||
bool TweakMulVerify(const CPubKey& res, const uint256& tweak) const;
|
||||
};
|
||||
|
||||
class XOnlyPubKey
|
||||
|
|
@ -221,7 +224,7 @@ public:
|
|||
*
|
||||
* 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;
|
||||
bool CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const;
|
||||
|
||||
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
|
||||
|
|
|
|||
|
|
@ -433,6 +433,17 @@ public:
|
|||
};
|
||||
}
|
||||
|
||||
// Check the script has sufficient sigops budget for checksig(crypto) operation
|
||||
inline bool update_validation_weight(ScriptExecutionData& execdata, ScriptError* serror)
|
||||
{
|
||||
assert(execdata.m_validation_weight_left_init);
|
||||
execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED;
|
||||
if (execdata.m_validation_weight_left < 0) {
|
||||
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPubKey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& fSuccess)
|
||||
{
|
||||
assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0);
|
||||
|
|
@ -459,6 +470,47 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool EvalTapScriptCheckSigFromStack(const valtype& sig, const valtype& vchPubKey, ScriptExecutionData& execdata, unsigned int flags, const valtype& msg, SigVersion sigversion, ScriptError* serror, bool& success)
|
||||
{
|
||||
// This code follows the behaviour of EvalCheckSigTapscript
|
||||
assert(sigversion == 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.
|
||||
*/
|
||||
success = !sig.empty();
|
||||
if (success) {
|
||||
// Implement the sigops/witnesssize ratio test.
|
||||
// Passing with an upgradable public key version is also counted.
|
||||
if (!update_validation_weight(execdata, serror)) return false; // serror is set
|
||||
}
|
||||
if (vchPubKey.size() == 0) {
|
||||
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
|
||||
} else if (vchPubKey.size() == 32) {
|
||||
if (success) {
|
||||
if (sig.size() != 64)
|
||||
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE);
|
||||
const XOnlyPubKey pubkey{vchPubKey};
|
||||
if (!pubkey.VerifySchnorr(msg, sig))
|
||||
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* New public key version softforks should be defined before this `else` block.
|
||||
* Generally, the new code should not do anything but failing the script execution. To avoid
|
||||
* consensus bugs, it should not modify any existing values (including `success`).
|
||||
*/
|
||||
if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) {
|
||||
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success)
|
||||
{
|
||||
assert(sigversion == SigVersion::TAPSCRIPT);
|
||||
|
|
@ -473,11 +525,7 @@ static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, Scr
|
|||
if (success) {
|
||||
// Implement the sigops/witnesssize ratio test.
|
||||
// Passing with an upgradable public key version is also counted.
|
||||
assert(execdata.m_validation_weight_left_init);
|
||||
execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED;
|
||||
if (execdata.m_validation_weight_left < 0) {
|
||||
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
|
||||
}
|
||||
if (!update_validation_weight(execdata, serror)) return false; // serror is set
|
||||
}
|
||||
if (pubkey.size() == 0) {
|
||||
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
|
||||
|
|
@ -519,6 +567,11 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con
|
|||
assert(false);
|
||||
}
|
||||
|
||||
static const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
|
||||
static const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements");
|
||||
static const CHashWriter HASHER_TAPTWEAK_ELEMENTS = TaggedHash("TapTweak/elements");
|
||||
static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements");
|
||||
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror)
|
||||
{
|
||||
static const CScriptNum bnZero(0);
|
||||
|
|
@ -1664,29 +1717,40 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
|||
valtype& vchSig = stacktop(-3);
|
||||
valtype& vchData = stacktop(-2);
|
||||
valtype& vchPubKey = stacktop(-1);
|
||||
bool fSuccess;
|
||||
// Different semantics for CHECKSIGFROMSTACK for taproot and pre-taproot
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0)
|
||||
{
|
||||
// Sigs from stack have no hash byte ever
|
||||
if (!CheckSignatureEncoding(vchSig, (flags | SCRIPT_NO_SIGHASH_BYTE), serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) {
|
||||
//serror is set
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sigs from stack have no hash byte ever
|
||||
if (!CheckSignatureEncoding(vchSig, (flags | SCRIPT_NO_SIGHASH_BYTE), serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) {
|
||||
//serror is set
|
||||
return false;
|
||||
valtype vchHash(CSHA256::OUTPUT_SIZE);
|
||||
CSHA256().Write(vchData.data(), vchData.size()).Finalize(vchHash.data());
|
||||
uint256 hash(vchHash);
|
||||
|
||||
CPubKey pubkey(vchPubKey);
|
||||
fSuccess = pubkey.Verify(hash, vchSig);
|
||||
// CHECKSIGFROMSTACK in pre-tapscript cannot be failed.
|
||||
if (!fSuccess)
|
||||
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
|
||||
} else {
|
||||
// New BIP 340 semantics for CHECKSIGFROMSTACK
|
||||
if (!EvalTapScriptCheckSigFromStack(vchSig, vchPubKey, execdata, flags, vchData, sigversion, serror, fSuccess)) return false;
|
||||
}
|
||||
|
||||
valtype vchHash(CSHA256::OUTPUT_SIZE);
|
||||
CSHA256().Write(vchData.data(), vchData.size()).Finalize(vchHash.data());
|
||||
uint256 hash(vchHash);
|
||||
|
||||
CPubKey pubkey(vchPubKey);
|
||||
bool fSuccess = pubkey.Verify(hash, vchSig);
|
||||
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
stack.push_back(fSuccess ? vchTrue : vchFalse);
|
||||
if (opcode == OP_CHECKSIGFROMSTACKVERIFY)
|
||||
popstack(stack);
|
||||
|
||||
if (!fSuccess)
|
||||
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
|
||||
{
|
||||
if (fSuccess)
|
||||
popstack(stack);
|
||||
else
|
||||
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -2118,6 +2182,58 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
|||
push8_le(stack, static_cast<int64_t>(num));
|
||||
}
|
||||
break;
|
||||
case OP_ECMULSCALARVERIFY:
|
||||
{
|
||||
// OP_ECMULSCALARVERIFY is available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
valtype& vchRes = stacktop(-3);
|
||||
valtype& vchGenerator = stacktop(-2);
|
||||
valtype& vchScalar = stacktop(-1);
|
||||
|
||||
CPubKey pk(vchGenerator);
|
||||
CPubKey res(vchRes);
|
||||
if (!pk.IsCompressed() || !res.IsCompressed())
|
||||
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
|
||||
|
||||
if (!update_validation_weight(execdata, serror)) return false; // serror is set
|
||||
|
||||
if (vchScalar.size() != 32 || !res.TweakMulVerify(pk, uint256(vchScalar)))
|
||||
return set_error(serror, SCRIPT_ERR_ECMULTVERIFYFAIL);
|
||||
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
}
|
||||
break;
|
||||
|
||||
//crypto opcodes
|
||||
case OP_TWEAKVERIFY:
|
||||
{
|
||||
// OP_TWEAKVERIFY is available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
valtype& vchTweakedKey = stacktop(-3);
|
||||
valtype& vchTweak = stacktop(-2);
|
||||
valtype& vchInternalKey = stacktop(-1);
|
||||
|
||||
if (vchTweakedKey.size() != CPubKey::COMPRESSED_SIZE || (vchTweakedKey[0] != 0x02 && vchTweakedKey[0] != 0x03)
|
||||
|| vchInternalKey.size() != 32 || vchTweak.size() != 32)
|
||||
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
|
||||
|
||||
if (!update_validation_weight(execdata, serror)) return false; // serror is set
|
||||
|
||||
const XOnlyPubKey tweakedXOnlyKey{Span<const unsigned char>{vchTweakedKey.data() + 1, vchTweakedKey.data() + CPubKey::COMPRESSED_SIZE}};
|
||||
const uint256 tweak(vchTweak);
|
||||
const XOnlyPubKey internalKey{vchInternalKey};
|
||||
if (!tweakedXOnlyKey.CheckPayToContract(internalKey, tweak, vchTweakedKey[0] & 1))
|
||||
return set_error(serror, SCRIPT_ERR_ECMULTVERIFYFAIL);
|
||||
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
|
@ -2490,12 +2606,6 @@ template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo,
|
|||
template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo);
|
||||
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
|
||||
|
||||
|
||||
static const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
|
||||
static const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements");
|
||||
static const CHashWriter HASHER_TAPTWEAK_ELEMENTS = TaggedHash("TapTweak/elements");
|
||||
static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements");
|
||||
|
||||
PrecomputedTransactionData::PrecomputedTransactionData(const uint256& hash_genesis_block)
|
||||
: m_tapsighash_hasher(CHashWriter(HASHER_TAPSIGHASH_ELEMENTS) << hash_genesis_block << hash_genesis_block) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -181,6 +181,11 @@ std::string GetOpName(opcodetype opcode)
|
|||
case OP_SCRIPTNUMTOLE64 : return "OP_SCRIPTNUMTOLE64";
|
||||
case OP_LE64TOSCRIPTNUM : return "OP_LE64TOSCRIPTNUM";
|
||||
case OP_LE32TOLE64 : return "OP_LE32TOLE64";
|
||||
|
||||
// Crypto opcodes
|
||||
case OP_ECMULSCALARVERIFY : return "OP_ECMULSCALARVERIFY";
|
||||
case OP_TWEAKVERIFY : return "OP_TWEAKVERIFY";
|
||||
|
||||
case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";
|
||||
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -259,11 +259,15 @@ enum opcodetype
|
|||
OP_LE64TOSCRIPTNUM = 0xe1,
|
||||
OP_LE32TOLE64 = 0xe2,
|
||||
|
||||
// Crypto opcodes
|
||||
OP_ECMULSCALARVERIFY = 0xe3,
|
||||
OP_TWEAKVERIFY = 0xe4,
|
||||
|
||||
OP_INVALIDOPCODE = 0xff,
|
||||
};
|
||||
|
||||
// Maximum value that an opcode can be
|
||||
static const unsigned int MAX_OPCODE = OP_LE32TOLE64; // 0xe6
|
||||
static const unsigned int MAX_OPCODE = OP_TWEAKVERIFY; // 0xe4
|
||||
|
||||
std::string GetOpName(opcodetype opcode);
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,8 @@ std::string ScriptErrorString(const ScriptError serror)
|
|||
return "Arithmetic opcodes expect 8 bytes operands";
|
||||
case SCRIPT_ERR_ARITHMETIC64:
|
||||
return "Arithmetic opcode error";
|
||||
case SCRIPT_ERR_ECMULTVERIFYFAIL:
|
||||
return "EC scalar mult verify fail";
|
||||
case SCRIPT_ERR_UNKNOWN_ERROR:
|
||||
case SCRIPT_ERR_ERROR_COUNT:
|
||||
default: break;
|
||||
|
|
|
|||
|
|
@ -95,10 +95,11 @@ typedef enum ScriptError_t
|
|||
SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE,
|
||||
SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS,
|
||||
SCRIPT_ERR_EXPECTED_8BYTES,
|
||||
SCRIPT_ERR_ARITHMETIC64
|
||||
SCRIPT_ERR_ARITHMETIC64,
|
||||
SCRIPT_ERR_ECMULTVERIFYFAIL
|
||||
} ScriptError;
|
||||
|
||||
#define SCRIPT_ERR_LAST SCRIPT_ERR_ARITHMETIC64
|
||||
#define SCRIPT_ERR_LAST SCRIPT_ERR_ECMULTVERIFYFAIL
|
||||
|
||||
std::string ScriptErrorString(const ScriptError error);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue