set MAX_TR_SIGNERS=32, docs
This commit is contained in:
parent
ebc1832b91
commit
824bbbc3b2
@ -75,7 +75,10 @@
|
||||
|
||||
# Taproot
|
||||
|
||||
- taproot limitation are listed in `docs/taproot.md`
|
||||
- only `TREE` of depth 0 is allowed
|
||||
- max 32 signers in TR multisig, only allowed script is: `sortedmulti_a`
|
||||
- if we can sign by both key path and script path: key path has precedence.
|
||||
- more background and detail in `docs/taproot.md`
|
||||
|
||||
|
||||
# SIGHASH types
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
# Taproot
|
||||
|
||||
**COLDCARD<sup>®</sup>** Mk4 experimental `EDGE` version `5.2.0X`
|
||||
**COLDCARD<sup>®</sup>** Mk4 experimental `EDGE` versions will
|
||||
support Schnorr signatures ([BIP-0340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki)),
|
||||
Taproot ([BIP-0341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki))
|
||||
and very limited Tapscript ([BIP-0342](https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki)) support.
|
||||
Tapscript support will get more versatile with next iterations.
|
||||
Tapscript support will get more versatile with future iterations.
|
||||
|
||||
## Output script (a.k.a address) generation
|
||||
|
||||
@ -40,7 +40,7 @@ for multisig.
|
||||
|
||||
## Limitations
|
||||
|
||||
### Tapscript limitations
|
||||
### Tapscript Limitations
|
||||
|
||||
In current version only `TREE` of depth 0 is allowed. Meaning that only one leaf script is allowed
|
||||
and tagged hash of this single leaf script is also a merkle root. Only allowed script (for now) is `sortedmulti_a`.
|
||||
@ -48,7 +48,7 @@ Taproot multisig currently has artificial limit of max 32 signers (M=N=32).
|
||||
|
||||
If Coldcard can sign by both key path and script path - key path has precedence.
|
||||
|
||||
### PSBT limitations
|
||||
### PSBT Requirements
|
||||
|
||||
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.
|
||||
|
||||
@ -8,7 +8,8 @@ from utils import str_to_keypath, problem_file_line, export_prompt_builder, pars
|
||||
from ux import ux_show_story, ux_confirm, ux_dramatic_pause, ux_clear_keys, ux_enter_bip32_index
|
||||
from files import CardSlot, CardMissingError, needs_microsd
|
||||
from descriptor import MultisigDescriptor, multisig_descriptor_template
|
||||
from public_constants import AF_P2SH, AF_P2WSH_P2SH, AF_P2WSH, AFC_SCRIPT, MAX_SIGNERS, AF_P2TR
|
||||
from public_constants import AF_P2SH, AF_P2WSH_P2SH, AF_P2WSH, AFC_SCRIPT, MAX_SIGNERS
|
||||
from public_constants import MAX_TR_SIGNERS, AF_P2TR
|
||||
from menu import MenuSystem, MenuItem
|
||||
from opcodes import OP_CHECKMULTISIG, OP_CHECKSIG, OP_NUMEQUAL, OP_CHECKSIGADD
|
||||
from exceptions import FatalPSBTIssue
|
||||
@ -28,7 +29,7 @@ class MultisigOutOfSpace(RuntimeError):
|
||||
pass
|
||||
|
||||
def disassemble_multisig_mn(redeem_script):
|
||||
# pull out just M and N from script. Simple, faster, no memory.
|
||||
# Pull out just M and N from script. Simple, faster, no memory.
|
||||
|
||||
assert redeem_script[-1] == OP_CHECKMULTISIG, 'need CHECKMULTISIG'
|
||||
|
||||
@ -39,8 +40,8 @@ def disassemble_multisig_mn(redeem_script):
|
||||
|
||||
|
||||
def disassemble_multisig_mn_tr(script):
|
||||
# pull out just M and N from script. Simple, faster, no memory.
|
||||
# more validation is done in next steps
|
||||
# Pull out just M and N from taproot script.
|
||||
# - more validation is done in following steps
|
||||
assert script[-1] == OP_NUMEQUAL, 'need OP_NUMEQUAL'
|
||||
num_cs = 0
|
||||
num_csa = 0
|
||||
@ -68,6 +69,7 @@ def disassemble_multisig_mn_tr(script):
|
||||
last = next(gen)[1]
|
||||
assert last == OP_NUMEQUAL
|
||||
M = int.from_bytes(bt[0], "little")
|
||||
|
||||
assert M
|
||||
N = num_cs + num_csa
|
||||
return M, N
|
||||
@ -142,15 +144,15 @@ def make_redeem_script(M, nodes, subkey_idx):
|
||||
|
||||
|
||||
def make_redeem_script_tr(M, nodes, subkey_idx):
|
||||
# take a list of BIP-32 nodes, and derive Nth subkey (subkey_idx) and make
|
||||
# Take a list of BIP-32 nodes, and derive Nth subkey (subkey_idx) and make
|
||||
# a taproot M-of-N redeem script for that. Always applies BIP-67 sorting.
|
||||
# - tapscript multisig does not use OP_CHECKMULTISIG and therefore limit is
|
||||
# much higher (998 of 999 was demonstrated)
|
||||
# - for now, MAX_TR_SIGNERS is 32, but this is artificial limit for tapscript
|
||||
# and could be something bigger
|
||||
|
||||
N = len(nodes)
|
||||
# TODO this is artificial limit for tapscript and should be something bigger
|
||||
# MAX_SIGNERS is actually old P2SH limit
|
||||
# limit for segwit v0 multisig is 20 (OP_CHECKMULTISIG limit)
|
||||
# tapscript multisig does not use OP_CHECKMULTISIG and therefore limit is much higher (998of999 was tried)
|
||||
# double current limit
|
||||
assert 1 <= M <= N <= 32
|
||||
assert 1 <= M <= N <= MAX_TR_SIGNERS
|
||||
|
||||
pubkeys = []
|
||||
for n in nodes:
|
||||
@ -173,7 +175,7 @@ def make_redeem_script_tr(M, nodes, subkey_idx):
|
||||
if M <= 16:
|
||||
script += bytes([80 + M, OP_NUMEQUAL])
|
||||
else:
|
||||
# up to 127
|
||||
assert M < 128
|
||||
script += bytes([0x01, M, OP_NUMEQUAL])
|
||||
|
||||
return script
|
||||
@ -187,7 +189,7 @@ class MultisigWallet:
|
||||
# - required during signing to verify change outputs
|
||||
# - can reconstruct any redeem script from this
|
||||
# Challenges:
|
||||
# - can be big, taking big % of 4k storage in nvram
|
||||
# - can be big, taking big % of storage in nvram
|
||||
# - complex object, want to have flexibility going forward
|
||||
FORMAT_NAMES = [
|
||||
(AF_P2SH, 'p2sh'),
|
||||
@ -581,6 +583,7 @@ class MultisigWallet:
|
||||
ch = chains.current_chain()
|
||||
internal_key = None
|
||||
xfp_deriv = None
|
||||
|
||||
for key, lhs_path in taproot_subpaths.items():
|
||||
if not lhs_path[0]:
|
||||
internal_key = key
|
||||
@ -601,6 +604,7 @@ class MultisigWallet:
|
||||
return internal_key
|
||||
|
||||
def make_multisig_tr(self, taproot_subpaths):
|
||||
# Make the redeem script for leafs
|
||||
ch = chains.current_chain()
|
||||
index = None
|
||||
nodes = []
|
||||
@ -626,8 +630,7 @@ class MultisigWallet:
|
||||
nodes.append(node)
|
||||
|
||||
# this assumes we have same index for all keys
|
||||
script = make_redeem_script_tr(self.M, nodes, index)
|
||||
return script
|
||||
return make_redeem_script_tr(self.M, nodes, index)
|
||||
|
||||
def validate_script(self, redeem_script, subpaths=None, xfp_paths=None):
|
||||
# Check we can generate all pubkeys in the redeem script, raise on errors.
|
||||
@ -645,7 +648,8 @@ class MultisigWallet:
|
||||
M, N, pubkeys = disassemble_multisig(redeem_script)
|
||||
assert M==self.M and N == self.N, 'wrong M/N in script'
|
||||
|
||||
if self.disable_checks: return ['UNVERIFIED']
|
||||
if self.disable_checks:
|
||||
return ['UNVERIFIED']
|
||||
|
||||
for pk_order, pubkey in enumerate(pubkeys):
|
||||
check_these = []
|
||||
@ -743,6 +747,7 @@ class MultisigWallet:
|
||||
xpubs = []
|
||||
addr_fmt = AF_P2SH
|
||||
my_xfp = settings.get('xfp')
|
||||
|
||||
for ln in lines:
|
||||
# remove comments
|
||||
comm = ln.find('#')
|
||||
@ -811,6 +816,7 @@ class MultisigWallet:
|
||||
is_mine = cls.check_xpub(xfp, value, deriv, chains.current_chain().ctype, my_xfp, xpubs)
|
||||
if is_mine:
|
||||
has_mine += 1
|
||||
|
||||
return name, addr_fmt, xpubs, has_mine, M, N
|
||||
|
||||
@classmethod
|
||||
@ -826,6 +832,7 @@ class MultisigWallet:
|
||||
is_mine = cls.check_xpub(xfp, xpub, deriv, chains.current_chain().ctype, my_xfp, xpubs)
|
||||
if is_mine:
|
||||
has_mine += 1
|
||||
|
||||
return None, desc.addr_fmt, xpubs, has_mine, desc.M, desc.N, desc.internal_key
|
||||
|
||||
def to_descriptor(self):
|
||||
|
||||
@ -2540,7 +2540,7 @@ def test_tapscript_multisig(cc_first, m_n, internal_key_spendable, use_regtest,
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.parametrize("change", [True, False])
|
||||
@pytest.mark.parametrize('M_N', [(3, 15), (2, 2), (3, 5), (15, 15)])
|
||||
@pytest.mark.parametrize('M_N', [(3, 15), (2, 2), (3, 5), (32, 32)])
|
||||
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
|
||||
def test_bitcoind_taproot_ms_address(change, M_N, clear_ms, goto_home, need_keypress, pick_menu_item, cap_menu,
|
||||
cap_story, make_multisig, import_ms_wallet, microsd_path, bitcoind_multisig,
|
||||
@ -2704,7 +2704,7 @@ def test_ms_xpub_ordering(descriptor, m_n, clear_ms, make_multisig, import_ms_wa
|
||||
|
||||
@pytest.mark.parametrize('cmn_pth_from_root', [True, False])
|
||||
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
|
||||
@pytest.mark.parametrize('M_N', [(3, 15), (2, 2), (3, 5), (15, 15)])
|
||||
@pytest.mark.parametrize('M_N', [(3, 15), (2, 2), (3, 5), (32, 32)])
|
||||
@pytest.mark.parametrize('addr_fmt', [AF_P2WSH, AF_P2SH, AF_P2WSH_P2SH])
|
||||
def test_multisig_descriptor_export(M_N, way, addr_fmt, cmn_pth_from_root, clear_ms, make_multisig,
|
||||
import_ms_wallet, goto_home, pick_menu_item, cap_menu,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user