Files
zeekayandHanzo Dev b00f4bfea2 Bring the primitive seam back into the tree
chains/cpp/core and quantumvm asked for LUX_GPU_DIR and stopped with a
fatal error when nothing set it, so the public P, X and Q builds could
not configure at all for anyone outside this machine. The seam is an ABI
of four symbols over a complete CPU backend, not a kernel, so it belongs
beside the chains defined over it; the kernels stay private in
lux-gpu/gpu and are still only ever dlopen'd.

The D and F ports move with it, to lux-cpp/chains and lux-rs/chains,
both public. luxfi/compute held nothing its licence did not already
cover, being the same BSD-3-Clause as this repository's.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 21:42:43 -07:00

257 lines
12 KiB
CMake

# Copyright (C) 2026, Lux Industries, Inc. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause-Eco
#
# quantumvm — the Q-chain, in C++.
#
# Four reused trees, found where they actually sit, matched on a FILE rather
# than a directory name so a same-named sibling cannot be mistaken for one:
#
# luxcpp/blst the BLS12-381 backend a validator signature is checked on
# luxcpp/pqclean the ML-DSA (FIPS 204) reference body
# luxcpp/crypto the first-party ML-DSA surface over it, and SHA-512
# lux-cpp/node the VM seam this chain is registered through, and the
# lux-cpp/consensus id type that seam is written in
#
# Every one of them is REQUIRED. A Q-chain that cannot check a post-quantum
# signature is not a Q-chain, and a chain that cannot check a validator's BLS
# signature finalizes on arithmetic rather than cryptography — so a build that
# cannot do either does not produce this chain at all. There is no mode in which
# a signature check is skipped.
cmake_minimum_required(VERSION 3.20)
project(lux_quantumvm LANGUAGES C CXX ASM)
# The wire this chain speaks comes from the published SDK, not from here.
include(${CMAKE_CURRENT_SOURCE_DIR}/../zap.cmake)
# The primitive seam. The Q-chain identifies a transaction and a UTXO by
# SHA-256, and it asks for that hash the same way every other chain here does —
# see gpu/README.md. Carrying its own copy would be a third implementation of
# one hash in one C++ tree.
# The seam is gpu/cpp in this repository. LUX_GPU_DIR overrides that, for a
# tree that keeps it elsewhere.
if(NOT LUX_GPU_DIR)
set(LUX_GPU_DIR "$ENV{LUX_GPU_DIR}")
endif()
if(NOT LUX_GPU_DIR)
set(LUX_GPU_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../gpu/cpp")
endif()
add_subdirectory(${LUX_GPU_DIR} ${CMAKE_CURRENT_BINARY_DIR}/lux_gpu)
# The wire's accessors: schema/wire.zap in, include/lux/quantumvm/gen out.
zap_schema(wire-schema
${CMAKE_CURRENT_SOURCE_DIR}/schema/wire.zap
${CMAKE_CURRENT_SOURCE_DIR}/include/lux/quantumvm/gen)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(QUANTUMVM_SANITIZE "" CACHE STRING "Sanitizer for quantumvm code only (e.g. address,undefined)")
# find_checkout(VAR MARKER candidate...) — first candidate holding MARKER wins.
function(find_checkout out marker)
foreach(_root ${ARGN})
get_filename_component(_abs "${_root}" ABSOLUTE)
if(EXISTS "${_abs}/${marker}")
set(${out} "${_abs}" PARENT_SCOPE)
return()
endif()
endforeach()
set(${out} "${out}-NOTFOUND" PARENT_SCOPE)
endfunction()
# node2 sits at ~/work/lux/node2; this file at chains/cpp/quantumvm. The C++
# node and its consensus checkout live one level up from the lux checkout, under
# lux-cpp, and blst/crypto/pqclean under luxcpp.
set(_up "${CMAKE_CURRENT_SOURCE_DIR}/../../..")
find_checkout(_luxcpp "blst/bindings/blst.h"
"${_up}/../../luxcpp" "${_up}/../luxcpp" "${_up}/.." "$ENV{HOME}/work/luxcpp")
find_checkout(_node "include/lux/node/vm.hpp"
"${NODE_DIR}" "${_up}/../../lux-cpp/node" "${_up}/../lux-cpp/node" "${_up}/../node"
"$ENV{HOME}/work/lux-cpp/node")
find_checkout(_consensus "include/lux/consensus/quorum_cert_engine.hpp"
"${CONSENSUS_DIR}" "${_up}/../../lux-cpp/consensus" "${_up}/../../luxcpp/consensus"
"${_up}/../lux-cpp/consensus" "${_up}/../consensus" "$ENV{HOME}/work/lux-cpp/consensus"
"$ENV{HOME}/work/luxcpp/consensus")
set(LUXCPP_ROOT "${_luxcpp}" CACHE PATH "luxcpp checkout root (blst, crypto, pqclean)")
set(NODE_DIR "${_node}" CACHE PATH "lux-cpp/node checkout (the VM seam)")
set(CONSENSUS_DIR "${_consensus}" CACHE PATH "lux-cpp/consensus checkout (the id type)")
set(BLST_DIR "${LUXCPP_ROOT}/blst" CACHE PATH "blst source tree")
set(CRYPTO_DIR "${LUXCPP_ROOT}/crypto" CACHE PATH "luxcpp/crypto checkout (ML-DSA, SHA-512)")
find_checkout(_pqclean "crypto_sign/ml-dsa-65/clean/sign.c"
"${PQCLEAN_DIR}" "${LUXCPP_ROOT}/pqclean" "${CRYPTO_DIR}/build/_deps/luxcpp_pqclean-src")
set(PQCLEAN_DIR "${_pqclean}" CACHE PATH "luxcpp/pqclean checkout (the ML-DSA body)")
foreach(_p "${BLST_DIR}/bindings/blst.h" "${BLST_DIR}/src/server.c" "${BLST_DIR}/build/assembly.S")
if(NOT EXISTS "${_p}")
message(FATAL_ERROR "quantumvm: blst is required and was not found: ${_p}\n"
"set -DLUXCPP_ROOT=/path/to/luxcpp")
endif()
endforeach()
foreach(_p "${CRYPTO_DIR}/mldsa/cpp/mldsa.cpp" "${CRYPTO_DIR}/mldsa/cpp/mldsa.hpp"
"${CRYPTO_DIR}/ed25519/cpp/sha512_minimal.hpp")
if(NOT EXISTS "${_p}")
message(FATAL_ERROR "quantumvm: the first-party crypto is required and was not found: ${_p}\n"
"set -DCRYPTO_DIR=/path/to/luxcpp/crypto")
endif()
endforeach()
if(NOT PQCLEAN_DIR OR NOT EXISTS "${PQCLEAN_DIR}/common/fips202.c")
message(FATAL_ERROR "quantumvm: the ML-DSA body (luxcpp/pqclean) is required and was not found.\n"
" PQCLEAN_DIR=${PQCLEAN_DIR}\n"
"set -DPQCLEAN_DIR=/path/to/luxcpp/pqclean")
endif()
# blst ISA selection, mirroring blst/build.sh: without -D__ADX__ every field
# multiplication takes the slower generic path on a host that has adx.
set(BLST_ISA "auto" CACHE STRING "blst ISA: auto | adx | portable | generic")
set(BLST_ISA_FLAGS "")
if(BLST_ISA STREQUAL "portable")
set(BLST_ISA_FLAGS -D__BLST_PORTABLE__)
elseif(BLST_ISA STREQUAL "adx")
set(BLST_ISA_FLAGS -D__ADX__)
elseif(BLST_ISA STREQUAL "auto")
if(EXISTS /proc/cpuinfo)
file(READ /proc/cpuinfo _cpuinfo)
if(_cpuinfo MATCHES "[ \t]adx[ \t\n]")
set(BLST_ISA_FLAGS -D__ADX__)
endif()
endif()
endif()
if(NOT TARGET blst)
add_library(blst STATIC "${BLST_DIR}/src/server.c" "${BLST_DIR}/build/assembly.S")
target_compile_options(blst PRIVATE -O2 -fno-builtin ${BLST_ISA_FLAGS})
target_include_directories(blst PUBLIC "${BLST_DIR}/bindings")
endif()
# ML-DSA. The three parameter sets are compiled as SEPARATE object libraries,
# each with only its own directory on the include path: every set has a
# params.h of its own and they must not see each other's. The SHAKE body and
# the entropy hook are compiled ONCE and shared, because their symbols are not
# namespaced and three copies would not link.
if(NOT TARGET lux_qvm_mldsa)
add_library(lux_qvm_mldsa_common OBJECT
"${PQCLEAN_DIR}/common/fips202.c" "${PQCLEAN_DIR}/common/randombytes.c")
target_include_directories(lux_qvm_mldsa_common PRIVATE "${PQCLEAN_DIR}/common")
set_target_properties(lux_qvm_mldsa_common PROPERTIES C_STANDARD 99)
foreach(_set 44 65 87)
file(GLOB _srcs "${PQCLEAN_DIR}/crypto_sign/ml-dsa-${_set}/clean/*.c")
add_library(lux_qvm_mldsa_${_set} OBJECT ${_srcs})
target_include_directories(lux_qvm_mldsa_${_set} PRIVATE
"${PQCLEAN_DIR}/crypto_sign/ml-dsa-${_set}/clean" "${PQCLEAN_DIR}/common")
set_target_properties(lux_qvm_mldsa_${_set} PROPERTIES C_STANDARD 99)
endforeach()
add_library(lux_qvm_mldsa STATIC "${CRYPTO_DIR}/mldsa/cpp/mldsa.cpp"
$<TARGET_OBJECTS:lux_qvm_mldsa_common>
$<TARGET_OBJECTS:lux_qvm_mldsa_44>
$<TARGET_OBJECTS:lux_qvm_mldsa_65>
$<TARGET_OBJECTS:lux_qvm_mldsa_87>)
target_include_directories(lux_qvm_mldsa PUBLIC
"${CRYPTO_DIR}/mldsa/cpp" "${PQCLEAN_DIR}/crypto_sign")
target_compile_options(lux_qvm_mldsa PRIVATE -O2)
endif()
add_library(lux_quantumvm STATIC
src/error.cpp
src/config.cpp
src/signer.cpp
src/wire.cpp
src/transaction.cpp
src/block.cpp
src/store.cpp
src/quasar.cpp
src/vm.cpp
src/service.cpp)
target_include_directories(lux_quantumvm PUBLIC include "${CRYPTO_DIR}/ed25519/cpp")
target_link_libraries(lux_quantumvm PUBLIC zap::zap blst lux_qvm_mldsa lux_gpu)
target_compile_options(lux_quantumvm PRIVATE -Wall -Wextra)
find_package(Threads REQUIRED)
target_link_libraries(lux_quantumvm PUBLIC Threads::Threads)
# The VM seam is header-only; a chain that registers through it needs both the
# node's include tree and the consensus id it is written in.
if(NODE_DIR AND CONSENSUS_DIR)
target_include_directories(lux_quantumvm PUBLIC "${NODE_DIR}/include" "${CONSENSUS_DIR}/include")
else()
message(FATAL_ERROR
"quantumvm: the node VM seam was not found.\n"
" NODE_DIR=${NODE_DIR}\n CONSENSUS_DIR=${CONSENSUS_DIR}\n"
"set -DNODE_DIR=/path/to/lux-cpp/node -DCONSENSUS_DIR=/path/to/lux-cpp/consensus")
endif()
if(QUANTUMVM_SANITIZE)
target_compile_options(lux_quantumvm PRIVATE -fsanitize=${QUANTUMVM_SANITIZE} -fno-omit-frame-pointer)
target_link_options(lux_quantumvm PUBLIC -fsanitize=${QUANTUMVM_SANITIZE})
endif()
option(QUANTUMVM_BUILD_TESTS "Build the ported Go test suite" ON)
if(QUANTUMVM_BUILD_TESTS)
enable_testing()
add_library(qvm_test_main STATIC test/harness.cpp)
target_include_directories(qvm_test_main PUBLIC test include)
target_link_libraries(qvm_test_main PUBLIC lux_quantumvm)
foreach(_t config signer wire store transaction block genesis vm quasar edges parse service)
add_executable(qvm_${_t}_test test/${_t}_test.cpp)
target_link_libraries(qvm_${_t}_test PRIVATE qvm_test_main)
target_compile_options(qvm_${_t}_test PRIVATE -Wall -Wextra)
add_test(NAME qvm_${_t} COMMAND qvm_${_t}_test)
endforeach()
# The differential evaluator `make chains` runs: it reads the shared corpus
# and PRINTS this chain's answers for every Q vector, and says nothing about
# whether they are right — deciding that needs every implementation's answer,
# which is the runner's job.
#
# Not a test, and built unconditionally below rather than under this block, so
# a chain that stopped answering is a build failure rather than a silently
# missing voice.
# The OLDER evaluator, against the frozen JSON corpus. It is still here
# because chains/rust reads that file too; when Rust moves to vectors.tsv both
# it and the JSON go, and there is one corpus again.
#
# It is registered as a TEST as well, because the corpus carries Go's expected
# id for every vector: an id this implementation derives differently is a
# failure here rather than a line for a human to notice.
add_executable(qvm_differential_test test/differential.cpp)
target_link_libraries(qvm_differential_test PRIVATE lux_quantumvm)
target_include_directories(qvm_differential_test PRIVATE test)
target_compile_options(qvm_differential_test PRIVATE -Wall -Wextra)
add_test(NAME qvm_differential COMMAND qvm_differential_test)
# Where the corpus is, said once. The evaluator also searches upward from the
# working directory and takes NODE2_CORPUS_PATH, so this only spares ctest the
# search; a corpus that is not there is a failure, never a silent pass.
set(QVM_CORPUS "${CMAKE_CURRENT_SOURCE_DIR}/../../../conformance/corpus/chain_differential.json")
if(EXISTS "${QVM_CORPUS}")
set_tests_properties(qvm_differential PROPERTIES ENVIRONMENT "NODE2_CORPUS_PATH=${QVM_CORPUS}")
endif()
endif()
# ── the differential evaluator ───────────────────────────────────────────────
#
# Outside the tests block: a differential that quietly lost one of its voices
# would report agreement among whoever was left, so this is built whether or
# not tests are.
add_executable(qvm_conformance test/conformance.cpp)
target_link_libraries(qvm_conformance PRIVATE lux_quantumvm)
# The corpus format, the verdict words and the error-word table are shared with
# every other C++ evaluator, because a table that differed between two of them
# reported its own difference as a chain's.
target_include_directories(qvm_conformance PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../conformance/include)
target_compile_options(qvm_conformance PRIVATE -Wall -Wextra)