Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/build-with-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ jobs:
# is fine.
EXTRA_CONAN_OPTS=""
if [ "${{ inputs.enable-asan }}" = "true" ]; then
EXTRA_CONAN_OPTS="-o secp256k1_use_asm=False"
EXTRA_CONAN_OPTS="-o secp256k1_asm=off"
fi

# Choose build strategy based on whether we need to upload
Expand Down
182 changes: 8 additions & 174 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,23 +78,9 @@ class KthRecipe(KnuthConanFileV2):
"utxoz_compact": [True, False],
"asio_standalone": [True, False],

# secp256k1 options
"secp256k1_enable_coverage": [True, False],
"secp256k1_enable_branch_coverage": [True, False],
"secp256k1_enable_bignum": [True, False],
"secp256k1_use_asm": [True, False],
"secp256k1_enable_module_ecdh": [True, False],
"secp256k1_enable_module_multiset": [True, False],
"secp256k1_enable_module_recovery": [True, False],
"secp256k1_enable_module_schnorr": [True, False],
"secp256k1_enable_external_default_callbacks": [True, False],
"secp256k1_enable_endomorphism": [True, False],
"secp256k1_ecmult_window_size": ["ANY"],
"secp256k1_ecmult_gen_precision": [2, 4, 8],
"secp256k1_ecmult_static_precomputation": [True, False],
"secp256k1_enable_jni": [True, False],
"use_field": ["", "64bit", "32bit"],
"use_scalar": ["", "64bit", "32bit"],
# secp256k1 assembly selection (modern SECP256K1_ASM). "off" is used by
# the ASan CI build, whose field_5x52 asm otherwise exhausts registers.
"secp256k1_asm": ["auto", "off"],
}


Expand Down Expand Up @@ -129,24 +115,7 @@ class KthRecipe(KnuthConanFileV2):
"embed_utxo_bloom": False,
"utxoz_compact": False,
"asio_standalone": True,

# secp256k1 options
"secp256k1_enable_coverage": False,
"secp256k1_enable_branch_coverage": False,
"secp256k1_enable_bignum": False,
"secp256k1_use_asm": True,
"secp256k1_enable_module_ecdh": False,
"secp256k1_enable_module_multiset": True,
"secp256k1_enable_module_recovery": True,
"secp256k1_enable_module_schnorr": True,
"secp256k1_enable_external_default_callbacks": False,
"secp256k1_enable_endomorphism": True,
"secp256k1_ecmult_window_size": 15,
"secp256k1_ecmult_gen_precision": 4,
"secp256k1_ecmult_static_precomputation": True,
"secp256k1_enable_jni": False,
"use_field": "",
"use_scalar": ""
"secp256k1_asm": "auto",
}

# `data/utxo_bloom.dat` (~68 MB) lives under `data/` but is intentionally
Expand Down Expand Up @@ -224,25 +193,12 @@ def requirements(self):


def build_requirements(self):
self.tool_requires("secp256k1-precompute/1.0.0")
if self.options.tests:
self.test_requires("catch2/3.15.0")
self.test_requires("nanobench/4.3.11")

def config_options(self):
KnuthConanFileV2.config_options(self)
# Disable ecmult static precomputation for Emscripten to avoid native build issues
# if self.settings.os == "Emscripten":
# self.output.info("Setting secp256k1_ecmult_static_precomputation to False for Emscripten")
# self.options.secp256k1_ecmult_static_precomputation = False

# Disable ASM for architectures that don't support it
# Based on the logic in secp256k1's CMakeLists.txt
# Only x86_64 and arm-linux-gnueabihf are supported
arch = str(self.settings.arch)
if arch != "x86_64" and arch != "armv7":
self.output.info(f"Setting secp256k1_use_asm to False for architecture: {arch}")
self.options.secp256k1_use_asm = False

