From 75a1f853588d28da1c91c054acb3134b307ba1be Mon Sep 17 00:00:00 2001 From: odudex Date: Fri, 28 Mar 2025 16:05:45 -0300 Subject: [PATCH] miniscript - add literal boolean operators --- src/embit/descriptor/miniscript.py | 45 +++++++++++++++++++++++++----- tests/tests/test_descriptor.py | 6 +++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/embit/descriptor/miniscript.py b/src/embit/descriptor/miniscript.py index 990e3e5..c1b3bf7 100644 --- a/src/embit/descriptor/miniscript.py +++ b/src/embit/descriptor/miniscript.py @@ -54,11 +54,25 @@ def type(self): @classmethod def read_from(cls, s, taproot=False): - op, char = read_until(s, b"(") + def wrapped(m_script): + for w in reversed(wrappers): + if w not in WRAPPER_NAMES: + raise MiniscriptError("Unknown wrapper") + WrapperCls = WRAPPERS[WRAPPER_NAMES.index(w)] + m_script = WrapperCls(m_script, taproot=taproot) + return m_script + + op, char = read_until(s, b"(,)") + if char in (b",", b")"): + s.seek(-1, 1) op = op.decode() wrappers = "" if ":" in op: wrappers, op = op.split(":") + # handle boolean literals: 0 or 1 + if op in ("0", "1"): + miniscript = JustOne() if op == "1" else JustZero() + return wrapped(miniscript) if char != b"(": raise MiniscriptError("Missing operator") if op not in OPERATOR_NAMES: @@ -67,12 +81,7 @@ def read_from(cls, s, taproot=False): MiniscriptCls = OPERATORS[OPERATOR_NAMES.index(op)] args = MiniscriptCls.read_arguments(s, taproot=taproot) miniscript = MiniscriptCls(*args, taproot=taproot) - for w in reversed(wrappers): - if w not in WRAPPER_NAMES: - raise MiniscriptError("Unknown wrapper") - WrapperCls = WRAPPERS[WRAPPER_NAMES.index(w)] - miniscript = WrapperCls(miniscript, taproot=taproot) - return miniscript + return wrapped(miniscript) @classmethod def read_arguments(cls, s, taproot=False): @@ -119,6 +128,28 @@ def len_args(self): ########### Known fragments (miniscript operators) ############## +class JustZero(Miniscript): + TYPE = "B" + PROPS = "zud" + + def inner_compile(self): + return Number(0).compile() + + def __str__(self): + return "0" + + +class JustOne(Miniscript): + TYPE = "B" + PROPS = "zu" + + def inner_compile(self): + return Number(1).compile() + + def __str__(self): + return "1" + + class OneArg(Miniscript): NARGS = 1 diff --git a/tests/tests/test_descriptor.py b/tests/tests/test_descriptor.py index de3e4a6..ff87dd1 100644 --- a/tests/tests/test_descriptor.py +++ b/tests/tests/test_descriptor.py @@ -5,6 +5,7 @@ from embit.descriptor.miniscript import OPERATORS, WRAPPERS from embit.descriptor.errors import MiniscriptError from embit.descriptor.checksum import add_checksum, DescriptorError +from embit.networks import NETWORKS from embit import ec @@ -194,10 +195,13 @@ def test_miniscript_compat(self): "wsh(andor(thresh(1,pk(xpub6BaZSKgpaVvibu2k78QsqeDWXp92xLHZxiu1WoqLB9hKhsBf3miBUDX7PJLgSPvkj66ThVHTqdnbXpeu8crXFmDUd4HeM4s4miQS2xsv3Qb/*)),and_v(v:multi(2,03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a,0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce),older(6)),thresh(2,pkh(xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/*),a:pkh(xpub6AaffFGfH6WXfm6pwWzmUMuECQnoLeB3agMKaLyEBZ5ZVfwtnS5VJKqXBt8o5ooCWVy2H87GsZshp7DeKE25eWLyd1Ccuh2ZubQUkgpiVux/*))))#76jsyzdg", "wsh(or_d(pk([40259ab7/48'/1'/0'/2']tpubDFcY6nTiMAWBd5d2bS8JZvjcaLjC6GE6XnPAJAPUkVj5wa5Pyb4gumx1ZWvnXQ8tmorCmpAyai69K9hD2mGQUeNkuXfjztsfqnE5FMk1CCh/<0;1>/*),and_v(v:pkh([842a626e/48'/1'/0'/2']tpubDENBboujRvpkS8SgZsrpqG2BCUBoaAc4c57jHFe1NwKAtfVjDZDUadQKYv4pkAEF2afPv6TtQ2BoYFJAPLbuKpL1usiySERZekGo4JmnWhh/<0;1>/*),older(65535))))#deguz53x", "wsh(or_d(pk([40259ab7/48h/1h/0h/2h]tpubDFcY6nTiMAWBd5d2bS8JZvjcaLjC6GE6XnPAJAPUkVj5wa5Pyb4gumx1ZWvnXQ8tmorCmpAyai69K9hD2mGQUeNkuXfjztsfqnE5FMk1CCh/<0;1>/*),and_v(v:pkh([842a626e/48H/1H/0H/2H]tpubDENBboujRvpkS8SgZsrpqG2BCUBoaAc4c57jHFe1NwKAtfVjDZDUadQKYv4pkAEF2afPv6TtQ2BoYFJAPLbuKpL1usiySERZekGo4JmnWhh/<0;1>/*),older(65535))))#deguz53x", + "wsh(and_v(v:0,and_v(v:pk([40259ab7/48h/1h/0h/2h]tpubDFcY6nTiMAWBd5d2bS8JZvjcaLjC6GE6XnPAJAPUkVj5wa5Pyb4gumx1ZWvnXQ8tmorCmpAyai69K9hD2mGQUeNkuXfjztsfqnE5FMk1CCh/<0;1>/*),pk([40259ab7/48h/1h/0h/2h]tpubDFcY6nTiMAWBd5d2bS8JZvjcaLjC6GE6XnPAJAPUkVj5wa5Pyb4gumx1ZWvnXQ8tmorCmpAyai69K9hD2mGQUeNkuXfjztsfqnE5FMk1CCh/<0;1>/*))))", + "wsh(and_v(v:0,and_v(v:0,0)))", ] for desc in generalistic_descs: - Descriptor.from_string(desc) + desc = Descriptor.from_string(desc) + desc.derive(0, 0).address(network=NETWORKS["main"]) def test_len(self): """Checks that len(miniscript) returns correct length"""