further
This commit is contained in:
parent
0ac89511f1
commit
68ffcecc30
@ -22,6 +22,7 @@ item with `<name>` is added to `Address Explorer` menu.
|
||||
## Limitations
|
||||
* no duplicate keys in miniscript (at least change indexes in subderivation has to be different)
|
||||
* subderivation may be omitted during the import - default `<0;1>/*` is implied
|
||||
* only keys with key origin info `[xfp/p/a/t/h]xpub`
|
||||
* both keys with key origin info `[xfp/p/a/t/h]xpub/<0;1>/*` & blinded keys `xpub/<2;3>/*` allowed
|
||||
* use of blinded keys for co-signers requires PSBT provider to supply path from current key fingerprint
|
||||
* maximum number of keys allowed in segwit v0 miniscript is 20
|
||||
* check MiniTapscript limitations in `docs/taproot.md`
|
||||
@ -31,7 +31,8 @@ There are 2 methods to provide provably unspendable internal key, if users wish
|
||||
|
||||
`tr(xpub/<0:1>/*, sortedmulti_a(2,@0,@1))` which is the same thing as `tr(xpub, sortedmulti_a(2,@0,@1))` because `/<0;1>/*` is implied if not derivation path not provided.
|
||||
|
||||
2. **(recommended)** Use `unspend(` [notation](https://gist.github.com/sipa/06c5c844df155d4e5044c2c8cac9c05e#unspendable-keys). Has to be ranged.
|
||||
### Below option was deprecated in version 6.3.5X & 6.3.5QX
|
||||
2. Use `unspend(` [notation](https://gist.github.com/sipa/06c5c844df155d4e5044c2c8cac9c05e#unspendable-keys). Has to be ranged.
|
||||
|
||||
`tr(unspend(77ec0c0fdb9733e6a3c753b1374c4a465cba80dff52fc196972640a26dd08b76)/<0:1>/*, sortedmulti_a(2,@0,@1))`
|
||||
|
||||
@ -58,7 +59,7 @@ Options 4. and 5. are problematic to some extent as internal key is static. Use
|
||||
|
||||
In current version only `TREE` of max depth 4 is allowed (max 8 leaf script allowed).
|
||||
Taproot single leaf multisig has artificial limit of max 32 signers (M=N=32).
|
||||
Number of keys in taptree is limited to 32.
|
||||
Number of keys in whole taptree is limited to 32.
|
||||
|
||||
If Coldcard can sign by both key path and script path - key path has precedence.
|
||||
|
||||
@ -68,9 +69,9 @@ PSBT provider MUST provide following Taproot specific input fields in PSBT:
|
||||
1. `PSBT_IN_TAP_BIP32_DERIVATION` with all the necessary keys with their leaf hashes and derivation (including XFP). Internal key has to be specified here with empty leaf hashes.
|
||||
2. `PSBT_IN_TAP_INTERNAL_KEY` MUST match internal key provided in `PSBT_IN_TAP_BIP32_DERIVATION`
|
||||
3. `PSBT_IN_TAP_MERKLE_ROOT` MUST be empty if there is no script path. Otherwise it MUST match what Coldcard can calculate from registered descriptor.
|
||||
4. `PSBT_IN_TAP_LEAF_SCRIPT` MUST be specified if there is a script path. Currently MUST be of length 1 (only one script allowed)
|
||||
4. `PSBT_IN_TAP_LEAF_SCRIPT` MUST be specified if there is a script path.
|
||||
|
||||
PSBT provider MUST provide following Taproot specific output fields in PSBT:
|
||||
1. `PSBT_OUT_TAP_BIP32_DERIVATION` with all the necessary keys with their leaf hashes and derivation (including XFP). Internal key has to be specified here with empty leaf hashes.
|
||||
2. `PSBT_OUT_TAP_INTERNAL_KEY` must match internal key provided in `PSBT_OUT_TAP_BIP32_DERIVATION`
|
||||
3. `PSBT_OUT_TAP_TREE` with depth, leaf version and script defined. Currently only one script is allowed.
|
||||
3. `PSBT_OUT_TAP_TREE` with depth, leaf version and script defined.
|
||||
@ -98,12 +98,7 @@ def multisig_descriptor_template(xpub, path, xfp, addr_fmt):
|
||||
|
||||
|
||||
def read_until(s, chars=b",)(#"):
|
||||
# TODO potential infinite loop
|
||||
# what is the longest possible element? (proly some raw( but that is unsupported)
|
||||
#
|
||||
res = b""
|
||||
chunk = b""
|
||||
char = None
|
||||
while True:
|
||||
chunk = s.read(1)
|
||||
if len(chunk) == 0:
|
||||
@ -111,14 +106,13 @@ def read_until(s, chars=b",)(#"):
|
||||
if chunk in chars:
|
||||
return res, chunk
|
||||
res += chunk
|
||||
return res, None
|
||||
|
||||
|
||||
class KeyOriginInfo:
|
||||
def __init__(self, fingerprint: bytes, derivation: list):
|
||||
def __init__(self, fingerprint: bytes, derivation: list, cc_fp=None):
|
||||
self.fingerprint = fingerprint
|
||||
self.derivation = derivation
|
||||
self.cc_fp = swab32(int(b2a_hex(self.fingerprint).decode(), 16))
|
||||
self._cc_fp = cc_fp
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.psbt_derivation() == other.psbt_derivation()
|
||||
@ -126,6 +120,12 @@ class KeyOriginInfo:
|
||||
def __hash__(self):
|
||||
return hash(tuple(self.psbt_derivation()))
|
||||
|
||||
@property
|
||||
def cc_fp(self):
|
||||
if self._cc_fp is None:
|
||||
self._cc_fp = ustruct.unpack('<I', self.fingerprint)[0]
|
||||
return self._cc_fp
|
||||
|
||||
def str_derivation(self):
|
||||
return keypath_to_str(self.derivation, prefix='m/', skip=0)
|
||||
|
||||
@ -157,32 +157,13 @@ class KeyDerivationInfo:
|
||||
def __init__(self, indexes=None):
|
||||
self.indexes = indexes
|
||||
if self.indexes is None:
|
||||
self.indexes = [[0, 1], WILDCARD]
|
||||
self.indexes = ((0, 1), WILDCARD)
|
||||
self.multi_path_index = 0
|
||||
else:
|
||||
self.multi_path_index = None
|
||||
|
||||
@property
|
||||
def is_int_ext(self):
|
||||
if self.multi_path_index is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_external(self):
|
||||
if self.is_int_ext:
|
||||
return True
|
||||
elif self.indexes[-2] % 2 == 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def branches(self):
|
||||
if self.is_int_ext:
|
||||
return self.indexes[self.multi_path_index]
|
||||
else:
|
||||
return [self.indexes[-2]]
|
||||
def __hash__(self):
|
||||
return hash(self.indexes)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, s):
|
||||
@ -200,7 +181,7 @@ class KeyDerivationInfo:
|
||||
assert char, err
|
||||
|
||||
multi_i = len(idxs)
|
||||
idxs.append([int(ext_num.decode()), int(int_num.decode())])
|
||||
idxs.append((int(ext_num.decode()), int(int_num.decode())))
|
||||
|
||||
|
||||
elif got == b"*":
|
||||
@ -212,15 +193,16 @@ class KeyDerivationInfo:
|
||||
assert (b"'" not in got) and (b"h" not in got), "Cannot use hardened sub derivation path"
|
||||
idxs.append(int(got.decode()))
|
||||
|
||||
|
||||
assert idxs[-1] == WILDCARD, "All keys must be ranged"
|
||||
if idxs == [0, WILDCARD]:
|
||||
# normalize and instead save as <0;1> as change derivation was not provided
|
||||
obj = cls()
|
||||
else:
|
||||
assert idxs[-1] == WILDCARD, "All keys must be ranged"
|
||||
|
||||
if multi_i is not None:
|
||||
assert len(idxs[multi_i]) == 2, "wrong multipath"
|
||||
|
||||
obj = cls(idxs)
|
||||
obj = cls(tuple(idxs))
|
||||
obj.multi_path_index = multi_i
|
||||
|
||||
return obj
|
||||
@ -228,7 +210,7 @@ class KeyDerivationInfo:
|
||||
def to_string(self, external=True, internal=True):
|
||||
res = []
|
||||
for i in self.indexes:
|
||||
if isinstance(i, list):
|
||||
if isinstance(i, tuple):
|
||||
if internal is True and external is False:
|
||||
i = str(i[1])
|
||||
elif internal is False and external is True:
|
||||
@ -240,16 +222,12 @@ class KeyDerivationInfo:
|
||||
res.append(i)
|
||||
return "/".join(res)
|
||||
|
||||
def to_int_list(self, branch_idx, idx):
|
||||
assert branch_idx in self.indexes[0]
|
||||
return [branch_idx, idx]
|
||||
|
||||
|
||||
class Key:
|
||||
def __init__(self, node, origin, derivation=None, taproot=False, chain_type=None):
|
||||
self.origin = origin
|
||||
self.node = node
|
||||
self.derivation = derivation
|
||||
self.derivation = derivation or KeyDerivationInfo()
|
||||
self.taproot = taproot
|
||||
self.chain_type = chain_type
|
||||
|
||||
@ -258,7 +236,8 @@ class Key:
|
||||
and self.derivation.indexes == other.derivation.indexes
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.to_string())
|
||||
# return hash(self.to_string())
|
||||
return hash(self.origin) + hash(self.derivation)
|
||||
|
||||
def __len__(self):
|
||||
return 34 - int(self.taproot) # <33:sec> or <32:xonly>
|
||||
@ -286,17 +265,20 @@ class Key:
|
||||
origin = KeyOriginInfo.from_string(prefix.decode())
|
||||
else:
|
||||
s.seek(-1, 1)
|
||||
|
||||
k, char = read_until(s, b",)/")
|
||||
der = b""
|
||||
der = None
|
||||
if char == b"/":
|
||||
der = KeyDerivationInfo.parse(s)
|
||||
if char is not None:
|
||||
s.seek(-1, 1)
|
||||
|
||||
# parse key
|
||||
node, chain_type = cls.parse_key(k)
|
||||
if origin is None:
|
||||
origin = KeyOriginInfo(ustruct.pack('<I', swab32(node.my_fp())), [])
|
||||
return cls(node, origin, der or KeyDerivationInfo(), chain_type=chain_type)
|
||||
cc_fp = swab32(node.my_fp())
|
||||
origin = KeyOriginInfo(ustruct.pack('<I', cc_fp), [], cc_fp)
|
||||
return cls(node, origin, der, chain_type=chain_type)
|
||||
|
||||
@classmethod
|
||||
def parse_key(cls, key_str):
|
||||
@ -312,7 +294,9 @@ class Key:
|
||||
node = ngu.hdnode.HDNode()
|
||||
node.deserialize(key_str)
|
||||
|
||||
assert node.privkey() is None
|
||||
try:
|
||||
assert node.privkey() is None
|
||||
except: pass
|
||||
|
||||
return node, chain_type
|
||||
|
||||
@ -361,13 +345,13 @@ class Key:
|
||||
new_node = self.node.copy()
|
||||
new_node.derive(idx, False)
|
||||
if self.origin:
|
||||
origin = KeyOriginInfo(self.origin.fingerprint, self.origin.derivation + [idx])
|
||||
origin = KeyOriginInfo(self.origin.fingerprint, self.origin.derivation + [idx],
|
||||
self.origin.cc_fp)
|
||||
else:
|
||||
fp = ustruct.pack('<I', swab32(self.node.my_fp()))
|
||||
origin = KeyOriginInfo(fp, [idx])
|
||||
origin = KeyOriginInfo(self.origin.fingerprint, [idx], self.origin.cc_fp)
|
||||
|
||||
derivation = KeyDerivationInfo(self.derivation.indexes[1:])
|
||||
return type(self)(new_node, origin, derivation, taproot=self.taproot)
|
||||
return type(self)(new_node, origin, KeyDerivationInfo(self.derivation.indexes[1:]),
|
||||
taproot=self.taproot)
|
||||
|
||||
@classmethod
|
||||
def read_from(cls, s, taproot=False):
|
||||
@ -431,34 +415,3 @@ def bip388_wallet_policy_to_descriptor(desc_tmplt, keys_info):
|
||||
ph = "@%d" % i
|
||||
desc_tmplt = desc_tmplt.replace(ph, k_str)
|
||||
return desc_tmplt
|
||||
|
||||
# ph_len = len(ph)
|
||||
# while True:
|
||||
# ix = policy.find(ph)
|
||||
# if ix == -1:
|
||||
# break
|
||||
#
|
||||
# assert policy[ix+ph_len] == "/"
|
||||
# # subderivation is part of the policy
|
||||
# x = ix + ph_len
|
||||
# substr = policy[x:x+26] # 26 is the longest possible subderivation allowed "/<2147483647;2147483646>/*"
|
||||
# mp_start = substr.find("<")
|
||||
# assert mp_start != -1
|
||||
# mp_end = substr.find(">")
|
||||
# mp = substr[mp_start:mp_end + 1]
|
||||
# _ext, _int = mp[1:-1].split(";")
|
||||
# if external and not internal:
|
||||
# sub = _ext
|
||||
# elif internal and not external:
|
||||
# sub = _int
|
||||
# else:
|
||||
# sub = None
|
||||
#
|
||||
# if sub is not None:
|
||||
# policy = policy[:x + mp_start] + sub + policy[x + mp_end + 1:]
|
||||
#
|
||||
# x = policy[ix:ix + ph_len]
|
||||
# assert x == ph
|
||||
# policy = policy[:ix] + k + policy[ix + ph_len:]
|
||||
#
|
||||
# return policy
|
||||
|
||||
@ -6,7 +6,7 @@ import ngu, chains
|
||||
from io import BytesIO
|
||||
from collections import OrderedDict
|
||||
from binascii import hexlify as b2a_hex
|
||||
from utils import cleanup_deriv_path, check_xpub, xfp2str, swab32
|
||||
from utils import xfp2str
|
||||
from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2TR
|
||||
from public_constants import AF_P2WSH, AF_P2WSH_P2SH, AF_P2SH, MAX_SIGNERS, MAX_TR_SIGNERS
|
||||
from desc_utils import parse_desc_str, append_checksum, descriptor_checksum, Key
|
||||
@ -14,14 +14,6 @@ from miniscript import Miniscript
|
||||
from precomp_tag_hash import TAP_BRANCH_H
|
||||
|
||||
|
||||
class DescriptorException(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class WrongCheckSumError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Tapscript:
|
||||
def __init__(self, tree):
|
||||
self.tree = tree # miniscript or (tapscript, tapscript)
|
||||
@ -112,8 +104,6 @@ class Tapscript:
|
||||
|
||||
s.seek(-1, 1)
|
||||
ms = Miniscript.read_from(s, taproot=True)
|
||||
ms.is_sane(taproot=True)
|
||||
ms.verify()
|
||||
return cls(ms)
|
||||
|
||||
def script_tree(self):
|
||||
@ -148,32 +138,39 @@ class Descriptor:
|
||||
self.addr_fmt = addr_fmt
|
||||
|
||||
def validate(self):
|
||||
# should only be run once while importing wallet
|
||||
from glob import settings
|
||||
if self.miniscript:
|
||||
if self.is_basic_multisig:
|
||||
assert len(self.keys) <= MAX_SIGNERS
|
||||
else:
|
||||
assert len(self.keys) <= 20
|
||||
self.miniscript.verify()
|
||||
if self.miniscript.type != "B":
|
||||
raise DescriptorException("Top level miniscript should be 'B'")
|
||||
|
||||
has_mine = 0
|
||||
my_xfp = settings.get('xfp')
|
||||
|
||||
c = 0
|
||||
has_mine = 0
|
||||
err_top_B = "Top level miniscript should be 'B'"
|
||||
max_signers = 20
|
||||
|
||||
if self.tapscript:
|
||||
assert self.key # internal key (would fail during parse)
|
||||
max_signers = MAX_TR_SIGNERS
|
||||
for l in self.tapscript.iter_leaves():
|
||||
assert l.type == "B", err_top_B
|
||||
l.verify()
|
||||
l.is_sane(taproot=True)
|
||||
# cannot have same keys in single miniscript
|
||||
# provably unspendable taproot internal key is not covered here
|
||||
assert len(l.keys) == len(set(l.keys)), "Insane"
|
||||
|
||||
elif self.miniscript:
|
||||
assert self.key is None
|
||||
assert self.miniscript.type == "B", err_top_B
|
||||
self.miniscript.verify()
|
||||
self.miniscript.is_sane(taproot=False)
|
||||
# cannot have same keys in single miniscript
|
||||
assert len(self.miniscript.keys) == len(set(self.miniscript.keys)), "Insane"
|
||||
|
||||
my_xfp = settings.get('xfp')
|
||||
for k in self.keys:
|
||||
has_mine += k.validate(my_xfp)
|
||||
c += 1
|
||||
|
||||
if self.tapscript:
|
||||
if self.key.is_provably_unspendable:
|
||||
c -= 1
|
||||
|
||||
assert c <= MAX_TR_SIGNERS
|
||||
assert self.key # internal key (would fail during parse)
|
||||
else:
|
||||
assert self.key is None and self.miniscript, "not miniscript"
|
||||
assert c <= max_signers, "max signers"
|
||||
|
||||
assert has_mine != 0, 'My key %s missing in descriptor.' % xfp2str(my_xfp).upper()
|
||||
|
||||
@ -207,11 +204,9 @@ class Descriptor:
|
||||
for k in self.keys:
|
||||
if self.is_taproot and k.is_provably_unspendable and skip_unspend_ik:
|
||||
continue
|
||||
elif k.origin:
|
||||
res.append(k.origin.psbt_derivation())
|
||||
else:
|
||||
# origin less - TODO should not be here, origin should already be created
|
||||
res.append([swab32(self.key.node.my_fp())])
|
||||
|
||||
res.append(k.origin.psbt_derivation())
|
||||
|
||||
return res
|
||||
|
||||
@property
|
||||
@ -301,7 +296,7 @@ class Descriptor:
|
||||
|
||||
@classmethod
|
||||
def is_descriptor(cls, desc_str):
|
||||
"""Quick method to guess whether this is a descriptor"""
|
||||
# Quick method to guess whether this is a descriptor
|
||||
try:
|
||||
temp = parse_desc_str(desc_str)
|
||||
except:
|
||||
@ -328,15 +323,15 @@ class Descriptor:
|
||||
return desc_w_checksum, None
|
||||
calc_checksum = descriptor_checksum(desc)
|
||||
if calc_checksum != checksum:
|
||||
raise WrongCheckSumError("Wrong checksum %s, expected %s" % (checksum, calc_checksum))
|
||||
raise ValueError("Wrong checksum %s, expected %s" % (checksum, calc_checksum))
|
||||
return desc, checksum
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, desc, checksum=False):
|
||||
def from_string(cls, desc, checksum=False, validate=True):
|
||||
desc = parse_desc_str(desc)
|
||||
desc, cs = cls.checksum_check(desc)
|
||||
s = BytesIO(desc.encode())
|
||||
res = cls.read_from(s)
|
||||
res = cls.read_from(s, validate)
|
||||
left = s.read()
|
||||
if len(left) > 0:
|
||||
raise ValueError("Unexpected characters after descriptor: %r" % left)
|
||||
@ -347,7 +342,7 @@ class Descriptor:
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def read_from(cls, s):
|
||||
def read_from(cls, s, validate=True):
|
||||
start = s.read(8)
|
||||
af = AF_CLASSIC
|
||||
internal_key = None
|
||||
@ -389,7 +384,6 @@ class Descriptor:
|
||||
nbrackets = 1
|
||||
elif af in [AF_P2SH, AF_P2WSH_P2SH, AF_P2WSH]:
|
||||
miniscript = Miniscript.read_from(s)
|
||||
miniscript.is_sane(taproot=False)
|
||||
key = internal_key
|
||||
nbrackets = 1 + int(af == AF_P2WSH_P2SH)
|
||||
else:
|
||||
@ -400,9 +394,10 @@ class Descriptor:
|
||||
if end != b")" * nbrackets:
|
||||
raise ValueError("Invalid descriptor")
|
||||
|
||||
o = cls(key, miniscript, tapscript, af)
|
||||
o.validate()
|
||||
return o
|
||||
desc = cls(key, miniscript, tapscript, af)
|
||||
if validate:
|
||||
desc.validate()
|
||||
return desc
|
||||
|
||||
def to_string(self, external=True, internal=True, checksum=True, unspent_compat=False):
|
||||
if self.is_taproot:
|
||||
|
||||
@ -8,7 +8,7 @@ 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
|
||||
from public_constants import MAX_TR_SIGNERS, AF_P2TR
|
||||
from wallet import BaseStorageWallet
|
||||
from wallet import BaseStorageWallet, MAX_BIP32_IDX
|
||||
from menu import MenuSystem, MenuItem
|
||||
from ux import ux_show_story, ux_confirm, ux_dramatic_pause
|
||||
from files import CardSlot, CardMissingError, needs_microsd
|
||||
@ -37,7 +37,6 @@ class MiniScriptWallet(BaseStorageWallet):
|
||||
self.desc = desc
|
||||
self.addr_fmt = af
|
||||
self.ik_u = ik_u
|
||||
# self.chain =
|
||||
|
||||
@property
|
||||
def chain(self):
|
||||
@ -64,7 +63,8 @@ class MiniScriptWallet(BaseStorageWallet):
|
||||
else:
|
||||
desc_str = bip388_wallet_policy_to_descriptor(self.desc_tmplt, self.keys_info)
|
||||
print("loading... filled policy:\n", desc_str)
|
||||
self.desc = Descriptor.from_string(desc_str)
|
||||
# no need to validate already saved descriptor - was validated upon enroll
|
||||
self.desc = Descriptor.from_string(desc_str, validate=False)
|
||||
# cache len always 1
|
||||
glob.DESC_CACHE = {}
|
||||
glob.DESC_CACHE[self.name] = self.desc
|
||||
@ -90,12 +90,14 @@ class MiniScriptWallet(BaseStorageWallet):
|
||||
if addr_fmt is not None:
|
||||
if rv.addr_fmt != addr_fmt:
|
||||
continue
|
||||
|
||||
if rv.matching_subpaths(xfp_paths):
|
||||
return rv
|
||||
return None
|
||||
|
||||
def matching_subpaths(self, xfp_paths):
|
||||
my_xfp_paths = self.to_descriptor().xfp_paths()
|
||||
|
||||
if len(xfp_paths) != len(my_xfp_paths):
|
||||
return False
|
||||
|
||||
@ -262,6 +264,8 @@ class MiniScriptWallet(BaseStorageWallet):
|
||||
dd = self.to_descriptor().derive(None, change=change)
|
||||
idx = start_idx
|
||||
while count:
|
||||
if idx > MAX_BIP32_IDX:
|
||||
break
|
||||
# make the redeem script, convert into address
|
||||
d = dd.derive(idx)
|
||||
scr = d.miniscript.compile() if d.miniscript else None
|
||||
@ -712,11 +716,6 @@ class Miniscript:
|
||||
|
||||
def is_sane(self, taproot=False):
|
||||
err = "multi mixin"
|
||||
keys = self.keys
|
||||
# cannot have same keys in single miniscript
|
||||
# provably unspendable taproot internal key is not covered here
|
||||
# all other keys (miniscript,tapscript) require key origin info
|
||||
assert len(keys) == len(set(keys)), "Insane"
|
||||
forbiden = (Sortedmulti, Multi) if taproot else (Sortedmulti_a, Multi_a)
|
||||
assert type(self) not in forbiden, err
|
||||
|
||||
@ -736,11 +735,10 @@ class Miniscript:
|
||||
def derive(self, idx, key_map=None, change=False):
|
||||
args = []
|
||||
for arg in self.args:
|
||||
if hasattr(arg, "derive"):
|
||||
if isinstance(arg, Key): # KeyHash is subclass of Key
|
||||
arg = self.key_derive(arg, idx, key_map, change=change)
|
||||
else:
|
||||
arg = arg.derive(idx, key_map, change)
|
||||
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)
|
||||
|
||||
@ -13,7 +13,7 @@ from descriptor import Descriptor
|
||||
from miniscript import Key, Sortedmulti, Number, Multi
|
||||
from desc_utils import multisig_descriptor_template
|
||||
from public_constants import AF_P2SH, AF_P2WSH_P2SH, AF_P2WSH, AFC_SCRIPT, MAX_SIGNERS, AF_P2TR, AF_CLASSIC
|
||||
from menu import MenuSystem, MenuItem, NonDefaultMenuItem, start_chooser
|
||||
from menu import MenuSystem, MenuItem, start_chooser
|
||||
from opcodes import OP_CHECKMULTISIG
|
||||
from exceptions import FatalPSBTIssue
|
||||
from glob import settings
|
||||
|
||||
@ -280,7 +280,7 @@ def address_explorer_check(goto_home, pick_menu_item, need_keypress, cap_menu,
|
||||
wal_name = m[-1]
|
||||
pick_menu_item(wal_name)
|
||||
|
||||
time.sleep(5)
|
||||
time.sleep(1)
|
||||
if way == "qr":
|
||||
need_keypress(KEY_QR)
|
||||
cc_addrs = []
|
||||
@ -296,13 +296,13 @@ def address_explorer_check(goto_home, pick_menu_item, need_keypress, cap_menu,
|
||||
press_select()
|
||||
|
||||
|
||||
time.sleep(2)
|
||||
time.sleep(1)
|
||||
title, story = cap_story()
|
||||
|
||||
assert "change addresses." in story and "(0)" in story
|
||||
need_keypress("0")
|
||||
|
||||
time.sleep(2)
|
||||
time.sleep(1)
|
||||
title, story = cap_story()
|
||||
|
||||
assert "(0)" not in story
|
||||
@ -347,7 +347,7 @@ def address_explorer_check(goto_home, pick_menu_item, need_keypress, cap_menu,
|
||||
else:
|
||||
external_desc = desc["desc"]
|
||||
|
||||
time.sleep(5)
|
||||
time.sleep(1)
|
||||
|
||||
if export_check:
|
||||
desc_export = miniscript_descriptors(cc_minsc_name)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user