1156 lines
30 KiB
Python
1156 lines
30 KiB
Python
# (c) Copyright 2020 by Stepan Snigirev, see <https://github.com/diybitcoinhardware/embit/blob/master/LICENSE>
|
|
#
|
|
# Changes (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
|
#
|
|
import ngu
|
|
from binascii import unhexlify as a2b_hex
|
|
from binascii import hexlify as b2a_hex
|
|
from serializations import ser_compact_size
|
|
from desc_utils import Key, read_until
|
|
from public_constants import MAX_TR_SIGNERS
|
|
|
|
|
|
class Number:
|
|
def __init__(self, num):
|
|
self.num = num
|
|
|
|
@classmethod
|
|
def read_from(cls, s, taproot=False):
|
|
num = 0
|
|
char = s.read(1)
|
|
while char in b"0123456789":
|
|
num = 10 * num + int(char.decode())
|
|
char = s.read(1)
|
|
s.seek(-1, 1)
|
|
return cls(num)
|
|
|
|
def compile(self):
|
|
if self.num == 0:
|
|
return b"\x00"
|
|
if self.num <= 16:
|
|
return bytes([80 + self.num])
|
|
b = self.num.to_bytes(32, "little").rstrip(b"\x00")
|
|
if b[-1] >= 128:
|
|
b += b"\x00"
|
|
return bytes([len(b)]) + b
|
|
|
|
def __len__(self):
|
|
return len(self.compile())
|
|
|
|
def to_string(self, *args, **kwargs):
|
|
return "%d" % self.num
|
|
|
|
|
|
class KeyHash(Key):
|
|
@classmethod
|
|
def parse_key(cls, k: bytes, *args, **kwargs):
|
|
# convert to string
|
|
kd = k.decode()
|
|
# raw 20-byte hash
|
|
if len(kd) == 40:
|
|
return kd, None
|
|
return super().parse_key(k, *args, **kwargs)
|
|
|
|
def serialize(self, *args, **kwargs):
|
|
start = 1 if self.taproot else 0
|
|
return ngu.hash.hash160(self.node.pubkey()[start:33])
|
|
|
|
def __len__(self):
|
|
return 21 # <20:pkh>
|
|
|
|
def compile(self):
|
|
d = self.serialize()
|
|
return ser_compact_size(len(d)) + d
|
|
|
|
|
|
class Raw:
|
|
def __init__(self, raw):
|
|
if len(raw) != self.LEN * 2:
|
|
raise ValueError("Invalid raw element length: %d" % len(raw))
|
|
self.raw = a2b_hex(raw)
|
|
|
|
@classmethod
|
|
def read_from(cls, s, taproot=False):
|
|
return cls(s.read(2 * cls.LEN).decode())
|
|
|
|
def to_string(self, *args, **kwargs):
|
|
return b2a_hex(self.raw).decode()
|
|
|
|
def compile(self):
|
|
return ser_compact_size(len(self.raw)) + self.raw
|
|
|
|
def __len__(self):
|
|
return len(ser_compact_size(self.LEN)) + self.LEN
|
|
|
|
|
|
class Raw32(Raw):
|
|
LEN = 32
|
|
def __len__(self):
|
|
return 33
|
|
|
|
|
|
class Raw20(Raw):
|
|
LEN = 20
|
|
def __len__(self):
|
|
return 21
|
|
|
|
|
|
class Miniscript:
|
|
def __init__(self, *args, **kwargs):
|
|
self.args = args
|
|
self.taproot = kwargs.get("taproot", False)
|
|
|
|
def compile(self):
|
|
return self.inner_compile()
|
|
|
|
def verify(self):
|
|
for arg in self.args:
|
|
if isinstance(arg, Miniscript):
|
|
arg.verify()
|
|
|
|
@property
|
|
def keys(self):
|
|
res = []
|
|
for arg in self.args:
|
|
if isinstance(arg, Miniscript):
|
|
res += arg.keys
|
|
elif isinstance(arg, Key): # KeyHash is subclass of Key
|
|
res.append(arg)
|
|
return res
|
|
|
|
def is_sane(self, taproot=False):
|
|
err = "multi mixin"
|
|
forbiden = (Sortedmulti, Multi) if taproot else (Sortedmulti_a, Multi_a)
|
|
assert type(self) not in forbiden, err
|
|
|
|
for arg in self.args:
|
|
assert type(arg) not in forbiden, err
|
|
if isinstance(arg, Miniscript):
|
|
arg.is_sane(taproot=taproot)
|
|
|
|
@staticmethod
|
|
def key_derive(key, idx, key_map=None, change=False):
|
|
if key_map and key in key_map:
|
|
kd = key_map[key]
|
|
else:
|
|
kd = key.derive(idx, change=change)
|
|
return kd
|
|
|
|
def derive(self, idx, key_map=None, change=False):
|
|
args = []
|
|
for arg in self.args:
|
|
if isinstance(arg, Key): # KeyHash is subclass of Key
|
|
arg = self.key_derive(arg, idx, key_map, change=change)
|
|
elif hasattr(arg, "derive"):
|
|
arg = arg.derive(idx, key_map, change)
|
|
|
|
args.append(arg)
|
|
return type(self)(*args)
|
|
|
|
@property
|
|
def properties(self):
|
|
return self.PROPS
|
|
|
|
@property
|
|
def type(self):
|
|
return self.TYPE
|
|
|
|
@classmethod
|
|
def read_from(cls, s, taproot=False):
|
|
op, char = read_until(s, b"(")
|
|
op = op.decode()
|
|
wrappers = ""
|
|
if ":" in op:
|
|
wrappers, op = op.split(":")
|
|
if char != b"(":
|
|
raise ValueError("Missing operator")
|
|
if op not in OPERATOR_NAMES:
|
|
raise ValueError("Unknown operator '%s'" % op)
|
|
# number of arguments, classes of arguments, compile function, type, validity checker
|
|
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 ValueError("Unknown wrapper %s" % w)
|
|
WrapperCls = WRAPPERS[WRAPPER_NAMES.index(w)]
|
|
miniscript = WrapperCls(miniscript, taproot=taproot)
|
|
return miniscript
|
|
|
|
@classmethod
|
|
def read_arguments(cls, s, taproot=False):
|
|
args = []
|
|
if cls.NARGS is None:
|
|
if type(cls.ARGCLS) == tuple:
|
|
firstcls, nextcls = cls.ARGCLS
|
|
else:
|
|
firstcls, nextcls = cls.ARGCLS, cls.ARGCLS
|
|
|
|
args.append(firstcls.read_from(s, taproot=taproot))
|
|
while True:
|
|
char = s.read(1)
|
|
if char == b",":
|
|
args.append(nextcls.read_from(s, taproot=taproot))
|
|
elif char == b")":
|
|
break
|
|
else:
|
|
raise ValueError(
|
|
"Expected , or ), got: %s" % (char + s.read())
|
|
)
|
|
else:
|
|
for i in range(cls.NARGS):
|
|
args.append(cls.ARGCLS.read_from(s, taproot=taproot))
|
|
if i < cls.NARGS - 1:
|
|
char = s.read(1)
|
|
if char != b",":
|
|
raise ValueError("Missing arguments, %s" % char)
|
|
char = s.read(1)
|
|
if char != b")":
|
|
raise ValueError("Expected ) got %s" % (char + s.read()))
|
|
return args
|
|
|
|
def to_string(self, external=True, internal=True):
|
|
# meh
|
|
res = type(self).NAME + "("
|
|
res += ",".join([
|
|
arg.to_string(external, internal)
|
|
for arg in self.args
|
|
])
|
|
res += ")"
|
|
return res
|
|
|
|
def __len__(self):
|
|
"""Length of the compiled script, override this if you know the length"""
|
|
return len(self.compile())
|
|
|
|
def len_args(self):
|
|
return sum([len(arg) for arg in self.args])
|
|
|
|
########### Known fragments (miniscript operators) ##############
|
|
|
|
|
|
class OneArg(Miniscript):
|
|
NARGS = 1
|
|
# small handy functions
|
|
@property
|
|
def arg(self):
|
|
return self.args[0]
|
|
|
|
@property
|
|
def carg(self):
|
|
return self.arg.compile()
|
|
|
|
|
|
class PkK(OneArg):
|
|
# <key>
|
|
NAME = "pk_k"
|
|
ARGCLS = Key
|
|
TYPE = "K"
|
|
PROPS = "ondu"
|
|
|
|
def inner_compile(self):
|
|
return self.carg
|
|
|
|
def __len__(self):
|
|
return self.len_args()
|
|
|
|
|
|
class PkH(OneArg):
|
|
# DUP HASH160 <HASH160(key)> EQUALVERIFY
|
|
NAME = "pk_h"
|
|
ARGCLS = KeyHash
|
|
TYPE = "K"
|
|
PROPS = "ndu"
|
|
|
|
def inner_compile(self):
|
|
return b"\x76\xa9" + self.carg + b"\x88"
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 3
|
|
|
|
class After(OneArg):
|
|
# <n> CHECKLOCKTIMEVERIFY
|
|
NAME = "after"
|
|
ARGCLS = Number
|
|
TYPE = "B"
|
|
PROPS = "z"
|
|
|
|
def inner_compile(self):
|
|
return self.carg + b"\xb1"
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
assert 0 < self.arg.num < 0x80000000, "%s out of range [1, 2147483647]" % self.NAME
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 1
|
|
|
|
class Older(OneArg):
|
|
# <n> CHECKSEQUENCEVERIFY
|
|
NAME = "older"
|
|
ARGCLS = Number
|
|
TYPE = "B"
|
|
PROPS = "z"
|
|
|
|
def inner_compile(self):
|
|
return self.carg + b"\xb2"
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
# not consensus valid
|
|
# https://github.com/bitcoin/bitcoin/pull/33135 older(65536) is equivalent to older(1)
|
|
if self.arg.num & (1 << 22):
|
|
# time-based
|
|
assert 0x400000 < self.arg.num < 0x410000, "Time-based %s out of range [4194305, 4259839]" % self.NAME
|
|
else:
|
|
# block-based
|
|
assert 0 < self.arg.num < 0x10000, "Block-based %s out of range [1, 65535]" % self.NAME
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 1
|
|
|
|
class Sha256(OneArg):
|
|
# SIZE <32> EQUALVERIFY SHA256 <h> EQUAL
|
|
NAME = "sha256"
|
|
ARGCLS = Raw32
|
|
TYPE = "B"
|
|
PROPS = "ondu"
|
|
|
|
def inner_compile(self):
|
|
# Number(32).compile() --> b"\x01\x20"
|
|
return b"\x82\x01\x20\x88\xa8" + self.carg + b"\x87"
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 6
|
|
|
|
class Hash256(Sha256):
|
|
# SIZE <32> EQUALVERIFY HASH256 <h> EQUAL
|
|
NAME = "hash256"
|
|
|
|
def inner_compile(self):
|
|
# Number(32).compile() --> b"\x01\x20"
|
|
return b"\x82\x01\x20\x88\xaa" + self.carg + b"\x87"
|
|
|
|
|
|
class Ripemd160(Sha256):
|
|
# SIZE <32> EQUALVERIFY RIPEMD160 <h> EQUAL
|
|
NAME = "ripemd160"
|
|
ARGCLS = Raw20
|
|
|
|
def inner_compile(self):
|
|
# Number(32).compile() --> b"\x01\x20"
|
|
return b"\x82\x01\x20\x88\xa6" + self.carg + b"\x87"
|
|
|
|
|
|
class Hash160(Ripemd160):
|
|
# SIZE <32> EQUALVERIFY HASH160 <h> EQUAL
|
|
NAME = "hash160"
|
|
|
|
def inner_compile(self):
|
|
# Number(32).compile() --> b"\x01\x20"
|
|
return b"\x82\x01\x20\x88\xa9" + self.carg + b"\x87"
|
|
|
|
|
|
class AndOr(Miniscript):
|
|
# [X] NOTIF [Z] ELSE [Y] ENDIF
|
|
NAME = "andor"
|
|
NARGS = 3
|
|
ARGCLS = Miniscript
|
|
|
|
@property
|
|
def type(self):
|
|
# type same as Y/Z
|
|
return self.args[1].type
|
|
|
|
def verify(self):
|
|
# requires: X is Bdu; Y and Z are both B, K, or V
|
|
super().verify()
|
|
if self.args[0].type != "B":
|
|
raise ValueError("andor: X should be 'B'")
|
|
px = self.args[0].properties
|
|
if "d" not in px and "u" not in px:
|
|
raise ValueError("andor: X should be 'du'")
|
|
if self.args[1].type != self.args[2].type:
|
|
raise ValueError("andor: Y and Z should have the same types")
|
|
if self.args[1].type not in "BKV":
|
|
raise ValueError("andor: Y and Z should be B K or V")
|
|
|
|
@property
|
|
def properties(self):
|
|
# props: z=zXzYzZ; o=zXoYoZ or oXzYzZ; u=uYuZ; d=dZ
|
|
props = ""
|
|
px, py, pz = [arg.properties for arg in self.args]
|
|
if "z" in px and "z" in py and "z" in pz:
|
|
props += "z"
|
|
if ("z" in px and "o" in py and "o" in pz) or (
|
|
"o" in px and "z" in py and "z" in pz
|
|
):
|
|
props += "o"
|
|
if "u" in py and "u" in pz:
|
|
props += "u"
|
|
if "d" in pz:
|
|
props += "d"
|
|
return props
|
|
|
|
def inner_compile(self):
|
|
return (
|
|
self.args[0].compile()
|
|
+ b"\x64"
|
|
+ self.args[2].compile()
|
|
+ b"\x67"
|
|
+ self.args[1].compile()
|
|
+ b"\x68"
|
|
)
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 3
|
|
|
|
class AndV(Miniscript):
|
|
# [X] [Y]
|
|
NAME = "and_v"
|
|
NARGS = 2
|
|
ARGCLS = Miniscript
|
|
|
|
def inner_compile(self):
|
|
return self.args[0].compile() + self.args[1].compile()
|
|
|
|
def __len__(self):
|
|
return self.len_args()
|
|
|
|
def verify(self):
|
|
# X is V; Y is B, K, or V
|
|
super().verify()
|
|
if self.args[0].type != "V":
|
|
raise ValueError("and_v: X should be 'V'")
|
|
if self.args[1].type not in "BKV":
|
|
raise ValueError("and_v: Y should be B K or V")
|
|
|
|
@property
|
|
def type(self):
|
|
# same as Y
|
|
return self.args[1].type
|
|
|
|
@property
|
|
def properties(self):
|
|
# z=zXzY; o=zXoY or zYoX; n=nX or zXnY; u=uY
|
|
px, py = [arg.properties for arg in self.args]
|
|
props = ""
|
|
if "z" in px and "z" in py:
|
|
props += "z"
|
|
if ("z" in px and "o" in py) or ("z" in py and "o" in px):
|
|
props += "o"
|
|
if "n" in px or ("z" in px and "n" in py):
|
|
props += "n"
|
|
if "u" in py:
|
|
props += "u"
|
|
return props
|
|
|
|
|
|
class AndB(Miniscript):
|
|
# [X] [Y] BOOLAND
|
|
NAME = "and_b"
|
|
NARGS = 2
|
|
ARGCLS = Miniscript
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return self.args[0].compile() + self.args[1].compile() + b"\x9a"
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 1
|
|
|
|
def verify(self):
|
|
# X is B; Y is W
|
|
super().verify()
|
|
if self.args[0].type != "B":
|
|
raise ValueError("and_b: X should be B")
|
|
if self.args[1].type != "W":
|
|
raise ValueError("and_b: Y should be W")
|
|
|
|
@property
|
|
def properties(self):
|
|
# z=zXzY; o=zXoY or zYoX; n=nX or zXnY; d=dXdY; u
|
|
px, py = [arg.properties for arg in self.args]
|
|
props = ""
|
|
if "z" in px and "z" in py:
|
|
props += "z"
|
|
if ("z" in px and "o" in py) or ("z" in py and "o" in px):
|
|
props += "o"
|
|
if "n" in px or ("z" in px and "n" in py):
|
|
props += "n"
|
|
if "d" in px and "d" in py:
|
|
props += "d"
|
|
props += "u"
|
|
return props
|
|
|
|
|
|
class AndN(Miniscript):
|
|
# [X] NOTIF 0 ELSE [Y] ENDIF
|
|
# andor(X,Y,0)
|
|
NAME = "and_n"
|
|
NARGS = 2
|
|
ARGCLS = Miniscript
|
|
|
|
def inner_compile(self):
|
|
return (
|
|
self.args[0].compile()
|
|
+ b"\x64\x00\x67"
|
|
+ self.args[1].compile()
|
|
+ b"\x68"
|
|
)
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 4
|
|
|
|
@property
|
|
def type(self):
|
|
# type same as Y/Z
|
|
return self.args[1].type
|
|
|
|
def verify(self):
|
|
# requires: X is Bdu; Y and Z are both B, K, or V
|
|
super().verify()
|
|
if self.args[0].type != "B":
|
|
raise ValueError("and_n: X should be 'B'")
|
|
px = self.args[0].properties
|
|
if "d" not in px and "u" not in px:
|
|
raise ValueError("and_n: X should be 'du'")
|
|
if self.args[1].type != "B":
|
|
raise ValueError("and_n: Y should be B")
|
|
|
|
@property
|
|
def properties(self):
|
|
# props: z=zXzYzZ; o=zXoYoZ or oXzYzZ; u=uYuZ; d=dZ
|
|
props = ""
|
|
px, py = [arg.properties for arg in self.args]
|
|
pz = "zud"
|
|
if "z" in px and "z" in py and "z" in pz:
|
|
props += "z"
|
|
if ("z" in px and "o" in py and "o" in pz) or (
|
|
"o" in px and "z" in py and "z" in pz
|
|
):
|
|
props += "o"
|
|
if "u" in py and "u" in pz:
|
|
props += "u"
|
|
if "d" in pz:
|
|
props += "d"
|
|
return props
|
|
|
|
|
|
class OrB(Miniscript):
|
|
# [X] [Z] BOOLOR
|
|
NAME = "or_b"
|
|
NARGS = 2
|
|
ARGCLS = Miniscript
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return self.args[0].compile() + self.args[1].compile() + b"\x9b"
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 1
|
|
|
|
def verify(self):
|
|
# X is Bd; Z is Wd
|
|
super().verify()
|
|
if self.args[0].type != "B":
|
|
raise ValueError("or_b: X should be B")
|
|
if "d" not in self.args[0].properties:
|
|
raise ValueError("or_b: X should be d")
|
|
if self.args[1].type != "W":
|
|
raise ValueError("or_b: Z should be W")
|
|
if "d" not in self.args[1].properties:
|
|
raise ValueError("or_b: Z should be d")
|
|
|
|
@property
|
|
def properties(self):
|
|
# z=zXzZ; o=zXoZ or zZoX; d; u
|
|
props = ""
|
|
px, pz = [arg.properties for arg in self.args]
|
|
if "z" in px and "z" in pz:
|
|
props += "z"
|
|
if ("z" in px and "o" in pz) or ("z" in pz and "o" in px):
|
|
props += "o"
|
|
props += "du"
|
|
return props
|
|
|
|
|
|
class OrC(Miniscript):
|
|
# [X] NOTIF [Z] ENDIF
|
|
NAME = "or_c"
|
|
NARGS = 2
|
|
ARGCLS = Miniscript
|
|
TYPE = "V"
|
|
|
|
def inner_compile(self):
|
|
return self.args[0].compile() + b"\x64" + self.args[1].compile() + b"\x68"
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 2
|
|
|
|
def verify(self):
|
|
# X is Bdu; Z is V
|
|
super().verify()
|
|
if self.args[0].type != "B":
|
|
raise ValueError("or_c: X should be B")
|
|
if self.args[1].type != "V":
|
|
raise ValueError("or_c: Z should be V")
|
|
px = self.args[0].properties
|
|
if "d" not in px or "u" not in px:
|
|
raise ValueError("or_c: X should be du")
|
|
|
|
@property
|
|
def properties(self):
|
|
# z=zXzZ; o=oXzZ
|
|
props = ""
|
|
px, pz = [arg.properties for arg in self.args]
|
|
if "z" in px and "z" in pz:
|
|
props += "z"
|
|
if "o" in px and "z" in pz:
|
|
props += "o"
|
|
return props
|
|
|
|
|
|
class OrD(Miniscript):
|
|
# [X] IFDUP NOTIF [Z] ENDIF
|
|
NAME = "or_d"
|
|
NARGS = 2
|
|
ARGCLS = Miniscript
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return self.args[0].compile() + b"\x73\x64" + self.args[1].compile() + b"\x68"
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 3
|
|
|
|
def verify(self):
|
|
# X is Bdu; Z is B
|
|
super().verify()
|
|
if self.args[0].type != "B":
|
|
raise ValueError("or_d: X should be B")
|
|
if self.args[1].type != "B":
|
|
raise ValueError("or_d: Z should be B")
|
|
px = self.args[0].properties
|
|
if "d" not in px or "u" not in px:
|
|
raise ValueError("or_d: X should be du")
|
|
|
|
@property
|
|
def properties(self):
|
|
# z=zXzZ; o=oXzZ; d=dZ; u=uZ
|
|
props = ""
|
|
px, pz = [arg.properties for arg in self.args]
|
|
if "z" in px and "z" in pz:
|
|
props += "z"
|
|
if "o" in px and "z" in pz:
|
|
props += "o"
|
|
if "d" in pz:
|
|
props += "d"
|
|
if "u" in pz:
|
|
props += "u"
|
|
return props
|
|
|
|
|
|
class OrI(Miniscript):
|
|
# IF [X] ELSE [Z] ENDIF
|
|
NAME = "or_i"
|
|
NARGS = 2
|
|
ARGCLS = Miniscript
|
|
|
|
def inner_compile(self):
|
|
return (
|
|
b"\x63"
|
|
+ self.args[0].compile()
|
|
+ b"\x67"
|
|
+ self.args[1].compile()
|
|
+ b"\x68"
|
|
)
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 3
|
|
|
|
def verify(self):
|
|
# both are B, K, or V
|
|
super().verify()
|
|
if self.args[0].type != self.args[1].type:
|
|
raise ValueError("or_i: X and Z should be the same type")
|
|
if self.args[0].type not in "BKV":
|
|
raise ValueError("or_i: X and Z should be B K or V")
|
|
|
|
@property
|
|
def type(self):
|
|
return self.args[0].type
|
|
|
|
@property
|
|
def properties(self):
|
|
# o=zXzZ; u=uXuZ; d=dX or dZ
|
|
props = ""
|
|
px, pz = [arg.properties for arg in self.args]
|
|
if "z" in px and "z" in pz:
|
|
props += "o"
|
|
if "u" in px and "u" in pz:
|
|
props += "u"
|
|
if "d" in px or "d" in pz:
|
|
props += "d"
|
|
return props
|
|
|
|
|
|
class Thresh(Miniscript):
|
|
# [X1] [X2] ADD ... [Xn] ADD ... <k> EQUAL
|
|
NAME = "thresh"
|
|
NARGS = None
|
|
ARGCLS = (Number, Miniscript)
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return (
|
|
self.args[1].compile()
|
|
+ b"".join([arg.compile()+b"\x93" for arg in self.args[2:]])
|
|
+ self.args[0].compile()
|
|
+ b"\x87"
|
|
)
|
|
|
|
def __len__(self):
|
|
return self.len_args() + len(self.args) - 1
|
|
|
|
def verify(self):
|
|
# 1 <= k <= n; X1 is Bdu; others are Wdu
|
|
super().verify()
|
|
if self.args[0].num < 1 or self.args[0].num >= len(self.args):
|
|
raise ValueError(
|
|
"thresh: Invalid k! Should be 1 <= k <= %d, got %d"
|
|
% (len(self.args) - 1, self.args[0].num)
|
|
)
|
|
if self.args[1].type != "B":
|
|
raise ValueError("thresh: X1 should be B")
|
|
px = self.args[1].properties
|
|
if "d" not in px or "u" not in px:
|
|
raise ValueError("thresh: X1 should be du")
|
|
for i, arg in enumerate(self.args[2:]):
|
|
if arg.type != "W":
|
|
raise ValueError("thresh: X%d should be W" % (i + 1))
|
|
p = arg.properties
|
|
if "d" not in p or "u" not in p:
|
|
raise ValueError("thresh: X%d should be du" % (i + 1))
|
|
|
|
@property
|
|
def properties(self):
|
|
# z=all are z; o=all are z except one is o; d; u
|
|
props = ""
|
|
parr = [arg.properties for arg in self.args[1:]]
|
|
zarr = ["z" for p in parr if "z" in p]
|
|
if len(zarr) == len(parr):
|
|
props += "z"
|
|
noz = [p for p in parr if "z" not in p]
|
|
if len(noz) == 1 and "o" in noz[0]:
|
|
props += "o"
|
|
props += "du"
|
|
return props
|
|
|
|
|
|
class Multi(Miniscript):
|
|
# <k> <key1> ... <keyn> <n> CHECKMULTISIG
|
|
NAME = "multi"
|
|
NARGS = None
|
|
ARGCLS = (Number, Key)
|
|
TYPE = "B"
|
|
PROPS = "ndu"
|
|
N_MAX = 20
|
|
|
|
def inner_compile(self):
|
|
# scr = [arg.compile() for arg in self.args[1:]]
|
|
# optimization - it is all keys with known length (xonly keys not allowed here)
|
|
scr = [b'\x21' + arg.key_bytes() for arg in self.args[1:]]
|
|
if self.NAME == "sortedmulti":
|
|
scr.sort()
|
|
return (
|
|
self.args[0].compile()
|
|
+ b"".join(scr)
|
|
+ Number(len(self.args) - 1).compile()
|
|
+ b"\xae"
|
|
)
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 2
|
|
|
|
def m_n(self):
|
|
return self.args[0].num, len(self.args[1:])
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
N = (len(self.args) - 1)
|
|
assert N <= self.N_MAX, 'M/N range'
|
|
M = self.args[0].num
|
|
if M < 1 or M > N:
|
|
raise ValueError(
|
|
"M must be <= N: 1 <= M <= %d, got %d" % ((len(self.args) - 1), self.args[0].num)
|
|
)
|
|
|
|
|
|
class Sortedmulti(Multi):
|
|
# <k> <key1> ... <keyn> <n> CHECKMULTISIG
|
|
NAME = "sortedmulti"
|
|
|
|
|
|
class Multi_a(Multi):
|
|
# <key1> CHECKSIG <key> CHECKSIGADD ... <keyn> CHECKSIGADD EQUALVERIFY
|
|
NAME = "multi_a"
|
|
PROPS = "du"
|
|
N_MAX = MAX_TR_SIGNERS
|
|
|
|
def inner_compile(self):
|
|
from opcodes import OP_CHECKSIGADD, OP_NUMEQUAL, OP_CHECKSIG
|
|
script = b""
|
|
# scr = [arg.compile() for arg in self.args[1:]]
|
|
# optimization - it is all keys with known length (only xonly keys allowed here)
|
|
scr = [b"\x20" + arg.key_bytes() for arg in self.args[1:]]
|
|
if self.NAME == "sortedmulti_a":
|
|
scr.sort()
|
|
|
|
for i, key in enumerate(scr):
|
|
script += key
|
|
if i == 0:
|
|
script += bytes([OP_CHECKSIG])
|
|
else:
|
|
script += bytes([OP_CHECKSIGADD])
|
|
|
|
script += self.args[0].compile() # M (threshold)
|
|
script += bytes([OP_NUMEQUAL])
|
|
return script
|
|
|
|
def __len__(self):
|
|
# len(M) + len(k0) ... + len(kN) + len(keys) + 1
|
|
return self.len_args() + len(self.args)
|
|
|
|
|
|
class Sortedmulti_a(Multi_a):
|
|
# <key1> CHECKSIG <key> CHECKSIGADD ... <keyn> CHECKSIGADD EQUALVERIFY
|
|
NAME = "sortedmulti_a"
|
|
|
|
|
|
class Pk(OneArg):
|
|
# <key> CHECKSIG
|
|
NAME = "pk"
|
|
ARGCLS = Key
|
|
TYPE = "B"
|
|
PROPS = "ondu"
|
|
|
|
def inner_compile(self):
|
|
return self.carg + b"\xac"
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 1
|
|
|
|
|
|
class Pkh(OneArg):
|
|
# DUP HASH160 <HASH160(key)> EQUALVERIFY CHECKSIG
|
|
NAME = "pkh"
|
|
ARGCLS = KeyHash
|
|
TYPE = "B"
|
|
PROPS = "ndu"
|
|
|
|
def inner_compile(self):
|
|
return b"\x76\xa9" + self.carg + b"\x88\xac"
|
|
|
|
def __len__(self):
|
|
return self.len_args() + 4
|
|
|
|
|
|
OPERATORS = [
|
|
PkK,
|
|
PkH,
|
|
Older,
|
|
After,
|
|
Sha256,
|
|
Hash256,
|
|
Ripemd160,
|
|
Hash160,
|
|
AndOr,
|
|
AndV,
|
|
AndB,
|
|
AndN,
|
|
OrB,
|
|
OrC,
|
|
OrD,
|
|
OrI,
|
|
Thresh,
|
|
Multi,
|
|
Sortedmulti,
|
|
Multi_a,
|
|
Sortedmulti_a,
|
|
Pk,
|
|
Pkh,
|
|
]
|
|
OPERATOR_NAMES = [cls.NAME for cls in OPERATORS]
|
|
|
|
|
|
class Wrapper(OneArg):
|
|
ARGCLS = Miniscript
|
|
|
|
@property
|
|
def op(self):
|
|
return type(self).__name__.lower()
|
|
|
|
def to_string(self, *args, **kwargs):
|
|
# more wrappers follow
|
|
if isinstance(self.arg, Wrapper):
|
|
return self.op + self.arg.to_string(*args, **kwargs)
|
|
# we are the last wrapper
|
|
return self.op + ":" + self.arg.to_string(*args, **kwargs)
|
|
|
|
|
|
class A(Wrapper):
|
|
# TOALTSTACK [X] FROMALTSTACK
|
|
TYPE = "W"
|
|
|
|
def inner_compile(self):
|
|
return b"\x6b" + self.carg + b"\x6c"
|
|
|
|
def __len__(self):
|
|
return len(self.arg) + 2
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
if self.arg.type != "B":
|
|
raise ValueError("a: X should be B")
|
|
|
|
@property
|
|
def properties(self):
|
|
props = ""
|
|
px = self.arg.properties
|
|
if "d" in px:
|
|
props += "d"
|
|
if "u" in px:
|
|
props += "u"
|
|
return props
|
|
|
|
|
|
class S(Wrapper):
|
|
# SWAP [X]
|
|
TYPE = "W"
|
|
|
|
def inner_compile(self):
|
|
return b"\x7c" + self.carg
|
|
|
|
def __len__(self):
|
|
return len(self.arg) + 1
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
if self.arg.type != "B":
|
|
raise ValueError("s: X should be B")
|
|
if "o" not in self.arg.properties:
|
|
raise ValueError("s: X should be o")
|
|
|
|
@property
|
|
def properties(self):
|
|
props = ""
|
|
px = self.arg.properties
|
|
if "d" in px:
|
|
props += "d"
|
|
if "u" in px:
|
|
props += "u"
|
|
return props
|
|
|
|
|
|
class C(Wrapper):
|
|
# [X] CHECKSIG
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return self.carg + b"\xac"
|
|
|
|
def __len__(self):
|
|
return len(self.arg) + 1
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
if self.arg.type != "K":
|
|
raise ValueError("c: X should be K")
|
|
|
|
@property
|
|
def properties(self):
|
|
props = ""
|
|
px = self.arg.properties
|
|
for p in ["o", "n", "d"]:
|
|
if p in px:
|
|
props += p
|
|
props += "u"
|
|
return props
|
|
|
|
|
|
class T(Wrapper):
|
|
# [X] 1
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return self.carg + b"\x51" # Number(1).compile() --> b"\x51"
|
|
|
|
def __len__(self):
|
|
return len(self.arg) + 1
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
if self.arg.type != "V":
|
|
raise ValueError("t: X must be of type V")
|
|
|
|
@property
|
|
def properties(self):
|
|
# z=zXzY; o=zXoY or zYoX; n=nX or zXnY; u=uY
|
|
px = self.arg.properties
|
|
py = "zu"
|
|
props = ""
|
|
if "z" in px and "z" in py:
|
|
props += "z"
|
|
if ("z" in px and "o" in py) or ("z" in py and "o" in px):
|
|
props += "o"
|
|
if "n" in px or ("z" in px and "n" in py):
|
|
props += "n"
|
|
if "u" in py:
|
|
props += "u"
|
|
return props
|
|
|
|
|
|
class D(Wrapper):
|
|
# DUP IF [X] ENDIF
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return b"\x76\x63" + self.carg + b"\x68"
|
|
|
|
def __len__(self):
|
|
return len(self.arg) + 3
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
if self.arg.type != "V":
|
|
raise ValueError("d: X should be V")
|
|
if "z" not in self.arg.properties:
|
|
raise ValueError("d: X should be z")
|
|
|
|
@property
|
|
def properties(self):
|
|
# https://github.com/bitcoin/bitcoin/pull/24906
|
|
if self.taproot:
|
|
props = "ndu"
|
|
else:
|
|
props = "nd"
|
|
px = self.arg.properties
|
|
if "z" in px:
|
|
props += "o"
|
|
return props
|
|
|
|
|
|
class V(Wrapper):
|
|
# [X] VERIFY (or VERIFY version of last opcode in [X])
|
|
TYPE = "V"
|
|
|
|
def inner_compile(self):
|
|
"""Checks last check code and makes it verify"""
|
|
if self.carg[-1] in [0xAC, 0xAE, 0x9C, 0x87]:
|
|
return self.carg[:-1] + bytes([self.carg[-1] + 1])
|
|
return self.carg + b"\x69"
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
if self.arg.type != "B":
|
|
raise ValueError("v: X should be B")
|
|
|
|
@property
|
|
def properties(self):
|
|
props = ""
|
|
px = self.arg.properties
|
|
for p in ["z", "o", "n"]:
|
|
if p in px:
|
|
props += p
|
|
return props
|
|
|
|
|
|
class J(Wrapper):
|
|
# SIZE 0NOTEQUAL IF [X] ENDIF
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return b"\x82\x92\x63" + self.carg + b"\x68"
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
if self.arg.type != "B":
|
|
raise ValueError("j: X should be B")
|
|
if "n" not in self.arg.properties:
|
|
raise ValueError("j: X should be n")
|
|
|
|
@property
|
|
def properties(self):
|
|
props = "nd"
|
|
px = self.arg.properties
|
|
for p in ["o", "u"]:
|
|
if p in px:
|
|
props += p
|
|
return props
|
|
|
|
|
|
class N(Wrapper):
|
|
# [X] 0NOTEQUAL
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return self.carg + b"\x92"
|
|
|
|
def __len__(self):
|
|
return len(self.arg) + 1
|
|
|
|
def verify(self):
|
|
super().verify()
|
|
if self.arg.type != "B":
|
|
raise ValueError("n: X should be B")
|
|
|
|
@property
|
|
def properties(self):
|
|
props = "u"
|
|
px = self.arg.properties
|
|
for p in ["z", "o", "n", "d"]:
|
|
if p in px:
|
|
props += p
|
|
return props
|
|
|
|
|
|
class L(Wrapper):
|
|
# IF 0 ELSE [X] ENDIF
|
|
TYPE = "B"
|
|
|
|
def inner_compile(self):
|
|
return b"\x63\x00\x67" + self.carg + b"\x68"
|
|
|
|
def __len__(self):
|
|
return len(self.arg) + 4
|
|
|
|
def verify(self):
|
|
# both are B, K, or V
|
|
super().verify()
|
|
if self.arg.type != "B":
|
|
raise ValueError("or_i: X and Z should be the same type")
|
|
|
|
@property
|
|
def properties(self):
|
|
# o=zXzZ; u=uXuZ; d=dX or dZ
|
|
props = "d"
|
|
pz = self.arg.properties
|
|
if "z" in pz:
|
|
props += "o"
|
|
if "u" in pz:
|
|
props += "u"
|
|
return props
|
|
|
|
|
|
class U(L):
|
|
# IF [X] ELSE 0 ENDIF
|
|
def inner_compile(self):
|
|
return b"\x63" + self.carg + b"\x67\x00\x68"
|
|
|
|
def __len__(self):
|
|
return len(self.arg) + 4
|
|
|
|
|
|
WRAPPERS = [A, S, C, T, D, V, J, N, L, U]
|
|
WRAPPER_NAMES = [w.__name__.lower() for w in WRAPPERS] |