learntaproot/session/answers.py

1049 lines
44 KiB
Python

"""
#markdown
# Session Objectives
* Learn Schnorr Signatures
* Learn Taproot Key Spend
* Learn Taproot Path Spend
#endmarkdown
#markdown
# Schnorr Signatures
#endmarkdown
#markdown
# Motivation
* Conceptually Simpler
* ECDSA uses DER, which is 72-73 bytes, Schnorr uses 64
* Fewer Elliptic Curve Operations (hash instead)
* Key Aggregation/Signature Aggregation/Batch Verification
#endmarkdown
#markdown
# ECDSA Signing
* $eG=P$, $z$ is hash of what's being signed, choose a random $k$
* Compute $kG=R=(x,y)$, let $r=x$
* Compute $s=\frac{z+re}{k}$
* Signature is the pair $(r,s)$
#endmarkdown
#markdown
# ECDSA Verification
* $eG=P$, $z$ is hash of what's being signed, choose a random $k$
* Signature is $(r,s)$ where $s=\frac{z+re}{k}$
* Compute $u=\frac{z}{s}, v=\frac{r}{s}$
* $$uG+vP=\frac{z}{s}G+\frac{r}{s}P=\frac{z}{s}G+\frac{re}{s}G \\ =\frac{z+re}{s}G =\frac{(z+re)k}{z+re}G \\ =kG=R=(r,y)$$
#endmarkdown
#markdown
# ECDSA
* $u$ is used to commit to $z$, or the tx being attested to
* $v$ is used to commit to $r$, or the target/challenge that we're trying to hit/respond to
* Kludgy, uses field division, which is expensive computationally
* Developed after Schnorr and used in Bitcoin due to Patent issues (expired 2008)
#endmarkdown
#markdown
# Schnorr
* Uses a hash function instead of field division
* That hash can commit to everything at once, instead of just one thing.
* $H(a||b||c||...)$
* Target is a point on the curve $R$, not just the $x$ coordinate
* Aggregation of keys and signatures now possible!
* Batch verification possible!
* BIP340
#endmarkdown
#markdown
# Tagged Hashes
* Each hash is different so that hashes cannot feasibly collide
* There are 10 different contexts, each creating its own set of hashes
* The hash is SHA256, but with 64 bytes before the actual bytes being hashed
* The 64 bytes are another SHA256 of the tag (e.g. "BIP340/aux") repeated twice
* H_aux(x) = SHA256(SHA256("BIP340/aux") + SHA256("BIP340/aux") + x)
#endmarkdown
#code
>>> import ecc
>>> import hash
>>> import op
>>> import taproot
#endcode
#code
>>> # Example Tagged Hashes
>>> from hash import sha256
>>> challenge_tag = b"BIP0340/challenge"
>>> msg = b"some message"
>>> challenge_hash = sha256(challenge_tag)
>>> hash_challenge = sha256(challenge_hash + challenge_hash + msg)
>>> print(hash_challenge.hex())
233a1e9353c5f782c96c1c08323fe9fca47ad161ee69d008846b68625c221113
#endcode
#exercise
What is the tagged hash "BIP0340/aux" of "hello world"?
----
>>> from hash import sha256
>>> # define the challenge tag and the message
>>> challenge_tag = b"BIP0340/aux" #/
>>> msg = b"hello world" #/
>>> # calculate the challenge tag hash using sha256
>>> challenge_tag_hash = sha256(challenge_tag) #/
>>> # calculate the hash of the challenge
>>> hash_challenge = sha256(challenge_tag_hash + challenge_tag_hash + msg) #/
>>> print(hash_challenge.hex()) #/
1d721a19d161e978e7436d9e73bb810a0a32cbdffc7a9b29e11713b1940a4126
#endexercise
#unittest
hash:HashTest:test_tagged_hash:
#endunittest
#markdown
# $x$-only keys
* Assume $y$ is even
* Serialized as 32-bytes
* The private key $e$ is flipped to $N-e$ if $y$ is odd
* $eG=P=(x,y)$ means $(N-e)G=0-eG=-P=(x,-y)$
* Lots of flipping!
#endmarkdown
#code
>>> # Example X-only pubkey
>>> from ecc import PrivateKey, S256Point
>>> from helper import int_to_big_endian
>>> pubkey = PrivateKey(12345).point
>>> xonly = int_to_big_endian(pubkey.x.num, 32)
>>> print(xonly.hex())
f01d6b9018ab421dd410404cb869072065522bf85734008f105cf385a023a80f
>>> pubkey2 = S256Point.parse(xonly)
>>> print(pubkey.xonly() == pubkey2.xonly())
True
#endcode
#exercise
Find the $x$-only pubkey format for the private key with the secret 21,000,000
---
>>> from ecc import PrivateKey
>>> secret = 21000000
>>> # create a private key with the secret
>>> priv = PrivateKey(secret) #/
>>> # get the public point for the private key
>>> point = priv.point #/
>>> # convert the x coordinate to a big-endian integer 32 bytes
>>> xonly = int_to_big_endian(point.x.num, 32) #/
>>> print(xonly.hex()) #/
e79c4eb45764bd015542f6779cc70fef44b7a2432f839264768288efab886291
#endexercise
#unittest
ecc:XOnlyTest:test_xonly:
#endunittest
#markdown
# Schnorr Signature
* $x$-only keys (Always even $y$)
* Uses tagged hashes (different tagged per operation)
* $k$-generation has a separate process
* Target is an $x$-only key
* $(R,s)$, where $R$ is the pubkey of the target and $s$ is 32 bytes
* Serialization is $R$ as $x$-only followed by $s$ in big endian
#endmarkdown
#markdown
# ECDSA Verification
* $eG=P$, $z$ message, $kG=(r,y)$
* Signature is $(r,s)$ where $s=\frac{z+re}{k}$
* Compute $u=\frac{z}{s}, v=\frac{r}{s}$
* $uG+vP=\frac{z}{s}G+\frac{r}{s}P=\frac{z}{s}G+\frac{re}{s}G \\ =\frac{z+re}{s}G =\frac{(z+re)k}{z+re}G \\ =kG=R=(r,y)$
#endmarkdown
#markdown
# Schnorr Verification
* $eG=P$, $m$ message, $kG=R$, $H$ is a hash function
* Signature is $(R,s)$ where $s=k + e H(R||P||m)$
* $$-H(R||P||m)P+sG \\ =-H(R||P||m)P+(k+e H(R||P||m))G \\ =-H(R||P||m)P+kG+H(R||P||m)(eG) \\ =R+H(R||P||m)P-H(R||P||m)P=R$$
#endmarkdown
#code
>>> from ecc import S256Point, SchnorrSignature, G, N
>>> from helper import sha256, big_endian_to_int
>>> from hash import hash_challenge
>>> # the message we're signing
>>> msg = sha256(b"I attest to understanding Schnorr Signatures")
>>> # the signature we're using
>>> sig_raw = bytes.fromhex("f3626c99fe36167e5fef6b95e5ed6e5687caa4dc828986a7de8f9423c0f77f9bc73091ed86085ce43de0e255b3d0afafc7eee41ddc9970c3dc8472acfcdfd39a")
>>> sig = SchnorrSignature.parse(sig_raw)
>>> # the pubkey we are using
>>> xonly = bytes.fromhex("f01d6b9018ab421dd410404cb869072065522bf85734008f105cf385a023a80f")
>>> point = S256Point.parse(xonly)
>>> # calculate the commitment which is R || P || msg
>>> commitment = sig.r.xonly() + point.xonly() + msg
>>> # hash_challenge the commitment, interpret as big endian and mod by N
>>> challenge = big_endian_to_int(hash_challenge(commitment)) % N
>>> # the target is the -challenge * point + s * G
>>> target = -challenge * point + sig.s * G
>>> print(target == sig.r)
True
#endcode
#exercise
Verify this Schnorr Signature
Pubkey = cbaa648dbfe734646ce958e2f14a874149fae4010fdeabde4bae6a732537fd91
Signature = 2ae68f873376a0ff302258964632f7b98b21e3bbc72dcc8fb31de8acf01696b951f3dbb6fc5532558219472fb63e061f9a4c7d1760cc588da551c74374cd0de4
Message = 1a84547db188f0b1d2c9f0beac230afebbd5e6e6c1a46fc69841815194bf8612
---
>>> from ecc import SchnorrSignature, S256Point, N, G
>>> from hash import hash_challenge
>>> from helper import big_endian_to_int
>>> p_raw = bytes.fromhex("cbaa648dbfe734646ce958e2f14a874149fae4010fdeabde4bae6a732537fd91")
>>> pubkey = S256Point.parse(p_raw)
>>> sig_raw = bytes.fromhex("2ae68f873376a0ff302258964632f7b98b21e3bbc72dcc8fb31de8acf01696b951f3dbb6fc5532558219472fb63e061f9a4c7d1760cc588da551c74374cd0de4")
>>> sig = SchnorrSignature.parse(sig_raw)
>>> msg = bytes.fromhex("1a84547db188f0b1d2c9f0beac230afebbd5e6e6c1a46fc69841815194bf8612")
>>> # create the commitment: R || P || m (points should be xonly)
>>> commitment = sig.r.xonly() + pubkey.xonly() + msg #/
>>> # hash the commitment with hash_challenge and then big_endian_to_int it
>>> h = big_endian_to_int(hash_challenge(commitment)) #/
>>> # check that -hP+sG=R
>>> print(-h*pubkey + sig.s*G == sig.r) #/
True
#endexercise
#unittest
ecc:SchnorrTest:test_verify:
#endunittest
#markdown
# ECDSA Signing
* $eG=P$, $z$ message, $k$ random
* $kG=R=(x,y)$, let $r=x$
* $s=\frac{z+re}{k}$
* Signature is $(r,s)$
#endmarkdown
#markdown
# Schnorr Signing
* $eG=P$, $m$ message, $k$ random
* $kG=R$, $H$ is a hash function
* $s=k+e H(R||P||m)$ where $R$ and $P$ are $x$-only
* Signature is $(R,s)$
#endmarkdown
#code
>>> # Example Signing
>>> from ecc import PrivateKey, N, G
>>> from helper import sha256, big_endian_to_int
>>> priv = PrivateKey(12345)
>>> if priv.point.y.num % 2 == 1:
... d = N - priv.secret
... else:
... d = priv.secret
>>> msg = sha256(b"I attest to understanding Schnorr Signatures")
>>> k = 21016020145315867006318399104346325815084469783631925097217883979013588851039
>>> r = k * G
>>> if r.y.num % 2 == 1:
... k = N - k
... r = k * G
>>> commitment = r.xonly() + priv.point.xonly() + msg
>>> e = big_endian_to_int(hash_challenge(commitment)) % N
>>> s = (k + e * d) % N
>>> sig = SchnorrSignature(r, s)
>>> if not priv.point.verify_schnorr(msg, sig):
... raise RuntimeError("Bad Signature")
>>> print(sig.serialize().hex())
f3626c99fe36167e5fef6b95e5ed6e5687caa4dc828986a7de8f9423c0f77f9bc73091ed86085ce43de0e255b3d0afafc7eee41ddc9970c3dc8472acfcdfd39a
#endcode
#exercise
Sign the message b"I'm learning Taproot!" with the private key 21,000,000
----
>>> from ecc import PrivateKey, N, G
>>> from helper import sha256
>>> # create the private key
>>> priv = PrivateKey(21000000) #/
>>> # calculate d (working secret) based on whether the y is even or odd
>>> if priv.point.y.num % 2 == 1:
... d = N - priv.secret
... else:
... d = priv.secret
>>> # create the message
>>> msg = sha256(b"I'm learning Taproot!") #/
>>> # We'll learn more about k later, for now use 987654321
>>> k = 987654321
>>> # get the resulting R=kG point
>>> r = k * G #/
>>> # if R's y coordinate is odd, flip the k
>>> if r.y.num % 2 == 1: #/
... # set k to N - k
... k = N - k #/
... # recalculate R
... r = k * G #/
>>> # calculate the commitment which is: R || P || msg
>>> commitment = r.xonly() + priv.point.xonly() + msg #/
>>> # hash_challenge the result and interpret as a big endian integer mod the result by N and this is your e
>>> e = big_endian_to_int(hash_challenge(commitment)) % N #/
>>> # calculate s which is (k+ed) mod N
>>> s = (k + e * d) % N #/
>>> # create a SchnorrSignature object using the R and s
>>> sig = SchnorrSignature(r, s) #/
>>> # check that this schnorr signature verifies
>>> if not priv.point.verify_schnorr(msg, sig): #/
... raise RuntimeError("Bad Signature") #/
>>> # print the serialized hex of the signature
>>> print(sig.serialize().hex()) #/
5ad2703f5b4f4b9dea4c28fa30d86d3781d28e09dd51aae1208de80bb6155bee7d9dee36de5540efd633445a8d743816cbbc15fb8a1c7768984190d5b873a341
#endexercise
#unittest
ecc:SchnorrTest:test_sign:
#endunittest
#markdown
# $k$ Generation
* Revealing $k$ reveals the private key
* Bad random number generator for $k$ will reveal the private key
* BIP340 starts with a random number $a$ called the auxillary
* Then xor $a$ with the secret to make it impossible to guess
* Then we hash with the message to generate the $k$
* This makes $k$ unique to both the secret and the message
* 32 0-bytes $a$ can be used to create a deterministic $k$
#endmarkdown
#markdown
# Batch Verification
* $e_iG=P_i$, $m_i$ message, $H$
* Signature is $(R_i,s_i)$, $h_i=H(R_i||P_i||m_i)$
* $-h_i P_1+s_1G=R_1$
* $-h_i P_2+s_2G=R_2$
* $-h_1 P_1-h_2 P_1+(s_1+s_2)G=R_1+R_2$
* $(s_1+s_2)G=R_1+R_2+h_1 P_1+h_2 P_2$
#endmarkdown
#exercise
Batch Verify these two Schnorr Signatures
Pubkey 1 = cbaa648dbfe734646ce958e2f14a874149fae4010fdeabde4bae6a732537fd91
Pubkey 2 = e79c4eb45764bd015542f6779cc70fef44b7a2432f839264768288efab886291
Signature 1 = 2ae68f873376a0ff302258964632f7b98b21e3bbc72dcc8fb31de8acf01696b951f3dbb6fc5532558219472fb63e061f9a4c7d1760cc588da551c74374cd0de4
Signature 2 = b6e52f38bc24f1420c4fdae8fa0f04b9b0374a12f18fd4699b06df53eb1386bfa88c1835cd19470cf8c76550eb549c988f9c8fac00cc56fadd4fcc3bf9d8800e
Message 1 = 1a84547db188f0b1d2c9f0beac230afebbd5e6e6c1a46fc69841815194bf8612
Message 2 = af1c325abcb0cced3a4166ce67be1db659ae1dd574fe49b0f2941d8d4882d62c
---
>>> from ecc import SchnorrSignature, S256Point, N, G
>>> from hash import hash_challenge
>>> from helper import big_endian_to_int
>>> p1_raw = bytes.fromhex("cbaa648dbfe734646ce958e2f14a874149fae4010fdeabde4bae6a732537fd91")
>>> p2_raw = bytes.fromhex("e79c4eb45764bd015542f6779cc70fef44b7a2432f839264768288efab886291")
>>> p1 = S256Point.parse(p1_raw)
>>> p2 = S256Point.parse(p2_raw)
>>> sig1_raw = bytes.fromhex("2ae68f873376a0ff302258964632f7b98b21e3bbc72dcc8fb31de8acf01696b951f3dbb6fc5532558219472fb63e061f9a4c7d1760cc588da551c74374cd0de4")
>>> sig2_raw = bytes.fromhex("b6e52f38bc24f1420c4fdae8fa0f04b9b0374a12f18fd4699b06df53eb1386bfa88c1835cd19470cf8c76550eb549c988f9c8fac00cc56fadd4fcc3bf9d8800e")
>>> sig1 = SchnorrSignature.parse(sig1_raw)
>>> sig2 = SchnorrSignature.parse(sig2_raw)
>>> msg1 = bytes.fromhex("1a84547db188f0b1d2c9f0beac230afebbd5e6e6c1a46fc69841815194bf8612")
>>> msg2 = bytes.fromhex("af1c325abcb0cced3a4166ce67be1db659ae1dd574fe49b0f2941d8d4882d62c")
>>> # define s as the s_i sum (make sure to mod by N)
>>> s = (sig1.s + sig2.s) % N #/
>>> # define r as the signatures' r sum
>>> r = sig1.r + sig2.r #/
>>> # create the commitments: R_i||P_i||m_i
>>> commitment_1 = sig1.r.xonly() + p1.xonly() + msg1 #/
>>> commitment_2 = sig2.r.xonly() + p2.xonly() + msg2 #/
>>> # define the h's as the hash_challenge of the commitment as a big endian integer
>>> h1 = big_endian_to_int(hash_challenge(commitment_1)) #/
>>> h2 = big_endian_to_int(hash_challenge(commitment_2)) #/
>>> # compute the sum of the h_i P_i's
>>> h = h1*p1 + h2*p2 #/
>>> # check that sG=R+h
>>> print(s*G == r+h) #/
True
#endexercise
#markdown
# Taproot Key Path Spend
#endmarkdown
#markdown
# Taproot
* BIP341
* Combines both single-key and arbitrary script type addresses
* p2pkh and p2sh did those previously
* p2wpkh and p2wsh did those in Segwit
#endmarkdown
#markdown
# Taproot Architecture
* KeyPath Spend (single-key like p2pkh and p2wpkh)
* ScriptPath Spend (arbitrary script like p2sh and p2wsh)
* ScriptPath is a Merkle Tree of TapScripts
* TapScripts are like Script, but with slightly different OP codes
#endmarkdown
#markdown
# Taproot Implementation
* Segwit version 1
* Requires OP_1 and 32 bytes
* The 32 bytes are an $x$-only public key $Q$ (external public key)
* KeyPath spend's public key is $P$ (internal public key)
* The Merkle Root of the ScriptPath Spend combined with the internal public key generates the tweak ($t$)
* $Q=P+tG$
#endmarkdown
#markdown
# How to spend from the KeyPath
* You have to know the Merkle Root of the ScriptPath
* The internal public key is hashed together with the Merkle Root to generate the tweak $t$
* The formula is $t=H(P||t)$ where H is a Tagged Hash (TapTweak)
* $Q=P+tG$, and $eG=P$ which means $Q=eG+tG$ and $Q=(e+t)G$
* $e+t$ is your private key, which can sign for the public key Q
* Witness only needs the Schnorr Signature
* If you don't want a script path, $t$ is just empty
#endmarkdown
#code
>>> # Example Q calculation for a single-key
>>> from ecc import S256Point, G
>>> from hash import hash_taptweak
>>> from helper import big_endian_to_int
>>> from script import P2TRScriptPubKey
>>> internal_pubkey_raw = bytes.fromhex("cbaa648dbfe734646ce958e2f14a874149fae4010fdeabde4bae6a732537fd91")
>>> internal_pubkey = S256Point.parse(internal_pubkey_raw)
>>> tweak = big_endian_to_int(hash_taptweak(internal_pubkey_raw))
>>> external_pubkey = internal_pubkey + tweak * G
>>> script_pubkey = P2TRScriptPubKey(external_pubkey)
>>> print(script_pubkey)
OP_1 578444b411276eee17e2f69988d192b7e728f4375525a868f4a9c2b78e12af16
#endcode
#exercise
Make a P2TR ScriptPubKey using the private key 9284736473
----
>>> from ecc import PrivateKey, G
>>> from hash import hash_taptweak
>>> from helper import big_endian_to_int
>>> from script import P2TRScriptPubKey
>>> priv = PrivateKey(9284736473)
>>> # get the internal pubkey
>>> internal_pubkey = priv.point #/
>>> # calculate the tweak
>>> tweak = big_endian_to_int(hash_taptweak(internal_pubkey.xonly())) #/
>>> # Q = P + tG
>>> external_pubkey = internal_pubkey + tweak * G #/
>>> # use P2TRScriptPubKey to create the ScriptPubKey
>>> script_pubkey = P2TRScriptPubKey(external_pubkey) #/
>>> # print the ScriptPubKey
>>> print(script_pubkey) #/
OP_1 a6b9f4b7999f9c6de76165342c9feac354d5d3062a41761ed1616eaf9e3c38ec
#endexercise
#unittest
ecc:TapRootTest:test_default_tweak:
#endunittest
#unittest
ecc:TapRootTest:test_tweaked_key:
#endunittest
#unittest
ecc:TapRootTest:test_p2tr_script:
#endunittest
#markdown
# P2TR Addresses
* Uses Bech32m, which is different than Bech32 (BIP350)
* Segwit v0 uses Bech32
* Taproot (Segwit v1) uses Bech32m
* Has error correcting capability and uses 32 letters/numbers
* Segwit v0 addresses start with bc1q and p2wpkh is shorter than p2wsh
* Segwit v1 addresses start with bc1p and they're all one length
#endmarkdown
#code
>>> # Example of getting a p2tr address
>>> from ecc import S256Point
>>> internal_pubkey_raw = bytes.fromhex("cbaa648dbfe734646ce958e2f14a874149fae4010fdeabde4bae6a732537fd91")
>>> internal_pubkey = S256Point.parse(internal_pubkey_raw)
>>> print(internal_pubkey.p2tr_address())
bc1p27zyfdq3yahwu9lz76vc35vjklnj3aph25j6s68548pt0rsj4utql46j72
>>> print(internal_pubkey.p2tr_address(network="signet"))
tb1p27zyfdq3yahwu9lz76vc35vjklnj3aph25j6s68548pt0rsj4utqgavay9
#endcode
#exercise
Make your own Signet P2TR Address
Write down your address at [this link](https://docs.google.com/spreadsheets/d/1wUNeR-g5qY_2lh18gxg5JIQr352fOW2wZia_QErj4V0/edit?usp=sharing) under "keypath address"
----
>>> from ecc import PrivateKey
>>> from helper import sha256
>>> my_email = b"jimmy@programmingblockchain.com" #/my_secret = b"<fill this in with your email>"
>>> my_secret = big_endian_to_int(sha256(my_email))
>>> # create the private key object
>>> priv = PrivateKey(my_secret) #/
>>> # get the public point
>>> point = priv.point #/
>>> # print the p2tr_address with network set to "signet"
>>> print(point.p2tr_address(network="signet")) #/
tb1pfx2ys8pzcg0mdufk9v25hphv85zgjpv5kyn6uevdmfmvdsw0ea0qyvv87u
#endexercise
#markdown
# Spending P2TR KeyPath
* We need the tweak $t$ and the private key $e$ to be able to sign the transaction
* The pubkey is in the UTXO as an $x$-only key
* All we need is the Schnorr Signature in the Witness field
* We use the <code>sign_schnorr</code> method in the <code>PrivateKey</code> to do this.
#endmarkdown
#markdown
# Spending plan
* We have 20,000 sats in this output: 871864d7631024465fc210e553fa9f50e7f0f2359288ad121aa733d65e366995:0
* We want to spend all of it to tb1ptaqplrhnyh3kq85n7dtm5vcpgstt0ev80f4wd8ngeppch4fzu8mquchufq
* 1 input/1 output transaction
#endmarkdown
#code
>>> # Spending from a p2tr
>>> from ecc import PrivateKey, N
>>> from helper import sha256, big_endian_to_int
>>> from script import address_to_script_pubkey
>>> from tx import Tx, TxIn, TxOut
>>> my_email = b"jimmy@programmingblockchain.com"
>>> my_secret = big_endian_to_int(sha256(my_email))
>>> priv = PrivateKey(my_secret)
>>> prev_tx = bytes.fromhex("871864d7631024465fc210e553fa9f50e7f0f2359288ad121aa733d65e366995")
>>> prev_index = 0
>>> target_address = "tb1ptaqplrhnyh3kq85n7dtm5vcpgstt0ev80f4wd8ngeppch4fzu8mquchufq"
>>> fee = 500
>>> tx_in = TxIn(prev_tx, prev_index)
>>> target_script_pubkey = address_to_script_pubkey(target_address)
>>> target_amount = tx_in.value(network="signet") - fee
>>> tx_out = TxOut(target_amount, target_script_pubkey)
>>> tx_obj = Tx(1, [tx_in], [tx_out], network="signet", segwit=True)
>>> tweaked_secret = (priv.secret + big_endian_to_int(priv.point.tweak())) % N
>>> tweaked_key = PrivateKey(tweaked_secret)
>>> tx_obj.sign_p2tr_keypath(0, tweaked_key)
True
>>> print(tx_obj.serialize().hex())
010000000001019569365ed633a71a12ad889235f2f0e7509ffa53e510c25f46241063d76418870000000000ffffffff012c4c0000000000002251205f401f8ef325e3601e93f357ba33014416b7e5877a6ae69e68c8438bd522e1f601403697a0f0f49a451668b9b0361ec7c3b857299f0f80b8ce8c50e1d3cc87f44382de2b6eeccabe0efda3b1639841c342fce64ba28a2a018d4a9a69f5e7a0d43f6b00000000
#endcode
#unittest
ecc:PrivateKeyTest:test_tweaked_key:
#endunittest
#exercise
## Checkpoint Exercise
You have been sent 100,000 sats to your address on Signet. Send 40,000 sats back to <code>tb1q7kn55vf3mmd40gyj46r245lw87dc6us5n50lrg</code>, the rest to yourself.
----
>>> from ecc import PrivateKey
>>> from helper import sha256
>>> from script import address_to_script_pubkey
>>> from tx import Tx, TxIn, TxOut
>>> my_email = b"jimmy@programmingblockchain.com" #/my_secret = b"<fill this in with your email>"
>>> my_secret = big_endian_to_int(sha256(my_email))
>>> priv = PrivateKey(my_secret) #/
>>> prev_tx = bytes.fromhex("25096348891ff6b120b88c944501791f8809698474569cc994d63dc5bcfe6a37") #/prev_tx = bytes.fromhex("<fill in from block explorer>")
>>> target_address = "tb1q7kn55vf3mmd40gyj46r245lw87dc6us5n50lrg"
>>> target_amount = 40000
>>> fee = 500
>>> # fill this in from the block explorer
>>> prev_index = 0 #/prev_index = -1
>>> # create the one input
>>> tx_in = TxIn(prev_tx, prev_index) #/
>>> # use the address_to_script_pubkey to get the ScriptPubKey
>>> target_script_pubkey = address_to_script_pubkey(target_address) #/
>>> # create the target output
>>> tx_out_1 = TxOut(target_amount, target_script_pubkey) #/
>>> # calculate the change amount
>>> change_amount = 100000 - target_amount - fee #/
>>> # use the private key's point's p2tr_script method to get the change ScriptPubkey
>>> change_script_pubkey = priv.point.p2tr_script() #/
>>> # create the change output
>>> tx_out_2 = TxOut(change_amount, change_script_pubkey) #/
>>> # create the transaction
>>> tx_obj = Tx(1, [tx_in], [tx_out_1, tx_out_2], network="signet", segwit=True) #/
>>> # sign the transaction using the tweaked key and the sign_p2tr_keypath method
>>> tx_obj.sign_p2tr_keypath(0, priv.tweaked_key()) #/
True
>>> # print the serialized hex
>>> print(tx_obj.serialize().hex()) #/
01000000000101376afebcc53dd694c99c5674846909881f790145948cb820b1f61f89486309250000000000ffffffff02409c000000000000160014f5a74a3131dedb57a092ae86aad3ee3f9b8d72146ce80000000000002251204994481c22c21fb6f1362b154b86ec3d04890594b127ae658dda76c6c1cfcf5e014002de2a8a88783937f10742235dfdf6a0f9526f4e8eee9d3d4cd11d5813269a0d1b56b5028b81735dae9d3dd9b9f2fe2193474dba0569cff087c2575f0f8f5b5f00000000
#endexercise
#markdown
# Taproot Script Path Spend
#endmarkdown
#markdown
# TapScript
* Defined in BIP342
* Same as Script except for a few New/Changed OP Codes
* OP_CHECKSIG and OP_CHECKSIGVERIFY verify Schnorr Signatures
* OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY are disabled
* OP_CHECKSIGADD is added to replace multisig and verifies Schnorr Signatures
#endmarkdown
#markdown
# OP_CHECKSIGADD
* Consumes the top three elements: a pubkey, a number, and a signature.
* Valid sig, returns the number+1 to the stack
* Invalid sig, returns the number back to the stack
#endmarkdown
#unittest
op:TapScriptTest:test_opchecksigadd:
#endunittest
#markdown
# Example TapScripts
* 1-of-1 (pay-to-pubkey) [pubkey, OP_CHECKSIG]
* 2-of-2 [pubkey A, OP_CHECKSIGVERIFY, pubkey B, OP_CHECKSIG]
* 2-of-3 [pubkey A, OP_CHECKSIG, pubkey B, OP_CHECKSIGADD, pubkey C, OP_CHECKSIGADD, OP_2, OP_EQUAL]
* halvening timelock 1-of-1 [840000, OP_CHECKLOCKTIMEVERIFY, OP_DROP, pubkey, OP_CHECKSIG]
#endmarkdown
#code
>>> # Example TapScripts
>>> from ecc import PrivateKey
>>> from op import encode_minimal_num
>>> from taproot import TapScript
>>> pubkey_a = PrivateKey(11111111).point.xonly()
>>> pubkey_b = PrivateKey(22222222).point.xonly()
>>> pubkey_c = PrivateKey(33333333).point.xonly()
>>> # 1-of-1 (0xAC is OP_CHECKSIG)
>>> tap_script = TapScript([pubkey_a, 0xAC])
>>> print(tap_script)
331a8f6a14e1b41a6b523ddb505fbc0662a6446bd42408692497297d3474aeec OP_CHECKSIG
>>> # 2-of-2 (0xAD is OP_CHECKSIGVERIFY)
>>> tap_script = TapScript([pubkey_a, 0xAD, pubkey_b, 0xAC])
>>> print(tap_script)
331a8f6a14e1b41a6b523ddb505fbc0662a6446bd42408692497297d3474aeec OP_CHECKSIGVERIFY 158a49d62c384c539a453e41a70214cfb85184954ae5c8df4b47eb74d58ff16f OP_CHECKSIG
>>> # 2-of-3 (0xBA is OP_CHECKSIGADD, 0x52 is OP_2, 0x87 is OP_EQUAL)
>>> tap_script = TapScript([pubkey_a, 0xAD, pubkey_b, 0xBA, pubkey_c, 0xBA, 0x52, 0x87])
>>> print(tap_script)
331a8f6a14e1b41a6b523ddb505fbc0662a6446bd42408692497297d3474aeec OP_CHECKSIGVERIFY 158a49d62c384c539a453e41a70214cfb85184954ae5c8df4b47eb74d58ff16f OP_CHECKSIGADD 582662e8e47df59489d6756615aa3db3fa3bbaa75a424b9c78036265858f5544 OP_CHECKSIGADD OP_2 OP_EQUAL
>>> # halvening timelock 1-of-1 (0xB1 is OP_CLTV, 0x75 is OP_DROP)
>>> tap_script = TapScript([encode_minimal_num(840000), 0xB1, 0x75, pubkey_a, 0xAC])
>>> print(tap_script)
40d10c OP_CHECKLOCKTIMEVERIFY OP_DROP 331a8f6a14e1b41a6b523ddb505fbc0662a6446bd42408692497297d3474aeec OP_CHECKSIG
#endcode
#exercise
Make a TapScript for 4-of-4 using pubkeys from private keys which correspond to 10101, 20202, 30303, 40404
----
>>> from ecc import PrivateKey
>>> from taproot import TapScript
>>> pubkey_1 = PrivateKey(10101).point.xonly()
>>> pubkey_2 = PrivateKey(20202).point.xonly()
>>> pubkey_3 = PrivateKey(30303).point.xonly()
>>> pubkey_4 = PrivateKey(40404).point.xonly()
>>> # create a 4-of-4 tapscript that uses OP_CHECKSIGVERIFY (0xad) and OP_CHECKSIG (0xac)
>>> tap_script = TapScript([pubkey_1, 0xAD, pubkey_2, 0xAD, pubkey_3, 0xAD, pubkey_4, 0xAC]) #/
>>> # print the TapScript
>>> print(tap_script) #/
134ba4d9c35a66017e9d525a879700a9fb9209a3f43a651fdaf71f3a085a77d3 OP_CHECKSIGVERIFY 027aa71d9cdb31cd8fe037a6f441e624fe478a2deece7affa840312b14e971a4 OP_CHECKSIGVERIFY 165cfd87a31d8fab4431c955b0462804f1ba79b41970ab7e8b0e4e4686f5f8b4 OP_CHECKSIGVERIFY 9e5f5a5c29d33c32185a3dc0a9ccb3e72743744dd869dd40b6265a23fd84a402 OP_CHECKSIG
#endexercise
#markdown
# TapLeaf
* These are the leaves of the Merkle Tree
* Has a TapLeaf Version (<code>0xc0</code>) and TapScript
* Any Leaf can be executed to satisfy the Taproot Script Path
* Hash of a TapLeaf is a Tagged Hash (TapLeaf) of the version + TapScript
#endmarkdown
#code
>>> # Example of making a TapLeaf and calculating the hash
>>> from ecc import PrivateKey
>>> from hash import hash_tapleaf
>>> from helper import int_to_byte
>>> from taproot import TapScript, TapLeaf
>>> pubkey_a = PrivateKey(11111111).point.xonly()
>>> pubkey_b = PrivateKey(22222222).point.xonly()
>>> tap_script = TapScript([pubkey_a, 0xAD, pubkey_b, 0xAC])
>>> tap_leaf = TapLeaf(tap_script)
>>> h = hash_tapleaf(int_to_byte(tap_leaf.tapleaf_version) + tap_leaf.tap_script.serialize())
>>> print(h.hex())
d1b3ee8e8c175e5db7e2ff7a87435e8f751d148b77fb1f00e14ff8ffa1c09a40
#endcode
#exercise
Calculate the TapLeaf hash whose TapScript is a 2-of-4 using pubkeys from private keys which correspond to 10101, 20202, 30303, 40404
----
>>> from ecc import PrivateKey
>>> from hash import hash_tapleaf
>>> from helper import int_to_byte
>>> from taproot import TapScript, TapLeaf
>>> pubkey_1 = PrivateKey(10101).point.xonly()
>>> pubkey_2 = PrivateKey(20202).point.xonly()
>>> pubkey_3 = PrivateKey(30303).point.xonly()
>>> pubkey_4 = PrivateKey(40404).point.xonly()
>>> # create a 2-of-4 TapScript that uses OP_CHECKSIG (0xac), OP_CHECKSIGADD (0xba), OP_2 (0x52) and OP_EQUAL (0x87)
>>> tap_script = TapScript([pubkey_1, 0xAC, pubkey_2, 0xBA, pubkey_3, 0xBA, pubkey_4, 0xBA, 0x52, 0x87]) #/
>>> # create the TapLeaf with the TapScript
>>> tap_leaf = TapLeaf(tap_script) #/
>>> # calculate the hash
>>> h = hash_tapleaf(int_to_byte(tap_leaf.tapleaf_version) + tap_leaf.tap_script.serialize()) #/
>>> # print the hash hex
>>> print(h.hex()) #/
0787f5aba506f118a90cefaf00ccfdb2785cf5998d40c3d43ebfaa5b4c6bcb7d
#endexercise
#unittest
taproot:TapRootTest:test_tapleaf_hash:
#endunittest
#markdown
# TapBranch
* These are the branches of the Merkle Tree
* These connect a left child and a right child.
* Each child is a TapLeaf or TapBranch
* Hash of a TapBranch is a Tagged Hash (TapBranch) of the left hash and right hash, sorted
#endmarkdown
#code
# Example of making a TapBranch and calculating the hash
>>> from ecc import PrivateKey
>>> from hash import hash_tapbranch
>>> from helper import int_to_byte
>>> from taproot import TapScript, TapLeaf, TapBranch
>>> pubkey_1 = PrivateKey(11111111).point.xonly()
>>> pubkey_2 = PrivateKey(22222222).point.xonly()
>>> tap_script_1 = TapScript([pubkey_1, 0xAC])
>>> tap_script_2 = TapScript([pubkey_2, 0xAC])
>>> tap_leaf_1 = TapLeaf(tap_script_1)
>>> tap_leaf_2 = TapLeaf(tap_script_2)
>>> tap_branch = TapBranch(tap_leaf_1, tap_leaf_2)
>>> left_hash = tap_branch.left.hash()
>>> right_hash = tap_branch.right.hash()
>>> if left_hash > right_hash:
... h = hash_tapbranch(left_hash + right_hash)
... else:
... h = hash_tapbranch(right_hash + left_hash)
>>> print(h.hex())
60f57015577d9cc2326d980355bc0896c80a9f94dc692d8738069bc05895634c
#endcode
#exercise
TabBranch Calculation
Calculate the TabBranch hash whose left and right nodes are TapLeafs whose TapScripts are for a 1-of-2 using pubkeys from private keys which correspond to (10101, 20202) for the left, (30303, 40404) for the right
----
>>> from ecc import PrivateKey
>>> from hash import hash_tapbranch
>>> from helper import int_to_byte
>>> from taproot import TapScript, TapLeaf, TapBranch
>>> pubkey_1 = PrivateKey(10101).point.xonly()
>>> pubkey_2 = PrivateKey(20202).point.xonly()
>>> pubkey_3 = PrivateKey(30303).point.xonly()
>>> pubkey_4 = PrivateKey(40404).point.xonly()
>>> # create two 1-of-2 TapScripts
>>> tap_script_1 = TapScript([pubkey_1, 0xAC, pubkey_2, 0xBA, 0x51, 0x87]) #/
>>> tap_script_2 = TapScript([pubkey_3, 0xAC, pubkey_4, 0xBA, 0x51, 0x87]) #/
>>> # create two TapLeafs with the TapScripts
>>> tap_leaf_1 = TapLeaf(tap_script_1) #/
>>> tap_leaf_2 = TapLeaf(tap_script_2) #/
>>> # create the branch
>>> tap_branch = TapBranch(tap_leaf_1, tap_leaf_2) #/
>>> # get the left and right hashes
>>> left_hash = tap_branch.left.hash() #/
>>> right_hash = tap_branch.right.hash() #/
>>> # calculate the hash using the sorted order with hash_tapbranch
>>> if left_hash > right_hash: #/
... h = hash_tapbranch(left_hash + right_hash) #/
... else: #/
... h = hash_tapbranch(right_hash + left_hash) #/
>>> # print the hex of the hash
>>> print(h.hex()) #/
f938d6fa5e3335e540f07a4007ee296640a977c89178aca79f15f2ec6acc14b6
#endexercise
#unittest
taproot:TapRootTest:test_tapbranch_hash:
#endunittest
#markdown
# Merkle Root
* The Merkle Root of the Merkle Tree can be used to compute the tweak $t$
* A TapLeaf (1 condition) or TapBranch (more than 1 condition) or a deterministic hash (0 conditions)
* Any TapScript inside the Merkle Tree can unlock the UTXO
* Unlocking requires satisfying the TapScript and a Control Block
* Unlocking conditions are hidden until spent
* Unused unlocking conditions remain hidden
* Up to 128 levels, meaning $2^{128}$ conditions
#endmarkdown
#markdown
# Computing the Merkle Root
* The Merkle Root is the hash of the root element of the Merkle Tree
* For TapLeaf: Tagged hash (TapLeaf) of TapLeaf Version followed by the TapScript
* For TapBranch: Tagged hash (TapBranch) of the sorted children (left and right)
* It doesn't have to be the hash of anything, just has to be 32 bytes
#endmarkdown
#code
>>> # Example of Comupting the Merkle Root
>>> from ecc import PrivateKey
>>> from hash import hash_tapbranch
>>> from helper import int_to_byte
>>> from taproot import TapScript, TapLeaf, TapBranch
>>> pubkey_1 = PrivateKey(11111111).point.xonly()
>>> pubkey_2 = PrivateKey(22222222).point.xonly()
>>> pubkey_3 = PrivateKey(33333333).point.xonly()
>>> tap_script_1 = TapScript([pubkey_1, 0xAC])
>>> tap_script_2 = TapScript([pubkey_2, 0xAC])
>>> tap_script_3 = TapScript([pubkey_3, 0xAC])
>>> tap_leaf_1 = TapLeaf(tap_script_1)
>>> tap_leaf_2 = TapLeaf(tap_script_2)
>>> tap_leaf_3 = TapLeaf(tap_script_3)
>>> tap_branch_1 = TapBranch(tap_leaf_1, tap_leaf_2)
>>> tap_root = TapBranch(tap_branch_1, tap_leaf_3)
>>> merkle_root = tap_root.hash()
>>> print(merkle_root.hex())
f53fab2e9cf0a458609226b4c42d5c0264700cdf33850c2b1423543a44ad4234
#endcode
#exercise
Calculate the External PubKey for a Taproot output whose internal pubkey is 90909 and whose Merkle Root is from two TapBranches, each of which is a single signature TapLeaf. The private keys corresponding to the left TapBranch's TapLeafs are 10101 and 20202. The private keys corresponding to the right TapBranch's TapLeafs are 30303 and 40404.
----
>>> from ecc import PrivateKey
>>> from helper import big_endian_to_int
>>> from taproot import TapScript, TapLeaf, TapBranch
>>> internal_pubkey = PrivateKey(90909).point
>>> pubkey_1 = PrivateKey(10101).point.xonly()
>>> pubkey_2 = PrivateKey(20202).point.xonly()
>>> pubkey_3 = PrivateKey(30303).point.xonly()
>>> pubkey_4 = PrivateKey(40404).point.xonly()
>>> # create four tap scripts one for each pubkey
>>> tap_script_1 = TapScript([pubkey_1, 0xAC]) #/
>>> tap_script_2 = TapScript([pubkey_2, 0xAC]) #/
>>> tap_script_3 = TapScript([pubkey_3, 0xAC]) #/
>>> tap_script_4 = TapScript([pubkey_4, 0xAC]) #/
>>> # create four TapLeafs with the TapScripts
>>> tap_leaf_1 = TapLeaf(tap_script_1) #/
>>> tap_leaf_2 = TapLeaf(tap_script_2) #/
>>> tap_leaf_3 = TapLeaf(tap_script_3) #/
>>> tap_leaf_4 = TapLeaf(tap_script_4) #/
>>> # create two TapBranches that have these TapLeafs
>>> tap_branch_1 = TapBranch(tap_leaf_1, tap_leaf_2) #/
>>> tap_branch_2 = TapBranch(tap_leaf_3, tap_leaf_4) #/
>>> # create another TapBranch that corresponds to the merkle root and get its hash
>>> merkle_root = TapBranch(tap_branch_1, tap_branch_2).hash() #/
>>> # the external public key is the internal public key tweaked with the Merkle Root
>>> external_pubkey = internal_pubkey.tweaked_key(merkle_root) #/
>>> # print the hex of the xonly of the external pubkey
>>> print(external_pubkey.xonly().hex()) #/
8b9f09cd4a33e62b0c9d086056bbdeb7a218c1e4830291b9be56841b31d94ccb
#endexercise
#markdown
# Script Path Spending
* Merkle Proof and Internal Public Key in the Control Block (last element of Witness)
* TapScript is the second-to-last element of the Witness
* Unlock/Satisfy the TapScript, which are the other elements of the Witness
* Combine the TapScript and the Merkle Proof to get the Merkle Root
* Combine Merkle Root and Internal Public Key to get the External Public Key
#endmarkdown
#markdown
# Control Block
* Required for spending a TapScript, last element of Witness
* TapScript Version (<code>0xc0</code> or <code>0xc1</code>)
* The last bit expresses the parity of the external pubkey, which is necessary for batch verification
* Internal PubKey $P$
* Merkle Proof (list of hashes to combine to get to the Merkle Root)
#endmarkdown
#markdown
# Merkle Proof
* List of hashes
* Combine each with the hash of the TapScript, sorting them each time
* The result is the Merkle Root, which can be combined with the Internal PubKey $P$ to get the tweak $t$
* If the result of $P+tG=Q$ where $Q$ is the External PubKey from the UTXO, this is a valid TapScript
#endmarkdown
#code
>>> # Example of Control Block Validation
>>> from ecc import PrivateKey, S256Point
>>> from hash import hash_tapbranch
>>> from helper import int_to_byte
>>> from taproot import TapScript, TapLeaf, TapBranch
>>> external_pubkey_xonly = bytes.fromhex("cbe433288ae1eede1f24818f08046d4e647fef808cfbbffc7d10f24a698eecfd")
>>> pubkey_2 = bytes.fromhex("027aa71d9cdb31cd8fe037a6f441e624fe478a2deece7affa840312b14e971a4")
>>> tap_script_2 = TapScript([pubkey_2, 0xAC])
>>> tap_leaf_2 = TapLeaf(tap_script_2)
>>> tap_leaf_1_hash = bytes.fromhex("76f5c1cdfc8b07dc8edca5bef2b4991201c5a0e18b1dbbcfe00ef2295b8f6dff")
>>> tap_leaf_3_hash = bytes.fromhex("5dd270ec91aa5644d907059400edfd98e307a6f1c6fe3a2d1d4550674ff6bc6e")
>>> internal_pubkey = S256Point.parse(bytes.fromhex("407910a4cfa5fe195ad4844b6069489fcb429f27dff811c65e99f7d776e943e5"))
>>> current = tap_leaf_2.hash()
>>> for h in (tap_leaf_1_hash, tap_leaf_3_hash):
... if h < current:
... current = hash_tapbranch(h + current)
... else:
... current = hash_tapbranch(current + h)
>>> print(internal_pubkey.tweaked_key(current).xonly() == external_pubkey_xonly)
True
>>> print(internal_pubkey.p2tr_address(current, network="signet"))
tb1pe0jrx2y2u8hdu8eysx8ssprdfej8lmuq3namllrazrey56vwan7s5j2wr8
#endcode
#exercise
Validate the Control Block for the pubkey whose private key is 40404 for the previous external pubkey
----
>>> from ecc import PrivateKey, S256Point
>>> from helper import big_endian_to_int
>>> from taproot import TapScript, TapLeaf, TapBranch
>>> external_pubkey_xonly = bytes.fromhex("8b9f09cd4a33e62b0c9d086056bbdeb7a218c1e4830291b9be56841b31d94ccb")
>>> internal_pubkey = PrivateKey(90909).point
>>> hash_1 = bytes.fromhex("22cac0b60bc7344152a8736425efd62532ee4d83e3de473ed82a64383b4e1208")
>>> hash_2 = bytes.fromhex("a41d343d7419b99bfe8e66752fc3c45fd14aa2cc5ef5bf9073ed28dfc60e2e34")
>>> pubkey_4 = bytes.fromhex("9e5f5a5c29d33c32185a3dc0a9ccb3e72743744dd869dd40b6265a23fd84a402")
>>> # create the TapScript and TapLeaf for pubkey 4
>>> tap_script_4 = TapScript([pubkey_4, 0xAC]) #/
>>> tap_leaf_4 = TapLeaf(tap_script_4) #/
>>> # set the current hash to the TapLeaf's hash
>>> current = tap_leaf_4.hash() #/
>>> # loop through hash_1 and hash_2
>>> for h in (hash_1, hash_2): #/
... # do a hash_tapbranch of h and current, sorted alphabetically
... if h < current: #/
... current = hash_tapbranch(h + current) #/
... else: #/
... current = hash_tapbranch(current + h) #/
>>> # get the external pubkey using the current hash as the merkle root with the internal pubkey
>>> external_pubkey = internal_pubkey.tweaked_key(current) #/
>>> # check to see if the external pubkey's xonly is correct
>>> print(external_pubkey.xonly() == external_pubkey_xonly) #/
True
#endexercise
#unittest
taproot:TapRootTest:test_control_block:
#endunittest
#exercise
Create a Signet P2TR address with these Script Spend conditions:
1. Internal Public Key is <code>cd04c1bf88ca891af152fc57c36523ab59efb16b7ec07caca0cfc4a1f2051d9e</code>
2. Leaf 1 and Leaf 2 make Branch 1, Branch 1 and Leaf 3 make Branch 2, which is the Merkle Root
3. All TapLeaf are single key locked TapScripts (pubkey, OP_CHECKSIG)
4. Leaf 1 uses your xonly pubkey
5. Leaf 2 uses this xonly pubkey: <code>331a8f6a14e1b41a6b523ddb505fbc0662a6446bd42408692497297d3474aeec</code>
6. Leaf 3 uses this xonly pubkey: <code>158a49d62c384c539a453e41a70214cfb85184954ae5c8df4b47eb74d58ff16f</code>
----
>>> from ecc import PrivateKey, S256Point
>>> from helper import sha256, big_endian_to_int
>>> from taproot import TapScript, TapLeaf, TapBranch
>>> my_email = b"jimmy@programmingblockchain.com" #/my_secret = b"<fill this in with your email>"
>>> my_secret = big_endian_to_int(sha256(my_email))
>>> internal_pubkey = S256Point.parse(bytes.fromhex("cd04c1bf88ca891af152fc57c36523ab59efb16b7ec07caca0cfc4a1f2051d9e"))
>>> pubkey_2 = bytes.fromhex("331a8f6a14e1b41a6b523ddb505fbc0662a6446bd42408692497297d3474aeec")
>>> pubkey_3 = bytes.fromhex("158a49d62c384c539a453e41a70214cfb85184954ae5c8df4b47eb74d58ff16f")
>>> # get your xonly pubkey
>>> my_xonly = PrivateKey(my_secret).point.xonly() #/
>>> # make the first TapScript and TapLeaf using your xonly and OP_CHECKSIG (0xAC)
>>> tap_script_1 = TapScript([my_xonly, 0xAC]) #/
>>> tap_leaf_1 = TapLeaf(tap_script_1) #/
>>> # make the second and third TapLeaves using pubkey_2 and pubkey_3 respectively
>>> tap_script_2 = TapScript([pubkey_2, 0xAC]) #/
>>> tap_leaf_2 = TapLeaf(tap_script_2) #/
>>> tap_script_3 = TapScript([pubkey_3, 0xAC]) #/
>>> tap_leaf_3 = TapLeaf(tap_script_3) #/
>>> # make a TapBranch with leaf 1 and 2
>>> tap_branch_1 = TapBranch(tap_leaf_1, tap_leaf_2) #/
>>> # make a TapBranch with branch 1 and leaf 3
>>> tap_branch_2 = TapBranch(tap_branch_1, tap_leaf_3) #/
>>> # get the hash of this branch, this is the Merkle Root
>>> merkle_root = tap_branch_2.hash() #/
>>> # print the address using the p2tr_address method of internal_pubkey and specify signet
>>> print(internal_pubkey.p2tr_address(merkle_root, network="signet")) #/
tb1pxh7kypwsvxnat0z6588pufhx43r2fnqjyn846qj5kx8mgqcamvjsyn5cjg
#endexercise
#exercise
Send yourself the rest of the coins from the output of the previous exercise to the address you just created
----
>>> from ecc import PrivateKey, S256Point
>>> from helper import sha256, big_endian_to_int
>>> from script import address_to_script_pubkey
>>> from taproot import TapScript, TapLeaf, TapBranch
>>> from tx import Tx, TxIn, TxOut
>>> my_email = b"jimmy@programmingblockchain.com" #/my_secret = b"<fill this in with your email>"
>>> my_secret = big_endian_to_int(sha256(my_email))
>>> my_private_key = PrivateKey(my_secret)
>>> prev_tx = bytes.fromhex("69804495c439176266c473081e2f9a3cd298e60a17c8b035fd3070073b865a9c") #/prev_tx = bytes.fromhex("<fill this in with the tx where you spent last time>")
>>> prev_index = 1
>>> target_address = "tb1pxh7kypwsvxnat0z6588pufhx43r2fnqjyn846qj5kx8mgqcamvjsyn5cjg" #/target_address = "<fill this in with the address from the last exercise>"
>>> fee = 500
>>> # create a transaction input
>>> tx_in = TxIn(prev_tx, prev_index) #/
>>> # calculate the target amount
>>> target_amount = tx_in.value(network="signet") - fee
>>> target_script = address_to_script_pubkey(target_address)
>>> # create a transaction output
>>> tx_out = TxOut(target_amount, target_script) #/
>>> # create a transaction, segwit=True and network="signet"
>>> tx_obj = Tx(1, [tx_in], [tx_out], network="signet", segwit=True) #/
>>> # calculate the tweaked key from your private key
>>> signing_key = my_private_key.tweaked_key() #/
>>> # sign the transaction using sign_p2tr_keypath
>>> tx_obj.sign_p2tr_keypath(0, signing_key) #/
True
>>> # print the serialized hex
>>> print(tx_obj.serialize().hex())
010000000001019c5a863b077030fd35b0c8170ae698d23c9a2f1e0873c466621739c4954480690100000000ffffffff0178e600000000000022512035fd6205d061a7d5bc5aa1ce1e26e6ac46a4cc1224cf5d0254b18fb4031ddb250140b33905727e316ab7fc8c2816761d61af9f1c535cee632a210642f07d619af632c6df51d63099be31e6d12ecd2a465543861eab6e53feb09ccd49288bda1cb8f600000000
#endexercise
#exercise
Now spend this output using the script path from the second TapLeaf send it all to <code>tb1q7kn55vf3mmd40gyj46r245lw87dc6us5n50lrg</code>
----
>>> from ecc import PrivateKey, S256Point
>>> from helper import sha256, big_endian_to_int
>>> from script import address_to_script_pubkey
>>> from taproot import TapScript, TapLeaf, TapBranch
>>> from tx import Tx, TxIn, TxOut
>>> from witness import Witness
>>> my_email = b"jimmy@programmingblockchain.com" #/my_email = b"<fill this in with your email>"
>>> my_secret = big_endian_to_int(sha256(my_email))
>>> my_private_key = PrivateKey(my_secret)
>>> internal_pubkey = S256Point.parse(bytes.fromhex("cd04c1bf88ca891af152fc57c36523ab59efb16b7ec07caca0cfc4a1f2051d9e"))
>>> pubkey_2 = bytes.fromhex("331a8f6a14e1b41a6b523ddb505fbc0662a6446bd42408692497297d3474aeec")
>>> pubkey_3 = bytes.fromhex("158a49d62c384c539a453e41a70214cfb85184954ae5c8df4b47eb74d58ff16f")
>>> my_xonly = my_private_key.point.xonly()
>>> tap_script_1 = TapScript([my_xonly, 0xAC])
>>> tap_leaf_1 = TapLeaf(tap_script_1)
>>> tap_script_2 = TapScript([pubkey_2, 0xAC])
>>> tap_leaf_2 = TapLeaf(tap_script_2)
>>> tap_script_3 = TapScript([pubkey_3, 0xAC])
>>> tap_leaf_3 = TapLeaf(tap_script_3)
>>> prev_tx = bytes.fromhex("201409034581136743bd7fd0a63f659d8142f1a41031d5a3c96bbe72135ab8a2") #/prev_tx = bytes.fromhex("<fill this in with the tx you just submitted>")
>>> prev_index = 0
>>> target_address = "tb1q7kn55vf3mmd40gyj46r245lw87dc6us5n50lrg"
>>> fee = 500
>>> # create the two branches needed (leaf 1, leaf 2), (branch 1, leaf 3)
>>> tap_branch_1 = TapBranch(tap_leaf_1, tap_leaf_2) #/
>>> tap_branch_2 = TapBranch(tap_branch_1, tap_leaf_3) #/
>>> # create a transaction input
>>> tx_in = TxIn(prev_tx, prev_index) #/
>>> # calculate the target amount
>>> target_amount = tx_in.value(network="signet") - fee
>>> # calculate the target script
>>> target_script = address_to_script_pubkey(target_address)
>>> # create a transaction output
>>> tx_out = TxOut(target_amount, target_script) #/
>>> # create a transaction, segwit=True and network="signet"
>>> tx_obj = Tx(1, [tx_in], [tx_out], network="signet", segwit=True) #/
>>> # create the control block with variables left over from the exercise 2 times ago
>>> cb = tap_branch_2.control_block(internal_pubkey, tap_leaf_1)
>>> tx_in.witness = Witness([tap_script_1.raw_serialize(), cb.serialize()])
>>> msg = tx_obj.sig_hash(0)
>>> sig = my_private_key.sign_schnorr(msg).serialize()
>>> tx_in.witness.items.insert(0, sig)
>>> print(tx_obj.verify())
True
>>> # print the serialized hex
>>> print(tx_obj.serialize().hex())
01000000000101a2b85a1372be6bc9a3d53110a4f142819d653fa6d07fbd4367138145030914200000000000ffffffff0184e4000000000000160014f5a74a3131dedb57a092ae86aad3ee3f9b8d721403403b1681a67f40e6767b2db64744ad3f005d3971645135d58a3e1826d5c960bc281ce187bc9270c51ed7833fcf5e8415501862d51b0ebd051917d9878104778f292220cd04c1bf88ca891af152fc57c36523ab59efb16b7ec07caca0cfc4a1f2051d9eac61c0cd04c1bf88ca891af152fc57c36523ab59efb16b7ec07caca0cfc4a1f2051d9e76f5c1cdfc8b07dc8edca5bef2b4991201c5a0e18b1dbbcfe00ef2295b8f6dffaf5548715217f7a892c7c5ff787a97b6e2f123287a1a354fe3ccda09c39d5d7300000000
#endexercise
"""