Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/bench/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ add_executable(bench_bitcoin
mempool_eviction.cpp
mempool_stress.cpp
merkle_root.cpp
op_checkcontractverify.cpp
parse_hex.cpp
peer_eviction.cpp
poly1305.cpp
Expand Down
44 changes: 44 additions & 0 deletions src/bench/op_checkcontractverify.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) 2025 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 <bench/bench.h>
#include <pubkey.h>
#include <script/interpreter.h>
#include <script/script.h>

typedef std::vector<unsigned char> valtype;

static void DoubleTweak(benchmark::Bench& bench)
{
const XOnlyPubKey naked_key{ParseHex(
"50929B74C1A04954B78B4B6035E97A5E078A5A0F28EC96D547BFEE9ACE803AC0")};

const std::vector<unsigned char> data(MAX_SCRIPT_ELEMENT_SIZE, 0x42);

const uint256 merkle_root{ParseHex(
"E79925EF2A0DF8D2C0C08BFA7474D016384C5FC64C51EFC5E950AA1A97B88B61")};

const XOnlyPubKey result_key{ParseHex(
"F2DFC709E477C8D73A3625BC05C370B196703E466EB8655977FF69807EEF8F0E")};

bench.unit("ccv_doubletweak").run([&] {
bool ret = result_key.CheckDoubleTweak(naked_key, data, &merkle_root);
assert(ret);
});

const XOnlyPubKey schnorr_key{ParseHex(
"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9")};
const valtype msg{ParseHex(
"0000000000000000000000000000000000000000000000000000000000000000")};
const valtype sig{ParseHex(
"E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0")};

bench.unit("verify").run("schnorr-good-verify", [&] {
auto ret = schnorr_key.VerifySchnorr(uint256(msg), sig);
assert(ret);
});
}


BENCHMARK(DoubleTweak, benchmark::PriorityLevel::HIGH);
9 changes: 9 additions & 0 deletions src/binana/checkcontractverify.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"binana": [2025, 443, 0],
"deployment": "CHECKCONTRACTVERIFY",
"scriptverify": true,
"scriptverify_discourage": true,
"opcodes": {
"CHECKCONTRACTVERIFY": "0xbb"
}
}
34 changes: 34 additions & 0 deletions src/pubkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,40 @@ std::optional<std::pair<XOnlyPubKey, bool>> XOnlyPubKey::CreateTapTweak(const ui
return ret;
}

bool XOnlyPubKey::CheckDoubleTweak(const XOnlyPubKey& naked_key, const std::vector<unsigned char>& data, const uint256* merkle_root) const
{
int parity;
secp256k1_xonly_pubkey internal_xonly;
if (data.empty()) {
// No data tweak; the internal key is the naked key
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &internal_xonly, naked_key.data())) return false;
} else {
// Compute the sha256 of naked_key || data
uint256 data_tweak = (HashWriter{} << naked_key << MakeUCharSpan(data)).GetSHA256();
secp256k1_xonly_pubkey naked_key_parsed;
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &naked_key_parsed, naked_key.data())) return false;
secp256k1_pubkey internal;
if (!secp256k1_xonly_pubkey_tweak_add(secp256k1_context_static, &internal, &naked_key_parsed, data_tweak.data())) return false;
if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_static, &internal_xonly, &parity, &internal)) return false;
}
secp256k1_xonly_pubkey expected_xonly;
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &expected_xonly, m_keydata.data())) return false;
if (merkle_root != nullptr) {
// Compute the taptweak based on merkle_root
unsigned char pubkey_bytes[32];
secp256k1_xonly_pubkey_serialize(secp256k1_context_static, pubkey_bytes, &internal_xonly);
XOnlyPubKey internal_key = XOnlyPubKey(pubkey_bytes);
uint256 tweak = internal_key.ComputeTapTweakHash(merkle_root);
secp256k1_pubkey result;
if (!secp256k1_xonly_pubkey_tweak_add(secp256k1_context_static, &result, &internal_xonly, tweak.begin())) return false;
secp256k1_xonly_pubkey result_xonly;
if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_static, &result_xonly, &parity, &result)) return false;
return secp256k1_xonly_pubkey_cmp(secp256k1_context_static, &result_xonly, &expected_xonly) == 0;
} else {
// If merkle_root is nullptr, compare internal_xonly with expected_xonly
return secp256k1_xonly_pubkey_cmp(secp256k1_context_static, &internal_xonly, &expected_xonly) == 0;
}
}

bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
if (!IsValid())
Expand Down
6 changes: 6 additions & 0 deletions src/pubkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ class XOnlyPubKey
/** Construct a Taproot tweaked output point with this point as internal key. */
std::optional<std::pair<XOnlyPubKey, bool>> CreateTapTweak(const uint256* merkle_root) const;

/** Verify that this key is obtained from the x-only pubkey `naked` after applying in sequence:
* - the tweak with `data` (this tweak is skipped if `data` is empty);
* - the taptweak with `merkle_root` (unless it's null).
*/
bool CheckDoubleTweak(const XOnlyPubKey& naked_key, const std::vector<unsigned char>& data, const uint256* merkle_root) const;

/** Returns a list of CKeyIDs for the CPubKeys that could have been used to create this XOnlyPubKey.
* This is needed for key lookups since keys are indexed by CKeyID.
*/
Expand Down
Loading