Delete stale _zero_outputs_mutator; reduced code duplication
What changed, and why it matters
This commit is a routine code cleanup. It removes an unused test helper that set Bitcoin output amounts to zero, and consolidates duplicate placeholder-parsing code into a shared utility module. There is no indication it fixes or introduces a security vulnerability.
No security action required. Treat as normal refactoring/technical-debt cleanup.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff deletes _zero_outputs_mutator from a developer playground script (a test-only mutator that created high-fee PSBTs for manual testing). It also removes a local copy of PlainKeyPlaceholder, Musig2KeyPlaceholder, KeyPlaceholder, parse_placeholder, and extract_placeholders from test_utils/musig2.py, replacing them with imports from test_utils.wallet_policy. Class references are updated from Musig2KeyPlaceholder to MuSig2KeyPlaceholder to match the shared module’s naming. The changes are confined to test/development tooling.
Changed components
dev-tools/playground/presets.pytest_utils/musig2.pytests/test_sign_psbt_musig.pyInspect captured patch +14 / −66
diff --git a/dev-tools/playground/presets.py b/dev-tools/playground/presets.py
index 5a45d56..084526e 100644
--- a/dev-tools/playground/presets.py
+++ b/dev-tools/playground/presets.py
@@ -230,14 +230,6 @@ def _huge_fee_mutator(psbt: PSBT) -> PSBT:
return psbt
-def _zero_outputs_mutator(psbt: PSBT) -> PSBT:
- """Set every output value to zero — exercises the device's zero-amount
- handling and (via the resulting massive fee) the high-fee warning."""
- for vout in psbt.tx.vout:
- vout.nValue = 0
- return psbt
-
-
# Sign-psbt-only scenario presets. These reuse simple wallet policies but
# mutate the generated fake PSBT to put the device in an interesting state.
SIGN_PSBT_SCENARIO_PRESETS: List[PolicyPreset] = [
diff --git a/test_utils/musig2.py b/test_utils/musig2.py
index bb0a001..68e8d19 100644
--- a/test_utils/musig2.py
+++ b/test_utils/musig2.py
@@ -22,7 +22,6 @@ from io import BytesIO
import re
from re import Match
-from dataclasses import dataclass
import secrets
import struct
from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union
@@ -40,6 +39,12 @@ import base58
from test_utils.taproot_sighash import SIGHASH_DEFAULT, TaprootSignatureHash
from test_utils import bip0327, bip0340, hash160, sha256
from test_utils import taproot
+from test_utils.wallet_policy import (
+ PlainKeyPlaceholder,
+ MuSig2KeyPlaceholder,
+ KeyPlaceholder,
+ extract_placeholders,
+)
from bitcoin_client.ledger_bitcoin.embit.descriptor.miniscript import Miniscript
from bitcoin_client.ledger_bitcoin.psbt import PSBT, PartiallySignedInput
@@ -59,55 +64,6 @@ def tapleaf_hash(script: Optional[bytes], leaf_version=b'\xC0') -> Optional[byte
)
-@dataclass
-class PlainKeyPlaceholder:
- key_index: int
- num1: int
- num2: int
-
-
-@dataclass
-class Musig2KeyPlaceholder:
- key_indexes: List[int]
- num1: int
- num2: int
-
-
-KeyPlaceholder = Union[PlainKeyPlaceholder, Musig2KeyPlaceholder]
-
-
-def parse_placeholder(placeholder_str: str) -> KeyPlaceholder:
- """Parses a placeholder string to create a KeyPlaceholder object."""
- if placeholder_str.startswith('musig'):
- key_indexes_str = placeholder_str[6:placeholder_str.index(
- ')/<')].split(',')
- key_indexes = [int(index.strip('@')) for index in key_indexes_str]
-
- nums_part = placeholder_str[placeholder_str.index(')/<') + 3:-3]
- num1, num2 = map(int, nums_part.split(';'))
-
- return Musig2KeyPlaceholder(key_indexes, num1, num2)
- elif placeholder_str.startswith('@'):
- parts = placeholder_str.split('/')
- key_index = int(parts[0].strip('@'))
-
- # Remove '<' from the start and '>' from the end
- nums_part = parts[1][1:-1]
- num1, num2 = map(int, nums_part.split(';'))
-
- return PlainKeyPlaceholder(key_index, num1, num2)
- else:
- raise ValueError("Invalid placeholder string")
-
-
-def extract_placeholders(desc_tmpl: str) -> List[KeyPlaceholder]:
- """Extracts and parses all placeholders in a descriptor template, from left to right."""
-
- pattern = r'musig\((?:@\d+,)*(?:@\d+)\)/<\d+;\d+>/\*|@\d+/<\d+;\d+>/\*'
- matches = [(match.group(), match.start())
- for match in re.finditer(pattern, desc_tmpl)]
- sorted_matches = sorted(matches, key=lambda x: x[1])
- return [parse_placeholder(match[0]) for match in sorted_matches]
def unsorted_musig(pubkeys: Iterable[bytes], version_bytes: bytes) -> Tuple[str, bip0327.KeyAggContext]:
@@ -336,7 +292,7 @@ class TrDescriptorTemplate:
self.consume(';')
num2 = self.parse_num()
self.consume('>/*')
- return Musig2KeyPlaceholder(key_indexes, num1, num2)
+ return MuSig2KeyPlaceholder(key_indexes, num1, num2)
else:
raise Exception("Syntax error in key placeholder")
@@ -434,7 +390,7 @@ class PsbtMusig2Cosigner(ABC):
pass
-def find_change_and_addr_index_for_musig(input_psbt: PartiallySignedInput, placeholder: Musig2KeyPlaceholder, agg_xpub: ExtendedKey):
+def find_change_and_addr_index_for_musig(input_psbt: PartiallySignedInput, placeholder: MuSig2KeyPlaceholder, agg_xpub: ExtendedKey):
num1, num2 = placeholder.num1, placeholder.num2
agg_xpub_fingerprint = hash160(agg_xpub.pubkey)[0:4]
@@ -498,7 +454,7 @@ def get_bip32_tweaks(ext_key: ExtendedKey, steps: List[int]) -> List[bytes]:
def process_placeholder(
wallet_policy: WalletPolicy,
psbt_input: PartiallySignedInput,
- placeholder: Musig2KeyPlaceholder,
+ placeholder: MuSig2KeyPlaceholder,
keyagg_ctx: bip0327.KeyAggContext,
agg_xpub: ExtendedKey,
tapleaf_desc: Optional[str],
@@ -600,7 +556,7 @@ class HotMusig2Cosigner(PsbtMusig2Cosigner):
rand_seed = secrets.token_bytes(32)
for placeholder_index, (placeholder, tapleaf_desc) in enumerate(desc_tmpl.placeholders()):
- if not isinstance(placeholder, Musig2KeyPlaceholder):
+ if not isinstance(placeholder, MuSig2KeyPlaceholder):
continue
agg_xpub_str, keyagg_ctx = aggregate_musig_pubkey(
@@ -658,7 +614,7 @@ class HotMusig2Cosigner(PsbtMusig2Cosigner):
"No musig signing session for this psbt")
for placeholder_index, (placeholder, tapleaf_desc) in enumerate(desc_tmpl.placeholders()):
- if not isinstance(placeholder, Musig2KeyPlaceholder):
+ if not isinstance(placeholder, MuSig2KeyPlaceholder):
continue
agg_xpub_str, keyagg_ctx = aggregate_musig_pubkey(
@@ -790,7 +746,7 @@ def run_musig2_test(wallet_policy: WalletPolicy, psbt: PSBT, cosigners: List[Psb
wallet_policy.descriptor_template)
for placeholder, tapleaf_desc in desc_tmpl.placeholders():
- if not isinstance(placeholder, Musig2KeyPlaceholder):
+ if not isinstance(placeholder, MuSig2KeyPlaceholder):
continue
agg_xpub_str, keyagg_ctx = aggregate_musig_pubkey(
diff --git a/tests/test_sign_psbt_musig.py b/tests/test_sign_psbt_musig.py
index bde7ac2..2f120ab 100644
--- a/tests/test_sign_psbt_musig.py
+++ b/tests/test_sign_psbt_musig.py
@@ -15,7 +15,7 @@ from ragger.firmware import Firmware
from ledger_bitcoin.wallet import WalletPolicy
from ragger_bitcoin import RaggerClient
from test_utils import SpeculosGlobals, bip0327
-from test_utils.musig2 import HotMusig2Cosigner, Musig2KeyPlaceholder, PsbtMusig2Cosigner, TrDescriptorTemplate, run_musig2_test
+from test_utils.musig2 import HotMusig2Cosigner, MuSig2KeyPlaceholder, PsbtMusig2Cosigner, TrDescriptorTemplate, run_musig2_test
from .instructions import *
tests_root: Path = Path(__file__).parent
@@ -46,7 +46,7 @@ class LedgerMusig2Cosigner(PsbtMusig2Cosigner):
self.pubkey = None
for _, (placeholder, _) in enumerate(desc_tmpl.placeholders()):
- if not isinstance(placeholder, Musig2KeyPlaceholder):
+ if not isinstance(placeholder, MuSig2KeyPlaceholder):
continue
for i in placeholder.key_indexes:
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.