Get rid of SKIP_FOR_CMOCKA; compile tests with the speculos bridge
What changed, and why it matters
This commit is purely a testing infrastructure cleanup. It removes a workaround called SKIP_FOR_CMOCKA that previously hid parts of the code from unit tests, and instead compiles those parts using a more realistic emulator bridge (speculos). No user-facing behavior of the Bitcoin app changes, and no security bug is fixed or introduced in the diff.
No security action required; this is a test-only refactoring. Routine review/CI verification is sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors the Ledger Bitcoin app’s unit-test build. It deletes the SKIP_FOR_CMOCKA preprocessor guards around functions in src/common/script.c, src/common/script.h, src/common/wallet.c and src/common/wallet.h, removes the crypto_mocks library, and builds wallet.c, script.c and segwit_addr.c as part of an app_crypto target linked against the speculos syscall bridge. A pic() identity function is added to the bridge so the SDK PIC macro works on the host. The production source code is otherwise unchanged.
Changed components
unit-tests/CMakeLists.txtunit-tests/libs/speculos_bridge.cunit-tests/libs/crypto_mocks.cunit-tests/libs/crypto_mocks.hunit-tests/test_script.cunit-tests/test_wallet.csrc/common/script.csrc/common/script.hsrc/common/wallet.csrc/common/wallet.hInspect captured patch +37 / −82
diff --git a/src/common/script.c b/src/common/script.c
index b59cd22..265bf91 100644
--- a/src/common/script.c
+++ b/src/common/script.c
@@ -12,10 +12,7 @@
/* Local headers */
#include "segwit_addr.h"
-
-#ifndef SKIP_FOR_CMOCKA
#include "../crypto.h"
-#endif
size_t get_push_script_size(uint32_t n) {
if (n <= 16)
@@ -68,8 +65,6 @@ int get_script_type(const uint8_t script[], size_t script_len) {
return -1;
}
-#ifndef SKIP_FOR_CMOCKA
-
// TODO: add unit tests
int get_script_address(const uint8_t script[], size_t script_len, char *out, size_t out_len) {
int script_type = get_script_type(script, script_len);
@@ -121,8 +116,6 @@ int get_script_address(const uint8_t script[], size_t script_len, char *out, siz
return addr_len;
}
-#endif
-
int format_opscript_script(const uint8_t script[],
size_t script_len,
char out[static MAX_OPRETURN_OUTPUT_DESC_SIZE]) {
@@ -227,8 +220,6 @@ int format_opscript_script(const uint8_t script[],
return out_ctr;
}
-#ifndef SKIP_FOR_CMOCKA
-
bool format_script(const uint8_t script[],
size_t script_len,
char out[static MAX_OUTPUT_SCRIPT_DESC_SIZE]) {
@@ -245,5 +236,3 @@ bool format_script(const uint8_t script[],
}
return true;
}
-
-#endif
diff --git a/src/common/script.h b/src/common/script.h
index 0360098..4269ab3 100644
--- a/src/common/script.h
+++ b/src/common/script.h
@@ -189,8 +189,6 @@ size_t get_push_script_size(uint32_t n);
*/
int get_script_type(const uint8_t script[], size_t script_len);
-#ifndef SKIP_FOR_CMOCKA
-
/**
* Computes the address corresponding to the given script, if it has one.
* The termination character is added.
@@ -205,8 +203,6 @@ int get_script_type(const uint8_t script[], size_t script_len);
*/
int get_script_address(const uint8_t script[], size_t script_len, char *out, size_t out_len);
-#endif
-
// the longest OP_RETURN description is upper bounded by:
// - 9 bytes for "OP_RETURN"
// - 5 times 3 for the " 0x"
diff --git a/src/common/wallet.c b/src/common/wallet.c
index bb6b8f3..1638829 100644
--- a/src/common/wallet.c
+++ b/src/common/wallet.c
@@ -16,13 +16,7 @@
#include "segwit_addr.h"
#include "sw.h"
-#ifndef SKIP_FOR_CMOCKA
#include "../crypto.h"
-#else
-// disable problematic macros when compiling unit tests with CMOCKA
-#define PRINTF(...)
-#define PIC(x) (x)
-#endif
typedef struct {
PolicyNodeType type;
@@ -3041,8 +3035,6 @@ int traverse_policy_dfs(const policy_node_t *policy_node,
}
}
-#ifndef SKIP_FOR_CMOCKA
-
void get_policy_wallet_id(policy_map_wallet_header_t *wallet_header, uint8_t out[static 32]) {
cx_sha256_t wallet_hash_context;
cx_sha256_init(&wallet_hash_context);
@@ -3069,5 +3061,3 @@ void get_policy_wallet_id(policy_map_wallet_header_t *wallet_header, uint8_t out
crypto_hash_digest(&wallet_hash_context.header, out, 32);
}
-
-#endif
diff --git a/src/common/wallet.h b/src/common/wallet.h
index 0daf9ab..e2321ef 100644
--- a/src/common/wallet.h
+++ b/src/common/wallet.h
@@ -12,10 +12,8 @@
#include "constants.h"
#include "crypto.h"
-#ifndef SKIP_FOR_CMOCKA
#include "os.h"
#include "cx.h"
-#endif
// The maximum number of keys supported for CHECKMULTISIG{VERIFY}
// bitcoin-core supports up to 20, but we limit to 16 as bigger pushes require special handling.
@@ -532,8 +530,6 @@ int traverse_policy_dfs(const policy_node_t *policy_node,
policy_node_callback_t callback,
void *callback_state);
-#ifndef SKIP_FOR_CMOCKA
-
/**
* Computes the id of the policy map wallet (commitment to header + policy map + keys_info), as per
* specifications.
@@ -542,5 +538,3 @@ int traverse_policy_dfs(const policy_node_t *policy_node,
* @param out a pointer to a 32-byte array for the output
*/
void get_policy_wallet_id(policy_map_wallet_header_t *wallet_header, uint8_t out[static 32]);
-
-#endif
diff --git a/unit-tests/CMakeLists.txt b/unit-tests/CMakeLists.txt
index 970b5a4..4986dc8 100644
--- a/unit-tests/CMakeLists.txt
+++ b/unit-tests/CMakeLists.txt
@@ -72,7 +72,7 @@ if(SPECULOS)
endif()
endif()
-add_compile_definitions(TEST DEBUG=0 SKIP_FOR_CMOCKA PRINTF=printf COIN_NATIVE_SEGWIT_PREFIX=\"tb\")
+add_compile_definitions(TEST DEBUG=0 PRINTF=printf COIN_NATIVE_SEGWIT_PREFIX=\"tb\")
include_directories(../src)
include_directories(../src/debug-helpers)
@@ -87,8 +87,6 @@ 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_script test_script.c)
-add_executable(test_wallet test_wallet.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)
@@ -104,7 +102,6 @@ add_executable(test_get_merkleized_map test_get_merkleized_map.c)
add_executable(test_get_merkleized_map_value test_get_merkleized_map_value.c)
# Mock libraries
-add_library(crypto_mocks SHARED libs/crypto_mocks.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)
@@ -132,13 +129,9 @@ 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(script SHARED ../src/common/script.c)
add_library(varint SHARED $ENV{BOLOS_SDK}/lib_standard_app/varint.c)
-add_library(wallet SHARED ../src/common/wallet.c)
add_library(write SHARED $ENV{BOLOS_SDK}/lib_standard_app/write.c)
-# add_library(crypto SHARED ../src/crypto.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)
@@ -168,7 +161,6 @@ target_include_directories(test_get_merkleized_map PRIVATE ../src/handler ../src
target_include_directories(test_get_merkleized_map_value PRIVATE ../src/handler ../src/handler/lib ../src/common)
# Mock libraries
-target_link_libraries(crypto_mocks PUBLIC sha256)
target_link_libraries(cx_hash_mock PUBLIC sha256)
target_link_libraries(mock_dispatcher PUBLIC psbt_parse)
target_link_libraries(xpub PUBLIC base58 sha256)
@@ -178,8 +170,6 @@ 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_script PUBLIC cmocka gcov script buffer varint read write bip32)
-target_link_libraries(test_wallet PUBLIC cmocka gcov wallet script buffer buffer_ext varint read write bip32 base58 crypto_mocks)
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)
@@ -194,13 +184,10 @@ target_link_libraries(test_stream_merkleized_map_value PUBLIC cmocka gcov mock_d
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)
-# target_link_libraries(test_crypto PUBLIC cmocka gcov crypto)
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_script test_script)
-add_test(test_wallet test_wallet)
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)
@@ -281,24 +268,32 @@ if(SPECULOS AND SPECULOS_SRC)
)
target_link_libraries(speculos_bridge PUBLIC speculos_bolos OpenSSL::Crypto)
- # Build the application's crypto.c against the real SDK headers
- # (not the per-test mock_includes) so it sees the same syscall
- # signatures the speculos bridge implements.
+ # Build the application code against the real SDK headers (not the
+ # 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.
add_library(app_crypto STATIC
../src/crypto.c
../src/secp256k1.c
+ ../src/common/wallet.c
+ ../src/common/script.c
+ ../src/common/segwit_addr.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
- # crypto.c against the REAL SDK declarations.
+ # the app code against the REAL SDK declarations.
target_include_directories(app_crypto BEFORE PRIVATE
../src
+ ../src/common
../src/debug-helpers
../src/boilerplate
$ENV{BOLOS_SDK}/target/nanox/include
@@ -311,15 +306,23 @@ if(SPECULOS AND SPECULOS_SRC)
$ENV{BOLOS_SDK}/lib_cxng/src
$ENV{BOLOS_SDK}/lib_stusb/include
)
-
- add_executable(test_crypto test_crypto.c)
- target_include_directories(test_crypto PRIVATE libs)
- target_link_libraries(test_crypto PRIVATE
- cmocka gcov
- app_crypto
- base58 read write
+ # 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.
+ target_link_libraries(app_crypto PUBLIC
speculos_bridge
- xpub
+ base58 bip32 read write varint
)
+
+ add_executable(test_crypto test_crypto.c)
+ target_link_libraries(test_crypto PRIVATE cmocka gcov 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)
+ 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)
+ add_test(test_wallet test_wallet)
endif()
diff --git a/unit-tests/libs/crypto_mocks.c b/unit-tests/libs/crypto_mocks.c
deleted file mode 100644
index 79d09d0..0000000
--- a/unit-tests/libs/crypto_mocks.c
+++ /dev/null
@@ -1,10 +0,0 @@
-#include <stdint.h>
-#include "crypto_mocks.h"
-#include "sha-256.h"
-
-void crypto_get_checksum(const uint8_t *in, uint16_t in_len, uint8_t out[static 4]) {
- uint8_t buffer[32];
- calc_sha_256(buffer, in, in_len);
- calc_sha_256(buffer, buffer, 32);
- memmove(out, buffer, 4);
-}
diff --git a/unit-tests/libs/crypto_mocks.h b/unit-tests/libs/crypto_mocks.h
deleted file mode 100644
index 436076e..0000000
--- a/unit-tests/libs/crypto_mocks.h
+++ /dev/null
@@ -1,7 +0,0 @@
-// We're currently unable to compile the app's crypto.c in unit tests.
-// This library mocks the functions currently used in other modules that are part of
-// the unit tests.
-
-#include <stdint.h>
-
-void crypto_get_checksum(const uint8_t *in, uint16_t in_len, uint8_t out[static 4]);
\ No newline at end of file
diff --git a/unit-tests/libs/speculos_bridge.c b/unit-tests/libs/speculos_bridge.c
index 3112f03..500337e 100644
--- a/unit-tests/libs/speculos_bridge.c
+++ b/unit-tests/libs/speculos_bridge.c
@@ -655,6 +655,14 @@ 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;
+}
+
void speculos_bridge_init(void) {
/* Deterministic OpenSSL RNG seed for reproducible test runs. */
static const uint8_t seed[32] = {
diff --git a/unit-tests/test_script.c b/unit-tests/test_script.c
index a7b6b23..a2fc879 100644
--- a/unit-tests/test_script.c
+++ b/unit-tests/test_script.c
@@ -8,10 +8,6 @@
#include <cmocka.h>
-// missing definitions to make it compile without the SDK
-#define PRINTF(...) printf
-#define PIC(x) (x)
-
#include "common/script.h"
static void test_get_push_script_size(void **state) {
diff --git a/unit-tests/test_wallet.c b/unit-tests/test_wallet.c
index e98f56b..7da6878 100644
--- a/unit-tests/test_wallet.c
+++ b/unit-tests/test_wallet.c
@@ -10,10 +10,6 @@
#include "common/buffer_ext.h"
-// missing definitions to make it compile without the SDK
-#define PRINTF(...) printf
-#define PIC(x) (x)
-
#include "common/wallet.h"
static int parse_policy(const char *descriptor_template, uint8_t *out, size_t out_size) {
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.