# Disable tests for WebAssembly (Catch2 incompatible with shared-memory/threads)
# and force the JSON-RPC server off: it relies on the standalone-asio I/O
Expand Down Expand Up @@ -312,32 +268,11 @@ def generate(self):
tc.variables["KTH_UTXOZ_COMPACT_MODE"] = option_on_off(self.options.utxoz_compact)
tc.variables["KTH_ASIO_STANDALONE"] = option_on_off(self.options.asio_standalone)

# Secp256k1 --------------------------------------------
tc.variables["SECP256K1_ENABLE_COVERAGE"] = option_on_off(self.options.secp256k1_enable_coverage)
tc.variables["SECP256K1_ENABLE_BRANCH_COVERAGE"] = option_on_off(self.options.secp256k1_enable_branch_coverage)
tc.variables["SECP256K1_ENABLE_BIGNUM"] = option_on_off(self.options.secp256k1_enable_bignum)
tc.variables["SECP256K1_USE_ASM"] = option_on_off(self.options.secp256k1_use_asm)
tc.variables["SECP256K1_ENABLE_MODULE_ECDH"] = option_on_off(self.options.secp256k1_enable_module_ecdh)
tc.variables["SECP256K1_ENABLE_MODULE_MULTISET"] = option_on_off(self.options.secp256k1_enable_module_multiset)
tc.variables["SECP256K1_ENABLE_MODULE_RECOVERY"] = option_on_off(self.options.secp256k1_enable_module_recovery)
tc.variables["SECP256K1_ENABLE_MODULE_SCHNORR"] = option_on_off(self.options.secp256k1_enable_module_schnorr)
tc.variables["SECP256K1_ENABLE_EXTERNAL_DEFAULT_CALLBACKS"] = option_on_off(self.options.secp256k1_enable_external_default_callbacks)
tc.variables["SECP256K1_ENABLE_ENDOMORPHISM"] = option_on_off(self.options.secp256k1_enable_endomorphism)
tc.variables["SECP256K1_ECMULT_WINDOW_SIZE"] = self.options.secp256k1_ecmult_window_size
tc.variables["SECP256K1_ECMULT_GEN_PRECISION"] = self.options.secp256k1_ecmult_gen_precision
tc.variables["SECP256K1_ECMULT_STATIC_PRECOMPUTATION"] = option_on_off(self.options.secp256k1_ecmult_static_precomputation)
tc.variables["SECP256K1_ENABLE_JNI"] = option_on_off(self.options.secp256k1_enable_jni)

if self.options.use_field:
tc.variables["USE_FIELD"] = self.options.use_field

if self.options.use_scalar:
tc.variables["USE_SCALAR"] = self.options.use_scalar

# Enable compatibility with the tests - unify all test variables
# secp256k1 is vendored (bitcoin-core/secp256k1) and configured directly
# in src/CMakeLists.txt; the modern build self-generates its precomputed
# tables, so only the assembly selection is exposed here.
tc.variables["SECP256K1_ASM"] = "OFF" if self.options.secp256k1_asm == "off" else "AUTO"
tc.variables["ENABLE_TEST"] = option_on_off(self.options.tests)
tc.variables["SECP256K1_BUILD_TEST"] = option_on_off(self.options.tests)
# Secp256k1 -------------------------------------------- (END)

tc.variables["CURRENCY"] = self.options.currency
tc.variables["KTH_MEMPOOL_BACKEND"] = self.options.mempool_backend
Expand All @@ -352,9 +287,6 @@ def generate(self):
tc.variables["KTH_VERSION"] = kth_version
self.output.info(f"Knuth Node version: {kth_version}")

# Generate secp256k1 precomputed tables before CMake generation
self._generate_secp256k1_tables()

tc.generate()

deps = CMakeDeps(self)
Expand Down Expand Up @@ -412,8 +344,6 @@ def package_info(self):
self.cpp_info.components["secp256k1"].libs = ["secp256k1"]
self.cpp_info.components["secp256k1"].names["cmake_find_package"] = "secp256k1"
self.cpp_info.components["secp256k1"].names["cmake_find_package_multi"] = "secp256k1"
# secp256k1 requires GMP for big number operations
self.cpp_info.components["secp256k1"].requires = ["gmp::gmp"]

