Add CI job for producing a static bare metal binary
What changed, and why it matters
This commit adds a new continuous-integration (CI) build job that compiles a small, non-runnable test binary for a RISC-V bare-metal target. It does not change any production wallet, node, or consensus code, and it does not introduce a security vulnerability.
No security action required. Treat as routine CI/infrastructure change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces a CI environment script, a base-install step that clones and builds the riscv-gnu-toolchain, a test-script hook, and a custom linker script (link-riscv.sh) that builds a tiny ELF using libbitcoin_consensus, libbitcoin_crypto, and libsecp256k1 for a riscv32-unknown-elf target. It disables all normal Bitcoin binaries, tests, wallet, and IPC. The resulting binary is only for build/CI validation, not for deployment.
Changed components
ci/test/00_setup_env_riscv_bare_cross.shci/test/01_base_install.shci/test/03_test_script.shci/test/link-riscv.shInspect captured patch +123 / −0
diff --git a/ci/test/00_setup_env_riscv_bare_cross.sh b/ci/test/00_setup_env_riscv_bare_cross.sh
new file mode 100755
index 00000000..b6843408
--- /dev/null
+++ b/ci/test/00_setup_env_riscv_bare_cross.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+export LC_ALL=C.UTF-8
+
+export CONTAINER_NAME=ci_native_riscv_bare
+
+export GOAL="bitcoin_consensus bitcoin_crypto secp256k1"
+export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04"
+export HOST="riscv32-unknown-elf-gcc"
+export PACKAGES="autoconf automake autotools-dev curl python3 python3-pip libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev ninja-build git cmake libglib2.0-dev libslirp-dev"
+export BITCOIN_CONFIG="-DCMAKE_C_COMPILER=/opt/riscv-ilp32/bin/riscv32-unknown-elf-gcc \
+ -DCMAKE_CXX_COMPILER=/opt/riscv-ilp32/bin/riscv32-unknown-elf-g++ \
+ -DBUILD_KERNEL_LIB=OFF \
+ -DBUILD_UTIL_CHAINSTATE=OFF \
+ -DBUILD_TESTS=OFF \
+ -DBUILD_BENCH=OFF \
+ -DBUILD_FUZZ_BINARY=OFF \
+ -DBUILD_DAEMON=OFF \
+ -DBUILD_TX=OFF \
+ -DBUILD_UTIL=OFF \
+ -DBUILD_CLI=OFF \
+ -DBUILD_BITCOIN_BIN=OFF \
+ -DENABLE_WALLET=OFF \
+ -DENABLE_EXTERNAL_SIGNER=OFF \
+ -DENABLE_IPC=OFF \
+ -DCMAKE_SYSTEM_NAME=Generic \
+ -DIFADDR_LINKS_WITHOUT_LIBSOCKET=ON \
+ "
+
+export BARE_METAL_RISCV="true"
+export RUN_UNIT_TESTS="false"
+export RUN_FUNCTIONAL_TESTS="false"
+export NO_DEPENDS="true"
diff --git a/ci/test/01_base_install.sh b/ci/test/01_base_install.sh
index 54dafbe8..3dee1b73 100755
--- a/ci/test/01_base_install.sh
+++ b/ci/test/01_base_install.sh
@@ -87,6 +87,14 @@ if [[ -n "${USE_INSTRUMENTED_LIBCPP}" ]]; then
rm -rf /llvm-project
fi
+if [[ ${BARE_METAL_RISCV} == "true" ]]; then
+ ${CI_RETRY_EXE} git clone --depth=1 https://github.com/riscv-collab/riscv-gnu-toolchain -b 2026.06.06 /riscv/gcc
+ ( cd /riscv/gcc;
+ ./configure --prefix=/opt/riscv-ilp32 --with-arch=rv32gc --with-abi=ilp32 --disable-gdb;
+ make "$MAKEJOBS"; )
+ rm -rf /riscv/gcc
+fi
+
if [[ "${RUN_IWYU}" == true ]]; then
${CI_RETRY_EXE} git clone --depth=1 https://github.com/include-what-you-use/include-what-you-use -b clang_"${IWYU_LLVM_V}" /include-what-you-use
pushd /include-what-you-use
diff --git a/ci/test/03_test_script.sh b/ci/test/03_test_script.sh
index c55f4430..7650ab7d 100755
--- a/ci/test/03_test_script.sh
+++ b/ci/test/03_test_script.sh
@@ -167,6 +167,11 @@ if [ -n "${CI_LIMIT_STACK_SIZE}" ]; then
ulimit -s 512
fi
+if [[ ${BARE_METAL_RISCV} == "true" ]]; then
+ export BASE_BUILD_DIR
+ "${BASE_ROOT_DIR}/ci/test/link-riscv.sh"
+fi
+
if [ -n "$USE_VALGRIND" ]; then
"${BASE_ROOT_DIR}/ci/test/wrap-valgrind.py"
fi
diff --git a/ci/test/link-riscv.sh b/ci/test/link-riscv.sh
new file mode 100755
index 00000000..93bebfa8
--- /dev/null
+++ b/ci/test/link-riscv.sh
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+export LC_ALL=C.UTF-8
+set -o errexit -o xtrace -o pipefail
+
+GCC=/opt/riscv-ilp32/bin/riscv32-unknown-elf-gcc
+GXX=/opt/riscv-ilp32/bin/riscv32-unknown-elf-g++
+CRTBEGIN=$("${GCC}" -print-file-name=crtbegin.o)
+LIBGCC=$("${GCC}" -print-libgcc-file-name)
+LIBSTDCXX=$("${GXX}" -print-file-name=libstdc++.a)
+LIBC=$("${GCC}" -print-file-name=libc.a)
+LIBM=$("${GCC}" -print-file-name=libm.a)
+
+echo -e "#include <script/script_error.h>\n int main() { return ScriptErrorString(ScriptError_t::SCRIPT_ERR_UNKNOWN_ERROR).size() > 0; }" > test.cpp
+
+"${GXX}" -I "${BASE_ROOT_DIR}"/src -g -std=c++20 -c test.cpp -o test.o
+
+# Make the binary executable on linux for testing purposes
+echo -e ".section .text
+ .global _start
+ .type _start, @function
+
+ _start:
+ .option push
+ .option norelax
+ la gp, __global_pointer$
+ .option pop
+
+ call main
+
+ # Put Exit2 system call number into the a7 register
+ li a7, 93
+ ecall" > start.s
+
+"${GCC}" -c start.s -o start.o
+
+echo -e "#include <sys/stat.h>
+ void _exit(int code) { while(1); }
+ int _sbrk(int incr) { return 0; }
+ int _write(int file, char *ptr, int len) { return 0; }
+ int _close(int file) { return -1; }
+ int _fstat(int file, struct stat *st) { st->st_mode = S_IFCHR; return 0; }
+ int _isatty(int file) { return 1; }
+ int _lseek(int file, int ptr, int dir) { return 0; }
+ int _read(int file, char *ptr, int len) { return 0; }
+ int _kill(int pid, int sig) { return -1; }
+ int _getpid(void) { return -1; }" > syscalls.c
+
+"${GCC}" -g -c syscalls.c -o syscalls.o
+
+"${GXX}" -g -std=c++20 \
+ -nostdlib \
+ "${CRTBEGIN}" \
+ test.o \
+ start.o \
+ syscalls.o \
+ -Wl,--whole-archive \
+ "${BASE_BUILD_DIR}"/lib/libbitcoin_consensus.a \
+ "${BASE_BUILD_DIR}"/lib/libbitcoin_crypto.a \
+ "${BASE_BUILD_DIR}"/src/secp256k1/lib/libsecp256k1.a \
+ -Wl,--no-whole-archive \
+ "${LIBSTDCXX}" \
+ "${LIBC}" \
+ "${LIBM}" \
+ "${LIBGCC}" \
+ -o test.elf
+
+file test.elf
+
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.