CH07: Add MAST, P2C, scriptless multisignature, taproot, tapscript

develop
David A. Harding 1 year ago
parent fe575bb33e
commit 1a27ee296e

@ -1311,7 +1311,7 @@ using SHA256 without RIPEMD160 is that P2WSH commitments are 32 bytes
For the Pay-to-Taproot (P2TR) output, the witness program is a point on
the secp256k1 curve. It may be a simple public key, but in most cases
it should be a public key that commits to some additional data. We'll
learn more about that commitment in <<FIXME_later_chapter_about_taproot>>.
learn more about that commitment in <<taproot>>.
For the example of a future segwit version, we simply use the highest
possible segwit version number (16) and the smallest allowed witness

@ -114,6 +114,7 @@ possible to store private keys more securely than public keys.
.Deterministic key generation: a deterministic sequence of keys derived from a seed for a wallet database
image::images/mbc2_0502.png["Deterministic Wallet"]
[[public_child_key_derivation]]
==== Public Child Key Derivation
In <<public_key_derivation>>, we learned how to create a public key from a private key

@ -1245,6 +1245,7 @@ account directly.
Here's the redeemScript that Mohammed designs to achieve this (line
number prefixed as XX):
[[variable_timelock_multisig]]
.Variable Multi-Signature with Timelock
----
01 OP_IF
@ -1626,7 +1627,7 @@ Even though Alice's wallet has no support for segwit, the payment it
creates can be spent by Bob with a segwit transaction.((("",
startref="aliced")))
===== Pay-to-Witness-Script-Hash inside Pay-to-Script-Hash
===== Nested Pay-to-Witness-Script-Hash
Similarly, a P2WSH witness program for a multisig script or other
complicated script can be embedded inside a P2SH script and address,
@ -1697,3 +1698,367 @@ OP_HASH160 86762607e8fe87c0c37740cddee880988b9455b2 OP_EQUAL
Mohammed's company can then construct segwit transactions to spend these
payments, taking advantage of segwit features including lower
transaction fees.
=== Merklized Alternative Script Trees (MAST)
Using +OP_IF+, you can authorize multiple different spending conditions,
but this approach has several undesirable aspects:
- _Weight (cost):_ every condition you add increases the size of the
script, increasing the weight of the transaction and the amount of fee
that will need to be paid in order to spend bitcoins protected by
that script.
- _Limited size:_ even if you're willing to pay for extra conditions,
there's a limit to the maximum number you can put in a script. For
example, legacy script is limited to 10,000 bytes, practically
limiting you to a few hundred conditional branches at most. Even if
you could create a script as large as an entire block, it could still
only contain about 20,000 useful branches. That's a lot for simple
payments but tiny compared to some imagined uses of Bitcoin.
- _Lack of privacy:_ every condition you add to your script becomes
public knowledge when you spend bitcoins protected by that script.
For example, Mohammed's lawyer and business partners will be able to
see the entire script in <<variable_timelock_multisig>> whenever
anyone spends from it. That means their lawyer, even if he's not
needed for signing, will be able to track all of their transactions.
However, Bitcoin already uses a data structure known as a merkle tree
that allows verifying an element is a member of a set without
needing to identify every other member of the set.
We'll learn more about merkle trees in <<merkle_trees>>, but the
essential information is that members of the set of information we want
(e.g. authorization conditions of any length) can be passed into a hash
function to create a short commitment (called a _leaf_ of the merkle
tree). Each of those leaves is then paired with another leaf
and hashed again, creating a commitment to the leaves, called a
_branch_ commitment. A commitment to a pair of branches can be created
the same way. This step is repeated for the branches until only one
identifier remains, called the _merkle root_. Using our example script
from <<variable_timelock_multisig>>, we construct a merkle tree for each
of the three authorization conditions in <<diagram_mast1>>.
[[diagram_mast1]]
.A MAST with three sub-scripts
image::../images/mast1.dot.png["A MAST with three sub-scripts"]
We can now create a compact membership proof that proves a particular
authorization condition is a member of the merkle tree without
disclosing any details about the other members of the merkle tree. See
<<diagram_mast2>> and note that the nodes with diagonal corners can be
computed from other data provided by the user, so they don't need to be
specified at spend time.
[[diagram_mast2]]
.A MAST membership proof for one of the sub-scripts
image::../images/mast2.dot.png["A MAST membership proof for one of the sub-scripts"]
The hash digests used to create the commitments are each 32-bytes, so
proving the above spend is authorized (using a merkle tree and the
particular conditions) and authenticated (using signatures) uses 383
bytes. By comparison, the same spend without a merkle tree (i.e.
providing all possible authorization conditions) uses 412 bytes.
Saving 29 bytes (7%) in this example doesn't fully
capture the potential savings. The binary-tree nature of a merkle tree
means that you only need an additional 32-byte commitment every time
you double the number of members in the set (in this case, authorization
conditions). In this case, with three conditions, we need to use three
commitments (one of them being the merkle root, which will need to be
included in the authorization data); we could also have four
commitments for the same cost. An extra commitment would give us up to
eight conditions. With just 16 commitments--512 bytes of commitments--we could have
over 32,000 authorization conditions, far more that could be effectively
used in an entire block of +OP_IF+ statements. With 128 commitments
(4,096 bytes), the number of conditions we could create in theory far
exceeds the number of conditions that all the computers in the world
could create.
It's commonly the case that not every authorization condition is equally
as likely to be used. In the our example case, we expect Mohammed and
his partners to spend their money frequently; the time delayed
conditions only exist in case something goes wrong. We can re-structure
our tree with this knowledge in <<diagram_mast3>>.
[[diagram_mast3]]
.A MAST with the most-expected script in the best position
image::../images/mast3.dot.png["A MAST with the most-expected script in the best position"]
Now we only need to provide two commitments for the common case (saving 32
bytes), although we still need three commitments for the less common cases.
If you know (or can guess) the probabilities of
using the different authorization conditions, you can use the Huffman
algorithm to place them into a maximally-efficient tree; see BIP341 for
details.
Regardless of how the tree is constructed, we can see in the above
examples that we're only revealing the actual authorization conditions
that get used. The other conditions remain private. It even remains
private the number of conditions: a tree could have a single condition
or a trillion conditions--there's no way for someone looking only at the
onchain data for a single transaction to tell.
Except for increasing the complexity of Bitcoin slightly, there are no
significant downsides of MAST for Bitcoin and there were two solid
proposals for it, BIP114 and BIP116, before an improved approach was
discovered, which we'll see in <<taproot>>.
[[pay_to_contract]]
=== Pay to Contract (P2C)
As we saw in <<public_child_key_derivation>>, the math of Elliptic Curve
Cryptography (ECC) allows Alice to use a private key to derive a public
key that she gives to Bob. He can add an arbitrary value to that public
key create a derived public key. If he gives that arbitrary value to Alice, she can
add it to her private key to derive the private key for the derived
public key. In short, Bob can create child public keys for which only
Alice can create the corresponding private keys. This is useful for
BIP32-style Hierarchical Deterministic (HD) wallet recovery, but it can
also serve another use.
Let's imagine Bob wants to buy something from Alice but he also wants to
be able prove later what he paid for in case there's any dispute. Alice
and Bob agree on the name of the item or service being sold, e.g.
"Alice's podcast episode #123", and transform that description into a
number by hashing it and interpreting the hash digest as a number. Bob
adds that number to Alice's public key and pays it. The process is
called _key tweaking_ and the number is known as a _tweak_.
Alice can spend the funds by tweaking her private key using the same
number (tweak).
Later, Bob can prove to anyone what he paid Alice by revealing her
underlying key and the description they used. Anyone can verify that
the public key which was paid equals the underlying key plus the
hash commitment to the description. If Alice admits that key is hers,
then she received the payment. If Alice spent the funds, this further
proves she knew the description at the time she signed the spending
transaction, since she could only create a valid signature for the
tweaked public key if she knew the tweak (the description).
If neither Alice or Bob decided to publicly reveal the description they
use, the payment between them looks like any other payment. There's no
privacy loss.
Because P2C is private by default, we can't know how often it is used
for its original purpose--in theory every payment could be using it,
although we consider that unlikely. However, P2C is widely used today
in a slightly different form, which we'll see in <<taproot>>.
=== Scriptless Multisignature
In <<multisig>>, we looked at scripts which require signatures from
multiple keys. However, there's another way to require cooperation from
multiple keys, which is also confusingly called _multisignature_. To
distinguish between the two types in this section, we'll call the
version involving `OP_CHECKSIG`-style opcodes _script multisignatures_
and the other version _scriptless multisignatures_.
Scriptless multisignatures involves each participant creating their own
secret the same way they create a private key. We'll call this secret a
_partial private key_, although we should note that it's the same length
as a regular full private key. From the partial private key, each
participant derives a partial public key using the same algorithm used
for regular public keys we described in <<public_key_derivation>>. Each
participant shares their partial public keys with all the other
participants and then combines all of the keys together to create the
scriptless multisignature public key.
This combined public key looks the same as any other Bitcoin public key.
A third party can't distinguish between a multi-party public key and an
ordinary key generated by a single user.
To spend bitcoins protected by the scriptless multisignature public key,
each participant generates a partial signature. The partial signatures
are then combined to create a regular full signature. There are
many known methods for creating and combining the partial signatures;
we'll look at this topic more in <<c_signatures>>. Similar to the public
keys for scriptless multisignatures, the signatures generated by this
process look the same as any other Bitcoin signature. Third parties
can't determine whether a signature was created by a single person or a
million people cooperating with each other.
Scriptless multisignatures are smaller and more private than scripted
multisignatures. For scripted multisignatures, the number of bytes
placed in a transaction increases for every key and signature involved.
For scriptless multisignatures, the size is constant--a million
participants each providing their own partial key and partial signature
puts exactly the same amount of data in a transaction as an individual
using a single key and signature. The story is the same for privacy:
because each new key or signature adds data to a transaction, scripted
multisignatures disclose data about how many keys and signatures are
being used--which may make it easy to figure out which transactions were
created by which group of participants. But, because every scriptless
multisignatures looks identical to every other scriptless
multisignature and every single-signature, no privacy-reducing data is
leaked.
There are two downsides of scriptless multisignatures. The first is
that all known secure algorithms for creating them for Bitcoin require more
rounds of interaction or more careful management of state (or both) than
scripted multisignature. This can be challenging in cases where
signatures are being generated by nearly stateless hardware signing
devices and the keys are physically distributed. For example, if you
keep a hardware signing device in a bank safe deposit box, you would
need to visit that box once to create a scripted multisignature but
possibly two or three times for a scriptless multisignature.
The other downside is that threshold signing doesn't reveal who signed.
In scripted threshold signing, Alice, Bob, and Carol agree (for example)
that any two of them signing will be sufficient to spend the funds.
If Alice and Bob sign, this requires putting signatures from each of
them on chain, proving to anyone who knows their keys that they signed
and Carol didn't. In scriptless threshold signing, a signature from
Alice and Bob is indistinguishable from a signature between Alice and
Carol or Bob and Carol. This is beneficial for privacy, but it means
that, even if Carol claims she didn't sign, she can't
prove that she didn't, which may be bad for accountability and
auditability.
For many users and use cases, the always reduced sized and increased
privacy of multisignatures outweighs its occasional challenges for
creating and auditing signatures.
[[taproot]]
=== Taproot
One reason people choose to use Bitcoin is that it's possible to create
contracts with highly predictable outcomes. Legal contracts enforced by
a court of law depend in part on decisions by the judges and jurors
involved in the case. By contrast, Bitcoin contracts often require
actions by their participants but are otherwise enforced by thousands of
full nodes all running functionally identical code. When given the same
contract and the same input, every full node will always produce the
same result. Any deviation would mean that Bitcoin was broken.
Human judges and juries can be much more flexible than software, but
when that flexibility isn't wanted or needed, the predictability of
Bitcoin contracts is a major asset.
If all of the participants in a contract recognize that its outcome has
become completely predictable, there's not actually any need for them to
continue using the contract. They could just do whatever the contract
compels them to do and then terminate the contract. In society, this
is how most contracts terminate: if the interested parties are
satisfied, they never take the contract before a judge or jury. In
Bitcoin, it means that any contract which will use a significant amount
of block space to settle should also provide a clause that allows it to
instead be settled by mutual satisfaction.
In MAST and with scriptless multisignatures, a mutual satisfaction
clause is easy to design. We simply make one of the top leaves of the
script tree a scriptless multisignature between all interested parties.
We already saw a complex contract between several parties with a
simple mutual satisfaction clause in <<diagram_mast3>>. We could make
that more optimized by switching from scripted multisignature to
scriptless multisignature.
That's reasonably efficient and private. If the mutual satisfaction
clause is used, we only need to provide a single merkle branch and all
we reveal is that a signature was involved (it could be from one person
or it could be from thousands of different participants). But
developers in 2018 realized that we could do better if we also used
pay-to-contract.
In our previous description of pay-to-contract in <<pay_to_contract>>,
we tweaked a public key to commit to the text of an agreement between
Alice and Bob. We can instead commit to the program code of a contract
by committing to the root of a MAST. The public key we tweak
is a regular Bitcoin public key, meaning it could require a signature
from a single person or it could require a signature from multiple
people (or it could be created in a special way to make it impossible to
generate a signature for it). That means we can satisfy the contract
either with a single signature from all interested parties or by
revealing the MAST branch we want to use. That commitment tree
involving both a public key and a MAST is shown in [[diagram_taproot]].
[[diagram_taproot1]]
.A taproot with the public key committing to a merkle root
image::../images/taproot1.dot.png["A taproot with the public key committing to a merkle root"]
This makes the mutual satisfaction clause using a multisignature
extremely efficient and very private. It's even more private than it
may appear because any transaction created by a single user who wants it
to be satisfied by a single signature (or a multisignature generated by
multiple different wallets they control) looks identical onchain to a
mutual-satisfaction spend.
When spending is possible using just the key, such for a single signature
or scriptless multisignature, that is called _keypath spending_. When
the tree of scripts is used, that is called _scriptpath spending_.
For keypath spending, the data that gets put onchain is the public key
(in a witness program) and the signature (in the witness data).
For scriptpath spending, the onchain data also includes the public key,
which is placed in a witness program and called the _taproot output key_
in this context. The witness data includes the following information:
1. A version number
2. The underlying key--the key which existed before being tweaked by the
merkle root to produce the taproot output key. This underlying key
is called the _taproot internal key_
3. The script to execute
4. One 32-byte hash for each merkle branch connecting the script to the merkle root
5. Any data necessary to satisfy the script (such as signatures or hash preimages)
// Source for 33 bytes: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-February/017622.html
We're only aware of one significant described downside of taproot:
contracts whose participants want to use MAST but who don't want a
mutual satisfaction clause have to include a taproot internal key on the
blockchain, adding about 33 bytes of overhead. Given that almost
all contracts are expected to benefit from a mutual satisfaction clause,
or other multisignature clause that uses the top-level public key, and
all users benefit from the increased anonymity set of outputs looking
similar to each other, that rare overhead was not considered important
by most users who participated in taproot's activation.
Support for taproot was added to Bitcoin in a soft fork which activated
in November 2021.
=== Tapscript
Taproot enables MAST but only with a slightly different version of the
Bitcoin Script language than previously used, the new version being
called _tapscript_. The major differences include:
Scripted multisignature changes::
The old +OP_CHECKMULTSIG+ and +OP_CHECKMULTISIGVERIFY+ opcodes are
removed. Those opcodes don't combine well with one of the other
changes in the taproot soft fork, the ability to use schnorr signatures
with batch validation. A new +OP_CHECKSIGADD+ opcode is provided
instead. When it successfully verifies a signature, this new opcode
increments a counter by one, making it possible to conveniently count
how many signatures passed, which can be compared against the desired number
of successful signatures to reimplement the same behavior as
+OP_CHECKMULTISIG+.
Changes to all signatures::
All signature operations in tapscript use the schnorr signature
algorithm as defined in BIP340. We'll explore schnorr signatures more
in <<c_signatures>>.
+
Additionally, any signature-checking operation which is not expected
to succeed must be fed the value +OP_FALSE+ (also called +OP_0+)
instead of an actual signature. Providing anything else to a failed
signature-checking operation will cause the entire script to fail.
This also helps support batch validation of schnorr signatures.
OP_SUCCESSx opcodes::
Opcodes in previous versions of script which were unusable are now
redefined to be cause an entire script to succeed if they are used.
This allows future soft forks to redefine them as not succeeding, which
is a restriction and so is possible to do in a soft fork. (The
opposite, to define a not-succeeding operation as a success can only
be done in a hard fork, which is a much more challenging upgrade
path.)
Although we've looked at authorization and authentication in depth in
this chapter, we've skipped over one very important part of how Bitcoin
authenticates spenders: its signatures. We'll look at that next in
<<c_signatures>>.

@ -0,0 +1,21 @@
digraph merkle_tree {
splines=ortho;
node [shape=box, style="filled", color="black", fontcolor="black", fillcolor="white"];
"Merkle Root" -> "Hash AB";
"Merkle Root" -> "Hash C";
"Hash AB" -> "Hash A";
"Hash AB" -> "Hash B";
"Hash A" -> "A";
"Hash B" -> "B";
"Hash C" -> "C" [minlen = 2];
"Merkle Root" [label="Merkle Root"];
"Hash AB" [label="Hash AB"];
"Hash A" [label="Hash A"];
"Hash B" [label="Hash B"];
"Hash C" [label="Hash C"];
"A" [label="2 <M> <S> <Z> 3 OP_CHECKMULTISIG"];
"B" [label="<30 days> OP_CSV OP_DROP\n<Lawyer> OP_CHECKSIGVERIFY\n1 <M> <S> <Z> 3 OP_CHECKMULTISIG"];
"C" [label="<90 days> OP_CSV OP_DROP\n<Lawyer> OP_CHECKSIG"];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

@ -0,0 +1,21 @@
digraph merkle_tree {
splines=ortho;
node [shape=box, style="filled", color="black", fontcolor="black", fillcolor="white"];
"Merkle Root" -> "Hash AB";
"Merkle Root" -> "Hash C";
"Hash AB" -> "Hash A";
"Hash AB" -> "Hash B";
"Hash A" -> "A";
//"Hash B" -> "B";
//"Hash C" -> "C" [minlen = 2];
"Merkle Root" [label="Merkle Root", style = "diagonals"];
"Hash AB" [label="Hash AB", style = "diagonals" ];
"Hash A" [label="Hash A" style = "diagonals"];
"Hash B" [label="Hash B"];
"Hash C" [label="Hash C"];
"A" [label="2 <M> <S> <Z> 3 OP_CHECKMULTISIG"];
//"B" [label="<30 days> OP_CSV OP_DROP\n<Lawyer> OP_CHECKSIGVERIFY\n1 <M> <S> <Z> 3 OP_CHECKMULTISIG"];
//"C" [label="<90 days> OP_CSV OP_DROP\n<Lawyer> OP_CHECKSIG"];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

@ -0,0 +1,21 @@
digraph merkle_tree {
splines=ortho;
node [shape=box, style="filled", color="black", fontcolor="black", fillcolor="white"];
"Merkle Root" -> "Hash AB";
"Merkle Root" -> "Hash C";
"Hash AB" -> "Hash A";
"Hash AB" -> "Hash B";
"Hash A" -> "A";
"Hash B" -> "B";
"Hash C" -> "C" [minlen = 2];
"Merkle Root" [label="Merkle Root"];
"Hash AB" [label="Hash AB"];
"Hash A" [label="Hash A"];
"Hash B" [label="Hash B"];
"Hash C" [label="Hash C"];
"C" [label="2 <M> <S> <Z> 3 OP_CHECKMULTISIG", style="filled", fillcolor="silver"];
"B" [label="<30 days> OP_CSV OP_DROP\n<Lawyer> OP_CHECKSIGVERIFY\n1 <M> <S> <Z> 3 OP_CHECKMULTISIG"];
"A" [label="<90 days> OP_CSV OP_DROP\n<Lawyer> OP_CHECKSIG"];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@ -0,0 +1,16 @@
digraph merkle_tree {
splines=ortho;
node [shape=box, style="filled", color="black", fontcolor="black", fillcolor="white"];
"Public Key" -> "Merkle Root"
"Merkle Root" -> "Hash A";
"Merkle Root" -> "Hash B";
"Hash A" -> "A";
"Hash B" -> "B";
"Merkle Root" [label="Merkle Root"];
"Hash A" [label="Hash A"];
"Hash B" [label="Hash B"];
"B" [label="Script 0"];
"A" [label="Script 1"];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Loading…
Cancel
Save