"""Helpers for working with minimal SortedMulti descriptor templates.""" from __future__ import annotations from dataclasses import dataclass from typing import Sequence @dataclass(frozen=True) class SortedMultiDescriptorTemplate: """Minimal representation of a ``wsh(sortedmulti(m, ...))`` descriptor.""" threshold: int def witness_script(self, tweaked_keys: Sequence[bytes]) -> bytes: """Return the witness script for ``wsh(sortedmulti(threshold, tweaked_keys))``.""" if not tweaked_keys: raise ValueError("sortedmulti requires at least one key") if not 1 <= self.threshold <= len(tweaked_keys): raise ValueError("threshold must satisfy 1 <= m <= n") for key in tweaked_keys: if len(key) != 33: raise ValueError("sortedmulti keys must be 33-byte compressed pubkeys") sorted_keys = sorted(tweaked_keys) script = bytearray() script.append(_op_n(self.threshold)) for key in sorted_keys: script.append(len(key)) script.extend(key) script.append(_op_n(len(sorted_keys))) script.append(0xAE) # OP_CHECKMULTISIG return bytes(script) def _op_n(value: int) -> int: if not 0 <= value <= 16: raise ValueError("OP_N value out of range") if value == 0: return 0x00 return 0x50 + value