bip388 import/export
This commit is contained in:
parent
0b20ef5360
commit
37a677e6f9
@ -1451,7 +1451,8 @@ class NewMiniscriptEnrollRequest(UserAuthorizedAction):
|
||||
self.pop_menu()
|
||||
|
||||
|
||||
def maybe_enroll_xpub(sf_len=None, config=None, name=None, ux_reset=False, bsms_index=None, miniscript=False):
|
||||
def maybe_enroll_xpub(sf_len=None, config=None, name=None, ux_reset=False, bsms_index=None,
|
||||
miniscript=False):
|
||||
# Offer to import (enroll) a new multisig/miniscript wallet. Allow reject by user.
|
||||
from glob import dis
|
||||
from multisig import MultisigWallet
|
||||
@ -1461,6 +1462,7 @@ def maybe_enroll_xpub(sf_len=None, config=None, name=None, ux_reset=False, bsms_
|
||||
dis.fullscreen('Wait...')
|
||||
dis.busy_bar(True)
|
||||
|
||||
bip388 = False
|
||||
try:
|
||||
if sf_len:
|
||||
with SFFile(TXN_INPUT_OFFSET, length=sf_len) as fd:
|
||||
@ -1468,9 +1470,14 @@ def maybe_enroll_xpub(sf_len=None, config=None, name=None, ux_reset=False, bsms_
|
||||
|
||||
try:
|
||||
j_conf = ujson.loads(config)
|
||||
assert "desc" in j_conf, "'desc' key required"
|
||||
config = j_conf["desc"]
|
||||
assert config, "'desc' empty"
|
||||
if "desc_template" in j_conf and "keys_info" in j_conf:
|
||||
assert "name" in j_conf
|
||||
config = j_conf
|
||||
bip388 = miniscript = True
|
||||
else:
|
||||
assert "desc" in j_conf, "'desc' key required"
|
||||
config = j_conf["desc"]
|
||||
assert config, "'desc' empty"
|
||||
|
||||
if "name" in j_conf:
|
||||
# name from json has preference over filenames and desc checksum
|
||||
@ -1488,7 +1495,7 @@ def maybe_enroll_xpub(sf_len=None, config=None, name=None, ux_reset=False, bsms_
|
||||
msc = MiniScriptWallet.from_file(config, name=name)
|
||||
|
||||
elif miniscript:
|
||||
msc = MiniScriptWallet.from_file(config, name=name)
|
||||
msc = MiniScriptWallet.from_file(config, name=name, bip388=bip388)
|
||||
else:
|
||||
msc = MultisigWallet.from_file(config, name=name)
|
||||
|
||||
|
||||
@ -1047,7 +1047,7 @@ async def bsms_signer_round2(menu, label, item):
|
||||
nodes = []
|
||||
progress_counter = 0.2 # last displayed progress
|
||||
# (desired value after loop - last displayed progress) / N
|
||||
progress_chunk = (0.5 - progress_counter) / len(desc_obj.miniscript.keys)
|
||||
progress_chunk = (0.5 - progress_counter) / len(desc_obj.keys)
|
||||
for key in desc_obj.keys:
|
||||
if key.origin.cc_fp == my_xfp:
|
||||
my_keys.append(key)
|
||||
|
||||
@ -169,6 +169,9 @@ class KeyDerivationInfo:
|
||||
def not_hardened(x):
|
||||
assert (b"'" not in x) and (b"h" not in x), "Cannot use hardened sub derivation path"
|
||||
|
||||
def get_ext_int(self):
|
||||
return self.indexes[self.multi_path_index]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, s):
|
||||
err = "Malformed key derivation"
|
||||
@ -183,6 +186,7 @@ class KeyDerivationInfo:
|
||||
cls.not_hardened(ext_num)
|
||||
int_num, char = read_until(s, b">")
|
||||
assert char, err
|
||||
assert b";" not in int_num, "Solved cardinality > 2"
|
||||
cls.not_hardened(int_num)
|
||||
|
||||
assert int_num != ext_num # cannot be the same
|
||||
@ -241,12 +245,11 @@ class Key:
|
||||
self.chain_type = chain_type
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.origin == other.origin \
|
||||
and self.derivation.indexes == other.derivation.indexes
|
||||
return hash(self) == hash(other)
|
||||
|
||||
def __hash__(self):
|
||||
# return hash(self.to_string())
|
||||
return hash(self.origin) + hash(self.derivation)
|
||||
return hash(self.node.pubkey()) + hash(self.derivation)
|
||||
|
||||
def __len__(self):
|
||||
return 34 - int(self.taproot) # <33:sec> or <32:xonly>
|
||||
@ -422,4 +425,29 @@ def bip388_wallet_policy_to_descriptor(desc_tmplt, keys_info):
|
||||
k_str = keys_info[i]
|
||||
ph = "@%d" % i
|
||||
desc_tmplt = desc_tmplt.replace(ph, k_str)
|
||||
return desc_tmplt
|
||||
return desc_tmplt.replace("/**", "/<0;1>/*")
|
||||
|
||||
|
||||
def bip388_validate_policy(desc_tmplt, keys_info):
|
||||
from uio import BytesIO
|
||||
|
||||
s = BytesIO(desc_tmplt)
|
||||
r = []
|
||||
while True:
|
||||
got, char = read_until(s, b"@")
|
||||
if not char:
|
||||
# no more - done
|
||||
break
|
||||
|
||||
# key derivation info required for policy
|
||||
got, char = read_until(s, b"/")
|
||||
assert char, "key derivation missing"
|
||||
num = int(got.decode())
|
||||
if num not in r:
|
||||
r.append(num)
|
||||
|
||||
assert s.read(1) in b"<*", "need multipath"
|
||||
|
||||
|
||||
assert len(r) == len(keys_info), "Invalid policy"
|
||||
assert r == list(range(len(r))), "Out of order"
|
||||
|
||||
@ -144,21 +144,29 @@ class Descriptor:
|
||||
assert len(self.miniscript.keys) == len(set(self.miniscript.keys)), "Insane"
|
||||
|
||||
my_xfp = settings.get('xfp')
|
||||
ext_nums = set()
|
||||
int_nums = set()
|
||||
for k in self.keys:
|
||||
has_mine += k.validate(my_xfp)
|
||||
ext, int = k.derivation.get_ext_int()
|
||||
ext_nums.add(ext)
|
||||
int_nums.add(int)
|
||||
c += 1
|
||||
|
||||
assert ext_nums.isdisjoint(int_nums), "Non-disjoint multipath"
|
||||
assert c <= max_signers, "max signers"
|
||||
|
||||
assert has_mine > 0, 'My key %s missing in descriptor.' % xfp2str(my_xfp).upper()
|
||||
|
||||
def bip388_wallet_policy(self):
|
||||
# only same origin keys
|
||||
keys_info = OrderedDict()
|
||||
for k in self.keys:
|
||||
if k.origin not in keys_info:
|
||||
keys_info[k.origin] = k.to_string(subderiv=False)
|
||||
pk = k.node.pubkey()
|
||||
if pk not in keys_info:
|
||||
keys_info[pk] = k.to_string(subderiv=False)
|
||||
|
||||
desc_tmplt = self.to_string(checksum=False)
|
||||
desc_tmplt = self.to_string(checksum=False).replace("/<0;1>/*", "/**")
|
||||
|
||||
keys_info = list(keys_info.values())
|
||||
for i, k_str in enumerate(keys_info):
|
||||
@ -218,13 +226,16 @@ class Descriptor:
|
||||
|
||||
if self.tapscript:
|
||||
# internal is always first
|
||||
# otherwise order of keys is not preserved (after set ops)
|
||||
keys = set()
|
||||
# use ordered dict as order preserving set
|
||||
keys = OrderedDict()
|
||||
# add internal key
|
||||
keys[self.key] = None
|
||||
# taptree keys
|
||||
for lv in self.tapscript.iter_leaves():
|
||||
for k in lv.keys:
|
||||
keys.add(k)
|
||||
keys[k] = None
|
||||
|
||||
self._keys = [self.key] + list(keys)
|
||||
self._keys = list(keys)
|
||||
|
||||
elif self.miniscript:
|
||||
self._keys = self.miniscript.keys
|
||||
|
||||
@ -6,7 +6,7 @@ import ngu, ujson, uio, chains, ure, version, stash
|
||||
from binascii import unhexlify as a2b_hex
|
||||
from binascii import hexlify as b2a_hex
|
||||
from serializations import ser_compact_size, ser_string
|
||||
from desc_utils import Key, read_until, bip388_wallet_policy_to_descriptor, append_checksum
|
||||
from desc_utils import Key, read_until, bip388_wallet_policy_to_descriptor, append_checksum, bip388_validate_policy
|
||||
from public_constants import MAX_TR_SIGNERS, AF_P2TR
|
||||
from wallet import BaseStorageWallet, MAX_BIP32_IDX
|
||||
from menu import MenuSystem, MenuItem
|
||||
@ -23,7 +23,7 @@ class MiniScriptWallet(BaseStorageWallet):
|
||||
key_name = "miniscript"
|
||||
|
||||
def __init__(self, name, desc_tmplt=None, keys_info=None, desc=None,
|
||||
af=None, ik_u=None, chain=None):
|
||||
af=None, ik_u=None):
|
||||
|
||||
assert (desc_tmplt and keys_info) or desc
|
||||
|
||||
@ -51,7 +51,7 @@ class MiniScriptWallet(BaseStorageWallet):
|
||||
rv.storage_idx = idx
|
||||
return rv
|
||||
|
||||
def to_descriptor(self):
|
||||
def to_descriptor(self, validate=False):
|
||||
if self.desc is None:
|
||||
# actual descriptor is not loaded, but was asked for
|
||||
# fill policy - aka storage format - to actual descriptor
|
||||
@ -66,7 +66,7 @@ class MiniScriptWallet(BaseStorageWallet):
|
||||
desc_str = bip388_wallet_policy_to_descriptor(self.desc_tmplt, self.keys_info)
|
||||
print("loading... filled policy:\n", desc_str)
|
||||
# no need to validate already saved descriptor - was validated upon enroll
|
||||
self.desc = Descriptor.from_string(desc_str, validate=False)
|
||||
self.desc = Descriptor.from_string(desc_str, validate=validate)
|
||||
# cache len always 1
|
||||
glob.DESC_CACHE = {}
|
||||
glob.DESC_CACHE[self.name] = self.desc
|
||||
@ -191,23 +191,37 @@ class MiniScriptWallet(BaseStorageWallet):
|
||||
await ux_show_story(msg)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, config, name=None):
|
||||
def from_bip388_wallet_policy(cls, name, desc_template, keys_info):
|
||||
bip388_validate_policy(desc_template, keys_info)
|
||||
msc = cls(name, desc_template, keys_info)
|
||||
msc.to_descriptor(validate=True)
|
||||
return msc
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, config, name=None, bip388=False):
|
||||
from descriptor import Descriptor
|
||||
|
||||
if name is None:
|
||||
desc_obj, cs = Descriptor.from_string(config.strip(), checksum=True)
|
||||
name = cs
|
||||
if bip388:
|
||||
# config is JSON wallet policy
|
||||
wal = cls.from_bip388_wallet_policy(config["name"], config["desc_template"],
|
||||
config["keys_info"])
|
||||
else:
|
||||
name = to_ascii_printable(name)
|
||||
desc_obj = Descriptor.from_string(config.strip())
|
||||
if name is None:
|
||||
desc_obj, cs = Descriptor.from_string(config.strip(), checksum=True)
|
||||
name = cs
|
||||
else:
|
||||
name = to_ascii_printable(name)
|
||||
desc_obj = Descriptor.from_string(config.strip())
|
||||
|
||||
wal = cls(name, desc=desc_obj)
|
||||
wal = cls(name, desc=desc_obj)
|
||||
|
||||
# BIP388 wasn't generated yet - generating from descriptor upon import/enroll
|
||||
wal.desc_tmplt, wal.keys_info = desc_obj.bip388_wallet_policy()
|
||||
# BIP388 wasn't generated yet - generating from descriptor upon import/enroll
|
||||
wal.desc_tmplt, wal.keys_info = desc_obj.bip388_wallet_policy()
|
||||
|
||||
wal.ik_u = desc_obj.key and desc_obj.key.is_provably_unspendable
|
||||
wal.addr_fmt = desc_obj.addr_fmt
|
||||
bip388_validate_policy(wal.desc_tmplt, wal.keys_info)
|
||||
|
||||
wal.ik_u = wal.desc.key and wal.desc.key.is_provably_unspendable
|
||||
wal.addr_fmt = wal.desc.addr_fmt
|
||||
return wal
|
||||
|
||||
def find_duplicates(self):
|
||||
@ -337,7 +351,7 @@ class MiniScriptWallet(BaseStorageWallet):
|
||||
name = "BIP-388 Wallet Policy"
|
||||
fname_pattern = 'b388-%s.json' % self.name
|
||||
res = ujson.dumps({"name": self.name,
|
||||
"desc_tmplt": self.desc_tmplt.replace("/<0;1>/*", "/**"),
|
||||
"desc_template": self.desc_tmplt.replace("/<0;1>/*", "/**"),
|
||||
"keys_info": self.keys_info})
|
||||
else:
|
||||
name = "Miniscript"
|
||||
|
||||
145
testing/devtest/unit_bip388.py
Normal file
145
testing/devtest/unit_bip388.py
Normal file
@ -0,0 +1,145 @@
|
||||
# (c) Copyright 2025 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# BIP-0388 vectors https://github.com/bitcoin/bips/blob/master/bip-0388.mediawiki
|
||||
|
||||
valid = [
|
||||
(
|
||||
"pkh(@0/**)",
|
||||
["[6738736c/44'/0'/0']xpub6Br37sWxruYfT8ASpCjVHKGwgdnYFEn98DwiN76i2oyY6fgH1LAPmmDcF46xjxJr22gw4jmVjTE2E3URMnRPEPYyo1zoPSUba563ESMXCeb"],
|
||||
"pkh([6738736c/44'/0'/0']xpub6Br37sWxruYfT8ASpCjVHKGwgdnYFEn98DwiN76i2oyY6fgH1LAPmmDcF46xjxJr22gw4jmVjTE2E3URMnRPEPYyo1zoPSUba563ESMXCeb/<0;1>/*)",
|
||||
),
|
||||
(
|
||||
"sh(wpkh(@0/**))",
|
||||
["[6738736c/49'/0'/1']xpub6Bex1CHWGXNNwGVKHLqNC7kcV348FxkCxpZXyCWp1k27kin8sRPayjZUKDjyQeZzGUdyeAj2emoW5zStFFUAHRgd5w8iVVbLgZ7PmjAKAm9"],
|
||||
"sh(wpkh([6738736c/49'/0'/1']xpub6Bex1CHWGXNNwGVKHLqNC7kcV348FxkCxpZXyCWp1k27kin8sRPayjZUKDjyQeZzGUdyeAj2emoW5zStFFUAHRgd5w8iVVbLgZ7PmjAKAm9/<0;1>/*))",
|
||||
),
|
||||
(
|
||||
"wpkh(@0/**)",
|
||||
["[6738736c/84'/0'/2']xpub6CRQzb8u9dmMcq5XAwwRn9gcoYCjndJkhKgD11WKzbVGd932UmrExWFxCAvRnDN3ez6ZujLmMvmLBaSWdfWVn75L83Qxu1qSX4fJNrJg2Gt"],
|
||||
"wpkh([6738736c/84'/0'/2']xpub6CRQzb8u9dmMcq5XAwwRn9gcoYCjndJkhKgD11WKzbVGd932UmrExWFxCAvRnDN3ez6ZujLmMvmLBaSWdfWVn75L83Qxu1qSX4fJNrJg2Gt/<0;1>/*)",
|
||||
),
|
||||
(
|
||||
"tr(@0/**)",
|
||||
["[6738736c/86'/0'/0']xpub6CryUDWPS28eR2cDyojB8G354izmx294BdjeSvH469Ty3o2E6Tq5VjBJCn8rWBgesvTJnyXNAJ3QpLFGuNwqFXNt3gn612raffLWfdHNkYL"],
|
||||
"tr([6738736c/86'/0'/0']xpub6CryUDWPS28eR2cDyojB8G354izmx294BdjeSvH469Ty3o2E6Tq5VjBJCn8rWBgesvTJnyXNAJ3QpLFGuNwqFXNt3gn612raffLWfdHNkYL/<0;1>/*)",
|
||||
),
|
||||
(
|
||||
"wsh(sortedmulti(2,@0/**,@1/**))",
|
||||
["[6738736c/48'/0'/0'/2']xpub6FC1fXFP1GXLX5TKtcjHGT4q89SDRehkQLtbKJ2PzWcvbBHtyDsJPLtpLtkGqYNYZdVVAjRQ5kug9CsapegmmeRutpP7PW4u4wVF9JfkDhw",
|
||||
"[b2b1f0cf/48'/0'/0'/2']xpub6EWhjpPa6FqrcaPBuGBZRJVjzGJ1ZsMygRF26RwN932Vfkn1gyCiTbECVitBjRCkexEvetLdiqzTcYimmzYxyR1BZ79KNevgt61PDcukmC7"],
|
||||
"wsh(sortedmulti(2,[6738736c/48'/0'/0'/2']xpub6FC1fXFP1GXLX5TKtcjHGT4q89SDRehkQLtbKJ2PzWcvbBHtyDsJPLtpLtkGqYNYZdVVAjRQ5kug9CsapegmmeRutpP7PW4u4wVF9JfkDhw/<0;1>/*,[b2b1f0cf/48'/0'/0'/2']xpub6EWhjpPa6FqrcaPBuGBZRJVjzGJ1ZsMygRF26RwN932Vfkn1gyCiTbECVitBjRCkexEvetLdiqzTcYimmzYxyR1BZ79KNevgt61PDcukmC7/<0;1>/*))",
|
||||
),
|
||||
(
|
||||
"wsh(thresh(3,pk(@0/**),s:pk(@1/**),s:pk(@2/**),sln:older(12960)))",
|
||||
["[6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa",
|
||||
"[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js",
|
||||
"[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2"],
|
||||
"wsh(thresh(3,pk([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa/<0;1>/*),s:pk([b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js/<0;1>/*),s:pk([a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2/<0;1>/*),sln:older(12960)))",
|
||||
),
|
||||
(
|
||||
"wsh(or_d(pk(@0/**),and_v(v:multi(2,@1/**,@2/**,@3/**),older(65535))))",
|
||||
["[6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa",
|
||||
"[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js",
|
||||
"[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2",
|
||||
"[bb641298/44'/0'/0'/100']xpub6Dz8PHFmXkYkykQ83ySkruky567XtJb9N69uXScJZqweYiQn6FyieajdiyjCvWzRZ2GoLHMRE1cwDfuJZ6461YvNRGVBJNnLA35cZrQKSRJ"],
|
||||
"wsh(or_d(pk([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa/<0;1>/*),and_v(v:multi(2,[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js/<0;1>/*,[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2/<0;1>/*,[bb641298/44'/0'/0'/100']xpub6Dz8PHFmXkYkykQ83ySkruky567XtJb9N69uXScJZqweYiQn6FyieajdiyjCvWzRZ2GoLHMRE1cwDfuJZ6461YvNRGVBJNnLA35cZrQKSRJ/<0;1>/*),older(65535))))",
|
||||
),
|
||||
(
|
||||
"tr(@0/**,{sortedmulti_a(1,@0/<2;3>/*,@1/**),or_b(pk(@2/**),s:pk(@3/**))})",
|
||||
["[6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa",
|
||||
"xpub6Fc2TRaCWNgfT49nRGG2G78d1dPnjhW66gEXi7oYZML7qEFN8e21b2DLDipTZZnfV6V7ivrMkvh4VbnHY2ChHTS9qM3XVLJiAgcfagYQk6K",
|
||||
"xpub6GxHB9kRdFfTqYka8tgtX9Gh3Td3A9XS8uakUGVcJ9NGZ1uLrGZrRVr67DjpMNCHprZmVmceFTY4X4wWfksy8nVwPiNvzJ5pjLxzPtpnfEM",
|
||||
"xpub6GjFUVVYewLj5no5uoNKCWuyWhQ1rKGvV8DgXBG9Uc6DvAKxt2dhrj1EZFrTNB5qxAoBkVW3wF8uCS3q1ri9fueAa6y7heFTcf27Q4gyeh6"],
|
||||
"tr([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa/<0;1>/*,{sortedmulti_a(1,[6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa/<2;3>/*,xpub6Fc2TRaCWNgfT49nRGG2G78d1dPnjhW66gEXi7oYZML7qEFN8e21b2DLDipTZZnfV6V7ivrMkvh4VbnHY2ChHTS9qM3XVLJiAgcfagYQk6K/<0;1>/*),or_b(pk(xpub6GxHB9kRdFfTqYka8tgtX9Gh3Td3A9XS8uakUGVcJ9NGZ1uLrGZrRVr67DjpMNCHprZmVmceFTY4X4wWfksy8nVwPiNvzJ5pjLxzPtpnfEM/<0;1>/*),s:pk(xpub6GjFUVVYewLj5no5uoNKCWuyWhQ1rKGvV8DgXBG9Uc6DvAKxt2dhrj1EZFrTNB5qxAoBkVW3wF8uCS3q1ri9fueAa6y7heFTcf27Q4gyeh6/<0;1>/*))})",
|
||||
),
|
||||
# (
|
||||
# "tr(musig(@0,@1,@2)/**,{and_v(v:pk(musig(@0,@1)/**),older(12960)),{and_v(v:pk(musig(@0,@2)/**),older(12960)),and_v(v:pk(musig(@1,@2)/**),older(12960))}})",
|
||||
# ["[6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa",
|
||||
# "[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js",
|
||||
# "[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2"],
|
||||
# "tr(musig([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa,[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js,[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2)/<0;1>/*,{and_v(v:pk(musig([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa,[b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js)/<0;1>/*),older(12960)),{and_v(v:pk(musig([6738736c/48'/0'/0'/100']xpub6FC1fXFP1GXQpyRFfSE1vzzySqs3Vg63bzimYLeqtNUYbzA87kMNTcuy9ubr7MmavGRjW2FRYHP4WGKjwutbf1ghgkUW9H7e3ceaPLRcVwa,[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2)/<0;1>/*),older(12960)),and_v(v:pk(musig([b2b1f0cf/44'/0'/0'/100']xpub6EYajCJHe2CK53RLVXrN14uWoEttZgrRSaRztujsXg7yRhGtHmLBt9ot9Pd5ugfwWEu6eWyJYKSshyvZFKDXiNbBcoK42KRZbxwjRQpm5Js,[a666a867/44'/0'/0'/100']xpub6Dgsze3ujLi1EiHoCtHFMS9VLS1UheVqxrHGfP7sBJ2DBfChEUHV4MDwmxAXR2ayeytpwm3zJEU3H3pjCR6q6U5sP2p2qzAD71x9z5QShK2)/<0;1>/*),older(12960))}})",
|
||||
# ),
|
||||
]
|
||||
|
||||
invalid = [
|
||||
(
|
||||
# Key placeholder with no path following it
|
||||
"key derivation missing",
|
||||
"pkh(@0)",
|
||||
["[0f056943/99h/0h/0h]xpub6DMjVrmtVxXyn5hBuLScBtHeQ9X3ws6uasj7mWRu7ay7mQrX5suQKwYgNZBJYnWKugRk1KrgmTHtgwGvB7QcgELYCuacE3oA25SGMQZTiRg"],
|
||||
),
|
||||
(
|
||||
# Key placeholder with an explicit path present
|
||||
"need multipath",
|
||||
"pkh(@0/0/**)",
|
||||
["[0f056943/99h/0h/0h]xpub6DMjVrmtVxXyn5hBuLScBtHeQ9X3ws6uasj7mWRu7ay7mQrX5suQKwYgNZBJYnWKugRk1KrgmTHtgwGvB7QcgELYCuacE3oA25SGMQZTiRg"],
|
||||
),
|
||||
(
|
||||
# Key placeholders out of order
|
||||
"Out of order",
|
||||
"sh(multi(1,@1/**,@0/**))",
|
||||
["[0f056943/99h/0h/0h]xpub6DMjVrmtVxXyn5hBuLScBtHeQ9X3ws6uasj7mWRu7ay7mQrX5suQKwYgNZBJYnWKugRk1KrgmTHtgwGvB7QcgELYCuacE3oA25SGMQZTiRg",
|
||||
"[6738736c/44'/0'/0']xpub6Br37sWxruYfT8ASpCjVHKGwgdnYFEn98DwiN76i2oyY6fgH1LAPmmDcF46xjxJr22gw4jmVjTE2E3URMnRPEPYyo1zoPSUba563ESMXCeb"],
|
||||
),
|
||||
(
|
||||
# Skipped key placeholder @1
|
||||
"Out of order",
|
||||
"sh(multi(1,@0/**,@2/**))",
|
||||
["[0f056943/99h/0h/0h]xpub6DMjVrmtVxXyn5hBuLScBtHeQ9X3ws6uasj7mWRu7ay7mQrX5suQKwYgNZBJYnWKugRk1KrgmTHtgwGvB7QcgELYCuacE3oA25SGMQZTiRg",
|
||||
"[6738736c/44'/0'/0']xpub6Br37sWxruYfT8ASpCjVHKGwgdnYFEn98DwiN76i2oyY6fgH1LAPmmDcF46xjxJr22gw4jmVjTE2E3URMnRPEPYyo1zoPSUba563ESMXCeb"],
|
||||
),
|
||||
(
|
||||
# Repeated keys with the same path expression
|
||||
"Insane",
|
||||
"sh(multi(1,@0/**,@0/**))",
|
||||
["[0f056943/99h/0h/0h]xpub6DMjVrmtVxXyn5hBuLScBtHeQ9X3ws6uasj7mWRu7ay7mQrX5suQKwYgNZBJYnWKugRk1KrgmTHtgwGvB7QcgELYCuacE3oA25SGMQZTiRg"],
|
||||
),
|
||||
(
|
||||
# Non-disjoint multipath expressions (@0/1/* appears twice)
|
||||
"Non-disjoint multipath",
|
||||
"sh(multi(1,@0/<0;1>/*,@0/<1;2>/*))",
|
||||
["[0f056943/99h/0h/0h]xpub6DMjVrmtVxXyn5hBuLScBtHeQ9X3ws6uasj7mWRu7ay7mQrX5suQKwYgNZBJYnWKugRk1KrgmTHtgwGvB7QcgELYCuacE3oA25SGMQZTiRg"],
|
||||
),
|
||||
(
|
||||
# Taproot non-disjoint multipath expressions (@0/1/* appears twice in tapscript)
|
||||
"Non-disjoint multipath",
|
||||
"tr(@0/<5;6>/*,multi_a(1,@0/<0;1>/*,@0/<1;2>/*))",
|
||||
["[0f056943/99h/0h/0h]xpub6DMjVrmtVxXyn5hBuLScBtHeQ9X3ws6uasj7mWRu7ay7mQrX5suQKwYgNZBJYnWKugRk1KrgmTHtgwGvB7QcgELYCuacE3oA25SGMQZTiRg"],
|
||||
),
|
||||
(
|
||||
# Taproot non-disjoint multipath expressions (@0/1/* appears twice as internal key and tapscript key)
|
||||
"Non-disjoint multipath",
|
||||
"tr(@0/<0;1>/*,multi_a(1,@0/<5;6>/*,@0/<1;2>/*))",
|
||||
["[0f056943/99h/0h/0h]xpub6DMjVrmtVxXyn5hBuLScBtHeQ9X3ws6uasj7mWRu7ay7mQrX5suQKwYgNZBJYnWKugRk1KrgmTHtgwGvB7QcgELYCuacE3oA25SGMQZTiRg"],
|
||||
),
|
||||
(
|
||||
# solved cardinality > 2
|
||||
"Solved cardinality > 2",
|
||||
"pkh(@0/<0;1;2>/*)",
|
||||
["[0f056943/99h/0h/0h]xpub6DMjVrmtVxXyn5hBuLScBtHeQ9X3ws6uasj7mWRu7ay7mQrX5suQKwYgNZBJYnWKugRk1KrgmTHtgwGvB7QcgELYCuacE3oA25SGMQZTiRg"],
|
||||
)
|
||||
]
|
||||
|
||||
import glob
|
||||
from glob import settings
|
||||
from descriptor import Descriptor
|
||||
from miniscript import MiniScriptWallet
|
||||
|
||||
settings.set('chain', "BTC")
|
||||
|
||||
# valid vectors
|
||||
for policy, keys_info, desc in valid:
|
||||
d = Descriptor.from_string(desc, validate=False)
|
||||
pol, ki = d.bip388_wallet_policy()
|
||||
assert pol == policy, "\n" + pol + "\n" + policy
|
||||
assert keys_info == keys_info
|
||||
|
||||
# invalid vectors
|
||||
for err, policy, keys_info in invalid:
|
||||
glob.DESC_CACHE = {}
|
||||
try:
|
||||
msc = MiniScriptWallet.from_bip388_wallet_policy("random_name", policy, keys_info)
|
||||
assert False, "succeeded, but must have failed!"
|
||||
except BaseException as e:
|
||||
if err not in str(e):
|
||||
raise
|
||||
@ -394,4 +394,8 @@ def test_script(sim_execfile):
|
||||
res = sim_execfile('devtest/unit_script.py')
|
||||
assert res == ""
|
||||
|
||||
def test_bip388(sim_execfile):
|
||||
res = sim_execfile('devtest/unit_bip388.py')
|
||||
assert res == ""
|
||||
|
||||
# EOF
|
||||
|
||||
Loading…
Reference in New Issue
Block a user