# Core infrastructure component
self.cpp_info.components["infrastructure"].libs = ["infrastructure"]
Expand Down Expand Up @@ -562,99 +492,3 @@ def package_info(self):
self.cpp_info.components["kth"].requires = main_requires
self.cpp_info.components["kth"].names["cmake_find_package"] = "kth"
self.cpp_info.components["kth"].names["cmake_find_package_multi"] = "kth"

def _generate_secp256k1_tables(self):
"""Generate secp256k1 precomputed tables using external tool if needed"""
if not self.options.secp256k1_ecmult_static_precomputation:
return

import platform
import subprocess
from pathlib import Path

# Path to secp256k1 source directory and output file
secp256k1_src = Path(self.source_folder) / "src" / "secp256k1" / "src"
output_file = secp256k1_src / "ecmult_static_context.h"

# Skip if file already exists and is newer than source
gen_context_src = secp256k1_src / "gen_context.c"
if output_file.exists() and gen_context_src.exists():
if output_file.stat().st_mtime > gen_context_src.stat().st_mtime:
self.output.info("secp256k1 precomputed tables are up to date")
return

self.output.info("Generating secp256k1 precomputed tables...")

try:
# Get the gen_context executable from the tool package
# Find the executable in the dependencies
gen_context_exe = None
for req, dep_info in self.dependencies.items():
if req.ref.name == "secp256k1-precompute":
# Get the bin folder from the dependency
bin_folder = Path(dep_info.cpp_info.bindirs[0]) if dep_info.cpp_info.bindirs else Path("bin")
gen_context_exe = Path(dep_info.package_folder) / bin_folder / "gen_context"
if platform.system() == "Windows":
gen_context_exe = gen_context_exe.with_suffix(".exe")
break

if not gen_context_exe or not gen_context_exe.exists():
raise Exception(f"gen_context executable not found: {gen_context_exe}")

# Create the secp256k1 src directory if it doesn't exist
secp256k1_src.mkdir(parents=True, exist_ok=True)

# Create a temporary directory structure that gen_context expects
# gen_context writes to "src/ecmult_static_context.h" relative to its working directory
temp_dir = secp256k1_src.parent # This should be src/secp256k1
src_subdir = temp_dir / "src"
src_subdir.mkdir(exist_ok=True)

# Run the generator tool from secp256k1 directory so it can write to src/ecmult_static_context.h
self.output.info(f"gen_context_exe: {gen_context_exe}")
self.output.info(f"gen_context_exe exists: {gen_context_exe.exists()}")

# Check binary type (static vs dynamic)
import shutil
file_cmd = shutil.which("file")
ldd_cmd = shutil.which("ldd")

if file_cmd:
file_result = subprocess.run([file_cmd, str(gen_context_exe)], capture_output=True, text=True)
self.output.info(f"Binary type: {file_result.stdout.strip()}")
else:
self.output.warning("'file' command not found")

if ldd_cmd:
ldd_result = subprocess.run([ldd_cmd, str(gen_context_exe)], capture_output=True, text=True)
self.output.info(f"ldd output: {ldd_result.stdout.strip()}")
if ldd_result.stderr:
self.output.warning(f"ldd stderr: {ldd_result.stderr.strip()}")
else:
self.output.warning("'ldd' command not found")

result = subprocess.run(
[str(gen_context_exe),
str(self.options.secp256k1_ecmult_window_size),
str(self.options.secp256k1_ecmult_gen_precision)],
cwd=str(temp_dir),
capture_output=True,
text=True
)

if result.returncode != 0:
self.output.error(f"Failed to generate secp256k1 tables: {result.stderr}")
raise Exception("secp256k1 table generation failed")

if output_file.exists():
self.output.info(f"Successfully generated {output_file}")
else:
raise Exception("secp256k1 table generation completed but output file not found")

