What changed, and why it matters
This commit only adds new automated unit tests for an existing function called classify_sighash. It does not change any production wallet code, fix a bug, or alter behavior. The tests verify that different Bitcoin signature-hash types are correctly categorized as safe, non-safe, or unsupported for legacy, SegWit v0, and Taproot transactions. By itself, this is a testing/infrastructure change with no direct security impact.
No security action required. Treat as normal test-coverage improvement. If reviewing the broader SIGHASH gating feature, verify that the production classify_sighash implementation and its callers enforce the same policy as these tests assert.
Security signals we found
Tests codify a security-relevant policy decision (safe vs. non-safe vs. unsupported sighash types)
No change to production code, attack surface, or cryptographic handling
No bug fix, bounds check, or input validation change present in diff
Evidence from the diff
The diff adds a cmocka-based unit test (unit-tests/test_sighash.c) and registers it in unit-tests/CMakeLists.txt. The test exercises classify_sighash(uint32_t sighash_type, int segwit_version) from src/common/sighash.h with a truth-table of expected outputs: SIGHASH_ALL is safe for all versions; SIGHASH_DEFAULT is safe only for Taproot (v1); SIGHASH_NONE/SINGLE/ANYONECANPAY variants are non-safe for SegWit inputs; non-ALL legacy sighashes are unsupported; and malformed values are unsupported. No implementation code is modified.
Changed components
unit-tests/test_sighash.cunit-tests/CMakeLists.txtInspect captured patch +112 / −0
diff --git a/unit-tests/CMakeLists.txt b/unit-tests/CMakeLists.txt
index 74cfb67..a230429 100644
--- a/unit-tests/CMakeLists.txt
+++ b/unit-tests/CMakeLists.txt
@@ -327,6 +327,14 @@ if(SPECULOS AND SPECULOS_SRC)
target_link_libraries(test_script PRIVATE cmocka app_crypto)
add_test(test_script test_script)
+ # test_sighash exercises classify_sighash (src/common/sighash.h), which only
+ # depends on constants.h (and therefore the real SDK headers). No app object
+ # code or syscalls are needed, so it just links cmocka.
+ add_executable(test_sighash test_sighash.c)
+ app_apply_real_sdk_config(test_sighash)
+ target_link_libraries(test_sighash PRIVATE cmocka)
+ add_test(test_sighash test_sighash)
+
add_executable(test_wallet test_wallet.c)
app_apply_real_sdk_config(test_wallet)
target_link_libraries(test_wallet PRIVATE cmocka app_crypto buffer buffer_ext)
diff --git a/unit-tests/test_sighash.c b/unit-tests/test_sighash.c
new file mode 100644
index 0000000..cdb6bc1
--- /dev/null
+++ b/unit-tests/test_sighash.c
@@ -0,0 +1,104 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <cmocka.h>
+
+#include "common/sighash.h"
+
+// SIGHASH constants (mirrored from constants.h for test independence)
+#define SIGHASH_DEFAULT 0x00000000
+#define SIGHASH_ALL 0x00000001
+#define SIGHASH_NONE 0x00000002
+#define SIGHASH_SINGLE 0x00000003
+#define SIGHASH_ANYONECANPAY 0x00000080
+
+// ========================================================================
+// Tests for classify_sighash
+//
+// classify_sighash is a pure (sighash_type, segwit_version) -> class mapping,
+// so we test it as a truth table: each row is a (sighash_type, segwit_version,
+// expected class) triple. This keeps the policy auditable at a glance and makes
+// it trivial to add cases. On mismatch we fail_msg() the offending row so a
+// failure is easy to pinpoint.
+//
+// segwit_version: -1 = legacy (pre-segwit), 0 = segwit v0, 1 = Taproot (v1).
+// ========================================================================
+
+typedef struct {
+ uint32_t sighash_type;
+ int segwit_version;
+ sighash_class_t expected;
+} sighash_case_t;
+
+static const sighash_case_t classify_sighash_cases[] = {
+ // --- SAFE: SIGHASH_ALL (any version), SIGHASH_DEFAULT (Taproot only) ---
+ {SIGHASH_ALL, -1, SIGHASH_CLASS_SAFE},
+ {SIGHASH_ALL, 0, SIGHASH_CLASS_SAFE},
+ {SIGHASH_ALL, 1, SIGHASH_CLASS_SAFE},
+ {SIGHASH_DEFAULT, 1, SIGHASH_CLASS_SAFE},
+ // SIGHASH_DEFAULT (0x00) is only valid for Taproot: unsupported elsewhere
+ {SIGHASH_DEFAULT, 0, SIGHASH_CLASS_UNSUPPORTED},
+ {SIGHASH_DEFAULT, -1, SIGHASH_CLASS_UNSUPPORTED},
+
+ // --- NON_SAFE: NONE / SINGLE / ANYONECANPAY|* on segwit inputs (v0 and v1) ---
+ {SIGHASH_NONE, 0, SIGHASH_CLASS_NON_SAFE},
+ {SIGHASH_NONE, 1, SIGHASH_CLASS_NON_SAFE},
+ {SIGHASH_SINGLE, 0, SIGHASH_CLASS_NON_SAFE},
+ {SIGHASH_SINGLE, 1, SIGHASH_CLASS_NON_SAFE},
+ {SIGHASH_ANYONECANPAY | SIGHASH_ALL, 0, SIGHASH_CLASS_NON_SAFE},
+ {SIGHASH_ANYONECANPAY | SIGHASH_ALL, 1, SIGHASH_CLASS_NON_SAFE},
+ {SIGHASH_ANYONECANPAY | SIGHASH_NONE, 0, SIGHASH_CLASS_NON_SAFE},
+ {SIGHASH_ANYONECANPAY | SIGHASH_NONE, 1, SIGHASH_CLASS_NON_SAFE},
+ {SIGHASH_ANYONECANPAY | SIGHASH_SINGLE, 0, SIGHASH_CLASS_NON_SAFE},
+ {SIGHASH_ANYONECANPAY | SIGHASH_SINGLE, 1, SIGHASH_CLASS_NON_SAFE},
+
+ // --- Legacy (segwit_version < 0): any non-ALL sighash is UNSUPPORTED ---
+ {SIGHASH_NONE, -1, SIGHASH_CLASS_UNSUPPORTED},
+ {SIGHASH_SINGLE, -1, SIGHASH_CLASS_UNSUPPORTED},
+ {SIGHASH_ANYONECANPAY | SIGHASH_ALL, -1, SIGHASH_CLASS_UNSUPPORTED},
+ {SIGHASH_ANYONECANPAY | SIGHASH_NONE, -1, SIGHASH_CLASS_UNSUPPORTED},
+ {SIGHASH_ANYONECANPAY | SIGHASH_SINGLE, -1, SIGHASH_CLASS_UNSUPPORTED},
+
+ // --- UNSUPPORTED: malformed / unrecognized values ---
+ // ANYONECANPAY alone (no base type)
+ {SIGHASH_ANYONECANPAY, 0, SIGHASH_CLASS_UNSUPPORTED},
+ {SIGHASH_ANYONECANPAY, 1, SIGHASH_CLASS_UNSUPPORTED},
+ // 0x84 = ANYONECANPAY | 0x04, not a valid combination
+ {0x84, 0, SIGHASH_CLASS_UNSUPPORTED},
+ {0x84, 1, SIGHASH_CLASS_UNSUPPORTED},
+ // arbitrary out-of-range values
+ {0xFF, 0, SIGHASH_CLASS_UNSUPPORTED},
+ {0x04, 0, SIGHASH_CLASS_UNSUPPORTED},
+ {0x10, 1, SIGHASH_CLASS_UNSUPPORTED},
+ {0xDEAD, 1, SIGHASH_CLASS_UNSUPPORTED},
+};
+
+static void test_classify_sighash(void **state) {
+ (void) state;
+
+ for (size_t i = 0; i < sizeof(classify_sighash_cases) / sizeof(classify_sighash_cases[0]);
+ i++) {
+ const sighash_case_t *c = &classify_sighash_cases[i];
+ sighash_class_t got = classify_sighash(c->sighash_type, c->segwit_version);
+ if (got != c->expected) {
+ fail_msg("case[%zu]: classify_sighash(0x%02x, %d) = %d, expected %d",
+ i,
+ c->sighash_type,
+ c->segwit_version,
+ (int) got,
+ (int) c->expected);
+ }
+ }
+}
+
+int main() {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_classify_sighash),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
Why this scored 12/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.