pset: display liquid outputs for user confirmation
What changed, and why it matters
This commit adds user confirmation screens for Liquid (Elements) transactions in the Blockstream Jade hardware wallet. Previously, Liquid PSBT signing likely did not show the user the outputs and fee before signing, or used Bitcoin-only confirmation logic that did not properly handle Liquid assets. The change makes the device ask the user to confirm what is being spent and what fee is being paid before producing a signature. This is a security-hardening improvement rather than an obvious vulnerability fix, but the absence of such confirmation could have allowed malicious software to trick a user into signing an unwanted Liquid transaction.
Review the new show_elements_* UI activities to ensure they correctly parse and display all Liquid output fields (asset IDs, amounts, confidential vs explicit outputs). Verify that the disabled swap/partial paths cannot be reached or exploited, and consider completing the TODO/FIXME items before enabling full Liquid transaction support.
Security signals we found
Adds user-facing confirmation of transaction outputs before signing
Adds user-facing confirmation of transaction fee before signing
Separates Liquid/Elements confirmation path from Bitcoin path
Disables unsupported swap/partial transaction signing paths
Strengthens assertion that explicit fees are only valid on Liquid
Evidence from the diff
The patch modifies main/process/sign_psbt.c to branch transaction confirmation based on whether the network is Elements/Liquid. For Liquid, it now calls new UI activities show_elements_transaction_outputs_activity() and show_elements_fee_confirmation_activity() before signing. It also adds an explicit assertion that explicit fees are only valid on Liquid, and temporarily hard-codes txtype to TXTYPE_SEND_PAYMENT and disables swap/partial transaction support with #if 0. The Bitcoin path is preserved but moved into an else branch. The change improves user visibility into Liquid outputs and fees, reducing the risk of blind signing or fee manipulation.
Changed components
main/process/sign_psbt.cLiquid/Elements PSBT signing flowUser confirmation UI for transaction outputs and feesInspect captured patch +77 / −28
diff --git a/main/process/sign_psbt.c b/main/process/sign_psbt.c
index 0b97e26..29d1138 100644
--- a/main/process/sign_psbt.c
+++ b/main/process/sign_psbt.c
@@ -32,6 +32,15 @@ bool show_btc_transaction_outputs_activity(
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);
+bool show_elements_transaction_outputs_activity(network_t network_id, const struct wally_tx* tx,
+ const output_info_t* output_info, const asset_info_t* assets, size_t num_assets);
+bool show_elements_swap_activity(network_t network_id, bool initial_proposal, const asset_summary_t* in_sums,
+ size_t num_in_sums, const asset_summary_t* out_sums, size_t num_out_sums, const asset_info_t* assets,
+ size_t num_assets);
+bool show_elements_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 fees, TxType_t txtype,
+ bool tx_is_partial);
+
// From https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
static const uint8_t PSBT_MAGIC_PREFIX[5] = { 0x70, 0x73, 0x62, 0x74, 0xFF }; // 'psbt' + 0xff
@@ -848,37 +857,77 @@ int sign_psbt(const network_t network_id, struct wally_psbt* psbt, const char**
}
// Explicit fee is only valid for Liquid
- JADE_ASSERT(!explicit_fee || network_is_liquid(network_id));
-
- // Sanity check amounts
- uint64_t output_amount;
- JADE_WALLY_VERIFY(wally_tx_get_total_output_satoshi(tx, &output_amount));
- if (output_amount > input_amount) {
- *errmsg = "Invalid input/output amounts";
- retval = CBOR_RPC_BAD_PARAMETERS;
- goto cleanup;
- }
-
- // User to verify outputs and fee amount
- if (!show_btc_transaction_outputs_activity(network_id, tx, output_info)) {
- *errmsg = "User declined to sign psbt";
- retval = CBOR_RPC_USER_CANCELLED;
- goto cleanup;
- }
-
- JADE_LOGD("User accepted outputs");
+ 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;
+
+ if (txtype == TXTYPE_SWAP) {
+#if 0
+ // FIXME: Support swaps/partial txs
+ // Confirm wallet-summary info (ie. net inputs and outputs)
+ if (!show_elements_swap_activity(network_id, is_partial, in_sums, num_in_sums,
+ out_sums, num_out_sums, assets, num_assets)) {
+ *errmsg = "User declined to sign psbt";
+ retval = CBOR_RPC_USER_CANCELLED;
+ goto cleanup;
+ }
+#else
+ *errmsg = "Swap psbt signing is not yet supported";
+ retval = CBOR_RPC_BAD_PARAMETERS;
+ goto cleanup;
+#endif
+ } else {
+ // Confirm all non-change outputs
+ if (!show_elements_transaction_outputs_activity(network_id, tx, output_info, assets, num_assets)) {
+ *errmsg = "User declined to sign psbt";
+ retval = CBOR_RPC_USER_CANCELLED;
+ goto cleanup;
+ }
+ }
+ JADE_LOGD("User accepted outputs");
+
+ // User to agree fee amount
+ // Check to see whether user accepted or declined
+ if (!show_elements_fee_confirmation_activity(
+ network_id, tx, output_info, aggregate_inputs_scripts_flavour, explicit_fee, txtype, is_partial)) {
+ *errmsg = "User declined to sign psbt";
+ retval = CBOR_RPC_USER_CANCELLED;
+ goto cleanup;
+ }
+ JADE_LOGD("User accepted fee");
+ } else {
+ // Bitcoin: Sanity check amounts
+ uint64_t output_amount;
+ JADE_WALLY_VERIFY(wally_tx_get_total_output_satoshi(tx, &output_amount));
+ if (output_amount > input_amount) {
+ *errmsg = "Invalid input/output amounts";
+ retval = CBOR_RPC_BAD_PARAMETERS;
+ goto cleanup;
+ }
- // User to agree fee amount
- // Check to see whether user accepted or declined
- if (!show_btc_fee_confirmation_activity(
- network_id, tx, output_info, aggregate_inputs_scripts_flavour, input_amount, output_amount)) {
- *errmsg = "User declined to sign psbt";
- retval = CBOR_RPC_USER_CANCELLED;
- goto cleanup;
+ if (!show_btc_transaction_outputs_activity(network_id, tx, output_info)) {
+ *errmsg = "User declined to sign psbt";
+ retval = CBOR_RPC_USER_CANCELLED;
+ goto cleanup;
+ }
+ JADE_LOGD("User accepted outputs");
+
+ // User to agree fee amount
+ // Check to see whether user accepted or declined
+ if (!show_btc_fee_confirmation_activity(
+ network_id, tx, output_info, aggregate_inputs_scripts_flavour, input_amount, output_amount)) {
+ *errmsg = "User declined to sign psbt";
+ retval = CBOR_RPC_USER_CANCELLED;
+ goto cleanup;
+ }
+ JADE_LOGD("User accepted fee");
}
- JADE_LOGD("User accepted fee");
-
// Show warning if nothing to sign
if (!signing_flags) {
const char* message[] = { "There are no relevant", "inputs to be signed" };
Why this scored 28/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.