""" #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"" >>> 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 sign_schnorr method in the PrivateKey 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 tb1q7kn55vf3mmd40gyj46r245lw87dc6us5n50lrg, 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"" >>> my_secret = big_endian_to_int(sha256(my_email)) >>> priv = PrivateKey(my_secret) #/ >>> prev_tx = bytes.fromhex("25096348891ff6b120b88c944501791f8809698474569cc994d63dc5bcfe6a37") #/prev_tx = bytes.fromhex("") >>> 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 (0xc0) 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 (0xc0 or 0xc1) * 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 cd04c1bf88ca891af152fc57c36523ab59efb16b7ec07caca0cfc4a1f2051d9e 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: 331a8f6a14e1b41a6b523ddb505fbc0662a6446bd42408692497297d3474aeec 6. Leaf 3 uses this xonly pubkey: 158a49d62c384c539a453e41a70214cfb85184954ae5c8df4b47eb74d58ff16f ---- >>> 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"" >>> 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"" >>> my_secret = big_endian_to_int(sha256(my_email)) >>> my_private_key = PrivateKey(my_secret) >>> prev_tx = bytes.fromhex("69804495c439176266c473081e2f9a3cd298e60a17c8b035fd3070073b865a9c") #/prev_tx = bytes.fromhex("") >>> prev_index = 1 >>> target_address = "tb1pxh7kypwsvxnat0z6588pufhx43r2fnqjyn846qj5kx8mgqcamvjsyn5cjg" #/target_address = "" >>> 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 tb1q7kn55vf3mmd40gyj46r245lw87dc6us5n50lrg ---- >>> 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"" >>> 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("") >>> 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 """