except Exception as e:
self.output.error(f"Error generating secp256k1 tables: {e}")
# Fall back to disabling precomputation if tool fails
self.output.warning("Disabling secp256k1 static precomputation due to generation failure")
# We could modify the CMake variable here, but it's already been set
# For now, just let it fail and require the user to disable it manually
raise
2 changes: 1 addition & 1 deletion scripts/rebuild-asan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ cmake --preset conan-relwithdebinfo \
-DCMAKE_C_FLAGS="${SANITIZER_FLAGS}" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address" \
-DSECP256K1_USE_ASM=OFF \
-DSECP256K1_ASM=OFF \
-DENABLE_TSAN_TESTS=OFF

if [ $? -ne 0 ]; then
Expand Down
40 changes: 40 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,47 @@

cmake_minimum_required(VERSION 3.15)

# secp256k1 (modernized upstream libsecp256k1 + the BCH Schnorr module). Configure
# its options before adding it, since the vendored tree is kept pristine. Static
# only, no upstream tests/benches, and the modules Knuth needs: ECDSA recovery and
# the BCH Schnorr module for consensus, plus ECDH / schnorrsig / ellswift for the
# Stratum V2 Noise transport.
set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "" FORCE)
set(SECP256K1_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE)
set(SECP256K1_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(SECP256K1_BUILD_EXHAUSTIVE_TESTS OFF CACHE BOOL "" FORCE)
set(SECP256K1_BUILD_CTIME_TESTS OFF CACHE BOOL "" FORCE)
set(SECP256K1_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(SECP256K1_INSTALL OFF CACHE BOOL "" FORCE)
set(SECP256K1_ENABLE_MODULE_ECDH ON CACHE BOOL "" FORCE)
set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE BOOL "" FORCE)
set(SECP256K1_ENABLE_MODULE_EXTRAKEYS ON CACHE BOOL "" FORCE)
set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON CACHE BOOL "" FORCE)
set(SECP256K1_ENABLE_MODULE_ELLSWIFT ON CACHE BOOL "" FORCE)
set(SECP256K1_ENABLE_MODULE_MUSIG OFF CACHE BOOL "" FORCE)
set(SECP256K1_ENABLE_MODULE_SCHNORR_BCH ON CACHE BOOL "" FORCE)

add_subdirectory(secp256k1)

# Knuth links this alias; upstream exports the bare `secp256k1` target.
if(NOT TARGET secp256k1::secp256k1)
add_library(secp256k1::secp256k1 ALIAS secp256k1)
endif()

# Put secp256k1 in an installed export set (as the previous vendored build did),
# so the modules that link it (infrastructure, ...) can export their own targets.
# Upstream's own install is disabled (SECP256K1_INSTALL OFF); this is Knuth-side.
install(TARGETS secp256k1
EXPORT secp256k1-targets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
install(EXPORT secp256k1-targets
FILE secp256k1-targets.cmake
NAMESPACE secp256k1::
DESTINATION lib/cmake/secp256k1
)
add_subdirectory(infrastructure)
add_subdirectory(domain)

Expand Down
4 changes: 2 additions & 2 deletions src/domain/test/chain/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ TEST_CASE("script create endorsement - single input single output", "[script]")
auto const& out = out_result.value();
auto const result2 = encode_base16(out);
REQUIRE( ! result2.empty());
auto const expected = "304402200245ea46be39d72fed03c899aabc446b3c9baf93f57c2b382757856c3209854b0220795946074804a08c0053116eafe851c1a37b24414199afecf286f1eb4d82167801";
auto const expected = "3045022100e428d3cc67a724cb6cfe8634aa299e58f189d9c46c02641e936c40cc16c7e8ed0220083949910fe999c21734a1f33e42fca15fb463ea2e08f0a1bccd952aacaadbb801";
REQUIRE(result2 == expected);
}

