Various improvements, refactorings and simplifications from PR review
What changed, and why it matters
This commit is purely a build-system and test-harness cleanup. It refactors CMakeLists.txt, updates comments, simplifies the speculos test bridge, and removes a workaround in a test file. Nothing here changes the actual Ledger Bitcoin app that runs on the device, so it cannot directly affect user funds or device security.
No security action required. Treat as normal maintenance/refactoring commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff touches only unit-tests/CMakeLists.txt, unit-tests/README.md, unit-tests/libs/speculos_bridge.c, and unit-tests/test_crypto.c. Changes include: enabling CMAKE_EXPORT_COMPILE_COMMANDS, replacing explicit add_executable/add_test blocks with an add_unit_test helper, removing redundant gcov link flags, adding lib_standard_app/crypto_helpers.c to app_crypto instead of reimplementing bip32 helpers in the bridge, switching cx_iovec_t to the real SDK type, replacing magic error constants with CX_* macros, adding STUB_ABORT for unused crypto_helpers.c entry points, and removing the oversized cx_sha256_t storage workaround in test_crypto.c now that the test is compiled against real SDK headers. No device-side source files are modified.
Changed components
unit-tests/CMakeLists.txtunit-tests/libs/speculos_bridge.cunit-tests/test_crypto.cInspect captured patch +388 / −427
diff --git a/unit-tests/CMakeLists.txt b/unit-tests/CMakeLists.txt
index bac23dd..0627846 100644
--- a/unit-tests/CMakeLists.txt
+++ b/unit-tests/CMakeLists.txt
@@ -1,39 +1,38 @@
-cmake_minimum_required(VERSION 3.14) # FetchContent_MakeAvailable
+cmake_minimum_required(VERSION 3.14)
-if(${CMAKE_VERSION} VERSION_LESS 3.14)
- cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
-endif()
-
-# project information
project(unit_tests
VERSION 0.1
DESCRIPTION "Unit tests for Ledger Nano application"
LANGUAGES C)
-# guard against bad build-type strings
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
include(CTest)
-ENABLE_TESTING()
+enable_testing()
+
+# Emit build/compile_commands.json so editor tooling (clangd, VSCode's
+# C/C++ extension, ...) can pick up the exact compile flags / include
+# paths used for each source file. Without this, files like
+# libs/speculos_bridge.c — which depend on per-target BEFORE PRIVATE
+# include directories — show spurious "missing header" errors.
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
-# specify C standard
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED True)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -pedantic -g -O0 --coverage")
-set(GCC_COVERAGE_LINK_FLAGS "--coverage -lgcov")
-set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}")
+# --coverage on the link line already pulls in libgcov.
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
-# guard against in-source builds
-if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
- message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ")
+if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
+ message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt.")
endif()
if(NOT DEFINED ENV{BOLOS_SDK})
- message(FATAL_ERROR "BOLOS_SDK is not defined.")
+ message(FATAL_ERROR "BOLOS_SDK is not defined.")
endif()
# Speculos is used by the speculos-backed crypto tests as the pure-C
@@ -83,30 +82,22 @@ include_directories($ENV{BOLOS_SDK})
include_directories($ENV{BOLOS_SDK}/include)
include_directories($ENV{BOLOS_SDK}/lib_standard_app)
-add_executable(test_bitvector test_bitvector.c)
-add_executable(test_buffer test_buffer.c)
-add_executable(test_display_utils test_display_utils.c)
-add_executable(test_parser test_parser.c)
-add_executable(test_get_preimage test_get_preimage.c)
-add_executable(test_extract_bip32_derivation test_extract_bip32_derivation.c)
-add_executable(test_psbt_parse test_psbt_parse.c)
-add_executable(test_check_merkle_tree_sorted test_check_merkle_tree_sorted.c)
-add_executable(test_get_merkle_leaf_element test_get_merkle_leaf_element.c)
-add_executable(test_get_merkle_preimage test_get_merkle_preimage.c)
-add_executable(test_get_merkle_leaf_hash test_get_merkle_leaf_hash.c)
-add_executable(test_get_merkle_leaf_index test_get_merkle_leaf_index.c)
-add_executable(test_stream_preimage test_stream_preimage.c)
-add_executable(test_stream_merkle_leaf_element test_stream_merkle_leaf_element.c)
-add_executable(test_stream_merkleized_map_value test_stream_merkleized_map_value.c)
-add_executable(test_get_merkleized_map test_get_merkleized_map.c)
-add_executable(test_get_merkleized_map_value test_get_merkleized_map_value.c)
+# Include paths shared by every target under ../src/handler.
+set(HANDLER_INCLUDES ../src/handler ../src/handler/lib ../src/common)
+# ---------------------------------------------------------------------------
# Mock libraries
-add_library(sha256 SHARED libs/sha-256.c)
-add_library(cx_hash_mock SHARED libs/cx_hash_mock.c)
+# ---------------------------------------------------------------------------
+add_library(sha256 SHARED libs/sha-256.c)
+add_library(cx_hash_mock SHARED libs/cx_hash_mock.c)
add_library(mock_dispatcher SHARED libs/mock_dispatcher.c)
-add_library(psbt_parse SHARED libs/psbt_parse.c)
-add_library(xpub SHARED libs/xpub.c)
+add_library(psbt_parse SHARED libs/psbt_parse.c)
+add_library(xpub SHARED libs/xpub.c)
+
+target_include_directories(mock_dispatcher PRIVATE ${HANDLER_INCLUDES})
+
+target_link_libraries(cx_hash_mock PUBLIC sha256)
+target_link_libraries(mock_dispatcher PUBLIC psbt_parse)
# Vendored TOML parser (cktan/tomlc17, MIT). Used by the data-driven
# unit tests that load test vectors from a .toml file.
@@ -116,99 +107,109 @@ target_include_directories(tomlc17 PUBLIC libs)
# silence warnings on it without affecting the rest of the tree.
target_compile_options(tomlc17 PRIVATE -w)
-# App's libraries
-add_library(base58 SHARED $ENV{BOLOS_SDK}/lib_standard_app/base58.c)
-add_library(bip32 SHARED $ENV{BOLOS_SDK}/lib_standard_app/bip32.c)
-add_library(buffer SHARED $ENV{BOLOS_SDK}/lib_standard_app/buffer.c)
-add_library(buffer_ext SHARED ../src/common/buffer_ext.c)
-add_library(display_utils SHARED ../src/ui/display_utils.c)
-add_library(extract_bip32_derivation SHARED ../src/handler/sign_psbt/extract_bip32_derivation.c)
-add_library(get_merkle_leaf_hash SHARED ../src/handler/lib/get_merkle_leaf_hash.c)
-add_library(get_merkle_leaf_element SHARED ../src/handler/lib/get_merkle_leaf_element.c)
-add_library(get_merkle_preimage SHARED ../src/handler/lib/get_merkle_preimage.c)
-add_library(get_preimage SHARED ../src/handler/lib/get_preimage.c)
-add_library(check_merkle_tree_sorted SHARED ../src/handler/lib/check_merkle_tree_sorted.c)
-add_library(get_merkle_leaf_index SHARED ../src/handler/lib/get_merkle_leaf_index.c)
-add_library(merkle SHARED ../src/common/merkle.c)
-add_library(stream_merkle_leaf_element SHARED ../src/handler/lib/stream_merkle_leaf_element.c)
-add_library(stream_preimage SHARED ../src/handler/lib/stream_preimage.c)
+# ---------------------------------------------------------------------------
+# App libraries (built standalone for the non-speculos tests)
+# ---------------------------------------------------------------------------
+add_library(base58 SHARED $ENV{BOLOS_SDK}/lib_standard_app/base58.c)
+add_library(bip32 SHARED $ENV{BOLOS_SDK}/lib_standard_app/bip32.c)
+add_library(buffer SHARED $ENV{BOLOS_SDK}/lib_standard_app/buffer.c)
+add_library(read SHARED $ENV{BOLOS_SDK}/lib_standard_app/read.c)
+add_library(varint SHARED $ENV{BOLOS_SDK}/lib_standard_app/varint.c)
+add_library(write SHARED $ENV{BOLOS_SDK}/lib_standard_app/write.c)
+add_library(buffer_ext SHARED ../src/common/buffer_ext.c)
+add_library(merkle SHARED ../src/common/merkle.c)
+add_library(parser SHARED ../src/common/parser_ext.c)
+add_library(display_utils SHARED ../src/ui/display_utils.c)
+add_library(extract_bip32_derivation SHARED ../src/handler/sign_psbt/extract_bip32_derivation.c)
+add_library(check_merkle_tree_sorted SHARED ../src/handler/lib/check_merkle_tree_sorted.c)
+add_library(get_merkle_leaf_element SHARED ../src/handler/lib/get_merkle_leaf_element.c)
+add_library(get_merkle_leaf_hash SHARED ../src/handler/lib/get_merkle_leaf_hash.c)
+add_library(get_merkle_leaf_index SHARED ../src/handler/lib/get_merkle_leaf_index.c)
+add_library(get_merkle_preimage SHARED ../src/handler/lib/get_merkle_preimage.c)
+add_library(get_merkleized_map SHARED ../src/handler/lib/get_merkleized_map.c)
+add_library(get_merkleized_map_value SHARED ../src/handler/lib/get_merkleized_map_value.c)
+add_library(get_preimage SHARED ../src/handler/lib/get_preimage.c)
+add_library(stream_merkle_leaf_element SHARED ../src/handler/lib/stream_merkle_leaf_element.c)
add_library(stream_merkleized_map_value SHARED ../src/handler/lib/stream_merkleized_map_value.c)
-add_library(get_merkleized_map SHARED ../src/handler/lib/get_merkleized_map.c)
-add_library(get_merkleized_map_value SHARED ../src/handler/lib/get_merkleized_map_value.c)
-add_library(parser SHARED ../src/common/parser_ext.c)
-add_library(read SHARED $ENV{BOLOS_SDK}/lib_standard_app/read.c)
-add_library(varint SHARED $ENV{BOLOS_SDK}/lib_standard_app/varint.c)
-add_library(write SHARED $ENV{BOLOS_SDK}/lib_standard_app/write.c)
-
-# Additional include directories for handler code
-target_include_directories(extract_bip32_derivation PRIVATE ../src/handler ../src/handler/lib ../src/handler/sign_psbt ../src/common)
-target_include_directories(get_merkle_leaf_hash PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(get_preimage PRIVATE ../src/handler ../src/handler/lib)
-target_include_directories(mock_dispatcher PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(stream_merkle_leaf_element PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(stream_preimage PRIVATE ../src/handler ../src/handler/lib)
-target_include_directories(stream_merkleized_map_value PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(get_merkleized_map PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(get_merkleized_map_value PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_extract_bip32_derivation PRIVATE ../src/handler ../src/handler/lib ../src/handler/sign_psbt ../src/common)
-target_include_directories(get_merkle_leaf_element PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(get_merkle_preimage PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(check_merkle_tree_sorted PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(get_merkle_leaf_index PRIVATE ../src/handler ../src/handler/lib ../src/common)
+add_library(stream_preimage SHARED ../src/handler/lib/stream_preimage.c)
+
+target_include_directories(check_merkle_tree_sorted PRIVATE ${HANDLER_INCLUDES})
+target_include_directories(extract_bip32_derivation PRIVATE ${HANDLER_INCLUDES} ../src/handler/sign_psbt)
+target_include_directories(get_merkle_leaf_element PRIVATE ${HANDLER_INCLUDES})
+target_include_directories(get_merkle_leaf_hash PRIVATE ${HANDLER_INCLUDES})
+target_include_directories(get_merkle_leaf_index PRIVATE ${HANDLER_INCLUDES})
+target_include_directories(get_merkle_preimage PRIVATE ${HANDLER_INCLUDES})
+target_include_directories(get_merkleized_map PRIVATE ${HANDLER_INCLUDES})
+target_include_directories(get_merkleized_map_value PRIVATE ${HANDLER_INCLUDES})
+target_include_directories(get_preimage PRIVATE ../src/handler ../src/handler/lib)
+target_include_directories(stream_merkle_leaf_element PRIVATE ${HANDLER_INCLUDES})
+target_include_directories(stream_merkleized_map_value PRIVATE ${HANDLER_INCLUDES})
+target_include_directories(stream_preimage PRIVATE ../src/handler ../src/handler/lib)
+
target_link_libraries(get_merkle_leaf_index PUBLIC get_merkle_leaf_hash)
-target_include_directories(test_get_preimage PRIVATE ../src/handler ../src/handler/lib)
-target_include_directories(test_get_merkle_preimage PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_get_merkle_leaf_element PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_check_merkle_tree_sorted PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_get_merkle_leaf_hash PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_get_merkle_leaf_index PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_stream_preimage PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_stream_merkle_leaf_element PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_stream_merkleized_map_value PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_get_merkleized_map PRIVATE ../src/handler ../src/handler/lib ../src/common)
-target_include_directories(test_get_merkleized_map_value PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_link_libraries(xpub PUBLIC base58 sha256)
-# Mock libraries
-target_link_libraries(cx_hash_mock PUBLIC sha256)
-target_link_libraries(mock_dispatcher PUBLIC psbt_parse)
-target_link_libraries(xpub PUBLIC base58 sha256)
-
-# App's libraries
-target_link_libraries(test_bitvector PUBLIC cmocka gcov)
-target_link_libraries(test_buffer PUBLIC cmocka gcov buffer buffer_ext varint read write bip32)
-target_link_libraries(test_display_utils PUBLIC cmocka gcov display_utils)
-target_link_libraries(test_parser PUBLIC cmocka gcov parser buffer buffer_ext varint read write bip32)
-target_link_libraries(test_get_preimage PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_preimage)
-target_link_libraries(test_get_merkle_preimage PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkle_preimage)
-target_link_libraries(test_extract_bip32_derivation PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle extract_bip32_derivation stream_merkle_leaf_element get_merkle_leaf_hash stream_preimage psbt_parse)
-target_link_libraries(test_psbt_parse PUBLIC cmocka gcov psbt_parse)
-target_link_libraries(test_check_merkle_tree_sorted PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle check_merkle_tree_sorted get_merkle_leaf_element get_merkle_leaf_hash get_merkle_preimage stream_preimage psbt_parse)
-target_link_libraries(test_get_merkle_leaf_element PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkle_leaf_element get_merkle_leaf_hash get_merkle_preimage stream_preimage psbt_parse)
-target_link_libraries(test_get_merkle_leaf_hash PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkle_leaf_hash stream_preimage psbt_parse)
-target_link_libraries(test_get_merkle_leaf_index PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkle_leaf_hash get_merkle_leaf_index stream_preimage psbt_parse)
-target_link_libraries(test_stream_preimage PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle stream_preimage psbt_parse)
-target_link_libraries(test_stream_merkle_leaf_element PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle stream_merkle_leaf_element get_merkle_leaf_hash stream_preimage psbt_parse)
-target_link_libraries(test_stream_merkleized_map_value PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle stream_merkleized_map_value stream_merkle_leaf_element get_merkle_leaf_hash get_merkle_leaf_index stream_preimage psbt_parse)
-target_link_libraries(test_get_merkleized_map PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkleized_map check_merkle_tree_sorted get_merkle_leaf_element get_merkle_leaf_hash get_merkle_preimage stream_preimage psbt_parse)
-target_link_libraries(test_get_merkleized_map_value PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkleized_map_value get_merkle_leaf_element get_merkle_leaf_hash get_merkle_leaf_index get_merkle_preimage stream_preimage psbt_parse)
-
-add_test(test_bitvector test_bitvector)
-add_test(test_buffer test_buffer)
-add_test(test_display_utils test_display_utils)
-add_test(test_parser test_parser)
-add_test(test_get_preimage test_get_preimage)
-add_test(test_extract_bip32_derivation test_extract_bip32_derivation)
-add_test(test_psbt_parse test_psbt_parse)
-add_test(test_check_merkle_tree_sorted test_check_merkle_tree_sorted)
-add_test(test_get_merkle_leaf_element test_get_merkle_leaf_element)
-add_test(test_get_merkle_preimage test_get_merkle_preimage)
-add_test(test_get_merkle_leaf_hash test_get_merkle_leaf_hash)
-add_test(test_get_merkle_leaf_index test_get_merkle_leaf_index)
-add_test(test_stream_preimage test_stream_preimage)
-add_test(test_stream_merkle_leaf_element test_stream_merkle_leaf_element)
-add_test(test_stream_merkleized_map_value test_stream_merkleized_map_value)
-add_test(test_get_merkleized_map test_get_merkleized_map)
-add_test(test_get_merkleized_map_value test_get_merkleized_map_value)
+# ---------------------------------------------------------------------------
+# Test executables (non-speculos)
+# ---------------------------------------------------------------------------
+# Common dependency bundles, exposed as INTERFACE libraries so each test
+# only has to list its own extras.
+
+# Bare minimum every cmocka-based test needs.
+add_library(test_common INTERFACE)
+target_link_libraries(test_common INTERFACE cmocka)
+
+# Adds the mock dispatcher + SDK helpers needed by every handler/merkle
+# test. mock_dispatcher transitively pulls in psbt_parse.
+add_library(test_merkle_common INTERFACE)
+target_link_libraries(test_merkle_common INTERFACE
+ test_common
+ mock_dispatcher cx_hash_mock sha256
+ buffer buffer_ext varint read write bip32 merkle)
+target_include_directories(test_merkle_common INTERFACE ${HANDLER_INCLUDES})
+
+function(add_unit_test name)
+ cmake_parse_arguments(T "" "" "LIBS;INCLUDES" ${ARGN})
+ add_executable(${name} ${name}.c)
+ target_link_libraries(${name} PRIVATE ${T_LIBS})
+ if(T_INCLUDES)
+ target_include_directories(${name} PRIVATE ${T_INCLUDES})
+ endif()
+ add_test(NAME ${name} COMMAND ${name})
+endfunction()
+
+add_unit_test(test_bitvector LIBS test_common)
+add_unit_test(test_buffer LIBS test_common buffer buffer_ext varint read write bip32)
+add_unit_test(test_display_utils LIBS test_common display_utils)
+add_unit_test(test_parser LIBS test_common parser buffer buffer_ext varint read write bip32)
+add_unit_test(test_psbt_parse LIBS test_common psbt_parse)
+
+add_unit_test(test_get_preimage
+ LIBS test_merkle_common get_preimage
+ INCLUDES ../src/handler ../src/handler/lib)
+add_unit_test(test_get_merkle_preimage
+ LIBS test_merkle_common get_merkle_preimage)
+add_unit_test(test_extract_bip32_derivation
+ LIBS test_merkle_common extract_bip32_derivation stream_merkle_leaf_element get_merkle_leaf_hash stream_preimage
+ INCLUDES ${HANDLER_INCLUDES} ../src/handler/sign_psbt)
+add_unit_test(test_check_merkle_tree_sorted
+ LIBS test_merkle_common check_merkle_tree_sorted get_merkle_leaf_element get_merkle_leaf_hash get_merkle_preimage stream_preimage)
+add_unit_test(test_get_merkle_leaf_element
+ LIBS test_merkle_common get_merkle_leaf_element get_merkle_leaf_hash get_merkle_preimage stream_preimage)
+add_unit_test(test_get_merkle_leaf_hash
+ LIBS test_merkle_common get_merkle_leaf_hash stream_preimage)
+add_unit_test(test_get_merkle_leaf_index
+ LIBS test_merkle_common get_merkle_leaf_hash get_merkle_leaf_index stream_preimage)
+add_unit_test(test_stream_preimage
+ LIBS test_merkle_common stream_preimage)
+add_unit_test(test_stream_merkle_leaf_element
+ LIBS test_merkle_common stream_merkle_leaf_element get_merkle_leaf_hash stream_preimage)
+add_unit_test(test_stream_merkleized_map_value
+ LIBS test_merkle_common stream_merkleized_map_value stream_merkle_leaf_element get_merkle_leaf_hash get_merkle_leaf_index stream_preimage)
+add_unit_test(test_get_merkleized_map
+ LIBS test_merkle_common get_merkleized_map check_merkle_tree_sorted get_merkle_leaf_element get_merkle_leaf_hash get_merkle_preimage stream_preimage)
+add_unit_test(test_get_merkleized_map_value
+ LIBS test_merkle_common get_merkleized_map_value get_merkle_leaf_element get_merkle_leaf_hash get_merkle_leaf_index get_merkle_preimage stream_preimage)
# ---------------------------------------------------------------------------
# Speculos-backed crypto tests
@@ -269,10 +270,16 @@ if(SPECULOS AND SPECULOS_SRC)
add_library(speculos_bridge STATIC libs/speculos_bridge.c)
target_compile_definitions(speculos_bridge PRIVATE HAVE_BOLOS=1)
+ # Speculos paths come first so speculos's own definitions of cx_bn_t /
+ # cx_ecpoint_t etc. shadow the SDK ones for the types where they
+ # diverge. lib_cxng/include follows so the bridge can pick up the
+ # real cx_iovec_t (which speculos doesn't define and the mock_includes
+ # version of lcx_common.h omits).
target_include_directories(speculos_bridge BEFORE PRIVATE
${SPECULOS_SRC}/sdk
${SPECULOS_SRC}/src
${SPECULOS_SRC}/src/bolos
+ $ENV{BOLOS_SDK}/lib_cxng/include
)
target_link_libraries(speculos_bridge PUBLIC speculos_bolos OpenSSL::Crypto)
@@ -280,6 +287,42 @@ if(SPECULOS AND SPECULOS_SRC)
# per-test mock_includes) so it sees the same syscall signatures the
# speculos bridge implements. crypto.c can now be compiled this way,
# which removes the need to mock it for tests that depend on it.
+ # Compile flags shared between app_crypto and any test that needs to
+ # see the real SDK declarations (so e.g. sizeof(cx_sha256_t) matches
+ # the size app_crypto was built against, not the mock_includes one).
+ #
+ # The include paths are added with BEFORE PRIVATE so they take
+ # precedence over the mock_includes added at global scope above.
+ function(app_apply_real_sdk_config target)
+ target_compile_definitions(${target} PRIVATE
+ HAVE_HASH HAVE_RIPEMD160 HAVE_SHA256 HAVE_SHA512 HAVE_HMAC HAVE_MATH
+ HAVE_ECC HAVE_ECC_WEIERSTRASS HAVE_SECP256K1_CURVE HAVE_ECDSA HAVE_EDDSA
+ API_LEVEL=22 OS_IO_SEPH_BUFFER_SIZE=272 IO_USB_MAX_ENDPOINTS=6
+ BIP32_PUBKEY_VERSION=0x043587CF BIP44_COIN_TYPE=1
+ COIN_P2PKH_VERSION=111 COIN_P2SH_VERSION=196
+ COIN_COINID_SHORT=\"TEST\"
+ )
+ target_include_directories(${target} BEFORE PRIVATE
+ ../src
+ ../src/common
+ ../src/debug-helpers
+ ../src/boilerplate
+ ../src/handler
+ ../src/handler/lib
+ ../src/handler/sign_psbt
+ ../src/musig
+ $ENV{BOLOS_SDK}/target/nanox/include
+ $ENV{BOLOS_SDK}/include
+ $ENV{BOLOS_SDK}/io/include
+ $ENV{BOLOS_SDK}/io_legacy/include
+ $ENV{BOLOS_SDK}/protocol/include
+ $ENV{BOLOS_SDK}/lib_standard_app
+ $ENV{BOLOS_SDK}/lib_cxng/include
+ $ENV{BOLOS_SDK}/lib_cxng/src
+ $ENV{BOLOS_SDK}/lib_stusb/include
+ )
+ endfunction()
+
add_library(app_crypto STATIC
../src/crypto.c
../src/secp256k1.c
@@ -295,38 +338,9 @@ if(SPECULOS AND SPECULOS_SRC)
../src/handler/lib/get_preimage.c
../src/handler/sign_psbt/sign_psbt_cache.c
../src/musig/musig.c
+ $ENV{BOLOS_SDK}/lib_standard_app/crypto_helpers.c
)
- target_compile_options(app_crypto PRIVATE -fno-stack-protector)
- target_compile_definitions(app_crypto PRIVATE
- HAVE_HASH HAVE_RIPEMD160 HAVE_SHA256 HAVE_SHA512 HAVE_HMAC HAVE_MATH
- HAVE_ECC HAVE_ECC_WEIERSTRASS HAVE_SECP256K1_CURVE HAVE_ECDSA
- API_LEVEL=22 OS_IO_SEPH_BUFFER_SIZE=272 IO_USB_MAX_ENDPOINTS=6
- BIP32_PUBKEY_VERSION=0x043587CF BIP44_COIN_TYPE=1
- COIN_P2PKH_VERSION=111 COIN_P2SH_VERSION=196
- COIN_COINID_SHORT=\"TEST\"
- )
- # BEFORE: SDK headers take precedence over the mock_includes added at
- # global scope above. The whole point of this target is to compile
- # the app code against the REAL SDK declarations.
- target_include_directories(app_crypto BEFORE PRIVATE
- ../src
- ../src/common
- ../src/debug-helpers
- ../src/boilerplate
- ../src/handler
- ../src/handler/lib
- ../src/handler/sign_psbt
- ../src/musig
- $ENV{BOLOS_SDK}/target/nanox/include
- $ENV{BOLOS_SDK}/include
- $ENV{BOLOS_SDK}/io/include
- $ENV{BOLOS_SDK}/io_legacy/include
- $ENV{BOLOS_SDK}/protocol/include
- $ENV{BOLOS_SDK}/lib_standard_app
- $ENV{BOLOS_SDK}/lib_cxng/include
- $ENV{BOLOS_SDK}/lib_cxng/src
- $ENV{BOLOS_SDK}/lib_stusb/include
- )
+ app_apply_real_sdk_config(app_crypto)
# Anything linking app_crypto needs the speculos syscall bridge and the
# SDK helpers the app code calls into. Expose them as PUBLIC so each
# test only has to mention app_crypto plus its own extras.
@@ -336,20 +350,24 @@ if(SPECULOS AND SPECULOS_SRC)
)
add_executable(test_crypto test_crypto.c)
- target_link_libraries(test_crypto PRIVATE cmocka gcov app_crypto xpub)
+ # Compile the test against the real SDK headers too, so that types
+ # exposed by crypto.h (e.g. cx_sha256_t) have the same layout the
+ # app_crypto object code was built against.
+ app_apply_real_sdk_config(test_crypto)
+ target_link_libraries(test_crypto PRIVATE cmocka app_crypto xpub)
add_test(test_crypto test_crypto)
add_executable(test_script test_script.c)
- target_link_libraries(test_script PRIVATE cmocka gcov app_crypto)
+ target_link_libraries(test_script PRIVATE cmocka app_crypto)
add_test(test_script test_script)
add_executable(test_wallet test_wallet.c)
- target_link_libraries(test_wallet PRIVATE cmocka gcov app_crypto buffer buffer_ext)
+ target_link_libraries(test_wallet PRIVATE cmocka app_crypto buffer buffer_ext)
add_test(test_wallet test_wallet)
# test_get_wallet_address exercises get_wallet_script + get_script_address
# end-to-end against a mock dispatcher; vectors come from a separate
- # text file (see test_get_wallet_address_vectors.txt for the format).
+ # TOML file (see test_get_wallet_address_vectors.toml).
add_executable(test_get_wallet_address test_get_wallet_address.c)
target_include_directories(test_get_wallet_address PRIVATE
../src/common ../src/handler ../src/handler/lib ../src/handler/sign_psbt
@@ -358,7 +376,7 @@ if(SPECULOS AND SPECULOS_SRC)
TEST_VECTORS_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/test_get_wallet_address_vectors.toml\"
)
target_link_libraries(test_get_wallet_address PRIVATE
- cmocka gcov app_crypto mock_dispatcher buffer buffer_ext sha256 tomlc17
+ cmocka app_crypto mock_dispatcher buffer buffer_ext sha256 tomlc17
)
add_test(test_get_wallet_address test_get_wallet_address)
endif()
diff --git a/unit-tests/README.md b/unit-tests/README.md
index 60e75b7..edef9a7 100644
--- a/unit-tests/README.md
+++ b/unit-tests/README.md
@@ -4,7 +4,7 @@
Be sure to have installed:
-- CMake >= 3.10
+- CMake >= 3.14
- CMocka >= 1.1.5
and for code coverage generation:
diff --git a/unit-tests/libs/speculos_bridge.c b/unit-tests/libs/speculos_bridge.c
index 91672d6..3aa2eae 100644
--- a/unit-tests/libs/speculos_bridge.c
+++ b/unit-tests/libs/speculos_bridge.c
@@ -1,18 +1,24 @@
/**
- * Speculos bridge: SDK-name → speculos-name forwarders, and host-side
- * implementations of the lib_cxng high-level wrappers that the
- * application uses.
+ * Speculos bridge: bind the SDK symbol names called by the application
+ * to speculos's sys_cx_* / spec_cx_* primitives.
*
* The application is written against the Ledger SDK API (cx_bn_lock,
* cx_ecpoint_alloc, cx_hmac_sha512, cx_ecfp_add_point_no_throw, ...).
+ * Speculos provides a pure-C implementation of the underlying syscalls,
+ * but under sys_-prefixed symbol names because on the device they're
+ * reached through an SVC dispatcher.
*
- * Speculos provides the *pure-C* implementation of the underlying
- * primitives, but under sys_-prefixed symbol names because on the
- * device they're reached through an SVC dispatcher.
+ * This file is mostly thin forwarders: the SDK name calls into the
+ * corresponding sys_cx_* / spec_cx_*, with a calling-convention adapter
+ * where the signatures diverge. Two higher-level wrappers
+ * (cx_ecfp_scalar_mult_no_throw, cx_ecfp_add_point_no_throw) are
+ * reimplemented because speculos's flat variants drop semantics the
+ * app relies on. The SDK's bip32_derive_with_seed_* helpers come from
+ * compiling lib_standard_app/crypto_helpers.c directly into app_crypto
+ * via CMakeLists.txt — they are NOT reimplemented here.
*
* Functions not used by code-under-test are stubbed with explicit
- * aborts, in order to keep the linker happy but fail loudly if
- * called. Stubs can be replaced with real implementations as needed.
+ * aborts: they keep the linker happy and fail loudly if called.
*/
#include <stdint.h>
@@ -27,10 +33,27 @@
/* Pull in the speculos type/prototype definitions. We use the speculos
* header chain rather than the SDK one to avoid double-defining
- * cx_bn_t / cx_ecpoint_t. */
-#define _SDK_2_0_
+ * cx_bn_t / cx_ecpoint_t. cxlib.h itself defines _SDK_2_0_ before
+ * pulling in cx.h, so no pre-define is needed here. */
#include "bolos/cxlib.h"
+/* SDK header that defines cx_iovec_t. Speculos does NOT define this
+ * type, so we can pull the SDK definition in unconditionally; it has
+ * no other shared symbols to clash with the speculos headers above. */
+#include "lcx_common.h"
+
+/* Loudly abort from a stub for a syscall the test target doesn't
+ * exercise. Wrapped as a do/while(0) so the macro is statement-safe in
+ * any context. */
+#define STUB_ABORT(name) \
+ do { \
+ fprintf(stderr, \
+ "speculos_bridge: %s called but not implemented in this test" \
+ " harness.\n", \
+ name); \
+ abort(); \
+ } while (0)
+
/* ------------------------------------------------------------------
* Forwarders: SDK name -> sys_cx_* implementation
* ------------------------------------------------------------------ */
@@ -102,25 +125,13 @@ cx_err_t cx_ecpoint_rnd_scalarmul(cx_ecpoint_t *p, const uint8_t *k, size_t k_le
/* ------------------------------------------------------------------
* High-level lib_cxng wrappers, re-implemented here for the host.
*
- * These are literal copies of the SDK source (lib_cxng/src/cx_math.c
- * and cx_ecfp.c). We re-implement instead of compiling the SDK file
- * because the SDK source pulls in a large set of headers that conflict
- * with the unit-test mock environment.
+ * cx_ecfp_scalar_mult_no_throw and cx_ecfp_add_point_no_throw are
+ * literal copies of the SDK source (lib_cxng/src/cx_ecfp.c) — the
+ * speculos sys_cx_ecfp_* variants either errx-abort on SECP256K1 input
+ * (add_point) or fail to surface CX_EC_INFINITE_POINT to the caller
+ * (scalar_mult), so we keep the bn-based composition for those.
* ------------------------------------------------------------------ */
-cx_err_t cx_math_cmp_no_throw(const uint8_t *a, const uint8_t *b, size_t length, int *diff) {
- cx_err_t error;
- cx_bn_t bn_a, bn_b;
-
- if ((error = sys_cx_bn_lock(length, 0))) return error;
- if ((error = sys_cx_bn_alloc_init(&bn_a, length, a, length))) goto end;
- if ((error = sys_cx_bn_alloc_init(&bn_b, length, b, length))) goto end;
- error = sys_cx_bn_cmp(bn_a, bn_b, diff);
-end:
- sys_cx_bn_unlock();
- return error;
-}
-
cx_err_t cx_ecfp_scalar_mult_no_throw(cx_curve_t curve,
uint8_t *P,
const uint8_t *k,
@@ -166,14 +177,12 @@ end:
return error;
}
-/* HMAC-SHA256 is implemented directly by speculos under a spec_ prefix. */
-extern int spec_cx_hmac_sha256(const unsigned char *key,
- unsigned int key_len,
- const unsigned char *in,
- unsigned int len,
- unsigned char *out,
- unsigned int out_len);
+/* ------------------------------------------------------------------
+ * One-shot hash & HMAC wrappers
+ * ------------------------------------------------------------------ */
+/* HMAC-SHA256 / HMAC-SHA512 are implemented directly by speculos under
+ * a spec_ prefix. */
size_t cx_hmac_sha256(const uint8_t *key,
size_t key_len,
const uint8_t *in,
@@ -188,14 +197,6 @@ size_t cx_hmac_sha256(const uint8_t *key,
(unsigned int) out_len);
}
-/* HMAC-SHA512 is implemented directly by speculos under a spec_ prefix. */
-extern int spec_cx_hmac_sha512(const unsigned char *key,
- unsigned int key_len,
- const unsigned char *in,
- unsigned int len,
- unsigned char *out,
- unsigned int out_len);
-
size_t cx_hmac_sha512(const uint8_t *key,
size_t key_len,
const uint8_t *in,
@@ -211,34 +212,22 @@ size_t cx_hmac_sha512(const uint8_t *key,
}
/* SHA-256 one-shot. */
-extern int sys_cx_hash_sha256(const uint8_t *in, size_t len, uint8_t *out, size_t out_len);
-
int cx_hash_sha256(const uint8_t *in, size_t len, uint8_t *out, size_t out_len) {
return sys_cx_hash_sha256(in, len, out, out_len);
}
-/* RIPEMD-160 one-shot, forwarded as iovec wrapper. */
-extern int sys_cx_hash_ripemd160(const uint8_t *in, size_t in_len, uint8_t *out, size_t out_len);
-
-typedef struct {
- const uint8_t *iov_base;
- size_t iov_len;
-} cx_iovec_t_local;
-
-cx_err_t cx_ripemd160_hash_iovec(const cx_iovec_t_local *iovec,
- size_t iovec_count,
- uint8_t digest[20]) {
- /* The app currently uses a single-iovec call. Concatenate if needed. */
- if (iovec_count == 1) {
- return (sys_cx_hash_ripemd160(iovec[0].iov_base, iovec[0].iov_len, digest, 20) == 20)
- ? 0
- : 0xFFFFFF85;
- }
-
+/* RIPEMD-160 one-shot, forwarded as iovec wrapper. Concatenates first
+ * since speculos only exposes a flat (in, len) entry point. */
+cx_err_t cx_ripemd160_hash_iovec(const cx_iovec_t *iovec, size_t iovec_count, uint8_t digest[20]) {
size_t total = 0;
for (size_t i = 0; i < iovec_count; i++) total += iovec[i].iov_len;
+ if (total == 0) {
+ uint8_t dummy = 0;
+ int rc = sys_cx_hash_ripemd160(&dummy, 0, digest, 20);
+ return (rc == 20) ? CX_OK : CX_INTERNAL_ERROR;
+ }
uint8_t *buf = malloc(total);
- if (!buf) return 0xFFFFFF8B; /* CX_MEMORY_FULL */
+ if (!buf) return CX_MEMORY_FULL;
size_t off = 0;
for (size_t i = 0; i < iovec_count; i++) {
memcpy(buf + off, iovec[i].iov_base, iovec[i].iov_len);
@@ -246,22 +235,14 @@ cx_err_t cx_ripemd160_hash_iovec(const cx_iovec_t_local *iovec,
}
int rc = sys_cx_hash_ripemd160(buf, total, digest, 20);
free(buf);
- return (rc == 20) ? 0 : 0xFFFFFF85;
+ return (rc == 20) ? CX_OK : CX_INTERNAL_ERROR;
}
/* ------------------------------------------------------------------
- * Stubs for syscalls referenced by other parts of crypto.c that this
- * test target never exercises. Calling any of them is a programming
- * error in the test, so we abort loudly.
+ * Streaming-hash wrappers (cx_hash / cx_sha256)
* ------------------------------------------------------------------ */
-#define STUB_ABORT(name) \
- fprintf(stderr, \
- "speculos_bridge: %s called but not implemented in this test" \
- " harness.\n", \
- name); \
- abort()
-
+/* sys_cx_hash isn't declared in any speculos public header. */
extern unsigned long sys_cx_hash(cx_hash_t *hash,
int mode,
const uint8_t *in,
@@ -280,51 +261,43 @@ cx_err_t cx_hash_no_throw(cx_hash_t *hash,
* speculos THROWs on any error (mapped to abort by the bridge), so
* if we get here at all, treat it as success. */
(void) sys_cx_hash(hash, mode, in, len, out, out_len);
- return 0; /* CX_OK */
+ return CX_OK;
}
-/* cx_sha256_init is exposed directly by speculos (no sys_ prefix). */
-extern int cx_sha256_init(cx_sha256_t *hash);
-
cx_err_t cx_sha256_init_no_throw(cx_sha256_t *hash) {
/* Per the SDK header, this function always returns CX_OK. */
cx_sha256_init(hash);
- return 0;
+ return CX_OK;
}
-/* Reuses cx_iovec_t_local from the RIPEMD-160 forwarder above; same
- * layout as the SDK / mock cx_iovec_t. */
-cx_err_t cx_sha256_hash_iovec(const cx_iovec_t_local *iovec, size_t iovec_count,
- uint8_t *out) {
+cx_err_t cx_sha256_hash_iovec(const cx_iovec_t *iovec, size_t iovec_count, uint8_t *out) {
cx_sha256_t ctx;
cx_sha256_init(&ctx);
for (size_t i = 0; i < iovec_count; i++) {
if (iovec[i].iov_len > 0) {
- sys_cx_hash(&ctx.header, 0, iovec[i].iov_base, iovec[i].iov_len,
- NULL, 0);
+ sys_cx_hash(&ctx.header, 0, iovec[i].iov_base, iovec[i].iov_len, NULL, 0);
}
}
sys_cx_hash(&ctx.header, CX_LAST, NULL, 0, out, 32);
- return 0;
+ return CX_OK;
}
+/* ------------------------------------------------------------------
+ * Math wrappers
+ *
+ * The sys_cx_math_* primitives have useless return values (some return
+ * 0xdeadbeef, some return 0 unconditionally — see the speculos source).
+ * Treat any path through them as success; failure in speculos is
+ * signaled by an errx(1, ...) that aborts the process.
+ * ------------------------------------------------------------------ */
+
cx_err_t cx_math_addm_no_throw(uint8_t *r,
const uint8_t *a,
const uint8_t *b,
const uint8_t *m,
size_t len) {
- cx_bn_t bn_r, bn_a, bn_b, bn_m;
- cx_err_t error;
- if ((error = sys_cx_bn_lock(len, 0))) return error;
- if ((error = sys_cx_bn_alloc(&bn_r, len))) goto end;
- if ((error = sys_cx_bn_alloc_init(&bn_a, len, a, len))) goto end;
- if ((error = sys_cx_bn_alloc_init(&bn_b, len, b, len))) goto end;
- if ((error = sys_cx_bn_alloc_init(&bn_m, len, m, len))) goto end;
- if ((error = sys_cx_bn_mod_add(bn_r, bn_a, bn_b, bn_m))) goto end;
- error = sys_cx_bn_export(bn_r, r, len);
-end:
- sys_cx_bn_unlock();
- return error;
+ (void) sys_cx_math_addm(r, a, b, m, (unsigned int) len);
+ return CX_OK;
}
cx_err_t cx_math_powm_no_throw(uint8_t *r,
@@ -333,47 +306,40 @@ cx_err_t cx_math_powm_no_throw(uint8_t *r,
size_t len_e,
const uint8_t *m,
size_t len) {
- cx_bn_t bn_r, bn_a, bn_m;
- cx_err_t error;
- if ((error = sys_cx_bn_lock(len, 0))) return error;
- if ((error = sys_cx_bn_alloc(&bn_r, len))) goto end;
- if ((error = sys_cx_bn_alloc_init(&bn_a, len, a, len))) goto end;
- if ((error = sys_cx_bn_alloc_init(&bn_m, len, m, len))) goto end;
- if ((error = sys_cx_bn_mod_pow(bn_r, bn_a, e, (uint32_t) len_e, bn_m))) goto end;
- error = sys_cx_bn_export(bn_r, r, len);
-end:
- sys_cx_bn_unlock();
- return error;
+ (void) sys_cx_math_powm(r, a, e, len_e, m, len);
+ return CX_OK;
}
-extern int sys_cx_math_multm(uint8_t *r,
- const uint8_t *a,
- const uint8_t *b,
- const uint8_t *m,
- unsigned int len);
-
cx_err_t cx_math_multm_no_throw(uint8_t *r,
const uint8_t *a,
const uint8_t *b,
const uint8_t *m,
size_t len) {
- /* sys_cx_math_multm has a useless return value (a leftover marker
- * value, see the speculos source). Treat any path through it as
- * success. */
(void) sys_cx_math_multm(r, a, b, m, (unsigned int) len);
- return 0; /* CX_OK */
+ return CX_OK;
}
-extern int sys_cx_math_modm(uint8_t *v,
- unsigned int len_v,
- const uint8_t *m,
- unsigned int len_m);
-
cx_err_t cx_math_modm_no_throw(uint8_t *v, size_t len_v, const uint8_t *m, size_t len_m) {
(void) sys_cx_math_modm(v, (unsigned int) len_v, m, (unsigned int) len_m);
- return 0; /* CX_OK */
+ return CX_OK;
+}
+
+cx_err_t cx_math_sub_no_throw(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len) {
+ (void) sys_cx_math_sub(r, a, b, len);
+ return CX_OK;
+}
+
+/* sys_cx_math_cmp returns the int comparison result; the SDK wrapper
+ * passes it through a diff out-param. */
+cx_err_t cx_math_cmp_no_throw(const uint8_t *a, const uint8_t *b, size_t length, int *diff) {
+ *diff = sys_cx_math_cmp(a, b, (unsigned int) length);
+ return CX_OK;
}
+/* ------------------------------------------------------------------
+ * Misc utilities
+ * ------------------------------------------------------------------ */
+
/* Constant-time memcmp, matching the SDK char convention (0 on equal,
* nonzero otherwise). XOR-accumulate every byte pair so the loop's data
* access pattern and branch behavior don't depend on where the first
@@ -388,27 +354,14 @@ char os_secure_memcmp(const void *src1, const void *src2, size_t length) {
return diff == 0 ? 0 : 1;
}
-cx_err_t cx_math_sub_no_throw(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len) {
- cx_bn_t bn_r, bn_a, bn_b;
- cx_err_t error;
- if ((error = sys_cx_bn_lock(len, 0))) return error;
- if ((error = sys_cx_bn_alloc(&bn_r, len))) goto end;
- if ((error = sys_cx_bn_alloc_init(&bn_a, len, a, len))) goto end;
- if ((error = sys_cx_bn_alloc_init(&bn_b, len, b, len))) goto end;
- /* CX_CARRY is expected for sub when a < b; treat as ok. */
- error = sys_cx_bn_sub(bn_r, bn_a, bn_b);
- if (error && error != 0xFFFFFF21) goto end;
- error = sys_cx_bn_export(bn_r, r, len);
-end:
- sys_cx_bn_unlock();
- return error;
-}
-
-/* sys_cx_ecdsa_sign / sys_cx_ecdsa_verify are declared by speculos's
- * cx_ec.h, pulled in transitively via bolos/cxlib.h above. They use
- * unsigned int for the signature length (speculos returns the written
- * length via the function's return value), while the SDK's _no_throw
- * wrappers use size_t* in-out. Bridge the calling convention here. */
+/* ------------------------------------------------------------------
+ * ECDSA wrappers
+ *
+ * sys_cx_ecdsa_sign / sys_cx_ecdsa_verify use unsigned int for the
+ * signature length (speculos returns the written length via the
+ * function's return value), while the SDK's _no_throw wrappers use
+ * size_t* in-out. We bridge the calling convention here.
+ * ------------------------------------------------------------------ */
cx_err_t cx_ecdsa_sign_no_throw(const cx_ecfp_private_key_t *pvkey,
uint32_t mode,
@@ -427,10 +380,10 @@ cx_err_t cx_ecdsa_sign_no_throw(const cx_ecfp_private_key_t *pvkey,
sig,
(unsigned int) *sig_len,
&info_local);
- if (n < 0) return 0xFFFFFF85; /* CX_INTERNAL_ERROR */
+ if (n < 0) return CX_INTERNAL_ERROR;
*sig_len = (size_t) n;
if (info != NULL) *info = info_local;
- return 0; /* CX_OK */
+ return CX_OK;
}
bool cx_ecdsa_verify_no_throw(const cx_ecfp_public_key_t *pukey,
@@ -438,6 +391,8 @@ bool cx_ecdsa_verify_no_throw(const cx_ecfp_public_key_t *pukey,
size_t hash_len,
const uint8_t *sig,
size_t sig_len) {
+ /* sys_cx_ecdsa_verify ignores its hashID and mode parameters; we
+ * pass CX_SHA256 / 0 only to satisfy the signature. */
return sys_cx_ecdsa_verify(pukey,
0,
CX_SHA256,
@@ -450,21 +405,13 @@ bool cx_ecdsa_verify_no_throw(const cx_ecfp_public_key_t *pukey,
/* ------------------------------------------------------------------
* BIP32 seed derivation — forwarders to speculos's os_bip32.c.
*
- * Speculos exposes:
- * sys_os_perso_derive_node_with_seed_key(mode, curve, path, len,
- * privkey, chain, seed_key, seed_key_len)
- * sys_os_perso_get_master_key_identifier(identifier, length)
- * sys_cx_ecfp_init_private_key(curve, raw_key, key_len, privkey)
- * sys_cx_ecfp_generate_pair(curve, pubkey, privkey, keep_private)
- *
* The seed itself is read by os_bip32.c through env_get_seed(); we
* provide a deterministic stub for env_get_seed below.
+ *
+ * sys_os_perso_* aren't exposed by any speculos public header, so
+ * declare them here.
* ------------------------------------------------------------------ */
-/* sys_cx_ecfp_init_private_key and sys_cx_ecfp_generate_pair are
- * already declared by speculos's cx_ec.h, pulled in transitively via
- * bolos/cxlib.h above. Declare here only what isn't exposed in a
- * speculos public header. */
extern unsigned long sys_os_perso_derive_node_with_seed_key(unsigned int mode,
cx_curve_t curve,
const unsigned int *path,
@@ -510,19 +457,23 @@ cx_err_t os_derive_bip32_with_seed_no_throw(unsigned int mode,
chain,
seed_key,
(unsigned int) seed_key_len);
- return 0; /* CX_OK; THROW path is fatal in our test harness. */
+ return CX_OK; /* THROW path is fatal in our test harness. */
}
unsigned long os_perso_get_master_key_identifier(uint8_t *id, size_t id_len) {
return sys_os_perso_get_master_key_identifier(id, id_len);
}
+/* ------------------------------------------------------------------
+ * EC key init / generation
+ * ------------------------------------------------------------------ */
+
cx_err_t cx_ecfp_generate_pair_no_throw(cx_curve_t curve,
cx_ecfp_public_key_t *pubkey,
cx_ecfp_private_key_t *privkey,
int keepprivate) {
int rc = sys_cx_ecfp_generate_pair(curve, pubkey, privkey, keepprivate);
- return rc == 0 ? 0 : 0xFFFFFF85;
+ return rc == 0 ? CX_OK : CX_INTERNAL_ERROR;
}
cx_err_t cx_ecfp_generate_pair2_no_throw(cx_curve_t curve,
@@ -540,62 +491,54 @@ cx_err_t cx_ecfp_init_private_key_no_throw(cx_curve_t curve,
size_t raw_len,
cx_ecfp_private_key_t *privkey) {
int rc = sys_cx_ecfp_init_private_key(curve, raw, (unsigned int) raw_len, privkey);
- return rc >= 0 ? 0 : 0xFFFFFF85;
+ return rc >= 0 ? CX_OK : CX_INTERNAL_ERROR;
}
-/* SDK helpers, reimplemented inline. Equivalent to
- * lib_standard_app/crypto_helpers.c — but rewritten here because that
- * file pulls in the full SDK header chain (incompatible with the
- * unit-test mock environment). */
+/* ------------------------------------------------------------------
+ * Stubs for unused crypto_helpers.c entry points.
+ *
+ * bip32_derive_with_seed_* are provided by compiling
+ * lib_standard_app/crypto_helpers.c directly into app_crypto (see
+ * CMakeLists.txt). The signing-helper variants in that file
+ * (ecdsa_sign_rs / eddsa_sign) reference syscalls the app doesn't
+ * exercise; these stubs keep the linker happy and abort loudly if a
+ * test ever reaches them.
+ * ------------------------------------------------------------------ */
-cx_err_t bip32_derive_with_seed_init_privkey_256(unsigned int derivation_mode,
- cx_curve_t curve,
- const uint32_t *path,
- size_t path_len,
- cx_ecfp_private_key_t *privkey,
- uint8_t *chain_code,
- unsigned char *seed,
- size_t seed_len) {
- uint8_t raw[64] = {0};
- cx_err_t err = os_derive_bip32_with_seed_no_throw(derivation_mode,
- curve,
- path,
- path_len,
- raw,
- chain_code,
- seed,
- seed_len);
- if (err != 0) return err;
- return cx_ecfp_init_private_key_no_throw(curve, raw, 32, privkey);
-}
-
-cx_err_t bip32_derive_with_seed_get_pubkey_256(unsigned int derivation_mode,
- cx_curve_t curve,
- const uint32_t *path,
- size_t path_len,
- uint8_t raw_pubkey[65],
- uint8_t *chain_code,
- cx_md_t hashID,
- unsigned char *seed,
- size_t seed_len) {
- cx_ecfp_private_key_t privkey = {0};
- cx_ecfp_public_key_t pubkey = {0};
-
- cx_err_t err = bip32_derive_with_seed_init_privkey_256(derivation_mode,
- curve,
- path,
- path_len,
- &privkey,
- chain_code,
- seed,
- seed_len);
- if (err != 0) return err;
-
- err = cx_ecfp_generate_pair2_no_throw(curve, &pubkey, &privkey, 1, hashID);
- if (err != 0) return err;
- if (pubkey.W_len != 65) return 0xFFFFFFA3; /* CX_EC_INVALID_CURVE */
- memcpy(raw_pubkey, pubkey.W, 65);
- return 0;
+cx_err_t cx_ecdsa_sign_rs_no_throw(const cx_ecfp_private_key_t *key,
+ uint32_t mode,
+ cx_md_t hashID,
+ const uint8_t *hash,
+ size_t hash_len,
+ size_t rs_len,
+ uint8_t *sig_r,
+ uint8_t *sig_s,
+ uint32_t *info) {
+ (void) key;
+ (void) mode;
+ (void) hashID;
+ (void) hash;
+ (void) hash_len;
+ (void) rs_len;
+ (void) sig_r;
+ (void) sig_s;
+ (void) info;
+ STUB_ABORT("cx_ecdsa_sign_rs_no_throw");
+}
+
+cx_err_t cx_eddsa_sign_no_throw(const cx_ecfp_private_key_t *pvkey,
+ cx_md_t hashID,
+ const uint8_t *hash,
+ size_t hash_len,
+ uint8_t *sig,
+ size_t sig_len) {
+ (void) pvkey;
+ (void) hashID;
+ (void) hash;
+ (void) hash_len;
+ (void) sig;
+ (void) sig_len;
+ STUB_ABORT("cx_eddsa_sign_no_throw");
}
/* ------------------------------------------------------------------
@@ -609,10 +552,10 @@ cx_err_t bip32_derive_with_seed_get_pubkey_256(unsigned int derivation_mode,
* derivation-path restriction.
* ------------------------------------------------------------------ */
-// NOTE: the app does not have this flag, but allowing any derivation simplifies unit test
-// by allowing the use of any BIP32 test vector.
-/* 0x10 = APPLICATION_FLAG_DERIVE_MASTER — required by speculos's
- * os_bip32 to allow non-hardened derivation paths from the master. */
+/* 0x10 = APPLICATION_FLAG_DERIVE_MASTER. Required by speculos's
+ * os_bip32 to allow non-hardened derivation from the master key.
+ * The real app does not set this flag — we enable it here so unit
+ * tests can use any BIP32 test vector, hardened or not. */
uint64_t app_flags = 0x10u;
unsigned long get_app_derivation_path(uint8_t **derivationPath) {
@@ -641,7 +584,11 @@ size_t env_get_seed(uint8_t *seed, size_t max_size) {
}
/* ------------------------------------------------------------------
- * BOLOS runtime stubs
+ * BOLOS runtime: host-side glue and stubs
+ *
+ * On-device, these entry points are BOLOS primitives (exception stack,
+ * RNG, PIC relocation, ...). On the host we either short-circuit them
+ * (THROW → abort) or wire them to a host-equivalent (TRNG → OpenSSL).
* ------------------------------------------------------------------ */
/* Custom setjmp/longjmp are ARM-asm in speculos. On the host we don't
@@ -652,9 +599,10 @@ void os_longjmp(unsigned int exception) {
abort();
}
-/* sys_try_context_get is referenced by os_longjmp inside speculos's
- * exception.c, but on the host we redefine os_longjmp above, so this
- * is dead code. Provide a stub anyway. */
+/* sys_try_context_* are referenced by os_longjmp inside speculos's
+ * exception.c, but on the host we redefine os_longjmp above, so they
+ * are dead code. The application code calls the non-sys_ names via the
+ * SDK header; we forward them so a THROW-free path stays inert. */
void *sys_try_context_set(void *ctx) {
(void) ctx;
return NULL;
@@ -662,6 +610,12 @@ void *sys_try_context_set(void *ctx) {
void *sys_try_context_get(void) {
return NULL;
}
+void *try_context_set(void *ctx) {
+ return sys_try_context_set(ctx);
+}
+void *try_context_get(void) {
+ return sys_try_context_get();
+}
void __attribute__((noreturn)) assert_exit(bool confirm) {
(void) confirm;
@@ -677,6 +631,20 @@ unsigned long sys_cx_rng(uint8_t *buffer, unsigned int length) {
return (unsigned long) buffer;
}
+/* The SDK's PIC() macro forwards to a `pic()` function that on the device
+ * translates a link-time address into the runtime address used after the
+ * loader has applied the application's relocations. On the host we run a
+ * normal ELF so addresses don't change; the identity function suffices. */
+void *pic(void *link_address) {
+ return link_address;
+}
+
+/* mock_dispatcher.c resets cx_hash_mock's pool counter on every init. When
+ * the dispatcher is wired to the speculos-backed cx_ primitives instead,
+ * this global is never actually consulted — but it still has to be defined
+ * for the linker. */
+int g_sha256_pool_next = 0;
+
/* ED25519 entry points from libcrypto are not directly available on
* modern OpenSSL; speculos references them from libsodium. Our test
* target does not exercise ED25519, so stubbing keeps the linker happy. */
@@ -708,30 +676,9 @@ int ED25519_verify(const uint8_t *msg,
STUB_ABORT("ED25519_verify");
}
-/* The application code references `try_context_get` / `try_context_set`
- * via the SDK header. On the device these are the BOLOS exception-stack
- * primitives. On the host we redirect THROW to abort, so these never
- * fire; provide trivial forwarders. */
-void *try_context_set(void *ctx) {
- return sys_try_context_set(ctx);
-}
-void *try_context_get(void) {
- return sys_try_context_get();
-}
-
-/* The SDK's PIC() macro forwards to a `pic()` function that on the device
- * translates a link-time address into the runtime address used after the
- * loader has applied the application's relocations. On the host we run a
- * normal ELF so addresses don't change; the identity function suffices. */
-void *pic(void *link_address) {
- return link_address;
-}
-
-/* mock_dispatcher.c resets cx_hash_mock's pool counter on every init. When
- * the dispatcher is wired to the speculos-backed cx_ primitives instead,
- * this global is never actually consulted — but it still has to be defined
- * for the linker. */
-int g_sha256_pool_next = 0;
+/* ------------------------------------------------------------------
+ * Bridge init
+ * ------------------------------------------------------------------ */
void speculos_bridge_init(void) {
/* Deterministic OpenSSL RNG seed for reproducible test runs. */
diff --git a/unit-tests/test_crypto.c b/unit-tests/test_crypto.c
index 54dfc7c..164bb2e 100644
--- a/unit-tests/test_crypto.c
+++ b/unit-tests/test_crypto.c
@@ -298,6 +298,10 @@ static void check_pubkey_at_path(const uint32_t *path,
size_t path_len,
const char *expected_xpub,
bool with_chain_code) {
+ if (path_len > 255) {
+ fail_msg("path_len must fit in a uint8_t");
+ }
+
serialized_extended_pubkey_t expected = decode_xpub(expected_xpub);
uint8_t pubkey[33];
@@ -591,14 +595,8 @@ static void test_crypto_ecdsa_sign_sha256_hash_with_key_no_optional_outputs(
/* */
/* Test vectors verified against test_utils/taproot.py. */
/* */
-/* The streaming-init helpers take a cx_sha256_t*. The mock_includes */
-/* layout of that struct is a few bytes smaller than the real SDK */
-/* one used by app_crypto, so the tests allocate an oversized */
-/* aligned buffer and cast through it. */
/* ---------------------------------------------------------------- */
-#define CX_SHA256_T_STORAGE_BYTES 128
-
static void test_crypto_tr_tagged_hash_init_custom_tag(void **state) {
(void) state;
/* tagged_hash("TestTag", "hello" || "world") */
@@ -610,13 +608,12 @@ static void test_crypto_tr_tagged_hash_init_custom_tag(void **state) {
0x20, 0xd9,
};
- uint8_t ctx_storage[CX_SHA256_T_STORAGE_BYTES] __attribute__((aligned(8)));
- cx_sha256_t *ctx = (cx_sha256_t *) ctx_storage;
- crypto_tr_tagged_hash_init(ctx, tag, sizeof(tag) - 1);
- assert_int_equal(crypto_hash_update(&ctx->header, "hello", 5), 0);
- assert_int_equal(crypto_hash_update(&ctx->header, "world", 5), 0);
+ cx_sha256_t ctx;
+ crypto_tr_tagged_hash_init(&ctx, tag, sizeof(tag) - 1);
+ assert_int_equal(crypto_hash_update(&ctx.header, "hello", 5), 0);
+ assert_int_equal(crypto_hash_update(&ctx.header, "world", 5), 0);
uint8_t out[32];
- assert_int_equal(crypto_hash_digest(&ctx->header, out, sizeof(out)), 0);
+ assert_int_equal(crypto_hash_digest(&ctx.header, out, sizeof(out)), 0);
assert_memory_equal(out, expected, 32);
}
@@ -630,12 +627,11 @@ static void test_crypto_tr_tapleaf_hash_init(void **state) {
0xc2, 0x30,
};
- uint8_t ctx_storage[CX_SHA256_T_STORAGE_BYTES] __attribute__((aligned(8)));
- cx_sha256_t *ctx = (cx_sha256_t *) ctx_storage;
- crypto_tr_tapleaf_hash_init(ctx);
- assert_int_equal(crypto_hash_update(&ctx->header, "abc", 3), 0);
+ cx_sha256_t ctx;
+ crypto_tr_tapleaf_hash_init(&ctx);
+ assert_int_equal(crypto_hash_update(&ctx.header, "abc", 3), 0);
uint8_t out[32];
- assert_int_equal(crypto_hash_digest(&ctx->header, out, sizeof(out)), 0);
+ assert_int_equal(crypto_hash_digest(&ctx.header, out, sizeof(out)), 0);
assert_memory_equal(out, expected, 32);
}
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.