set MAX_TR_SIGNERS=32, docs

This commit is contained in:
Peter D. Gray 2023-05-09 11:39:36 -04:00
parent ebc1832b91
commit 824bbbc3b2
No known key found for this signature in database
GPG Key ID: F0E6CC6AFC16CF7B
4 changed files with 33 additions and 23 deletions

View File

@ -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

View File

@ -1,10 +1,10 @@
# Taproot
**COLDCARD<sup>&reg;</sup>** Mk4 experimental `EDGE` version `5.2.0X`
**COLDCARD<sup>&reg;</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.

View File

@ -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):

View File

@ -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,