From: Ali Sherief <ali@notatether•com>
To: bitcoin-dev@lists•linuxfoundation.org
Cc: luke_bipeditor@dashjr•org
Subject: [bitcoin-dev] [BIP] Implementing Multisig Using Taproot
Date: Sat, 20 Aug 2022 13:49:05 +0000 [thread overview]
Message-ID: <20220820134850.ofvz7225zwcyffit@artanis> (raw)
Greetings list.
Following the discussions I made about BIP322 delegation on this mailing list and in other places, I have decided that that delegation depends on a very similar problem that arises when writing contracts and commitments. That problem is how to implement private Multisig.
Of course, this can already be done using Taproot. But I doubt that many people know how to do it using script paths. BIP342 briefly touches on the subject but leaves it open. So I wrote a BIP to plug this hole, that precisely follows the guidelines hinted by BIPs 341 and 342, for creating and spending Multisig outputs.
I also managed to figure out the formula that BIP342 vaguely hinted at for figuring out "cost-effective multisignatures" (hint: it's not quadratic).
As far as I can tell, such a BIP hasn't been advanced before, so there should be no problem of "try working on the other BIP".
Use cases:
- Layer 2 protocols that use multisig (e.g. LN, Discrete Log Contracts)
- BIP322 message signatures, if it is decided that UTXO delegation is desireable.
I'm pasting the draft of the BIP below, with the metadata shaved off, but references are left intact. I haven't uploaded it to Github yet.
--------
== Summary ==
This document defines the proper way to construct Multisig outputs and spends that utilize the privacy provided by Taproot script paths.
== Copyright ==
This document is licensed under the 2-clause BSD license.
== Abstract ==
A Multisignature (also called Multisig) unspent transaction output (UTXO) attached to an address allows two or more parties to restrict the spending of the UTXO inside the address until a specified number of parties sign the output spending it. Multisig UTXOs are extremely useful for creating contracts, and is therefore used in many applications where delegation of funds to a committee is required, such as in Lightning Network channels, in DLCs (Discrete Log Contracts), and in other kinds of contracts.
== Motivation ==
OP_CHECKMULTISIG has the disadvantage of revealing all co-signer public keys involved in a transaction. This compromises the privacy of those signers. Additionally, this construct is not compatible with Taproot because OP_CHECKMULTISIG is disabled in TapScript, thus those applications are unable to make use of Pay-to-Taproot (P2TR) addresses.
Constructing a Multisig output on Taproot is technically possible, but its implementation has not been specified by any existing BIP, to the author's knowledge. Additionally, most developers of Bitcoin applications do not know how to construct Multisig Taproot outputs.
== Design ==
Taproot gives us three different options for implementing Multisig, each with their own advantages and disadvantages<ref>'''Multisig implementation options reference''' The options were originally enumerated in [https://jimmysong.github.io/taproot-multisig Jimmy Song's slideshow] in a more detailed manner.</ref>:
# Single-leaf with a TapScript implementing K-of-N Multisig. This is functionally equivalent to legacy OP_CHECKMULTISIG, and shares all its advantages and disadvantages. In particular, all public keys of signers are revealed in the TapScript embedded in the first element of the witness program, so the privacy advantages of Taproot are compromised.
# Multiple leaves, each with a TapScript implementing K-of-K Multisig.
# Multiple leaves, each with a TapScript implementing MuSig of K keys (i.e. aggregate of K public keys).
This document uses the second option for implementing Multisig, because it only reveals the public keys of those who sign the transaction.<ref>'''Why wasn't MuSig considered?''' Although MuSig provides even more privacy by not revealing any original public keys all together, it is a cumbersome process to create since K parties must be online not only at one point to create the aggregated keys, but also at another point to create a signature. There is the problem of who will be the trustee of the MuSigs themselves, as opposed to just the delegated UTXOs. Also, There is no BIP that implements MuSig, to the author's knowledge.</ref>
== Specification ==
Notations used here are specified in [[bip-0340.mediawiki#design|BIP340]].
''taproot_output_script'' and ''taproot_sign_script'' refers to the Python functions of [[bip-0341.mediawiki|BIP341]] with the same name.
=== Constructing K-of-N Multisig outputs ===
All of the participating TapScripts must be collected together at construction-time. This implies that all signers must know each other beforehand<ref>'''Why should all signers know each other beforehand?''' Knowing all possible signers of a multisignature is required for many instances of delegation, so that an unknown party cannot insert a rogue signature at spending-time.</ref>.
The algorithm takes as inputs:
* An integer value ''m'', greater than 0
* An array ''scripts'' of ''m'' TapScripts as byte-arrays.
** The scripts must be written in the following format: "[PubKey p<sub>1</sub>] OP_CHECKSIG [PubKey p<sub>2</sub>] OP_CHECKSIGADD ... [PubKey p<sub>K</sub>] OP_CHECKSIGADD OP_[K] OP_NUMEQUAL"<ref>'''1-of-N Multisig TapScripts''', it is possible to save two bytes in each script by dropping "OP_[K] OP_NUMEQUAL" from the end of each script. Since OP_CHECKSIG will return 1 on success and the empty array on failure, and the script interpreter considers a final stack of truthy values such as 1 as the script succeeding, and likewise for falsy values such as the empty array, the additional OP_NUMEQUAL comparison and associated number push is redundant.</ref>
Where the list p<sub>1</sub> ... p<sub>K</sub> represents a unique combination of K public keys from the total set of N public keys. In this way, each TapScript is a K-of-K multisig, requiring the signatures of all parties participating in the TapLeaf.
And returns as the outputs:
* The scriptPubKey, including the hash of the generated withness program and the push bytes.
Algorithm steps:
# Generate a random private key ''p'', in the range ''[0, n-1]''.
# Set the internal key ''internal_pubkey'' to ''lift_x(0x0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) + p*G''<ref>'''Why is an arbitrary public key used for signing and spending?''' All possible combinations of multisignature spends are already enumerated in the script path, so the internal public key is not only redundant, but a security hazard since it must be specified. Values that will make Taproot validation fail cannot be used. BIP341 advises that in such cases, an internal public key with unknown discrete logarithm should be used.</ref>, where ''G'' is the secp256k1 generator point.
# Set ''script_tree'' to the empty list.
# For each script (''i'' in the range ''[0,m-1]'', convert it into a tuple with first element a byte 0xc0 and second element the script itself, and append it to ''script_tree''.
# Return the result of ''taproot_output_script(internal_pubkey, script_tree)''.
=== Spending K-of-N Multisig outputs ===
Only one of the multisignature TapScripts will be spent in a K-of-N Taproot Multisig.
The algorithm takes as inputs:
* An integer value ''m'', greater than 0
* An array ''scripts'' of ''m'' TapScripts as byte-arrays, in the format taken by the Multisig Construction algorithm
* An integer value ''j'', greater than 0 and less than ''m'', that indicates which multisignature TapScript will be used to spend the output.
* The witness stack ''inputs'' of the script ''scripts[i]'', as an array of byte-arrays.
** The witness stack must be written in the following format: "[Signature s<sub>K</sub>] [Signature s<sub>K-1</sub>] ... [Signature s<sub>0</sub>]"
Where the list s<sub>1</sub> ... s<sub>K</sub> are the Schnorr signatures corresponding to the public keys p<sub>1</sub> ... p<sub>K</sub>. Note that the list of signatures is coded in the reverse order, because the script interpreter will pop the left-most byte-array as the first stack element, the second-left-most byte array as the second stack element, and so on.
And returns as the outputs:
* The witness, that can spend the scriptPubKey returned by the Multisig Construction algorithm.
Algorithm steps:
# Generate a random private key ''p'', in the range ''[0, n-1]''.
# Set the internal key ''internal_pubkey'' to ''lift_x(0x0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) + p*G''<ref>'''Why is an arbitrary public key used for signing and spending?''' All possible combinations of multisignature spends are already enumerated in the TapLeaves, so the internal public key is not only redundant, but a security hazard since it must be specified. Values that will make Taproot validation fail cannot be used. BIP341 advises that in such cases, an internal public key with unknown discrete logarithm should be used.</ref>.
# Set the internal key ''internal_pubkey'' to ''lift_x(0x0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) + p*G'', where ''G'' is the secp256k1 generator point.
# Set ''script_tree'' to the empty list.
# For each script (''i'' in the range ''[0,m-1]'', convert it into a tuple with first element a byte 0xc0 and second element the script itself, and append it to ''script_tree''.
# Return the result of ''taproot_sign_script(internal_pubkey, script_tree, j, inputs)''.
== Notes ==
[[bip342.mediawiki|BIP342]] mentions that a complete TapBranch of ''k-of-k'' multisignature leaves are "only more cost effective for small values of ''k'' (1-of-''n'' for any ''n'', 2-of-''n'' for ''n ≥ 6'', 3-of-''n'' for ''n ≥ 9'', ...)". Since the scripts are all of fixed size, and the number of TapLeaves can similarly be calculated, it is possible to derive a formula for the relative size in (v)bytes of a spent Multisig Taproot output.
* The size of each script is ''33*K + 2''.
* The size of the control block is ''33 + 32 * ceil(log2(nCr(N,K)))'', where ''nCr'' computes the binomial coefficient of ''N'' and ''K''.
* Therefore, the size of the witness inside the output is ''32*ceil(log2(nCr(N,K))) + 33*K + 35''. A table of output sizes is provided for the first few values of N and K.
N,K,Size (vbytes)
1,1,68
2,1,100
2,2,101
3,1,132
3,2,165
3,3,134
4,1,132
4,2,197
4,3,198
4,4,167
5,1,164
5,2,229
5,3,262
5,4,263
5,5,200
6,1,164
6,2,229
6,3,294
6,4,295
6,5,296
6,6,233
The data shows that 1-of-N Multisig TapScripts have the smallest witness output, and K-of-N Multisig Tapscripts with ''K > 1'' and progressively increasing to ''N-1'' have increasingly larger sizes. Where the K-of-N combination has a smaller size than the equivalent N-of-N combination, it is deemed to be cost-efficient. Hence, since 2 cosigners out of a maximum of 6 makes a transaction size smaller than 6-of-6, 2-of-6 multisig is the largest cost-effective combination for ''N = 6''. If the data is extended, it can similary be proven that 3-of-9 multisig is the largest cost-effective combination for ''N = 9''.<ref>'''Cost-effective delegations''' Several delegation schemes such as Lightning Network channels use only a combination of 1-of-N and N-of-N multisig transactions, with small N > 1.</ref>
The following Python 3.8 code an be used to calculate transaction sizes for ''K > 0'', ''N > 0'', and ''N ≥ K'':
<source lang="python">
>>> import math
>>> txsize = lambda n,k : 32*math.ceil(math.log2(math.comb(n, k))) + 33*k + 35
>>> txsize(1,1)
68
# ...
</source>
== Rationale ==
<references />
== Acknowledgements ==
Thanks to garlonicon, vjudeu, and Ali Ashraf for providing feedback about multisignatures while this document was being written.
--------
I appreciate any comments about this, especially from the BIP editors.
- Ali
reply other threads:[~2022-08-20 13:49 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220820134850.ofvz7225zwcyffit@artanis \
--to=ali@notatether$(echo .)com \
--cc=bitcoin-dev@lists$(echo .)linuxfoundation.org \
--cc=luke_bipeditor@dashjr$(echo .)org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox