mirror of
https://github.com/bitcoinbook/bitcoinbook
synced 2024-12-22 22:58:09 +00:00
CH07: Add MAST, P2C, scriptless multisignature, taproot, tapscript
This commit is contained in:
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>>.
|
||||
|
21
images/mast1.dot
Normal file
21
images/mast1.dot
Normal file
@ -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"];
|
||||
}
|
BIN
images/mast1.dot.png
Normal file
BIN
images/mast1.dot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
21
images/mast2.dot
Normal file
21
images/mast2.dot
Normal file
@ -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"];
|
||||
}
|
BIN
images/mast2.dot.png
Normal file
BIN
images/mast2.dot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
21
images/mast3.dot
Normal file
21
images/mast3.dot
Normal file
@ -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"];
|
||||
}
|
BIN
images/mast3.dot.png
Normal file
BIN
images/mast3.dot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
16
images/taproot1.dot
Normal file
16
images/taproot1.dot
Normal file
@ -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"];
|
||||
}
|
BIN
images/taproot1.dot.png
Normal file
BIN
images/taproot1.dot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
Loading…
Reference in New Issue
Block a user