sign_tx/sign_psbt: share code to validate supported sighash flags
What changed, and why it matters
This commit is a code cleanup that moves the rules for which Bitcoin/Liquid 'sighash' flags are allowed into one shared helper function used by both signing paths. Before, the PSBT signing path only rejected non-ALL sighashes for non-Taproot inputs, while the direct sign_tx path had more detailed rules (for example, allowing SINGLE|ANYONECANPAY for partial Liquid swaps). Unifying the logic reduces the chance that one path accidentally allows a dangerous sighash. The change itself does not add new user-facing behavior; it is defensive hardening/refactoring.
Treat as a low-risk hardening/refactoring commit. Reviewers should verify that sighash_is_supported() is called consistently in all signing entry points and that the Liquid partial-swap policy (SINGLE|ANYONECANPAY) is correctly applied when the FIXME assumptions are later replaced with real txtype/is_partial detection.
Security signals we found
Shared validation helper reduces duplicate sighash policy logic
PSBT signing path now uses the same sighash policy as direct sign_tx path
No new sighash types are enabled; policy is unchanged or slightly tightened for PSBT
Defensive refactoring with FIXME comments indicating Liquid assumptions are not final
Evidence from the diff
The patch extracts is_valid_sig_type() from sign_tx.c into a shared sighash_is_supported() in sign_utils.c/sign_utils.h and calls it from both sign_tx.c and sign_psbt.c. The logic remains functionally the same: for Liquid partial swaps, SINGLE|ANYONECANPAY is allowed; for Taproot, DEFAULT or ALL; otherwise ALL. In sign_psbt.c the previous inline check only rejected non-ALL sighashes when input->sighash was set, without considering swap/single-acp cases; the new code routes through the shared helper. The commit also moves two Liquid FIXME assumptions (txtype = TXTYPE_SEND_PAYMENT, is_partial = false) earlier in sign_psbt.c. There are also cosmetic line-wrap changes in get_commitments.c.
Changed components
main/process/sign_psbt.cmain/process/sign_tx.cmain/process/sign_utils.cmain/process/sign_utils.hmain/process/get_commitments.cInspect captured patch +31 / −32
diff --git a/main/process/get_commitments.c b/main/process/get_commitments.c
index 60f3b41..db35e69 100644
--- a/main/process/get_commitments.c
+++ b/main/process/get_commitments.c
@@ -47,8 +47,7 @@ void get_commitments_process(void* process_ptr)
ext_commitment_t ec;
if (!rpc_get_n_bytes("asset_id", ¶ms, sizeof(ec.c.asset_id), ec.c.asset_id)) {
- jade_process_reject_message(
- process, CBOR_RPC_BAD_PARAMETERS, "Failed to extract asset_id from parameters");
+ jade_process_reject_message(process, CBOR_RPC_BAD_PARAMETERS, "Failed to extract asset_id from parameters");
goto cleanup;
}
@@ -98,8 +97,7 @@ void get_commitments_process(void* process_ptr)
// Compute abf only
if (!wallet_get_blinding_factor(master_blinding_key, sizeof(master_blinding_key), hash_prevouts,
hash_prevouts_len, output_index, BF_ASSET, ec.c.abf, sizeof(ec.c.abf))) {
- jade_process_reject_message(
- process, CBOR_RPC_BAD_PARAMETERS, "Failed to compute abf from the parameters");
+ jade_process_reject_message(process, CBOR_RPC_BAD_PARAMETERS, "Failed to compute abf from the parameters");
goto cleanup;
}
}
diff --git a/main/process/sign_psbt.c b/main/process/sign_psbt.c
index 56b201c..4cae064 100644
--- a/main/process/sign_psbt.c
+++ b/main/process/sign_psbt.c
@@ -667,7 +667,9 @@ int sign_psbt(const network_t network_id, struct wally_psbt* psbt, const char**
return CBOR_RPC_BAD_PARAMETERS;
}
- uint64_t explicit_fee = 0;
+ const TxType_t txtype = TXTYPE_SEND_PAYMENT; // FIXME: Liquid: assumed for now
+ const bool is_partial = false; // FIXME: Liquid: assumed for now
+ uint64_t explicit_fee = 0; // Liquid: Value of the explicit fee output
struct wally_tx* tx = NULL; // Holds the extracted tx
// Fetch the tx to sign
@@ -739,12 +741,10 @@ int sign_psbt(const network_t network_id, struct wally_psbt* psbt, const char**
signing_flags |= PSBT_SIGNING_SINGLESIG;
}
- // Only support SIGHASH_ALL, or SIGHASH_DEFAULT for taproot atm.
- // SIGHASH_DEFAULT is 0 so passes this check, the 0 is
- // converted to ALL/DEFAULT by wally when signing
- if (input->sighash && input->sighash != WALLY_SIGHASH_ALL) {
+ // Check sighash, but only if one was provided (the default is always valid)
+ if (input->sighash && !sighash_is_supported(txtype, sig_type, input->sighash, is_elements, is_partial)) {
JADE_LOGW("Unsupported sighash for signing input %u", index);
- *errmsg = "Unsupported sighash";
+ *errmsg = "Unsupported sighash value";
retval = CBOR_RPC_BAD_PARAMETERS;
goto cleanup;
}
@@ -849,9 +849,6 @@ int sign_psbt(const network_t network_id, struct wally_psbt* psbt, const char**
JADE_ASSERT(!explicit_fee || is_elements);
if (is_elements) {
- // FIXME: some assumptions for now
- const TxType_t txtype = TXTYPE_SEND_PAYMENT;
- const bool is_partial = false;
const asset_info_t* assets = NULL;
const size_t num_assets = 0;
diff --git a/main/process/sign_tx.c b/main/process/sign_tx.c
index fcbfe5e..b7865d1 100644
--- a/main/process/sign_tx.c
+++ b/main/process/sign_tx.c
@@ -408,22 +408,6 @@ static void send_ec_signature_replies(
}
}
-// Whether or not a sighash type is valid
-static bool is_valid_sig_type(
- const input_data_t* const input_data, const TxType_t txtype, const bool for_liquid, const bool is_partial)
-{
- if (for_liquid && txtype == TXTYPE_SWAP && is_partial) {
- // Liquid partial swap: must be SINGLE | ACP
- return input_data->sighash == (WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYONECANPAY);
- }
- if (input_data->sig_type == WALLY_SIGTYPE_SW_V1) {
- // Taproot: must be ALL or DEFAULT
- return input_data->sighash == WALLY_SIGHASH_DEFAULT || input_data->sighash == WALLY_SIGHASH_ALL;
- }
- // All other cases must be ALL at present
- return input_data->sighash == WALLY_SIGHASH_ALL;
-}
-
/*
* The message flow here is complicated because we cater for both a legacy flow
* for standard deterministic EC signatures (see rfc6979) and a newer message
@@ -589,7 +573,8 @@ static void sign_tx_impl(jade_process_t* process, const bool for_liquid)
jade_process_reject_message(process, CBOR_RPC_BAD_PARAMETERS, errmsg);
goto cleanup;
}
- if (!is_valid_sig_type(input_data, txtype, for_liquid, is_partial)) {
+ if (!sighash_is_supported(txtype, input_data->sig_type, input_data->sighash, for_liquid, is_partial)) {
+ JADE_LOGW("Unsupported sighash for signing input %u", index);
jade_process_reject_message(process, CBOR_RPC_BAD_PARAMETERS, "Unsupported sighash value");
goto cleanup;
}
diff --git a/main/process/sign_utils.c b/main/process/sign_utils.c
index 4fc88dc..210f8ef 100644
--- a/main/process/sign_utils.c
+++ b/main/process/sign_utils.c
@@ -651,8 +651,24 @@ done:
return true;
}
-bool show_btc_fee_confirmation_activity(const network_t network_id, const struct wally_tx* tx, const output_info_t* outinfo,
- const script_flavour_t aggregate_inputs_scripts_flavour, const uint64_t input_amount, const uint64_t output_amount)
+bool sighash_is_supported(const TxType_t txtype, const uint32_t sig_type, const uint32_t sighash, const bool for_liquid,
+ const bool is_partial)
+{
+ if (for_liquid && txtype == TXTYPE_SWAP && is_partial) {
+ // Liquid partial swap: must be SINGLE | ACP
+ return sighash == (WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYONECANPAY);
+ }
+ if (sig_type == WALLY_SIGTYPE_SW_V1) {
+ // Taproot: must be ALL or DEFAULT
+ return sighash == WALLY_SIGHASH_DEFAULT || sighash == WALLY_SIGHASH_ALL;
+ }
+ // All other cases must be ALL at present
+ return sighash == WALLY_SIGHASH_ALL;
+}
+
+bool show_btc_fee_confirmation_activity(const network_t network_id, const struct wally_tx* tx,
+ const output_info_t* outinfo, const script_flavour_t aggregate_inputs_scripts_flavour, const uint64_t input_amount,
+ const uint64_t output_amount)
{
JADE_ASSERT(tx);
// outputinfo is optional
diff --git a/main/process/sign_utils.h b/main/process/sign_utils.h
index 992307c..f578924 100644
--- a/main/process/sign_utils.h
+++ b/main/process/sign_utils.h
@@ -43,6 +43,9 @@ bool validate_elements_outputs(jade_process_t* process, network_t network_id, co
TxType_t txtype, commitment_t* commitments, output_info_t* output_info, asset_summary_t* in_sums,
size_t num_in_sums, asset_summary_t* out_sums, size_t num_out_sums);
+// Whether or not the sighash flags for a given tx/signature type is supported
+bool sighash_is_supported(TxType_t txtype, uint32_t sig_type, uint32_t sighash, bool for_liquid, bool is_partial);
+
bool show_btc_fee_confirmation_activity(network_t network_id, const struct wally_tx* tx, const output_info_t* outinfo,
script_flavour_t aggregate_inputs_scripts_flavour, uint64_t input_amount, uint64_t output_amount);
Why this scored 45/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.