Expand All @@ -1020,7 +1020,7 @@ TEST_CASE("script create endorsement - single input no output", "[script]") {
auto const& out = out_result.value();
auto const result2 = encode_base16(out);
REQUIRE( ! result2.empty());
auto const expected = "304402202d32085880e02b7f58a23db8a01eebfe105b6efda19e426960148d152ae67c76022028868ba8d97a4983252b247ae7f3203106c691a6ff83cc0f9b11289115ce4f3801";
auto const expected = "3045022100ba57820be5f0b93a0d5b880fbf2a86f819d959ecc24dc31b6b2d4f6ed286f253022071ccd021d540868ee10ca7634f4d270dfac7aea0d5912cf2b104111ac9bc756b01";
REQUIRE(result2 == expected);
}

Expand Down
8 changes: 4 additions & 4 deletions src/domain/test/wallet/message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ using namespace kth::domain::wallet;

// $ bx base16-encode "Satoshi" | bx sha256
constexpr auto secret = "002688cc350a5333a87fa622eacec626c3d1c0ebf9f3793de3885fa254d7e393"_base16;
constexpr auto signature_compressed = "1fa7b234dc5168e6ee3cab90c9cb5517dc851d18d5ac6e0b183daa9ed4aec8a1e752982ff04d0dce0f80d1d20ef68d31aeb000aca1313b9ca72c0acb1cea555ea4"_base16;
constexpr auto signature_uncompressed = "1c4f4780ab587f0a9011e2f5543acfb8628710a5c1edc6937f62a524bdbea65a9462e8f8ea2767b917382182d5fa498c2790b18bc4dbd2579dcc41703c880f6c3b"_base16;
constexpr auto signature_compressed = "20c0ae26619db18abd1e8a84d005bafd336512eda7207cf7f4f6c36c9614ed6bcf531a954929ddc0a86578f4d28a26e19b676c890a49881d6f25e393befd6d1682"_base16;
constexpr auto signature_uncompressed = "1c3484d71301fbdd9eec713894add25867663d9a91d637682f09179a211d16a1f26068178de890a0117df61c436e9062f87ae1790579829caae2911833ba9e35b0"_base16;

// WIF keys also used in WIF test vectors.
constexpr auto wif_compressed_str = "L1WepftUBemj6H4XQovkiW1ARVjxMqaw4oj2kmkYqdG1xTnBcHfC";
constexpr auto wif_uncompressed_str = "5JngqQmHagNTknnCshzVUysLMWAjT23FWs1TgNU5wyFH5SB3hrP";
constexpr auto signature_wif_compressed = "1f667668be93a0be1f3cbb086a39326f3c82b2e6ef3fb98e370fe499e3a822111d23d82f01a4a6b1b9c6cfb9b2f01c4dde5e8a16b19b0be7d7452b6f8b29bc095d"_base16;
constexpr auto signature_wif_uncompressed = "1b1b6c97843dcea3f7b5557492c4da6bad50917be4d8e2cbde47e580e95abaff653f71ac8b796705811fc0e7f74492c3614f6ec0797146607fda4ce4252627c738"_base16;
constexpr auto signature_wif_compressed = "20813288c5d9e3a56a297758df28bec5ffe4ceb107ac66f1d215c156ecb7845ca65efb7a84a5267edc77538479ccb01efdb006837d35e246b2cacae22acb4c6e46"_base16;
constexpr auto signature_wif_uncompressed = "1b25c35d61aa2ff5353efc36d747ed3ef179bc0f5b3d1c60f0617006c9015a0d8271da05103b987d1c26a2ecb053a56ce885805cbacefa230e69bfe18727ea4a04"_base16;

// Generated using Electrum and above SECRET (compressed):
constexpr auto electrum_signature = "1f1429ddc5e03888411065e4b36eec7de4901d580d51e6209798b9c06fdd39461a4884679f35d1e8d7321fe01f3401ed916732383f6b5f8a688ea9ae4321fbf4ae"; // for decode_base16
Expand Down
Loading
Loading