Validate wrapped-segwit redeem script contents
What changed, and why it matters
This update tightens a recently added safety check in the COLDCARD firmware. For a specific type of Bitcoin address (P2SH-P2WSH), the device now verifies that the redeem script provided in the transaction data exactly matches the expected wrapper around the witness script. Previously, a malformed or junk redeem script could slip past the check, causing the device to sign the transaction while being unable to reliably verify the input amount and thus the fee. That could let an attacker trick a user into approving a transaction with a hidden or excessive fee.
Users should install a COLDCARD firmware release containing this commit before signing P2SH-P2WSH multisig transactions from untrusted PSBT sources. Wallet software that builds PSBTs for COLDCARD should ensure the redeem_script field is correctly populated with the P2WSH wrapper.
Security signals we found
Fixes incomplete validation of P2SH-P2WSH redeem scripts in PSBT signing
Prevents unknown-fee transaction approval when redeem script is present but incorrect
Adds regression test for junk redeem script reaching approval UX without the fix
Raises FatalPSBTIssue to abort signing rather than warn
Changelog explicitly describes the bugfix in security-relevant terms
Evidence from the diff
The patch changes shared/psbt.py so that for AF_P2SH inputs that also have a witness_script, the redeem_script must not only be present but must equal b’\x00\x20’ + sha256(witness_script). This is the canonical P2WSH wrapper. Before, the code only checked presence of redeem_script; a present-but-wrong redeem script would still fail witness_utxo_is_provably_segwit and leave fee verification relying on a warning. The commit adds a regression test that injects a junk redeem script and confirms signing is rejected with ‘Missing/bad redeem script for input #0’.
Changed components
shared/psbt.pytesting/test_multisig.pyCOLDCARD transaction signing flow for P2SH-P2WSH / P2WSH-P2SH inputsInspect captured patch +31 / −5
### releases/Next-ChangeLog.md
@@ -8,8 +8,9 @@ This lists the new changes that have not yet been published in a normal release.
disguised with forged key-path metadata or partial signatures.
- Bugfix: Detect and abort transaction signing if a Virtual Disk firmware import
overwrites the reviewed PSBT. Thanks to Huzaifa Jawaid.
-- Bugfix: Reject malformed PSBTs containing P2SH-P2WSH inputs without a redeem
- script, preventing transactions with an unknown fee from proceeding to approval.
+- Bugfix: Reject malformed PSBTs containing P2SH-P2WSH inputs with a missing or
+ incorrect redeem script, preventing transactions with an unknown fee from
+ proceeding to approval.
- Bugfix: Restore the ability to view the device-generated seed before adding user
entropy, which was available in the previous dice-roll workflow but was inadvertently
removed in 5.6.1/1.5.1Q. The new **View TRNG Words** menu item displays the full
### shared/psbt.py
@@ -836,8 +836,12 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt, cosign_xfp=None):
redeem_script = self.get(ks)
- if self.addr_fmt == AF_P2SH and self.witness_script and not self.redeem_script:
- raise FatalPSBTIssue('Missing redeem script for input #%d' % my_idx)
+ if self.addr_fmt == AF_P2SH and self.witness_script:
+ expect = b'\x00\x20' + ngu.hash.sha256s(self.get(self.witness_script))
+ if not self.redeem_script or self.get(self.redeem_script) != expect:
+ # redeem script is the only proof the UTXO is really segwit;
+ # without it the input amount (and fee) cannot be verified
+ raise FatalPSBTIssue('Missing/bad redeem script for input #%d' % my_idx)
self.scriptSig = redeem_script
### testing/test_multisig.py
@@ -1520,7 +1520,28 @@ def remove_redeem_script(psbt):
with pytest.raises(CCProtoError) as exc:
try_sign(psbt, False)
- assert "Missing redeem script for input #0" in str(exc.value)
+ assert "Missing/bad redeem script for input #0" in str(exc.value)
+
+
+def test_junk_p2sh_p2wsh_redeem_script_rejected(clear_ms, import_ms_wallet,
+ fake_ms_txn, try_sign):
+ # present-but-wrong redeem script: input would still be signed as segwit
+ # while its amount (and the fee) cannot be verified - must be rejected
+ clear_ms()
+ M, N = 2, 3
+ keys = import_ms_wallet(M, N, addr_fmt="p2sh-p2wsh", accept=True)
+
+ def junk_redeem_script(psbt):
+ # valid shape for a P2WSH wrapper, but not sha256 of the witness script
+ psbt.inputs[0].redeem_script = b'\x00\x20' + b'\x11' * 32
+
+ psbt = fake_ms_txn(1, 1, M, keys, fee=99_000_000, inp_af=AF_P2WSH_P2SH,
+ hack_psbt=junk_redeem_script)
+
+ with pytest.raises(CCProtoError) as exc:
+ try_sign(psbt, False)
+
+ assert "Missing/bad redeem script for input #0" in str(exc.value)
@pytest.mark.veryslow
@pytest.mark.unfinalizedWhy this scored 73/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.