From 847357aa9ae6c4c65ca52f4611a72eec44c0ce33 Mon Sep 17 00:00:00 2001 From: ViniciusCestarii Date: Thu, 15 Jan 2026 21:15:53 -0300 Subject: [PATCH 1/6] feat: implement opcode OP_EC_POINT_ADD --- src/binana.py | 6 ++ src/binana/ec_ops.json | 12 ++++ src/pubkey.cpp | 31 +++++++++ src/pubkey.h | 3 + src/script/interpreter.cpp | 28 ++++++++ src/script/script.h | 3 + src/test/CMakeLists.txt | 1 + src/test/ec_ops_tests.cpp | 136 +++++++++++++++++++++++++++++++++++++ 8 files changed, 220 insertions(+) create mode 100644 src/binana/ec_ops.json create mode 100644 src/test/ec_ops_tests.cpp diff --git a/src/binana.py b/src/binana.py index ed3cdcd6e1db..c629ed84d20c 100755 --- a/src/binana.py +++ b/src/binana.py @@ -122,6 +122,12 @@ def gen_binana_h(data, header, depjson): if discourage: defines["SUCCESS_OPCODES"].append(f' if (auto e = op_success_check(flags, SCRIPT_VERIFY_{dep}, SCRIPT_VERIFY_DISCOURAGE_{dep}, SCRIPT_ERR_DISCOURAGE_{dep}, serror)) return e; else break;') + if "errors" in b: + for errname, errmsg in b["errors"].items(): + defines["SCRIPTERR"].append(f'SCRIPT_ERR_{errname},') + defines["SCRIPTERR_STRING"].append(f'case SCRIPT_ERR_{errname}: return "{errmsg}";') + defines["SCRIPTERR_TEST_NAMES"].append(f'{{ SCRIPT_ERR_{errname}, "{errname}" }},') + header.write("// Automatically generated\n") header.write("#ifndef BINANA_H\n") header.write("#define BINANA_H\n\n") diff --git a/src/binana/ec_ops.json b/src/binana/ec_ops.json new file mode 100644 index 000000000000..827376f898b9 --- /dev/null +++ b/src/binana/ec_ops.json @@ -0,0 +1,12 @@ +{ + "binana": [2026, 6, 0], + "deployment": "EC_OPS", + "scriptverify": true, + "scriptverify_discourage": true, + "opcodes": { + "EC_POINT_ADD": "0xbb" + }, + "errors": { + "EC_POINT_ADD": "Invalid points for EC point addition" + } +} diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 02b732d77ac2..eea3a554fa49 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -184,6 +184,12 @@ int ecdsa_signature_parse_der_lax(secp256k1_ecdsa_signature* sig, const unsigned return 1; } +bool parse_ec_point(secp256k1_pubkey *pubkey, const unsigned char *input, size_t inputlen) { + if (inputlen != CPubKey::COMPRESSED_SIZE) + return false; + return secp256k1_ec_pubkey_parse(secp256k1_context_static, pubkey, input, inputlen); +} + /** Nothing Up My Sleeve (NUMS) point * * NUMS_H is a point with an unknown discrete logarithm, constructed by taking the sha256 of 'g' @@ -359,6 +365,31 @@ EllSwiftPubKey::EllSwiftPubKey(Span ellswift) noexcept std::copy(ellswift.begin(), ellswift.end(), m_pubkey.begin()); } +bool CPubKey::ComputeSum(const CPubKey& other, CPubKey& ret) const { + secp256k1_pubkey pubkey1, pubkey2; + if (!parse_ec_point(&pubkey1, vch, size())) { + return false; + } + if (!parse_ec_point(&pubkey2, other.vch, other.size())) { + return false; + } + const secp256k1_pubkey* pubkeys[2]; + pubkeys[0] = &pubkey1; + pubkeys[1] = &pubkey2; + secp256k1_pubkey pubkey_sum; + if (!secp256k1_ec_pubkey_combine(secp256k1_context_static, &pubkey_sum, pubkeys, 2)) { + /* The sum is Infinity. Set to an empty vector */ + unsigned char empty[1]; + ret.Set(empty, empty); + return true; + } + unsigned char out[COMPRESSED_SIZE]; + size_t outlen = COMPRESSED_SIZE; + secp256k1_ec_pubkey_serialize(secp256k1_context_static, out, &outlen, &pubkey_sum, SECP256K1_EC_COMPRESSED); + ret.Set(out, out + outlen); + return true; +} + CPubKey EllSwiftPubKey::Decode() const { secp256k1_pubkey pubkey; diff --git a/src/pubkey.h b/src/pubkey.h index 012c98262c28..0561d4015ab6 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -225,6 +225,9 @@ class CPubKey //! Derive BIP32 child pubkey. [[nodiscard]] bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; + + // Compute the sum of this point and another point + bool ComputeSum(const CPubKey& other, CPubKey& ret) const; }; class XOnlyPubKey diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 0617c424cbbd..c3d84d245a18 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1377,6 +1377,34 @@ bool EvalScript(std::vector >& stack, const CScript& break; } + case OP_EC_POINT_ADD: + { + // OP_EC_POINT_ADD is only available in Tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + assert(execdata.m_validation_weight_left_init); + execdata.m_validation_weight_left -= VALIDATION_WEIGHT_EC_POINT_ADD; + if (execdata.m_validation_weight_left < 0) { + return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); + } + if (stack.size() < 2) return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + const valtype& vch1 = stacktop(-2); + const valtype& vch2 = stacktop(-1); + CPubKey p1(vch1); + CPubKey p2(vch2); + + CPubKey pSum; + if (!p1.ComputeSum(p2, pSum)) { + return set_error(serror, SCRIPT_ERR_EC_POINT_ADD); + } + + popstack(stack); + popstack(stack); + stack.emplace_back(pSum.begin(), pSum.end()); + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } diff --git a/src/script/script.h b/src/script/script.h index 391fe23ddabb..b7c61b127236 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -61,6 +61,9 @@ static constexpr unsigned int ANNEX_TAG = 0x50; // Validation weight per passing signature (Tapscript only, see BIP 342). static constexpr int64_t VALIDATION_WEIGHT_PER_SIGOP_PASSED{50}; +// Validation weight cost for OP_EC_POINT_ADD (Tapscript only, see BIP ???). +static constexpr int64_t VALIDATION_WEIGHT_EC_POINT_ADD{10}; + // How much weight budget is added to the witness size (Tapscript only, see BIP 342). static constexpr int64_t VALIDATION_WEIGHT_OFFSET{50}; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 682e23fde873..b9d3112cea0b 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -41,6 +41,7 @@ add_executable(test_bitcoin denialofservice_tests.cpp descriptor_tests.cpp disconnected_transactions.cpp + ec_ops_tests.cpp feefrac_tests.cpp flatfile_tests.cpp fs_tests.cpp diff --git a/src/test/ec_ops_tests.cpp b/src/test/ec_ops_tests.cpp new file mode 100644 index 000000000000..f8b80551e2ac --- /dev/null +++ b/src/test/ec_ops_tests.cpp @@ -0,0 +1,136 @@ +// Copyright (c) XXXX-present 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 +#include +#include + +#include + +#include + +typedef std::vector valtype; + +script_verify_flags flags = SCRIPT_VERIFY_TAPROOT; + +BOOST_FIXTURE_TEST_SUITE(ec_ops_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(test_ec_point_add_opcode) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype point1 = ParseHex("03d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da612"); + valtype point2 = ParseHex("038d1eadc80f1d0bbf345f3c5202946a0b72e2c217242f5d8c3c8bc5d5467ff0ac"); + + stack.push_back(point1); + stack.push_back(point2); + + CScript script; + script << OP_EC_POINT_ADD; + + BOOST_CHECK(EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); + BOOST_CHECK_EQUAL(stack.size(), 1); + BOOST_CHECK_EQUAL(stack[0].size(), 33); + valtype expected_sum = ParseHex("0284df99cc50d1ec93e9bc32c666325a389dd69a7f42777b8f1670ad66d2e622c9"); + BOOST_CHECK_EQUAL(HexStr(stack[0]), HexStr(expected_sum)); + BOOST_CHECK_EQUAL(execdata.m_validation_weight_left, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - VALIDATION_WEIGHT_EC_POINT_ADD); +} + +BOOST_AUTO_TEST_CASE(test_ec_point_add_opcode_infinity) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype point1 = ParseHex("03d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da612"); + valtype point2 = ParseHex("02d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da612"); + + stack.push_back(point1); + stack.push_back(point2); + + CScript script; + script << OP_EC_POINT_ADD; + + BOOST_CHECK(EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); + BOOST_CHECK_EQUAL(stack.size(), 1); + BOOST_CHECK_EQUAL(stack[0].size(), 0); +} + +BOOST_AUTO_TEST_CASE(op_ec_point_add_invalid_points) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype point1 = ParseHex("deadbeef"); + valtype point2 = ParseHex("deadbeef"); + + stack.push_back(point1); + stack.push_back(point2); + + CScript script; + script << OP_EC_POINT_ADD; + + BOOST_CHECK(!EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_EC_POINT_ADD); +} + +BOOST_AUTO_TEST_CASE(test_ec_point_add_opcode_65_bytes_point) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype point1 = ParseHex("0437a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1bab96ced90a1b8f9b43a18fc900ff55af2be0e94b90a434fca5b9e226b835024cd"); + valtype point2 = ParseHex("02d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da612"); + + stack.push_back(point1); + stack.push_back(point2); + + CScript script; + script << OP_EC_POINT_ADD; + + BOOST_CHECK(!EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_EC_POINT_ADD); +} + +BOOST_AUTO_TEST_CASE(test_ec_point_add_opcode_validation_weight_exceeded) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - (SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - VALIDATION_WEIGHT_EC_POINT_ADD + 1); + ScriptError err; + + std::vector stack; + + valtype point1 = ParseHex("03d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da612"); + valtype point2 = ParseHex("038d1eadc80f1d0bbf345f3c5202946a0b72e2c217242f5d8c3c8bc5d5467ff0ac"); + + stack.push_back(point1); + stack.push_back(point2); + + CScript script; + script << OP_EC_POINT_ADD; + + BOOST_CHECK(!EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 385f45c76753938d50940142d2c451f4a06be90b Mon Sep 17 00:00:00 2001 From: ViniciusCestarii Date: Sun, 25 Jan 2026 10:22:33 -0300 Subject: [PATCH 2/6] feat: export secp256k1 hazmat module Expose internal functions by incorporating changes from https://github.com/bitcoin-core/secp256k1/pull/1635 to make them available. --- src/CMakeLists.txt | 1 + src/binana/ec_ops.json | 2 +- src/secp256k1/.gitignore | 1 + src/secp256k1/CMakeLists.txt | 44 +++- src/secp256k1/Makefile.am | 15 ++ src/secp256k1/configure.ac | 14 +- src/secp256k1/examples/CMakeLists.txt | 4 + src/secp256k1/examples/hazmat.c | 196 ++++++++++++++++++ src/secp256k1/include/secp256k1_hazmat.h | 54 +++++ src/secp256k1/src/CMakeLists.txt | 3 + .../src/modules/hazmat/Makefile.am.include | 2 + src/secp256k1/src/modules/hazmat/main_impl.h | 147 +++++++++++++ src/secp256k1/src/secp256k1.c | 4 + 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/secp256k1/examples/hazmat.c create mode 100644 src/secp256k1/include/secp256k1_hazmat.h create mode 100644 src/secp256k1/src/modules/hazmat/Makefile.am.include create mode 100644 src/secp256k1/src/modules/hazmat/main_impl.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 865167bbced0..5c63cc55fe92 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_ECDH OFF CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_MUSIG OFF CACHE BOOL "" FORCE) +set(SECP256K1_ENABLE_MODULE_HAZMAT ON CACHE BOOL "" FORCE) set(SECP256K1_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE) set(SECP256K1_BUILD_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE) set(SECP256K1_BUILD_EXHAUSTIVE_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE) diff --git a/src/binana/ec_ops.json b/src/binana/ec_ops.json index 827376f898b9..ecbaacf57887 100644 --- a/src/binana/ec_ops.json +++ b/src/binana/ec_ops.json @@ -7,6 +7,6 @@ "EC_POINT_ADD": "0xbb" }, "errors": { - "EC_POINT_ADD": "Invalid points for EC point addition" + "EC_POINT_ADD": "Using OP_EC_POINT_ADD to sum invalid points" } } diff --git a/src/secp256k1/.gitignore b/src/secp256k1/.gitignore index bffba8cb2c9d..16cbd810e399 100644 --- a/src/secp256k1/.gitignore +++ b/src/secp256k1/.gitignore @@ -12,6 +12,7 @@ ecdsa_example schnorr_example ellswift_example musig_example +hazmat_example *.exe *.so *.a diff --git a/src/secp256k1/CMakeLists.txt b/src/secp256k1/CMakeLists.txt index 041bfa3dca05..87b01e3e283e 100644 --- a/src/secp256k1/CMakeLists.txt +++ b/src/secp256k1/CMakeLists.txt @@ -7,7 +7,7 @@ project(libsecp256k1 # The package (a.k.a. release) version is based on semantic versioning 2.0.0 of # the API. All changes in experimental modules are treated as # backwards-compatible and therefore at most increase the minor version. - VERSION 0.6.0 + VERSION 0.6.1 DESCRIPTION "Optimized C library for ECDSA signatures and secret/public key operations on curve secp256k1." HOMEPAGE_URL "https://github.com/bitcoin-core/secp256k1" LANGUAGES C @@ -32,7 +32,7 @@ endif() # All changes in experimental modules are treated as if they don't affect the # interface and therefore only increase the revision. set(${PROJECT_NAME}_LIB_VERSION_CURRENT 5) -set(${PROJECT_NAME}_LIB_VERSION_REVISION 0) +set(${PROJECT_NAME}_LIB_VERSION_REVISION 1) set(${PROJECT_NAME}_LIB_VERSION_AGE 0) #============================= @@ -62,6 +62,45 @@ option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON) option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) +option(SECP256K1_ENABLE_MODULE_HAZMAT "Enable hazmat module." OFF) + +# Processing must be done in a topological sorting of the dependency graph +# (dependent module first). +if(SECP256K1_ENABLE_MODULE_HAZMAT) + add_compile_definitions(ENABLE_MODULE_HAZMAT=1) +endif() + +if(SECP256K1_ENABLE_MODULE_ELLSWIFT) + add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1) +endif() + +if(SECP256K1_ENABLE_MODULE_MUSIG) + if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG) + message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.") + endif() + set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON) + add_compile_definitions(ENABLE_MODULE_MUSIG=1) +endif() + +if(SECP256K1_ENABLE_MODULE_SCHNORRSIG) + if(DEFINED SECP256K1_ENABLE_MODULE_EXTRAKEYS AND NOT SECP256K1_ENABLE_MODULE_EXTRAKEYS) + message(FATAL_ERROR "Module dependency error: You have disabled the extrakeys module explicitly, but it is required by the schnorrsig module.") + endif() + set(SECP256K1_ENABLE_MODULE_EXTRAKEYS ON) + add_compile_definitions(ENABLE_MODULE_SCHNORRSIG=1) +endif() + +if(SECP256K1_ENABLE_MODULE_EXTRAKEYS) + add_compile_definitions(ENABLE_MODULE_EXTRAKEYS=1) +endif() + +if(SECP256K1_ENABLE_MODULE_RECOVERY) + add_compile_definitions(ENABLE_MODULE_RECOVERY=1) +endif() + +if(SECP256K1_ENABLE_MODULE_ECDH) + add_compile_definitions(ENABLE_MODULE_ECDH=1) +endif() # Processing must be done in a topological sorting of the dependency graph # (dependent module first). @@ -327,6 +366,7 @@ message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRA message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") +message(" hazmat .............................. ${SECP256K1_ENABLE_MODULE_HAZMAT}") message("Parameters:") message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") message(" ecmult gen table size ............... ${SECP256K1_ECMULT_GEN_KB} KiB") diff --git a/src/secp256k1/Makefile.am b/src/secp256k1/Makefile.am index a95b4809d486..11ecaa3d9c50 100644 --- a/src/secp256k1/Makefile.am +++ b/src/secp256k1/Makefile.am @@ -206,6 +206,17 @@ musig_example_LDFLAGS += -lbcrypt endif TESTS += musig_example endif +if ENABLE_MODULE_HAZMAT +noinst_PROGRAMS += hazmat_example +hazmat_example_SOURCES = examples/hazmat.c +hazmat_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC +hazmat_example_LDADD = libsecp256k1.la +hazmat_example_LDFLAGS = -static +if BUILD_WINDOWS +hazmat_example_LDFLAGS += -lbcrypt +endif +TESTS += hazmat_example +endif endif ### Precomputed tables @@ -300,3 +311,7 @@ endif if ENABLE_MODULE_ELLSWIFT include src/modules/ellswift/Makefile.am.include endif + +if ENABLE_MODULE_HAZMAT +include src/modules/hazmat/Makefile.am.include +endif diff --git a/src/secp256k1/configure.ac b/src/secp256k1/configure.ac index f880a3578ddd..cb262d29b5b6 100644 --- a/src/secp256k1/configure.ac +++ b/src/secp256k1/configure.ac @@ -5,7 +5,7 @@ AC_PREREQ([2.60]) # backwards-compatible and therefore at most increase the minor version. define(_PKG_VERSION_MAJOR, 0) define(_PKG_VERSION_MINOR, 6) -define(_PKG_VERSION_PATCH, 0) +define(_PKG_VERSION_PATCH, 1) define(_PKG_VERSION_IS_RELEASE, true) # The library version is based on libtool versioning of the ABI. The set of @@ -14,7 +14,7 @@ define(_PKG_VERSION_IS_RELEASE, true) # All changes in experimental modules are treated as if they don't affect the # interface and therefore only increase the revision. define(_LIB_VERSION_CURRENT, 5) -define(_LIB_VERSION_REVISION, 0) +define(_LIB_VERSION_REVISION, 1) define(_LIB_VERSION_AGE, 0) AC_INIT([libsecp256k1],m4_join([.], _PKG_VERSION_MAJOR, _PKG_VERSION_MINOR, _PKG_VERSION_PATCH)m4_if(_PKG_VERSION_IS_RELEASE, [true], [], [-dev]),[https://github.com/bitcoin-core/secp256k1/issues],[libsecp256k1],[https://github.com/bitcoin-core/secp256k1]) @@ -192,6 +192,10 @@ AC_ARG_ENABLE(module_ellswift, AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [], [SECP_SET_DEFAULT([enable_module_ellswift], [yes], [yes])]) +AC_ARG_ENABLE(module_hazmat, + AS_HELP_STRING([--enable-module-hazmat],[enable hazmat module [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_hazmat], [no], [yes])]) + AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) @@ -430,6 +434,10 @@ if test x"$enable_module_ecdh" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDH=1" fi +if test x"$enable_module_hazmat" = x"yes"; then + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_HAZMAT=1" +fi + if test x"$enable_external_default_callbacks" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_EXTERNAL_DEFAULT_CALLBACKS=1" fi @@ -463,6 +471,7 @@ AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x" AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_MUSIG], [test x"$enable_module_musig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_HAZMAT], [test x"$enable_module_hazmat" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"]) AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"]) @@ -486,6 +495,7 @@ echo " module extrakeys = $enable_module_extrakeys" echo " module schnorrsig = $enable_module_schnorrsig" echo " module musig = $enable_module_musig" echo " module ellswift = $enable_module_ellswift" +echo " module hazmat = $enable_module_hazmat" echo echo " asm = $set_asm" echo " ecmult window size = $set_ecmult_window" diff --git a/src/secp256k1/examples/CMakeLists.txt b/src/secp256k1/examples/CMakeLists.txt index c9da9de6be32..7f040f79ee06 100644 --- a/src/secp256k1/examples/CMakeLists.txt +++ b/src/secp256k1/examples/CMakeLists.txt @@ -29,3 +29,7 @@ endif() if(SECP256K1_ENABLE_MODULE_MUSIG) add_example(musig) endif() + +if(SECP256K1_ENABLE_MODULE_HAZMAT) + add_example(hazmat) +endif() diff --git a/src/secp256k1/examples/hazmat.c b/src/secp256k1/examples/hazmat.c new file mode 100644 index 000000000000..bd538b698c8a --- /dev/null +++ b/src/secp256k1/examples/hazmat.c @@ -0,0 +1,196 @@ +/************************************************************************* + * To the extent possible under law, the author(s) have dedicated all * + * copyright and related and neighboring rights to the software in this * + * file to the public domain worldwide. This software is distributed * + * without any warranty. For the CC0 Public Domain Dedication, see * + * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * + *************************************************************************/ + +#include +#include +#include + +#include +#include + +#include "examples_util.h" + +int main(void) { + secp256k1_context* ctx; + unsigned char randomize[32]; + secp256k1_hazmat_scalar a[3], a_sum; + secp256k1_hazmat_point A[3], A_sum; + unsigned char lhs_ser[33], rhs_ser[33]; + int return_val, i; + + /* Create a secp256k1 context + * Note that in the hazmat module, the context is only needed for multiplication + * with the generator point (function `secp256k1_hazmat_multiply_with_generator`). + */ + ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + if (!fill_random(randomize, sizeof(randomize))) { + printf("Failed to generate randomness\n"); + return 1; + } + /* Randomizing the context is recommended to protect against side-channel + * leakage. See `secp256k1_context_randomize` in secp256k1.h for more + * information about it. This should never fail. + */ + return_val = secp256k1_context_randomize(ctx, randomize); + assert(return_val); + + /* Generate keypairs */ + for (i = 0; i < 3; i++) { + unsigned char scalar_buf[32]; + unsigned char point_ser[33]; + + if (!fill_random(scalar_buf, sizeof(scalar_buf))) { + printf("Failed to generate randomness\n"); + return 1; + } + if (!secp256k1_hazmat_scalar_parse(&a[i], scalar_buf) || secp256k1_hazmat_scalar_is_zero(&a[i])) { + printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n"); + return 1; + } + secp256k1_hazmat_multiply_with_generator(ctx, &A[i], &a[i]); + + secp256k1_hazmat_point_serialize(point_ser, &A[i]); + printf("scalar a_%d: ", i+1); print_hex(scalar_buf, sizeof(scalar_buf)); + printf("point A_%d: ", i+1); print_hex(point_ser, sizeof(point_ser)); + + secure_erase(scalar_buf, sizeof(scalar_buf)); + } + + /* Simple example: verify that (a_1 + a_2 + a_3) * G = A_1 + A_2 + A_3 holds */ + secp256k1_hazmat_scalar_set_zero(&a_sum); + secp256k1_hazmat_point_set_infinity(&A_sum); + for (i = 0; i < 3; i++) { + secp256k1_hazmat_scalar_add(&a_sum, &a_sum, &a[i]); + secp256k1_hazmat_point_add(&A_sum, &A_sum, &A[i]); + } + + { + secp256k1_hazmat_point A_lhs; + + secp256k1_hazmat_multiply_with_generator(ctx, &A_lhs, &a_sum); + secp256k1_hazmat_point_serialize(lhs_ser, &A_lhs); + secp256k1_hazmat_point_serialize(rhs_ser, &A_sum); + + printf("\n"); + printf("(a_1 + a_2 + a_3) * G: "); + print_hex(lhs_ser, sizeof(lhs_ser)); + printf(" A_1 + A_2 + A_3: "); + print_hex(rhs_ser, sizeof(rhs_ser)); + + /* Verify equality for both the hazmat points and their serialization */ + return_val = secp256k1_hazmat_point_equal(&A_lhs, &A_sum); + assert(return_val == 1); + return_val = memcmp(lhs_ser, rhs_ser, sizeof(lhs_ser)); + assert(return_val == 0); + } + + /* Next example: verify that a_1 * A_2 = A_1 * a_2 (ECDH) */ + { + secp256k1_hazmat_point lhs, rhs; + + secp256k1_hazmat_multiply_with_point(&lhs, &a[0], &A[1]); + secp256k1_hazmat_multiply_with_point(&rhs, &a[1], &A[0]); + secp256k1_hazmat_point_serialize(lhs_ser, &lhs); + secp256k1_hazmat_point_serialize(rhs_ser, &rhs); + + printf("\n"); + printf(" a_1 * A_2: "); + print_hex(lhs_ser, sizeof(lhs_ser)); + printf(" A_1 * a_2: "); + print_hex(rhs_ser, sizeof(rhs_ser)); + + return_val = secp256k1_hazmat_point_equal(&lhs, &rhs); + assert(return_val == 1); + return_val = memcmp(lhs_ser, rhs_ser, sizeof(lhs_ser)); + assert(return_val == 0); + } + + /* Yet another example, to demonstrate also scalar multiplication: + * verify that (a_1 * a_2) * A_3 = a_1 * (a_2 * A_3) */ + { + secp256k1_hazmat_point lhs, rhs; + secp256k1_hazmat_scalar tmp_scalar; + secp256k1_hazmat_point tmp_point; + + secp256k1_hazmat_scalar_mul(&tmp_scalar, &a[0], &a[1]); + secp256k1_hazmat_multiply_with_point(&lhs, &tmp_scalar, &A[2]); + secp256k1_hazmat_multiply_with_point(&tmp_point, &a[1], &A[2]); + secp256k1_hazmat_multiply_with_point(&rhs, &a[0], &tmp_point); + secp256k1_hazmat_point_serialize(lhs_ser, &lhs); + secp256k1_hazmat_point_serialize(rhs_ser, &rhs); + + printf("\n"); + printf("(a_1 * a_2) * A_3: "); + print_hex(lhs_ser, sizeof(lhs_ser)); + printf(" a_1 * (a_2 * A_3): "); + print_hex(rhs_ser, sizeof(rhs_ser)); + + return_val = secp256k1_hazmat_point_equal(&lhs, &rhs); + assert(return_val == 1); + return_val = memcmp(lhs_ser, rhs_ser, sizeof(lhs_ser)); + assert(return_val == 0); + } + + /* Show negation and neutral elements for scalars and points: + * a_i - a_i = 0 + * A_i - A_i = point at infinity + */ + for (i = 0; i < 3; i++) { + secp256k1_hazmat_scalar a_result, a_negated; + secp256k1_hazmat_point A_result, A_negated; + + a_negated = a[i]; + secp256k1_hazmat_scalar_negate(&a_negated); + secp256k1_hazmat_scalar_add(&a_result, &a[i], &a_negated); + assert(secp256k1_hazmat_scalar_is_zero(&a_result)); + + A_negated = A[i]; + secp256k1_hazmat_point_negate(&A_negated); + secp256k1_hazmat_point_add(&A_result, &A[i], &A_negated); + assert(secp256k1_hazmat_point_is_infinity(&A_result)); + } + + /* To demonstrate parsing points and scalars, verify that the discrete log + * of the generator point is the scalar with value 1. */ + { + secp256k1_hazmat_point generator, generator_calculated; + secp256k1_hazmat_scalar scalar_one; + unsigned char generator_ser[33] = + "\x02\x79\xBE\x66\x7E\xF9\xDC\xBB\xAC\x55\xA0\x62\x95\xCE\x87\x0B\x07" + "\x02\x9B\xFC\xDB\x2D\xCE\x28\xD9\x59\xF2\x81\x5B\x16\xF8\x17\x98"; + unsigned char scalar_one_ser[32] = + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"; + unsigned char generator_calculated_ser[33]; + + return_val = secp256k1_hazmat_point_parse(&generator, generator_ser); + assert(return_val); + return_val = secp256k1_hazmat_scalar_parse(&scalar_one, scalar_one_ser); + assert(return_val); + secp256k1_hazmat_multiply_with_generator(ctx, &generator_calculated, &scalar_one); + secp256k1_hazmat_point_serialize(generator_calculated_ser, &generator_calculated); + return_val = secp256k1_hazmat_point_equal(&generator, &generator_calculated); + assert(return_val == 1); + return_val = memcmp(generator_ser, generator_calculated_ser, sizeof(generator_ser)); + assert(return_val == 0); + } + + /* It's best practice to try to clear secrets from memory after using them. + * This is done because some bugs can allow an attacker to leak memory, for + * example through "out of bounds" array access (see Heartbleed), or the OS + * swapping them to disk. Hence, we overwrite the secret key buffer with zeros. + * + * Here we are preventing these writes from being optimized out, as any good compiler + * will remove any writes that aren't used. */ + for (i = 0; i < 3; i++) { + secure_erase(&a[i], sizeof(a[i])); + } + secure_erase(&a_sum, sizeof(a_sum)); + + return 0; +} diff --git a/src/secp256k1/include/secp256k1_hazmat.h b/src/secp256k1/include/secp256k1_hazmat.h new file mode 100644 index 000000000000..0b591e91f741 --- /dev/null +++ b/src/secp256k1/include/secp256k1_hazmat.h @@ -0,0 +1,54 @@ +#ifndef SECP256K1_HAZMAT_H +#define SECP256K1_HAZMAT_H + +#include "secp256k1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* This module provides low-level cryptographic primitives of secp256k1. + * Note that these can be used incorrectly and require an in-depth knowledge + * of the cryptographic concepts at work, therefore we call this the + * "hazardous materials" library or "hazmat" for short. + */ + +/* Scalar */ +typedef union { + unsigned char data[32]; + uint64_t align8; /* ensure alignment on 8-bytes boundaries */ +} secp256k1_hazmat_scalar; + +SECP256K1_API int secp256k1_hazmat_scalar_parse(secp256k1_hazmat_scalar *s, const unsigned char *bin32); +SECP256K1_API void secp256k1_hazmat_scalar_serialize(unsigned char *bin32, const secp256k1_hazmat_scalar *s); +SECP256K1_API void secp256k1_hazmat_scalar_set_zero(secp256k1_hazmat_scalar *s); +SECP256K1_API int secp256k1_hazmat_scalar_is_zero(const secp256k1_hazmat_scalar *s); +SECP256K1_API void secp256k1_hazmat_scalar_add(secp256k1_hazmat_scalar *sres, const secp256k1_hazmat_scalar *s1, const secp256k1_hazmat_scalar *s2); +SECP256K1_API void secp256k1_hazmat_scalar_mul(secp256k1_hazmat_scalar *sres, const secp256k1_hazmat_scalar *s1, const secp256k1_hazmat_scalar *s2); +SECP256K1_API void secp256k1_hazmat_scalar_negate(secp256k1_hazmat_scalar *s); + +/* Point */ +typedef union { + unsigned char data[160]; + uint64_t align8; /* ensure alignment on 8-bytes boundaries */ +} secp256k1_hazmat_point; + +SECP256K1_API int secp256k1_hazmat_point_parse(secp256k1_hazmat_point *p, const unsigned char *pubkey33); +SECP256K1_API void secp256k1_hazmat_point_serialize(unsigned char *pubkey33, secp256k1_hazmat_point *p); +SECP256K1_API void secp256k1_hazmat_point_set_infinity(secp256k1_hazmat_point *p); +SECP256K1_API int secp256k1_hazmat_point_is_infinity(const secp256k1_hazmat_point *p); +SECP256K1_API void secp256k1_hazmat_point_add(secp256k1_hazmat_point *pres, secp256k1_hazmat_point *p1, secp256k1_hazmat_point *p2); +SECP256K1_API void secp256k1_hazmat_point_negate(secp256k1_hazmat_point *p); +SECP256K1_API int secp256k1_hazmat_point_equal(const secp256k1_hazmat_point *p1, const secp256k1_hazmat_point *p2); + +/* Point multiplication */ +SECP256K1_API void secp256k1_hazmat_multiply_with_generator(const secp256k1_context *ctx, secp256k1_hazmat_point *pres, const secp256k1_hazmat_scalar *s); +SECP256K1_API void secp256k1_hazmat_multiply_with_point(secp256k1_hazmat_point *pres, const secp256k1_hazmat_scalar *s, secp256k1_hazmat_point *p); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_HAZMAT_H */ diff --git a/src/secp256k1/src/CMakeLists.txt b/src/secp256k1/src/CMakeLists.txt index f31b8c8f5514..0fc339ffd287 100644 --- a/src/secp256k1/src/CMakeLists.txt +++ b/src/secp256k1/src/CMakeLists.txt @@ -138,6 +138,9 @@ if(SECP256K1_INSTALL) if(SECP256K1_ENABLE_MODULE_ELLSWIFT) list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_ellswift.h") endif() + if(SECP256K1_ENABLE_MODULE_HAZMAT) + list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_hazmat.h") + endif() install(FILES ${${PROJECT_NAME}_headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) diff --git a/src/secp256k1/src/modules/hazmat/Makefile.am.include b/src/secp256k1/src/modules/hazmat/Makefile.am.include new file mode 100644 index 000000000000..6339ff36bca3 --- /dev/null +++ b/src/secp256k1/src/modules/hazmat/Makefile.am.include @@ -0,0 +1,2 @@ +include_HEADERS += include/secp256k1_hazmat.h +noinst_HEADERS += src/modules/hazmat/main_impl.h diff --git a/src/secp256k1/src/modules/hazmat/main_impl.h b/src/secp256k1/src/modules/hazmat/main_impl.h new file mode 100644 index 000000000000..a6cd62845447 --- /dev/null +++ b/src/secp256k1/src/modules/hazmat/main_impl.h @@ -0,0 +1,147 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_HAZMAT_MAIN_H +#define SECP256K1_MODULE_HAZMAT_MAIN_H + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_hazmat.h" +#include "../../scalar.h" +#include "../../group.h" +#include "../../eckey.h" +#include "../../ecmult_const.h" + +typedef struct { + secp256k1_gej gej; + int z_is_one; /* set if z == 1, i.e. gej can be converted to ge trivially by assigning x/y */ +} secp256k1_hazmat_point_struct; + +/* Verify that the opaque data types are large enough to hold the underlying structures + (note that this function is never called at run-time and only exists since the STATIC_ASSERT + macro can only be used inside of functions) */ +static void secp256k1_hazmat_assertions(void) { + STATIC_ASSERT(sizeof(secp256k1_hazmat_scalar) >= sizeof(secp256k1_scalar)); + STATIC_ASSERT(sizeof(secp256k1_hazmat_point) >= sizeof(secp256k1_hazmat_point_struct)); +} + +int secp256k1_hazmat_scalar_parse(secp256k1_hazmat_scalar *s, const unsigned char *bin32) { + int overflow; + secp256k1_scalar_set_b32((secp256k1_scalar*)s, bin32, &overflow); + return !overflow; +} + +void secp256k1_hazmat_scalar_serialize(unsigned char *bin32, const secp256k1_hazmat_scalar *s) { + secp256k1_scalar_get_b32(bin32, (secp256k1_scalar*)s); +} + +void secp256k1_hazmat_scalar_set_zero(secp256k1_hazmat_scalar *s) { + *((secp256k1_scalar*)s) = secp256k1_scalar_zero; +} + +int secp256k1_hazmat_scalar_is_zero(const secp256k1_hazmat_scalar *s) { + return secp256k1_scalar_is_zero((secp256k1_scalar*)s); +} + +void secp256k1_hazmat_scalar_add(secp256k1_hazmat_scalar *sres, const secp256k1_hazmat_scalar *s1, const secp256k1_hazmat_scalar *s2) { + secp256k1_scalar_add((secp256k1_scalar*)sres, (secp256k1_scalar*)s1, (secp256k1_scalar*)s2); +} + +void secp256k1_hazmat_scalar_mul(secp256k1_hazmat_scalar *sres, const secp256k1_hazmat_scalar *s1, const secp256k1_hazmat_scalar *s2) { + secp256k1_scalar_mul((secp256k1_scalar*)sres, (secp256k1_scalar*)s1, (secp256k1_scalar*)s2); +} + +void secp256k1_hazmat_scalar_negate(secp256k1_hazmat_scalar *s) { + secp256k1_scalar_negate((secp256k1_scalar*)s, (secp256k1_scalar*)s); +} + +static void secp256k1_hazmat_point_to_ge(secp256k1_ge *ge, secp256k1_hazmat_point_struct *p) { + if (p->z_is_one) { + secp256k1_ge_set_xy(ge, &p->gej.x, &p->gej.y); + } else { + secp256k1_ge_set_gej(ge, &p->gej); + p->z_is_one = 1; + } +} + +int secp256k1_hazmat_point_parse(secp256k1_hazmat_point *p, const unsigned char *pubkey33) { + secp256k1_hazmat_point_struct *ps = (secp256k1_hazmat_point_struct*)p; + secp256k1_ge ge; + + if (!secp256k1_eckey_pubkey_parse(&ge, pubkey33, 33)) { + return 0; + } + secp256k1_gej_set_ge(&ps->gej, &ge); + ps->z_is_one = 1; + return 1; +} + +void secp256k1_hazmat_point_serialize(unsigned char *pubkey33, secp256k1_hazmat_point *p) { + secp256k1_hazmat_point_struct *ps = (secp256k1_hazmat_point_struct*)p; + secp256k1_ge ge; + size_t size; + int ret; + + secp256k1_hazmat_point_to_ge(&ge, ps); + ret = secp256k1_eckey_pubkey_serialize(&ge, pubkey33, &size, 1); + VERIFY_CHECK(ret == 1 && size == 33); + (void)ret; +} + +void secp256k1_hazmat_point_set_infinity(secp256k1_hazmat_point *p) { + secp256k1_hazmat_point_struct *ps = (secp256k1_hazmat_point_struct*)p; + + secp256k1_gej_set_infinity(&ps->gej); + ps->z_is_one = 0; +} + +int secp256k1_hazmat_point_is_infinity(const secp256k1_hazmat_point *p) { + const secp256k1_hazmat_point_struct *ps = (const secp256k1_hazmat_point_struct*)p; + + return secp256k1_gej_is_infinity(&ps->gej); +} + +void secp256k1_hazmat_point_add(secp256k1_hazmat_point *pres, secp256k1_hazmat_point *p1, secp256k1_hazmat_point *p2) { + secp256k1_hazmat_point_struct *press = (secp256k1_hazmat_point_struct*)pres; + secp256k1_hazmat_point_struct *p1s = (secp256k1_hazmat_point_struct*)p1; + secp256k1_hazmat_point_struct *p2s = (secp256k1_hazmat_point_struct*)p2; + secp256k1_ge ge; + + secp256k1_hazmat_point_to_ge(&ge, p2s); + secp256k1_gej_add_ge(&press->gej, &p1s->gej, &ge); + press->z_is_one = 0; +} + +void secp256k1_hazmat_point_negate(secp256k1_hazmat_point *p) { + secp256k1_hazmat_point_struct *ps = (secp256k1_hazmat_point_struct*)p; + + secp256k1_gej_neg(&ps->gej, &ps->gej); + /* negation only changes y; z is untouched, so no update of z_is_one is needed */ +} + +int secp256k1_hazmat_point_equal(const secp256k1_hazmat_point *p1, const secp256k1_hazmat_point *p2) { + const secp256k1_hazmat_point_struct *p1s = (secp256k1_hazmat_point_struct*)p1; + const secp256k1_hazmat_point_struct *p2s = (secp256k1_hazmat_point_struct*)p2; + + return secp256k1_gej_eq_var(&p1s->gej, &p2s->gej); +} + +void secp256k1_hazmat_multiply_with_generator(const secp256k1_context *ctx, secp256k1_hazmat_point *p, const secp256k1_hazmat_scalar *s) { + secp256k1_hazmat_point_struct *ps = (secp256k1_hazmat_point_struct*)p; + + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &ps->gej, (secp256k1_scalar*)s); + ps->z_is_one = 0; +} + +void secp256k1_hazmat_multiply_with_point(secp256k1_hazmat_point *pres, const secp256k1_hazmat_scalar *s, secp256k1_hazmat_point *p) { + secp256k1_hazmat_point_struct *press = (secp256k1_hazmat_point_struct*)pres; + secp256k1_hazmat_point_struct *ps = (secp256k1_hazmat_point_struct*)p; + secp256k1_ge ge; + + secp256k1_hazmat_point_to_ge(&ge, ps); + secp256k1_ecmult_const(&press->gej, &ge, (secp256k1_scalar*)s); + press->z_is_one = 0; +} + +#endif diff --git a/src/secp256k1/src/secp256k1.c b/src/secp256k1/src/secp256k1.c index a248519dfd84..65eeafe40c46 100644 --- a/src/secp256k1/src/secp256k1.c +++ b/src/secp256k1/src/secp256k1.c @@ -829,3 +829,7 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, #ifdef ENABLE_MODULE_ELLSWIFT # include "modules/ellswift/main_impl.h" #endif + +#ifdef ENABLE_MODULE_HAZMAT +# include "modules/hazmat/main_impl.h" +#endif From 500d3ab690c74a299e2f822dcacfc7efa1a29a9a Mon Sep 17 00:00:00 2001 From: ViniciusCestarii Date: Sun, 25 Jan 2026 10:26:02 -0300 Subject: [PATCH 3/6] feat: implement opcode OP_EC_POINT_MUL --- src/binana/ec_ops.json | 6 ++- src/pubkey.cpp | 45 ++++++++++++++++++++++ src/pubkey.h | 4 ++ src/script/interpreter.cpp | 27 ++++++++++++++ src/script/script.h | 3 ++ src/test/ec_ops_tests.cpp | 76 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 159 insertions(+), 2 deletions(-) diff --git a/src/binana/ec_ops.json b/src/binana/ec_ops.json index ecbaacf57887..29304b5898c3 100644 --- a/src/binana/ec_ops.json +++ b/src/binana/ec_ops.json @@ -4,9 +4,11 @@ "scriptverify": true, "scriptverify_discourage": true, "opcodes": { - "EC_POINT_ADD": "0xbb" + "EC_POINT_ADD": "0xbb", + "EC_POINT_MUL": "0xbc" }, "errors": { - "EC_POINT_ADD": "Using OP_EC_POINT_ADD to sum invalid points" + "EC_POINT_ADD": "Using OP_EC_POINT_ADD to sum invalid points", + "EC_POINT_MUL": "Using OP_EC_POINT_MUL with invalid scalar or point" } } diff --git a/src/pubkey.cpp b/src/pubkey.cpp index eea3a554fa49..abdaf66941c3 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -220,6 +221,8 @@ std::vector XOnlyPubKey::GetKeyIDs() const return out; } +secp256k1_context *secp256k1_context_sign = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + CPubKey XOnlyPubKey::GetEvenCorrespondingCPubKey() const { unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02}; @@ -390,6 +393,48 @@ bool CPubKey::ComputeSum(const CPubKey& other, CPubKey& ret) const { return true; } +bool CPubKey::ComputeMul(const valtype& scalar, CPubKey& ret) const { + if (scalar.size() != 32) { + return false; + } + secp256k1_hazmat_scalar factor; + secp256k1_hazmat_scalar_parse(&factor, scalar.data()); + if (secp256k1_hazmat_scalar_is_zero(&factor)) { + /* The mul is Infinity. Set to an empty vector */ + unsigned char empty[1]; + ret.Set(empty, empty); + return true; + } + unsigned char factor_be[32]; + std::memcpy(factor_be, factor.data, 32); + std::reverse(factor_be, factor_be + 32); + /* Use point G */ + if (size() == 0) { + secp256k1_pubkey pubkey; + /* secp256k1_ec_pubkey_create computes G * tweak */ + if (!secp256k1_ec_pubkey_create(secp256k1_context_sign, &pubkey, factor_be)) { + return false; + } + unsigned char out[COMPRESSED_SIZE]; + size_t outlen = COMPRESSED_SIZE; + secp256k1_ec_pubkey_serialize(secp256k1_context_static, out, &outlen, &pubkey, SECP256K1_EC_COMPRESSED); + ret.Set(out, out + outlen); + return true; + } + secp256k1_pubkey pubkey; + if (!parse_ec_point(&pubkey, vch, size())) { + return false; + } + if (!secp256k1_ec_pubkey_tweak_mul(secp256k1_context_static, &pubkey, factor_be)) { + return false; + } + unsigned char out[COMPRESSED_SIZE]; + size_t outlen = COMPRESSED_SIZE; + secp256k1_ec_pubkey_serialize(secp256k1_context_static, out, &outlen, &pubkey, SECP256K1_EC_COMPRESSED); + ret.Set(out, out + outlen); + return true; +} + CPubKey EllSwiftPubKey::Decode() const { secp256k1_pubkey pubkey; diff --git a/src/pubkey.h b/src/pubkey.h index 0561d4015ab6..3787cf782bd5 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -28,6 +28,7 @@ class CKeyID : public uint160 }; typedef uint256 ChainCode; +typedef std::vector valtype; /** An encapsulated public key. */ class CPubKey @@ -228,6 +229,9 @@ class CPubKey // Compute the sum of this point and another point bool ComputeSum(const CPubKey& other, CPubKey& ret) const; + + // Compute the multiplication of this point by a scalar + bool ComputeMul(const valtype& scalar, CPubKey& ret) const; }; class XOnlyPubKey diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index c3d84d245a18..df7c284e3f93 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1405,6 +1405,33 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_EC_POINT_MUL: + { + // OP_EC_POINT_MUL is only available in Tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + assert(execdata.m_validation_weight_left_init); + execdata.m_validation_weight_left -= VALIDATION_WEIGHT_EC_POINT_MUL; + if (execdata.m_validation_weight_left < 0) { + return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); + } + if (stack.size() < 2) return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + const valtype& scalar = stacktop(-2); + const valtype& vch = stacktop(-1); + CPubKey p(vch); + + CPubKey pMul; + if (!p.ComputeMul(scalar, pMul)) { + return set_error(serror, SCRIPT_ERR_EC_POINT_MUL); + } + + popstack(stack); + popstack(stack); + stack.emplace_back(pMul.begin(), pMul.end()); + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } diff --git a/src/script/script.h b/src/script/script.h index b7c61b127236..98724269a17b 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -64,6 +64,9 @@ static constexpr int64_t VALIDATION_WEIGHT_PER_SIGOP_PASSED{50}; // Validation weight cost for OP_EC_POINT_ADD (Tapscript only, see BIP ???). static constexpr int64_t VALIDATION_WEIGHT_EC_POINT_ADD{10}; +// Validation weight cost for OP_EC_POINT_MUL (Tapscript only, see BIP ???). +static constexpr int64_t VALIDATION_WEIGHT_EC_POINT_MUL{30}; + // How much weight budget is added to the witness size (Tapscript only, see BIP 342). static constexpr int64_t VALIDATION_WEIGHT_OFFSET{50}; diff --git a/src/test/ec_ops_tests.cpp b/src/test/ec_ops_tests.cpp index f8b80551e2ac..14a3650610e0 100644 --- a/src/test/ec_ops_tests.cpp +++ b/src/test/ec_ops_tests.cpp @@ -133,4 +133,80 @@ BOOST_AUTO_TEST_CASE(test_ec_point_add_opcode_validation_weight_exceeded) BOOST_CHECK_EQUAL(err, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); } +BOOST_AUTO_TEST_CASE(test_ec_point_mul_opcode) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype scalar = ParseHex("0000000000000000000000000000000000000000000000000000000000005678"); + valtype point = ParseHex("0337a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1ba"); + + stack.push_back(scalar); + stack.push_back(point); + + CScript script; + script << OP_EC_POINT_MUL; + + BOOST_CHECK(EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); + BOOST_CHECK_EQUAL(stack.size(), 1); + BOOST_CHECK_EQUAL(stack[0].size(), 33); + valtype expected_mul = ParseHex("033fbd85b56bc16aed085f2d0799e18569783e4a9ecd256741f69a231fbfb8f084"); + BOOST_CHECK_EQUAL(HexStr(stack[0]), HexStr(expected_mul)); + BOOST_CHECK_EQUAL(execdata.m_validation_weight_left, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - VALIDATION_WEIGHT_EC_POINT_MUL); +} + +BOOST_AUTO_TEST_CASE(test_ec_point_mul_opcode_point_g) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype scalar = ParseHex("0000000000000000000000000000000000000000000000000000000000005678"); + valtype point = ParseHex(""); + + stack.push_back(scalar); + stack.push_back(point); + + CScript script; + script << OP_EC_POINT_MUL; + + BOOST_CHECK(EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); + BOOST_CHECK_EQUAL(stack.size(), 1); + BOOST_CHECK_EQUAL(stack[0].size(), 33); + valtype expected_mul = ParseHex("0236298306e869232f364a2daf2000a5b4e990bb249182d7b4ebe02065d8ca1a79"); + BOOST_CHECK_EQUAL(HexStr(stack[0]), HexStr(expected_mul)); + BOOST_CHECK_EQUAL(execdata.m_validation_weight_left, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - VALIDATION_WEIGHT_EC_POINT_MUL); +} + +BOOST_AUTO_TEST_CASE(test_ec_point_mul_opcode_validation_weight_exceeded) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - (SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - VALIDATION_WEIGHT_EC_POINT_MUL + 1); + ScriptError err; + + std::vector stack; + + valtype scalar = ParseHex("0000000000000000000000000000000000000000000000000000000000005678"); + valtype point = ParseHex("0337a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1ba"); + + stack.push_back(scalar); + stack.push_back(point); + + CScript script; + script << OP_EC_POINT_MUL; + + BOOST_CHECK(!EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); +} + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 31a87ddcdd9ce5e8fc1357d0ae4b017a826a889f Mon Sep 17 00:00:00 2001 From: ViniciusCestarii Date: Sun, 25 Jan 2026 16:42:55 -0300 Subject: [PATCH 4/6] feat: implement opcode OP_EC_POINT_NEGATE --- src/binana/ec_ops.json | 6 ++-- src/pubkey.cpp | 18 ++++++++++ src/pubkey.h | 3 ++ src/script/interpreter.cpp | 25 ++++++++++++++ src/script/script.h | 2 ++ src/test/ec_ops_tests.cpp | 67 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/binana/ec_ops.json b/src/binana/ec_ops.json index 29304b5898c3..eac9b87a6f80 100644 --- a/src/binana/ec_ops.json +++ b/src/binana/ec_ops.json @@ -5,10 +5,12 @@ "scriptverify_discourage": true, "opcodes": { "EC_POINT_ADD": "0xbb", - "EC_POINT_MUL": "0xbc" + "EC_POINT_MUL": "0xbc", + "EC_POINT_NEGATE": "0xbd" }, "errors": { "EC_POINT_ADD": "Using OP_EC_POINT_ADD to sum invalid points", - "EC_POINT_MUL": "Using OP_EC_POINT_MUL with invalid scalar or point" + "EC_POINT_MUL": "Using OP_EC_POINT_MUL with invalid scalar or point", + "EC_POINT_NEGATE": "Using OP_EC_POINT_NEGATE with invalid point" } } diff --git a/src/pubkey.cpp b/src/pubkey.cpp index abdaf66941c3..a122339a31a1 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -435,6 +435,24 @@ bool CPubKey::ComputeMul(const valtype& scalar, CPubKey& ret) const { return true; } +bool CPubKey::Negate(CPubKey& ret) const { + if (size() == 0) { + /* The negation of Infinity is Infinity. Set to an empty vector */ + unsigned char empty[1]; + ret.Set(empty, empty); + return true; + } + secp256k1_pubkey pubkey; + if (!parse_ec_point(&pubkey, vch, size())) + return false; + std::ignore = secp256k1_ec_pubkey_negate(secp256k1_context_static, &pubkey); + unsigned char out[COMPRESSED_SIZE]; + size_t outlen = COMPRESSED_SIZE; + secp256k1_ec_pubkey_serialize(secp256k1_context_static, out, &outlen, &pubkey, SECP256K1_EC_COMPRESSED); + ret.Set(out, out + outlen); + return true; +} + CPubKey EllSwiftPubKey::Decode() const { secp256k1_pubkey pubkey; diff --git a/src/pubkey.h b/src/pubkey.h index 3787cf782bd5..b043375fbf86 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -232,6 +232,9 @@ class CPubKey // Compute the multiplication of this point by a scalar bool ComputeMul(const valtype& scalar, CPubKey& ret) const; + + // Compute the negation of this point + bool Negate(CPubKey& ret) const; }; class XOnlyPubKey diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index df7c284e3f93..a47b31cb6d2b 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1432,6 +1432,31 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_EC_POINT_NEGATE: + { + // OP_EC_POINT_NEGATE is only available in Tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + assert(execdata.m_validation_weight_left_init); + execdata.m_validation_weight_left -= VALIDATION_WEIGHT_EC_POINT_NEGATE; + if (execdata.m_validation_weight_left < 0) { + return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); + } + if (stack.size() < 1) return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + const valtype& vch = stacktop(-1); + CPubKey p(vch); + + CPubKey pNegate; + if (!p.Negate(pNegate)) { + return set_error(serror, SCRIPT_ERR_EC_POINT_NEGATE); + } + + popstack(stack); + stack.emplace_back(pNegate.begin(), pNegate.end()); + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } diff --git a/src/script/script.h b/src/script/script.h index 98724269a17b..1f5e918a87fa 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -67,6 +67,8 @@ static constexpr int64_t VALIDATION_WEIGHT_EC_POINT_ADD{10}; // Validation weight cost for OP_EC_POINT_MUL (Tapscript only, see BIP ???). static constexpr int64_t VALIDATION_WEIGHT_EC_POINT_MUL{30}; +static constexpr int64_t VALIDATION_WEIGHT_EC_POINT_NEGATE{5}; + // How much weight budget is added to the witness size (Tapscript only, see BIP 342). static constexpr int64_t VALIDATION_WEIGHT_OFFSET{50}; diff --git a/src/test/ec_ops_tests.cpp b/src/test/ec_ops_tests.cpp index 14a3650610e0..499eab99591c 100644 --- a/src/test/ec_ops_tests.cpp +++ b/src/test/ec_ops_tests.cpp @@ -209,4 +209,71 @@ BOOST_AUTO_TEST_CASE(test_ec_point_mul_opcode_validation_weight_exceeded) BOOST_CHECK_EQUAL(err, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); } +BOOST_AUTO_TEST_CASE(test_ec_point_negate_opcode) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype point = ParseHex("0337a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1ba"); + + stack.push_back(point); + + CScript script; + script << OP_EC_POINT_NEGATE; + + BOOST_CHECK(EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); + BOOST_CHECK_EQUAL(stack.size(), 1); + BOOST_CHECK_EQUAL(stack[0].size(), 33); + valtype expected_negate = ParseHex("0237a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1ba"); + BOOST_CHECK_EQUAL(HexStr(stack[0]), HexStr(expected_negate)); + BOOST_CHECK_EQUAL(execdata.m_validation_weight_left, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - VALIDATION_WEIGHT_EC_POINT_NEGATE); +} + +BOOST_AUTO_TEST_CASE(test_ec_point_negate_opcode_empty) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype point = ParseHex(""); + + stack.push_back(point); + + CScript script; + script << OP_EC_POINT_NEGATE; + + BOOST_CHECK(EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); + BOOST_CHECK_EQUAL(stack.size(), 1); + BOOST_CHECK_EQUAL(stack[0].size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_ec_point_negate_opcode_validation_weight_exceeded) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - (SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - VALIDATION_WEIGHT_EC_POINT_NEGATE + 1); + ScriptError err; + + std::vector stack; + + valtype point = ParseHex("0337a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1ba"); + + stack.push_back(point); + + CScript script; + script << OP_EC_POINT_NEGATE; + + BOOST_CHECK(!EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); +} + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 16a2a6f4d8d88b05ff1411091adbc3a1d349d6c3 Mon Sep 17 00:00:00 2001 From: ViniciusCestarii Date: Sun, 25 Jan 2026 17:08:39 -0300 Subject: [PATCH 5/6] feat: implement opcode OP_EC_POINT_X_COORD --- src/binana/ec_ops.json | 6 ++-- src/pubkey.cpp | 11 +++++++ src/pubkey.h | 3 ++ src/script/interpreter.cpp | 25 +++++++++++++++ src/script/script.h | 2 ++ src/test/ec_ops_tests.cpp | 65 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/binana/ec_ops.json b/src/binana/ec_ops.json index eac9b87a6f80..bf64acea4220 100644 --- a/src/binana/ec_ops.json +++ b/src/binana/ec_ops.json @@ -6,11 +6,13 @@ "opcodes": { "EC_POINT_ADD": "0xbb", "EC_POINT_MUL": "0xbc", - "EC_POINT_NEGATE": "0xbd" + "EC_POINT_NEGATE": "0xbd", + "EC_POINT_X_COORD": "0xbe" }, "errors": { "EC_POINT_ADD": "Using OP_EC_POINT_ADD to sum invalid points", "EC_POINT_MUL": "Using OP_EC_POINT_MUL with invalid scalar or point", - "EC_POINT_NEGATE": "Using OP_EC_POINT_NEGATE with invalid point" + "EC_POINT_NEGATE": "Using OP_EC_POINT_NEGATE with invalid point", + "EC_POINT_X_COORD": "Using OP_EC_POINT_X_COORD with invalid point" } } diff --git a/src/pubkey.cpp b/src/pubkey.cpp index a122339a31a1..628d622c78ef 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -453,6 +453,17 @@ bool CPubKey::Negate(CPubKey& ret) const { return true; } +bool CPubKey::GetX(valtype& ret) const { + secp256k1_pubkey pubkey; + if (!parse_ec_point(&pubkey, vch, size())) + return false; + unsigned char point[COMPRESSED_SIZE]; + size_t pointlen = COMPRESSED_SIZE; + secp256k1_ec_pubkey_serialize(secp256k1_context_static, point, &pointlen, &pubkey, SECP256K1_EC_COMPRESSED); + ret.assign(point + 1, point + pointlen); + return true; +} + CPubKey EllSwiftPubKey::Decode() const { secp256k1_pubkey pubkey; diff --git a/src/pubkey.h b/src/pubkey.h index b043375fbf86..797781acdf2e 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -235,6 +235,9 @@ class CPubKey // Compute the negation of this point bool Negate(CPubKey& ret) const; + + // Get the x-coordinate of this point + bool GetX(valtype& ret) const; }; class XOnlyPubKey diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index a47b31cb6d2b..30f3ab9ef09b 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1457,6 +1457,31 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_EC_POINT_X_COORD: + { + // OP_EC_POINT_X_COORD is only available in Tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + assert(execdata.m_validation_weight_left_init); + execdata.m_validation_weight_left -= VALIDATION_WEIGHT_EC_POINT_X_COORD; + if (execdata.m_validation_weight_left < 0) { + return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); + } + if (stack.size() < 1) return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + const valtype& vch = stacktop(-1); + CPubKey p(vch); + + valtype x; + if (!p.GetX(x)) { + return set_error(serror, SCRIPT_ERR_EC_POINT_X_COORD); + } + + popstack(stack); + stack.emplace_back(x.begin(), x.end()); + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } diff --git a/src/script/script.h b/src/script/script.h index 1f5e918a87fa..e41c9f7bbca6 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -69,6 +69,8 @@ static constexpr int64_t VALIDATION_WEIGHT_EC_POINT_MUL{30}; static constexpr int64_t VALIDATION_WEIGHT_EC_POINT_NEGATE{5}; +static constexpr int64_t VALIDATION_WEIGHT_EC_POINT_X_COORD{1}; + // How much weight budget is added to the witness size (Tapscript only, see BIP 342). static constexpr int64_t VALIDATION_WEIGHT_OFFSET{50}; diff --git a/src/test/ec_ops_tests.cpp b/src/test/ec_ops_tests.cpp index 499eab99591c..8c278f21d68b 100644 --- a/src/test/ec_ops_tests.cpp +++ b/src/test/ec_ops_tests.cpp @@ -276,4 +276,69 @@ BOOST_AUTO_TEST_CASE(test_ec_point_negate_opcode_validation_weight_exceeded) BOOST_CHECK_EQUAL(err, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); } +BOOST_AUTO_TEST_CASE(test_ec_point_x_coord_opcode) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype point = ParseHex("0337a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1ba"); + + stack.push_back(point); + + CScript script; + script << OP_EC_POINT_X_COORD; + + BOOST_CHECK(EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); + BOOST_CHECK_EQUAL(stack.size(), 1); + BOOST_CHECK_EQUAL(stack[0].size(), 32); + valtype expected_x = ParseHex("37a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1ba"); + BOOST_CHECK_EQUAL(HexStr(stack[0]), HexStr(expected_x)); + BOOST_CHECK_EQUAL(execdata.m_validation_weight_left, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - VALIDATION_WEIGHT_EC_POINT_X_COORD); +} + +BOOST_AUTO_TEST_CASE(test_ec_point_x_coord_opcode_empty) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT; + ScriptError err; + + std::vector stack; + + valtype point = ParseHex(""); + + stack.push_back(point); + + CScript script; + script << OP_EC_POINT_X_COORD; + + BOOST_CHECK(!EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EC_POINT_X_COORD, ScriptErrorString(err)); +} + +BOOST_AUTO_TEST_CASE(test_ec_point_x_coord_opcode_validation_weight_exceeded) +{ + ScriptExecutionData execdata; + execdata.m_validation_weight_left_init = true; + execdata.m_validation_weight_left = SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - (SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT - VALIDATION_WEIGHT_EC_POINT_X_COORD + 1); + ScriptError err; + + std::vector stack; + + valtype point = ParseHex("0337a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1ba"); + + stack.push_back(point); + + CScript script; + script << OP_EC_POINT_X_COORD; + + BOOST_CHECK(!EvalScript(stack, script, flags, BaseSignatureChecker(), SigVersion::TAPSCRIPT, execdata, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); +} + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 0861f7bc9c73c917455e00920ff4eba9ac1ca4f6 Mon Sep 17 00:00:00 2001 From: ViniciusCestarii Date: Mon, 23 Feb 2026 13:46:31 -0300 Subject: [PATCH 6/6] feat: add ec ops test vectors --- src/test/data/tx_invalid.json | 68 ++++++++++++++++++++++++++++++++++ src/test/data/tx_valid.json | 36 ++++++++++++++++++ src/test/transaction_tests.cpp | 5 +++ 3 files changed, 109 insertions(+) diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index 01b832c2fc62..169ac9e94596 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -496,5 +496,73 @@ "020000000297e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a2000000000151000000000b4c12e6dbe974dadd18ca139e6bce183817ac609f73213aa8aaeae5f123d6b6000000000151000000000ae80300000000000017a9143f163a8747557345ce2e6fe00c1894f2f281795e87d00700000000000017a9144cf13dfda93a7413b7e646611735656e5457657087b80b00000000000017a914868998b49df649c37a88d48c9d4a5b37290e507287a00f00000000000017a914034f9914a77571a6396482e9881745c92c3037c687881300000000000017a914a8238003e1732e2baf4334a8546d72be99af9bae87701700000000000017a91491dbac5d67d5941115a03fc7eaec09f31a5b4dfc87581b00000000000017a914e0c0f19fec3b2993b9c116c798b5429d4515596687401f00000000000017a914d6b40d98d94530f1a1eb57614680813c81a95ccd87282300000000000017a914fb0bfb072bb79611a4323981828108a3cf54b0a687102700000000000017a9149e2d11f06ba667e981b802af10be8dabd08eafff8700000000", "CHECKTEMPLATEVERIFY"], +["Insufficient stack items"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x5d11cca3ec3eab91d709ea18b2de550b82e3240ae0daad6c13c17037c6a3d09f", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a086010000000000000201bb21c1a328913bce52f1ed4a17cf00680f6c0f066a4a5c9f576db183ec422aa6a14f9900000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["Reject 32-byte x-only input"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xa287509e2698cfe262fa839890f7a513228dffbc8e156d0a65a9e7f48d685dfb", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a08601000000000000024420d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da61221038d1eadc80f1d0bbf345f3c5202946a0b72e2c217242f5d8c3c8bc5d5467ff0acbb21c110945c301563e2c6cda22d73c860cb83dbba2856cf6a2f9eb2200a83b953d89c00000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["Extract x from infinity fails"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xbe514e05bec6d4da7f6a35f165e32bad0c2b5d432ffd8c2ac3d3c28544c3f094", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a086010000000000000201be21c02c284a0fcd7c89fae0165d5dbe502ad504d29d396bf62fb366dd79feb0abb72800000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["Negate infinity"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xa59ef4c3e0653f3f9d28b866457f8f533b7fe047e645744b7b3466cc234d4aca", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a086010000000000000201bd21c110c090416c08837952647beb78b735e2ea9757ed396daac0ba14a0bd3661b5bb00000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["Extract x from 32-byte point"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x1405d112d7a8a582327ac8647deb8b1aa3345538e200075d767eea88e3bbd6df", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a08601000000000000024420a8d2660f97eb8b320b3951a7adc1a32c54119bdb779287f2c87825459ce43e13be20a8d2660f97eb8b320b3951a7adc1a32c54119bdb779287f2c87825459ce43e138721c1d7c5c3f0ebdad5a1f1c785b8be132051541c68716b01bfb73e182bed0df0015500000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["NEGATE: budget exceeded"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xcf35dfccc3e4382452fa3b58dbc108d2fb88192359799107105b8283371f430a", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002fd16022102a1eaff599957c9061e19d828eb20aa91ac021ace6bc4a9d2056401b5c964aa07bdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbd21c1618959f75103468eb65821c0f199f5f8cd3beb3c9979378eb6603a491d9afb7a00000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["X_COORD: insufficient stack"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x17f2e29eca6ddd0b56d8ca75dc5ff3db8c401c37dcd330fad11e85cb7295ea73", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a086010000000000000201be21c1e3b45dcd5b44dbad88ebcb4fbc80c8ac47b1a22fcfeda87557e3dbd60838460f00000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["X_COORD: invalid point"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x938771942788be4939998169ee5b961ce1d175a46f7836f1f0352deec8a9f5b0", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a08601000000000000020254be21c18547ec06c7e4a7190ceda28bfa9d567a66c597bcaadbf8ffeb88e92b059dc85900000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["ADD: invalid point encoding length"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x1d8282cc92f08b535493d3207abe78d91aa3bee261ffdcab8a77c0a9fb91a3ae", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002260202032102e2bb24c22b6c9cc29f9a54e7258735eb3f1ab2dab698fb69d75483349180058cbb21c0b775583e9cb1bb101da3740ec70e11680877f1e2bca699365216076e28b14b1000000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["Invalid point - x coordinate too large"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xd16722acd2e0bcb47c83adc068d7d1988c0c9cd22fe60410a38acf6d720fda72", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002452102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff21038d1eadc80f1d0bbf345f3c5202946a0b72e2c217242f5d8c3c8bc5d5467ff0acbb21c034f5f2a7ea908584632aba9320336fc99072c7d6d3c4ff3b4833849264cb2d4600000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["Invalid point"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xbd088a352386d4c7020bf37a02964228a1a7220725ef997fd291686c9f6e99d3", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002232102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbe21c1e5d3fbe9f737014a4092769380f8e1edf3bf3fc75804ecfe247bbb211ebef78700000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["MUL: insufficient stack"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x6dd3ba59a75cf0874457fbd7c7c355933cb9a74f8341a89c80d7f5d410c4ed7e", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a086010000000000000222200000000000000000000000000000000000000000000000000000000000000000bc21c0659db62321d339abc8c6105bd4e410e7902d018767a23b889140af7055c3175600000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["NEGATE: insufficient stack"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x9f48c70f375a1ab5ae6e1a4f42c3e0229dd7292d69dfa1d648619c83986083e1", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a086010000000000000201bd21c00d1c97f90e0e927ffef5fd6cd8366a477be707b17a66d63d8ab5faf9c80df78800000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["MUL: budget exceeded"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x2fc3d1ac4749696f46815849941ca248b679f4b6a0563d6baf921ef68b97adbf", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002fd72012000000000000000000000000000000000000000000000000000000000000000022102a1eaff599957c9061e19d828eb20aa91ac021ace6bc4a9d2056401b5c964aa076ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc756ebc75bc755121c03784b72912cce22aba765b3aa4713b732f7f96e08a294c1169b1c2f8adf05d2800000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["MUL: invalid scalar length"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x1e0b312ef7af7256bf3ad9f2cad29323464473d44145d46c503140ddca0c5663", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a086010000000000000227030102032102a1eaff599957c9061e19d828eb20aa91ac021ace6bc4a9d2056401b5c964aa07bc21c1b9bad3201fdf6daa0a226bbd4bbf46580c50a46437c2ec43cd73620c08b7289400000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["X_COORD: point at infinity"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x691dfa3b2fdb8b0c32d8de04dfd6720dce6a2c48fbf93de7f7b1b028090c84ae", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a08601000000000000020200be21c09bf7d51d7b83593bc6b5b118e9d389f304f3b82a0ca5cfacf9f142236942d80500000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + +["ADD: budget exceeded"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x595b5ed2b203b66290d4979a03af50060065777f1d4d3ab321a9ab9656a4ea2b", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002fdcb032102a1eaff599957c9061e19d828eb20aa91ac021ace6bc4a9d2056401b5c964aa072102e2bb24c22b6c9cc29f9a54e7258735eb3f1ab2dab698fb69d75483349180058c6ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb756ebb75bb755121c19e0877d4fa6432093b3aca5f501129c1a87e9404b44cb489824b3c6e7f488fc200000000", "P2SH,WITNESS,TAPROOT,EC_OPS"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index aeb18647b182..548adf217fe1 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -709,5 +709,41 @@ "0200000002d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000001510000000082b77fb944a40756a6341ee425c85d5850f2acbe6d51a7bcd211929a764b8ec8000000000100000000000ae80300000000000017a914d63e77972529f4db5b32efaa4e06f66ae0b5dc0987d00700000000000017a91488b6705f8c9568c52b55ed712c257f84f64a49f587b80b00000000000017a9142be57e9a179f8d9ff8f33a788d4b54512ea9e36087a00f00000000000017a91429261b4f65796f618908de9f51669014e2e2e04f87881300000000000017a914e3a1e1d24cbba3ca9369248082988bad3ceafcfb87701700000000000017a91403801b0a9591f3b5a00a5ea60fb34dc12b4a691187581b00000000000017a91465248bc2c732db2d88db0b0d677c1514b101025b87401f00000000000017a91420021e3dc4e80c7192c1a3cd04026d22d0f8d38287282300000000000017a914df27596dbff2028791bd7692846e65d16d8fed0d87102700000000000017a9142ed128e911cab04d3277d3635f79d5e3d7e6f4c48700000000", "CLEANSTACK,DISCOURAGE_CHECKTEMPLATEVERIFY"], +["Add two 33-byte points"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xbdccf1fb4466b26d66d764c61d1cabe8fcbb6598748b62fd64deb9260dde7f04", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002682103d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da61221038d1eadc80f1d0bbf345f3c5202946a0b72e2c217242f5d8c3c8bc5d5467ff0acbb210284df99cc50d1ec93e9bc32c666325a389dd69a7f42777b8f1670ad66d2e622c98721c1f1dd3079589438fb556253fa5b1d685518fe0dcda0cfd1dd28b11608b0651b6500000000", "DISCOURAGE_EC_OPS"], + +["Multiply by zero (infinity)"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x16c4722fac664bad20699b1b2673ea5898878d14fdd975adb052205c72ce898b", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002462000000000000000000000000000000000000000000000000000000000000000002102a95d0d38d0d6519fe5c7a77b07bf6c367099d2d3a9b6a8da36251bcc2863e20fbc008721c0bc1bcd8620ab6eaee4bd999a33d287d907ea8a3aa00fb7f58dfabd2afb54485700000000", "DISCOURAGE_EC_OPS"], + +["Multiply point by 2"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x982c493d24ce8a74e509b863a1a59926dbc7f438f0925da9122aaff17846871a", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002672000000000000000000000000000000000000000000000000000000000000000022102a95d0d38d0d6519fe5c7a77b07bf6c367099d2d3a9b6a8da36251bcc2863e20fbc2103519a934fadfca15c7fbb8b6bfb03464ee22bc594cecc6d842ce8089d99fe53718721c1e2d35660811d01bfe552a92937240da8420a1cbbb676a3df35a88686681e7fad00000000", "DISCOURAGE_EC_OPS"], + +["Extract x from 33-byte point"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xf8b034227c5cde4357d50a45c2c62329475a5c2fb4b14b4002fc0f9c17b87d0f", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002452103a8d2660f97eb8b320b3951a7adc1a32c54119bdb779287f2c87825459ce43e13be20a8d2660f97eb8b320b3951a7adc1a32c54119bdb779287f2c87825459ce43e138721c1e0c9660dc7ac5cfcdd53d7963cc7e278136398ec9a151a563ff2928ecb8f9c0500000000", "DISCOURAGE_EC_OPS"], + +["Multiply generator by scalar (empty point)"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xc534c653b0c46394875c8a377537a7fdd258d818ee1331e9ed804163d1dd8920", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a08601000000000000024620000000000000000000000000000000000000000000000000000000000000000200bc2102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee58721c04e5d0761dac697652bc9e660749f9a56cbc8dc6eb7f5fcf4efef44ee960b976200000000", "DISCOURAGE_EC_OPS"], + +["Negate point"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x48b2286b148876cba70b6e3efd0998b8ef8220362c926c6b5d74bec0e4d72add", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002462102c45ad60752c449497980924aa8f602fad3ce0414fbff83b4d7e48f3d2b1e82d5bd2103c45ad60752c449497980924aa8f602fad3ce0414fbff83b4d7e48f3d2b1e82d58721c1501665cef6fec900f205abd7261eef0151bf6f6708f0720afd93b43d70f9fa5d00000000", "DISCOURAGE_EC_OPS"], + +["Add point to itself (point doubling)"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x329376f19233d37860e6988a4ba29ebcdccd5813a97b16a85b59ab87069446a4", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002682103d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da6122103d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da612bb210365c08a6b61c8a225760df455512496a3cce0d74f597ad8d5338ca1688aa53bc88721c1b841414474a073f8baf79d1e724081b765ea3a3f87a8780bdc69483a0a7c75c400000000", "DISCOURAGE_EC_OPS"], + +["Computing a Taproot Tweak (P + tweak*G)"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0x11209b083c36057f641cc93419015e0cb44c08b0b499fd54b26c4eaad0d3afc5", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a08601000000000000026920000000000000000000000000000000000000000000000000000000000000000500bc210326c4dd2b3ed6cb114ac7981d958391f58f1d435a6800e4ba5fc4ec973d64c854bb21027e41f3468b33d03e76ed78f346c66644a7a31575dda359fe898520b9ed8245868721c19e76b08c79a7e0bb6ca20f7763ece21cf98e72efc326d26d1bf8d34c78b2c1f200000000", "DISCOURAGE_EC_OPS"], + +["Add point and its negation (infinity)"], +[[["0000000000000000000000000000000000000000000000000000000000000000", 0, "0x51 0x20 0xb213126b12c0c5bc9fea81869d4c98d8aeec2962bd79abccaf19cd8c9c5af6e7", 100000]], +"02000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0860100000000000002472103d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da6122102d5a5c6797a56d30378dba0484493302b5d8dc02dff2f550568641036796da612bb008721c0f53d4ee246500cbd5bc5010fcd5955f91040d658a2761f1700d477cfd3dae4cd00000000", "DISCOURAGE_EC_OPS"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 8b22f7570bdc..288a941d9ef3 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -238,6 +238,11 @@ BOOST_AUTO_TEST_CASE(tx_valid) // Check that flags are maximal: transaction should fail if any unset flags are set. for (auto flags_excluding_one : ExcludeIndividualFlags(verify_flags)) { + const auto excluded_flag = verify_flags & ~flags_excluding_one; + // DISCOURAGE_OP_SUCCESS and EC_OPS are never maximal by design: + // OP_SUCCESS opcodes make scripts trivially valid without DISCOURAGE_OP_SUCCESS + if (excluded_flag == SCRIPT_VERIFY_EC_OPS) continue; + if (excluded_flag == SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) continue; if (!CheckTxScripts(tx, mapprevOutScriptPubKeys, mapprevOutValues, ~flags_excluding_one, txdata, strTest, /*expect_valid=*/false)) { BOOST_ERROR("Too many flags unset: " << strTest); }