What changed, and why it matters
This commit changes the Blockstream Jade hardware wallet firmware so it can now sign Liquid/Elements PSET transactions instead of rejecting them. It also adds a check that the PSBT type matches the selected network (Bitcoin vs. Liquid). A new FIXME comment asks whether Bitcoin signing should only accept 'non-witness utxo' data, and descriptor-based wallet signing is disabled for Elements. The change is a feature addition with some defensive tightening, not a clear vulnerability fix, but it touches security-critical signing code.
Treat this as a notable change in security-critical code. Review the PSET parsing and signing path for Liquid-specific edge cases, ensure the new network/type mismatch check cannot be bypassed, resolve the FIXME about non-witness UTXO requirements, and verify descriptor-disabled handling does not fall back to unsafe defaults. Request additional commits, tests, or a security note from the vendor before concluding it is safe.
Security signals we found
Feature expansion: enables signing of Elements/Liquid PSETs, increasing attack surface for a previously unsupported transaction format
New consistency check: rejects when PSBT Elements flag mismatches the network's Liquid status
FIXME comment added about potentially requiring non-witness UTXO for Bitcoin inputs, suggesting an open security/design question
Descriptor-based wallet path explicitly disabled for Elements with a NOTE comment
Change is small (+7/-5) and partial; it does not include tests or full documentation of PSET security assumptions
Evidence from the diff
The patch modifies main/process/sign_psbt.c. Previously, sign_psbt() rejected any Elements/Liquid PSET by calling wally_psbt_is_elements() and returning CBOR_RPC_BAD_PARAMETERS with ‘Liquid/Elements PSET not supported’. Now it verifies the call with JADE_WALLY_VERIFY and only rejects if the Elements flag of the PSBT does not match whether the configured network is Liquid. It also adds a FIXME about restricting Bitcoin inputs to non-witness utxo, and skips descriptor-record lookup when is_elements is true. The change enables PSET signing and adds a network/type consistency check.
Changed components
Blockstream Jade firmwaremain/process/sign_psbt.cPSBT/PSET signing flowLiquid/Elements network supportInspect captured patch +7 / −5
diff --git a/main/process/sign_psbt.c b/main/process/sign_psbt.c
index 29d1138..ec13464 100644
--- a/main/process/sign_psbt.c
+++ b/main/process/sign_psbt.c
@@ -679,7 +679,7 @@ static bool validate_outputs(const network_t network_id, struct wally_psbt* psbt
return true;
}
-// Sign a psbt - the passed wally psbt struct is updated with any signatures.
+// Sign a psbt/pset - the passed wally psbt struct is updated with any signatures.
// Returns 0 if no errors occurred - does not necessarily indicate that signatures were added.
// Returns an rpc/message error code on error, and the error string should be populated.
int sign_psbt(const network_t network_id, struct wally_psbt* psbt, const char** errmsg)
@@ -688,10 +688,10 @@ int sign_psbt(const network_t network_id, struct wally_psbt* psbt, const char**
JADE_INIT_OUT_PPTR(errmsg);
JADE_ASSERT(network_id != NETWORK_NONE);
- // Elements/PSET not supported
size_t is_elements = 0;
- if (wally_psbt_is_elements(psbt, &is_elements) != WALLY_OK || is_elements) {
- *errmsg = "Liquid/Elements PSET not supported";
+ JADE_WALLY_VERIFY(wally_psbt_is_elements(psbt, &is_elements));
+ if (!is_elements != !network_is_liquid(network_id)) {
+ *errmsg = "Network/psbt type mismatch";
return CBOR_RPC_BAD_PARAMETERS;
}
@@ -727,6 +727,7 @@ int sign_psbt(const network_t network_id, struct wally_psbt* psbt, const char**
struct wally_psbt_input* input = &psbt->inputs[index];
// Get the utxo being spent
+ // FIXME: for btc only accept 'non-witness utxo' ?
const struct wally_tx_output* utxo = NULL;
if (wally_psbt_get_input_best_utxo(psbt, index, &utxo) != WALLY_OK || !utxo) {
*errmsg = "Input utxo missing";
@@ -824,7 +825,8 @@ int sign_psbt(const network_t network_id, struct wally_psbt* psbt, const char**
multisig_data = NULL;
}
- if (!multisig_data) {
+ // NOTE: descriptors not supported for elements atm
+ if (!multisig_data && !is_elements) {
descriptor = JADE_MALLOC(sizeof(descriptor_data_t));
if (get_suitable_descriptor_record(&iter, &path[path_tail_start], path_tail_len, utxo->script,
utxo->script_len, network_id, wallet_name, sizeof(wallet_name), descriptor)) {
Why this scored 40/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.