Update txmaker to allow creating transactions with external inputs
What changed, and why it matters
This commit only changes a test helper script (txmaker.py) used to build fake Bitcoin transactions for automated tests. It adds the ability to mark some inputs as 'external' (not owned by the wallet being tested) so the test suite can exercise transaction signing with mixed wallet and non-wallet inputs. There is no change to the actual Ledger app firmware or its security logic.
No security action required. Review as normal test-code change if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff modifies test_utils/txmaker.py to extend createPsbt() with an optional input_is_external parameter. When set, the helper generates synthetic P2WPKH witness UTXOs from random keys instead of deriving them from the wallet policy, and omits key-derivation information so the PSBT input is treated as external. This is purely a test-infrastructure enhancement to broaden test coverage for external-input scenarios; no production code is touched.
Changed components
test_utils/txmaker.pyInspect captured patch +35 / −3
diff --git a/test_utils/txmaker.py b/test_utils/txmaker.py
index 46bfc89..8e08f48 100644
--- a/test_utils/txmaker.py
+++ b/test_utils/txmaker.py
@@ -397,19 +397,44 @@ def fill_inout(wallet_policy: WalletPolicy, inout: Union[PartiallySignedInput, P
derived_key_origin = KeyOriginInfo(fpr, placeholder_der_subpath)
inout.hd_keypaths[derived_pubkey.pubkey] = derived_key_origin
-def createPsbt(wallet_policy: WalletPolicy, input_amounts: List[int], output_amounts: List[int], output_is_change: List[bool]) -> PSBT:
+def createPsbt(wallet_policy: WalletPolicy, input_amounts: List[int], output_amounts: List[int], output_is_change: List[bool], input_is_external: Optional[List[bool]] = None) -> PSBT:
assert len(output_amounts) == len(output_is_change)
assert sum(output_amounts) <= sum(input_amounts)
+ # input_is_external marks inputs the wallet policy does NOT own: they get a
+ # foreign prevout and no key-derivation info, so the app treats them as
+ # external. Defaults to all-internal (the historical behavior).
+ if input_is_external is None:
+ input_is_external = [False] * len(input_amounts)
+ assert len(input_is_external) == len(input_amounts)
+
vin: List[CTxIn] = [CTxIn() for _ in input_amounts]
vout: List[CTxOut] = [CTxOut() for _ in output_amounts]
- # create some credible prevout transactions
- prevouts: List[CTransaction] = []
+ # create some credible prevout transactions. For external inputs there is
+ # no wallet-derived prevout: external_utxos[i] holds a synthetic foreign
+ # witness UTXO, and the internal-only parallel lists get None placeholders
+ # to stay index-aligned.
+ prevouts: List[Optional[CTransaction]] = []
prevout_ns: List[int] = []
prevout_path_change: List[int] = []
prevout_path_addr_idx: List[int] = []
+ external_utxos: List[Optional[CTxOut]] = []
for i, prevout_amount in enumerate(input_amounts):
+ if input_is_external[i]:
+ # A P2WPKH to a random key: a valid, standard scriptPubKey that
+ # does not derive from the wallet policy.
+ external_utxos.append(CTxOut(prevout_amount, b"\x00\x14" + random_bytes(20)))
+ prevouts.append(None)
+ prevout_ns.append(0)
+ prevout_path_change.append(0)
+ prevout_path_addr_idx.append(0)
+
+ vin[i].prevout = COutPoint(uint256_from_str(random_txid()), 0)
+ vin[i].scriptSig = b''
+ vin[i].nSequence = 0
+ continue
+
n_inputs = randint(1, 10)
n_outputs = randint(1, 10)
prevout, idx, is_change, addr_idx = createFakeWalletTransaction(
@@ -418,6 +443,7 @@ def createPsbt(wallet_policy: WalletPolicy, input_amounts: List[int], output_amo
prevout_ns.append(idx)
prevout_path_change.append(is_change)
prevout_path_addr_idx.append(addr_idx)
+ external_utxos.append(None)
vin[i].prevout = COutPoint(prevout.sha256, idx)
vin[i].scriptSig = b''
@@ -465,6 +491,12 @@ def createPsbt(wallet_policy: WalletPolicy, input_amounts: List[int], output_amo
wallet_policy.descriptor_template)
for input_index, input in enumerate(psbt.inputs):
+ if input_is_external[input_index]:
+ # External input: only the (foreign) witness UTXO, so its amount is
+ # known, but deliberately no derivation info => seen as external.
+ input.witness_utxo = external_utxos[input_index]
+ continue
+
if desc_tmpl.is_segwit():
# add witness UTXO
input.witness_utxo = prevouts[input_index].vout[prevout_ns[input_index]]
Why this scored 15/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.