psbt: allow pset signing from usb/qrcodes
What changed, and why it matters
This commit lets a Blockstream Jade hardware wallet automatically detect whether a transaction file is for Bitcoin or the Liquid sidechain (including test networks) when signing via QR codes or USB. Before, the wallet always assumed Bitcoin, which could cause it to fail or behave incorrectly when signing a Liquid transaction through those offline methods. The change itself is a feature fix and does not appear to introduce a security vulnerability.
No immediate security action required. Treat as a normal functional fix. If reviewing for security, verify that `wally_psbt_is_elements()` cannot be influenced by a malformed PSBT to select an unintended network, and that downstream signing logic validates the network against the keychain restriction before producing signatures.
Security signals we found
Network-type selection now derives from the parsed PSBT/PSET rather than a hardcoded default
No new input parsing, memory allocation, or cryptographic code added
No explicit security claims or fixes in commit message
Evidence from the diff
The patch adds a helper network_from_psbt_type() that inspects the PSBT/PSET with wally_psbt_is_elements() and the existing keychain network restriction to choose the correct network constant. The QR-mode and USB-mode signing paths now call this helper instead of hardcoding NETWORK_BITCOIN/NETWORK_BITCOIN_TESTNET. This aligns offline signing with the network type encoded in the PSBT/PSET. There is no evidence in the diff of missing validation, buffer mishandling, or authentication bypass; it is a network-selection correction.
Changed components
main/process/sign_psbt.cmain/qrmode.cmain/usbhmsc/usbmode.cInspect captured patch +18 / −12
diff --git a/main/process/sign_psbt.c b/main/process/sign_psbt.c
index 528ec77..3dd0e0e 100644
--- a/main/process/sign_psbt.c
+++ b/main/process/sign_psbt.c
@@ -650,6 +650,18 @@ static bool psbt_update_outputs(const network_t network_id, struct wally_psbt* p
return true;
}
+// Deduce the network to try to sign with given a psbt/pset.
+network_t network_from_psbt_type(struct wally_psbt* psbt)
+{
+ JADE_ASSERT(psbt);
+ size_t is_elements = 0;
+ JADE_WALLY_VERIFY(wally_psbt_is_elements(psbt, &is_elements));
+ if (keychain_get_network_type_restriction() == NETWORK_TYPE_TEST) {
+ return is_elements ? NETWORK_LIQUID_TESTNET : NETWORK_BITCOIN_TESTNET;
+ }
+ return is_elements ? NETWORK_LIQUID : NETWORK_BITCOIN;
+}
+
// 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.
diff --git a/main/qrmode.c b/main/qrmode.c
index 10092fe..069dc49 100644
--- a/main/qrmode.c
+++ b/main/qrmode.c
@@ -89,6 +89,7 @@ bool select_registered_wallet(const char multisig_names[][NVS_KEY_NAME_MAX_SIZE]
// PSBT struct and functions
struct wally_psbt;
+network_t network_from_psbt_type(struct wally_psbt* psbt);
int sign_psbt(
jade_process_t* process, CborValue* params, network_t network_id, struct wally_psbt* psbt, const char** errmsg);
int wally_psbt_free(struct wally_psbt* psbt);
@@ -1092,12 +1093,8 @@ static bool parse_sign_display_bcur_psbt_qr(const uint8_t* cbor, const size_t cb
// Try to sign extracted PSBT
bool ret = false;
const char* errmsg = NULL;
- network_t network_id;
- if (keychain_get_network_type_restriction() == NETWORK_TYPE_TEST) {
- network_id = NETWORK_BITCOIN_TESTNET;
- } else {
- network_id = NETWORK_BITCOIN;
- }
+ const network_t network_id = network_from_psbt_type(psbt);
+
// Note we pass NULL process/params as we don't have any additional info
const int errcode = sign_psbt(NULL, NULL, network_id, psbt, &errmsg);
if (errcode) {
diff --git a/main/usbhmsc/usbmode.c b/main/usbhmsc/usbmode.c
index 6d1a9f9..91511bd 100644
--- a/main/usbhmsc/usbmode.c
+++ b/main/usbhmsc/usbmode.c
@@ -29,6 +29,7 @@ void await_qr_help_activity(const char* url);
// PSBT serialisation functions
bool deserialise_psbt(const uint8_t* bytes, size_t bytes_len, struct wally_psbt** psbt_out);
bool serialise_psbt(const struct wally_psbt* psbt, uint8_t** output, size_t* output_len);
+network_t network_from_psbt_type(struct wally_psbt* psbt);
int sign_psbt(
jade_process_t* process, CborValue* params, network_t network_id, struct wally_psbt* psbt, const char** errmsg);
@@ -828,12 +829,8 @@ static bool sign_usb_psbt(const usbstorage_action_context_t* ctx)
// Sign PSBT
const char* errmsg = NULL;
- network_t network_id;
- if (keychain_get_network_type_restriction() == NETWORK_TYPE_TEST) {
- network_id = NETWORK_BITCOIN_TESTNET;
- } else {
- network_id = NETWORK_BITCOIN;
- }
+ const network_t network_id = network_from_psbt_type(psbt);
+
// Note we pass NULL process/params as we don't have any additional info
const int errcode = sign_psbt(NULL, NULL, network_id, psbt, &errmsg);
if (errcode) {
Why this scored 30/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.