diff --git a/2.2-TapTweak.ipynb b/2.2-TapTweak.ipynb
new file mode 100644
index 000000000..d94dc5cf5
--- /dev/null
+++ b/2.2-TapTweak.ipynb
@@ -0,0 +1,598 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import hashlib\n",
+ "from io import BytesIO\n",
+ "import random\n",
+ "\n",
+ "import util\n",
+ "from test_framework.address import program_to_witness\n",
+ "from test_framework.key import ECKey, ECPubKey, SECP256K1_ORDER, generate_schnorr_nonce\n",
+ "from test_framework.messages import CTransaction, COutPoint, CTxIn, CTxOut, CScriptWitness, CTxInWitness, ser_string\n",
+ "from test_framework.musig import generate_musig_key, aggregate_schnorr_nonces, sign_musig, aggregate_musig_signatures\n",
+ "from test_framework.script import CScript, OP_1, OP_CHECKSIG, TaprootSignatureHash, TapLeaf, TapTree, Node"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# 2.2 TapTweak\n",
+ "\n",
+ "* Part 1 - Tweaking the Public Key\n",
+ "* Part 2 - Commitment Schemes with Tweaks\n",
+ "* Part 3 - TapTweak Script Commitments\n",
+ "\n",
+ "\n",
+ "## Part 1: Tweaking the Public Key\n",
+ "\n",
+ "The script commitment scheme of taproot is based on tweaking the public key. \n",
+ "\n",
+ "* `[01] [33B Tweaked Public Key]`\n",
+ "\n",
+ "Tweaking a public key means to alter it with a value (the tweak) such that it remains spendable with knowledge of the original private key and tweak. \n",
+ "\n",
+ "* Output Script: `[01] [P + T]` = `[01] [xG + tG]`\n",
+ "* Spendable by: `x + t`\n",
+ "* `P`: Internal Key\n",
+ "* `t`: TapTweak\n",
+ "* `T`: TapTweak Point\n",
+ "\n",
+ "\n",
+ "Importantly, an observer cannot distinguish between a tweaked and untweaked public key."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Example 1.1 - Signing with a tweaked keypair\n",
+ "\n",
+ "* A) Key pair generation: `xG = P`\n",
+ "* B) A tweak is positive scalar value where: `t < SECP256K1 Order` \n",
+ "* C) A tweak has a corresponding point: `T = t*G`\n",
+ "* D) The private key is tweaked by the tweak scalar: `x' = x + t`\n",
+ "* E) The public key is tweaked by the tweak point: `P' = P + T`\n",
+ "* F) Tweaked keypair `(x', P')` will produce a valid signature."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# A) Generate Key Pair.\n",
+ "privatekey = ECKey()\n",
+ "privatekey.generate()\n",
+ "publickey = privatekey.get_pubkey()\n",
+ "\n",
+ "# B) Tweak: t < SECP256K1 Order\n",
+ "tweak = hashlib.sha256(b'tweak').digest()\n",
+ "assert(int.from_bytes(tweak, 'big') < SECP256K1_ORDER)\n",
+ "\n",
+ "# C) Tweak point.\n",
+ "tweak_private = ECKey()\n",
+ "tweak_private.set(tweak, True)\n",
+ "tweak_point = tweak_private.get_pubkey()\n",
+ "\n",
+ "# D) Derive tweaked private key.\n",
+ "privatekey_tweaked = privatekey + tweak_private\n",
+ "\n",
+ "# E) Derive public key point.\n",
+ "publickey_tweaked = publickey + tweak_point\n",
+ "\n",
+ "# F) Sign message with tweaked key pair and verify signature.\n",
+ "msg = hashlib.sha256(b'msg').digest()\n",
+ "sig = privatekey_tweaked.sign_schnorr(msg)\n",
+ "print(publickey_tweaked.verify_schnorr(sig, msg))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Programming Exercise 1.2 - Signing with a tweaked 2-of-2 Musig key pairs.\n",
+ "\n",
+ "* A. 2-of-2 Musig key generation.\n",
+ " * Key pair generation for all participants: `xG = P`\n",
+ " * Aggregate all public keys: `pk_musig`\n",
+ " * Apply challenge factors to all private keys: `x' = c * x`\n",
+ "* B. Tweak the `pk_musig`.\n",
+ "* C. Signing\n",
+ " * Generate indidividual nonces & aggregate: `R_agg`\n",
+ " * Sign individually & aggregate: `sig_agg`\n",
+ " * _Question: Which participant(s) need to tweak keys?_\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# A1) Generate Key Pairs.\n",
+ "\n",
+ "\n",
+ "# A2) Aggregate all public keys: \n",
+ "\n",
+ "\n",
+ "# A3) Apply challenge factors to keys.\n",
+ "\n",
+ " \n",
+ "# B) Tweak musig public key.\n",
+ "# Given: Tweak.\n",
+ "tweak = hashlib.sha256(b'tweak').digest()\n",
+ "\n",
+ "\n",
+ "# C1) Nonce generation & aggregation.\n",
+ "\n",
+ "\n",
+ "# C2) Signing and signature aggregation.\n",
+ "msg = hashlib.sha256(b'msg').digest()\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Part 2: Commitment Schemes with Tweaks\n",
+ "\n",
+ "Taproot uses the tweak as a commitment for spending script paths. However, simply applying the committed value as a public key tweak is not sufficient, as this does not represent a cryptographic commitment.\n",
+ "\n",
+ "\n",
+ "\n",
+ "Instead, the committed value must first be hashed with the untweaked public key point. \n",
+ "\n",
+ "**This prevents modification of both untweaked secret and tweak for a given pubkey point Q.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Example 2.1- Modifying the tweak for a tweaked public key Q.\n",
+ "\n",
+ "* A) Tweaking the public key to obtain: `Point Q`\n",
+ "* B) Create a new tweak for the same point Q: `t'`\n",
+ "* C) Solve for `x'` such that `x'G + t'G = Q`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# A) Tweaking the public key.\n",
+ "x = ECKey()\n",
+ "x.generate()\n",
+ "pk = x.get_pubkey()\n",
+ "t = hashlib.sha256(b'tweak').digest()\n",
+ "t_int = int.from_bytes(t, \"big\")\n",
+ "Q = pk.tweak_add(t)\n",
+ "\n",
+ "# B) Modifying the tweak.\n",
+ "t2 = hashlib.sha256(b'tweak2').digest()\n",
+ "t2_int = int.from_bytes(t2, \"big\")\n",
+ "\n",
+ "# C) Solving for: x` = x - (t' - t)\n",
+ "x_int = int.from_bytes(x.get_bytes(),\"big\")\n",
+ "s = (t2_int + (SECP256K1_ORDER - t_int)% SECP256K1_ORDER) % SECP256K1_ORDER\n",
+ "x2_int = (x_int + (SECP256K1_ORDER - s)% SECP256K1_ORDER) % SECP256K1_ORDER\n",
+ "\n",
+ "print((x2_int + t2_int)% SECP256K1_ORDER == (x_int + t_int)% SECP256K1_ORDER)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Example 2.2 - Tweaking Pubkey with `H(P|msg)`\n",
+ "\n",
+ "* A) Key pair generation: \n",
+ "* B) The tweak is the hash of both P and message: **`t = H(P|msg)`**\n",
+ "* C) A tweak has a corresponding point: `T = t*G`\n",
+ " * A private key is tweaked by the tweak scalar: `x' = x + t`\n",
+ " * The public key can be tweaked by the tweak point: `P' = P + T`\n",
+ "* D) We can verify that signature with tweaked key pair is valid."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# A) Key pair generation.\n",
+ "privkey = ECKey()\n",
+ "privkey.generate()\n",
+ "publickey = privkey.get_pubkey()\n",
+ "\n",
+ "# B) Compute tweak from H(P|msg)\n",
+ "# Note: Taproot/Taptweak actualy uses tagged hashes (See Below).\n",
+ "tag = \"TapTweak\"\n",
+ "ss = hashlib.sha256(tag.encode('utf-8')).digest()\n",
+ "ss += ss\n",
+ "ss += hashlib.sha256(b'commitment').digest()\n",
+ "t = hashlib.sha256(ss).digest()\n",
+ "\n",
+ "# C) Determine Tweak point.\n",
+ "tweak_private = ECKey()\n",
+ "tweak_private.set(t, True)\n",
+ "tweak_point = tweak_private.get_pubkey()\n",
+ "\n",
+ "privkey_tweaked = privkey + tweak_private\n",
+ "publickey_tweaked = publickey + tweak_point\n",
+ "\n",
+ "# F) Sign message and verify signature.\n",
+ "msg = hashlib.sha256(b'msg').digest()\n",
+ "sig = privkey_tweaked.sign_schnorr(msg)\n",
+ "print(publickey_tweaked.verify_schnorr(sig, msg))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Taproot: Tagged Hashes.\n",
+ "\n",
+ "The Taproot Proposal describes tagged hashes for both the taptree and taptweak. \n",
+ "\n",
+ "The TapTweak uses this double nested hashing function because it already used in TapBranches and TapLeafs, where it provides context uniqueness across the Bitcoin protocol. The 64B length of the two sha256 digest concatenation lends itself to optimization in implementations. \n",
+ "\n",
+ "**Tagged Hash:**\n",
+ "* `tagged_hash` = `sha256(sha256(\"Tag\") + sha256(\"Tag\") + data)`\n",
+ "\n",
+ "\n",
+ "**TapTree Node Hashes:**\n",
+ "* `TapTweak` \n",
+ " * `= sha256(sha256(\"TapTweak\") + sha256(\"TapTweak\") + node_hash)`\n",
+ "* `TapBranch` \n",
+ " * `= sha256(sha256(\"TapBranch\") + sha256(\"TapBranch\") + node_hash_left|node_hash_right)`\n",
+ "* `TapLeaf` \n",
+ " * `= sha256(sha256(\"TapLeaf\") + sha256(\"TapLeaf\") + node_hash_left|node_hash_right)`\n",
+ " \n",
+ "_Ordering of left and right node hash data is determined lexicographically, see next section._"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Part 3 - TapTweak Script Commitments\n",
+ "\n",
+ "The TapTweak commits a Taptree to the segwit version 1 public key. It does so with a commitment structure resembling familiar merkle tree construction.\n",
+ "\n",
+ "_Please note that the taptree uses tagged hashes which prevent node height ambiguity currently found in the transaction merkle tree, which allow an attacker to create a node which can be reinterpreted as either a leaf or internal node._\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**The TapTree is different than the header merkle tree in the following ways:**\n",
+ "\n",
+ "* Tapleafs can be located at different heights.\n",
+ "* Ordering of TapLeafs is determined lexicograpically.\n",
+ "* Location of nodes are tagged (No ambiguity of node type).\n",
+ " \n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Programming Exercise 3.1 - Constructing a TapTweak from TapScripts.\n",
+ "\n",
+ "In the cell below, we will commit three pay-to-pubkey scripts to a taptweak and then derive the segwit address which can be spent by fulfilling these scriptpaths and the internal. We will use the same merkle tree structure as in the previous illustration.\n",
+ "\n",
+ "* 1. Compute TapLeafs A, B and C.\n",
+ "* 2. Compute Internal node TapBranch AB.\n",
+ "* 3. Compute TapTweak\n",
+ "* 4. Derive the segwit output address.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "TAPSCRIPT_VER = bytes([0xc0]) # See TapScript chapter for more details.\n",
+ "internal_pubkey = ECPubKey()\n",
+ "internal_pubkey.set(bytes.fromhex('03af455f4989d122e9185f8c351dbaecd13adca3eef8a9d38ef8ffed6867e342e3'))\n",
+ "\n",
+ "# Derive pay-to-pubkey scripts.\n",
+ "secA = ECKey()\n",
+ "secB = ECKey()\n",
+ "secC = ECKey()\n",
+ "secA.generate()\n",
+ "secB.generate()\n",
+ "secC.generate()\n",
+ "pkA = secA.get_pubkey()\n",
+ "pkB = secA.get_pubkey()\n",
+ "pkC = secA.get_pubkey()\n",
+ "scriptA = CScript([pkA.get_bytes(), OP_CHECKSIG])\n",
+ "scriptB = CScript([pkB.get_bytes(), OP_CHECKSIG])\n",
+ "scriptC = CScript([pkC.get_bytes(), OP_CHECKSIG])\n",
+ "\n",
+ "# Method: Returns Tagged Hash.\n",
+ "def tagged_hash(tag, input_data):\n",
+ " data = hashlib.sha256(tag.encode('utf-8')).digest()\n",
+ " data += data\n",
+ " data += input_data\n",
+ " return hashlib.sha256(data).digest()\n",
+ "\n",
+ "# Method: Returns TapBranch hash.\n",
+ "def tapbranch(taggedhash_left, taggedhash_right):\n",
+ " if taggedhash_left > taggedhash_right:\n",
+ " taggedhash_left, taggedhash_right = taggedhash_right, taggedhash_left\n",
+ " return tagged_hash(\"TapBranch\", taggedhash_left + taggedhash_right) \n",
+ "\n",
+ "# 1) Compute TapLeafs A, B and C.\n",
+ "# Note: ser_string(data) is a function which adds compactsize to input data.\n",
+ "\n",
+ "\n",
+ "# 2) Compute Internal node TapBranch AB.\n",
+ "\n",
+ "\n",
+ "# 3) Compute TapTweak.\n",
+ "\n",
+ "\n",
+ "# 4) Derive the segwit output address.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Run the cell below to check if you have correctly computed the TapTweak.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# This code uses the TapTree and TapLeaf classes to construct the same tweak as above\n",
+ "\n",
+ "tapleafA = TapLeaf()\n",
+ "tapleafB = TapLeaf()\n",
+ "tapleafC = TapLeaf()\n",
+ "taptree = TapTree()\n",
+ "\n",
+ "tapleafA.from_script(scriptA)\n",
+ "tapleafB.from_script(scriptB)\n",
+ "tapleafC.from_script(scriptC)\n",
+ "\n",
+ "tapbranchAB = Node()\n",
+ "tapbranchAB.left = tapleafA\n",
+ "tapbranchAB.right = tapleafB\n",
+ "\n",
+ "tapbranchABC = Node()\n",
+ "tapbranchABC.left = tapbranchAB\n",
+ "tapbranchABC.right = tapleafC\n",
+ "\n",
+ "taptree.root = tapbranchABC\n",
+ "taptree.key = internal_pubkey\n",
+ "\n",
+ "script_v1, tweak, c_map = taptree.construct()\n",
+ "print(\"TapTweak:\", tweak.hex())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Programming Exercise 3.2 - Spending a Taproot Output along the Key Path.\n",
+ "\n",
+ "Now that we have understood how public keys can be tweaked to commit pay-to-pubkey scripts to a Segwit version 1 output, let us spend this output along the key path. Such as spend does not reveal the script commitments to the observer and is indistinguishable any other key path spend.\n",
+ "\n",
+ "* 1. Construct taproot output with tweaked public key.\n",
+ "* 2. Send funds from the wallet to the taproot output (Segwit Address).\n",
+ "* 3. Create and sign transaction which sends funds back to wallet with the tweaked private key.\n",
+ "\n",
+ "**Note: Most of the cells have been populated already. Simply tweak the pubkey and signing key.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Start TestNodes**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Start TestNodes.\n",
+ "test = util.TestWrapper()\n",
+ "test.setup()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Generate Wallet Balance**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Generate Coins for Bitcoin Node Wallet.\n",
+ "test.nodes[0].generate(101)\n",
+ "balance = test.nodes[0].getbalance()\n",
+ "print(balance)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**1) Construct taproot output with tweaked public key.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Create Tweaked Pubkey Output.\n",
+ "sec = ECKey()\n",
+ "sec.generate()\n",
+ "internal_pubkey = sec.get_pubkey()\n",
+ "\n",
+ "# Taptweak from example 2.3.1\n",
+ "taptweak = bytes.fromhex('2a2fb476ec9962f262ff358800db0e7364287340db73e5e48db36d1c9f374e30')\n",
+ "\n",
+ "# TODO:\n",
+ "# taproot_pubkey = \n",
+ "\n",
+ "\n",
+ "# TODO: \n",
+ "# segwit_address =\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**2) Send funds from the wallet to the taproot output (Segwit Address).**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Send funds to taproot output.\n",
+ "txid = test.nodes[0].sendtoaddress(segwit_address, balance / 100000)\n",
+ "print(\"Funding tx:\", txid)\n",
+ "\n",
+ "# Deserialize wallet transaction.\n",
+ "tx = CTransaction()\n",
+ "tx_hex = test.nodes[0].getrawtransaction(txid)\n",
+ "tx.deserialize(BytesIO(bytes.fromhex(tx_hex)))\n",
+ "tx.rehash()\n",
+ "\n",
+ "# Determine Output Index of Segwit V1 Output.\n",
+ "# (Wallet places change output at a random txout index.)\n",
+ "outputs = iter(tx.vout)\n",
+ "output = next(outputs)\n",
+ "index = 0\n",
+ "\n",
+ "while (output.scriptPubKey != CScript([OP_1, taproot_pubkey_v1])):\n",
+ " output = next(outputs)\n",
+ " index += 1\n",
+ "output_value = output.nValue"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**3) Spend taproot output with key path.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tx_schnorr = CTransaction()\n",
+ "tx_schnorr.nVersion = 1\n",
+ "tx_schnorr.nLockTime = 0\n",
+ "outpoint = COutPoint(tx.sha256, index)\n",
+ "tx_schnorr_in = CTxIn(outpoint = outpoint)\n",
+ "tx_schnorr.vin = [tx_schnorr_in]\n",
+ "\n",
+ "# Generate new Bitcoin Core wallet address to send funds back to.\n",
+ "dest_addr = test.nodes[0].getnewaddress(address_type=\"bech32\")\n",
+ "scriptpubkey = bytes.fromhex(test.nodes[0].getaddressinfo(dest_addr)['scriptPubKey'])\n",
+ "\n",
+ "# Determine minimum fee required for mempool acceptance.\n",
+ "min_fee = int(test.nodes[0].getmempoolinfo()['mempoolminfee'] * 100000000)\n",
+ "\n",
+ "# Complete output which returns funds to Bitcoin Core wallet.\n",
+ "dest_output= CTxOut(nValue=output_value-min_fee, scriptPubKey=scriptpubkey)\n",
+ "tx_schnorr.vout = [dest_output]\n",
+ "\n",
+ "# Sign transaction with tweaked private key.\n",
+ "# TODO: \n",
+ "# sig =\n",
+ "\n",
+ "\n",
+ "# Construct transaction witness.\n",
+ "witness = CScriptWitness()\n",
+ "witness.stack.append(sig)\n",
+ "witness_in = CTxInWitness()\n",
+ "witness_in.scriptWitness = witness\n",
+ "tx_schnorr.wit.vtxinwit.append(witness_in)\n",
+ "\n",
+ "# Serialize Schnorr transaction for broadcast.\n",
+ "tx_schnorr_str = tx_schnorr.serialize().hex()\n",
+ "\n",
+ "# Test mempool acceptance.\n",
+ "print(test.nodes[0].testmempoolaccept([tx_schnorr_str]))\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Shutdown TestNodes**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Shutdown TestNodes.\n",
+ "test.shutdown()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/Solutions/2.2-TapTweak-Solutions.txt b/Solutions/2.2-TapTweak-Solutions.txt
new file mode 100644
index 000000000..809478152
--- /dev/null
+++ b/Solutions/2.2-TapTweak-Solutions.txt
@@ -0,0 +1,169 @@
+#### 1.2 - Signing with a tweaked 2-of-2 Musig key pairs
+
+```
+# A1) Generate Key Pairs.
+privkey0 = ECKey()
+privkey1 = ECKey()
+privkey0.generate()
+privkey1.generate()
+pk0 = privkey0.get_pubkey()
+pk1 = privkey1.get_pubkey()
+
+# A2) Aggregate all public keys:
+c_map, pk_musig = generate_musig_key([pk0, pk1])
+
+# A3) Apply challenge factors to keys.
+privkey0_c = privkey0.mul(c_map[pk0])
+privkey1_c = privkey1.mul(c_map[pk1])
+pk0_c = pk0.mul(c_map[pk0])
+pk1_c = pk1.mul(c_map[pk1])
+
+# B) Tweak musig public key.
+# Given: Tweak.
+tweak = hashlib.sha256(b'tweak').digest()
+pk_musig_tweaked = pk_musig.tweak_add(tweak)
+
+# C1) Nonce generation & aggregation.
+k0 = generate_schnorr_nonce()
+k1 = generate_schnorr_nonce()
+R0 = k0.get_pubkey()
+R1 = k1.get_pubkey()
+R_agg, negated = aggregate_schnorr_nonces([R0, R1])
+if negated:
+ k0.negate()
+ k1.negate()
+
+# C2) Signing and signature aggregation.
+msg = hashlib.sha256(b'msg').digest()
+
+# One person must tweak keys.
+privkey0_c_tweaked = privkey0_c.add(tweak)
+
+sig0 = sign_musig(privkey0_c_tweaked, k0, R_agg, pk_musig_tweaked, msg)
+sig1 = sign_musig(privkey1_c, k1, R_agg, pk_musig_tweaked, msg)
+sig_agg = aggregate_musig_signatures([sig0, sig1])
+pk_musig_tweaked.verify_schnorr(sig_agg, msg)
+```
+
+#### 3.1 - Constructing a TapTweak from TapScripts
+
+```
+TAPSCRIPT_VER = bytes([0xc0]) # See TapScript chapter for more details.
+internal_pubkey = ECPubKey()
+internal_pubkey.set(bytes.fromhex('03af455f4989d122e9185f8c351dbaecd13adca3eef8a9d38ef8ffed6867e342e3'))
+
+# Derive pay-to-pubkey scripts.
+privkeyA = ECKey()
+privkeyB = ECKey()
+privkeyC = ECKey()
+privkeyA.generate()
+privkeyB.generate()
+privkeyC.generate()
+pkA = privkeyA.get_pubkey()
+pkB = privkeyA.get_pubkey()
+pkC = privkeyA.get_pubkey()
+scriptA = CScript([pkA.get_bytes(), OP_CHECKSIG])
+scriptB = CScript([pkB.get_bytes(), OP_CHECKSIG])
+scriptC = CScript([pkC.get_bytes(), OP_CHECKSIG])
+
+# Method: Returns Tagged Hash.
+def tagged_hash(tag, input_data):
+ data = hashlib.sha256(tag.encode('utf-8')).digest()
+ data += data
+ data += input_data
+ return hashlib.sha256(data).digest()
+
+# Method: Returns TapBranch hash.
+def tapbranch(taggedhash_left, taggedhash_right):
+ if taggedhash_left > taggedhash_right:
+ taggedhash_left, taggedhash_right = taggedhash_right, taggedhash_left
+ return tagged_hash("TapBranch", taggedhash_left + taggedhash_right)
+
+# 1) Compute TapLeafs A, B and C.
+# Note: ser_string(data) is a function which adds compactsize to input data.
+hash_inputA = TAPSCRIPT_VER + ser_string(scriptA)
+hash_inputB = TAPSCRIPT_VER + ser_string(scriptB)
+hash_inputC = TAPSCRIPT_VER + ser_string(scriptC)
+taggedhash_leafA = tagged_hash("TapLeaf", hash_inputA)
+taggedhash_leafB = tagged_hash("TapLeaf", hash_inputB)
+taggedhash_leafC = tagged_hash("TapLeaf", hash_inputC)
+
+# 2) Compute Internal node TapBranch AB.
+internal_nodeAB = tapbranch(taggedhash_leafA, taggedhash_leafB)
+
+# 3) Compute TapTweak.
+rootABC = tapbranch(internal_nodeAB, taggedhash_leafC)
+taptweak = tagged_hash("TapTweak", internal_pubkey.get_bytes() + rootABC)
+print("TapTweak:", taptweak.hex())
+
+# 4) Derive the segwit output address.
+taproot_pubkey_b = internal_pubkey.tweak_add(taptweak).get_bytes()
+taproot_pubkey_v1 = bytes([taproot_pubkey_b[0] & 1]) + taproot_pubkey_b[1:]
+segwit_address = program_to_witness(1, taproot_pubkey_v1)
+print('Segwit address:', segwit_address)
+```
+
+#### 3.2 - Spending a Taproot Output along the Key Path
+
+##### 1) Construct taproot output with tweaked public key
+
+```
+# Create Tweaked Pubkey Output.
+sec = ECKey()
+sec.generate()
+internal_pubkey = sec.get_pubkey()
+
+# Taptweak from example 2.3.1
+taptweak = bytes.fromhex('2a2fb476ec9962f262ff358800db0e7364287340db73e5e48db36d1c9f374e30')
+
+# TODO: taproot_pubkey =
+taproot_pubkey = internal_pubkey.tweak_add(taptweak)
+taproot_pubkey_b = taproot_pubkey.get_bytes()
+
+# TODO: segwit_address =
+taproot_pubkey_v1 = bytes([taproot_pubkey_b[0] & 1]) + taproot_pubkey_b[1:]
+segwit_address = program_to_witness(1, taproot_pubkey_v1)
+print("Segwit Address:", segwit_address)
+```
+
+##### 3) Spend taproot output with key path
+
+```
+tx_schnorr = CTransaction()
+tx_schnorr.nVersion = 1
+tx_schnorr.nLockTime = 0
+outpoint = COutPoint(tx.sha256, index)
+tx_schnorr_in = CTxIn(outpoint = outpoint)
+tx_schnorr.vin = [tx_schnorr_in]
+
+# Generate new Bitcoin Core wallet address to send funds back to.
+dest_addr = test.nodes[0].getnewaddress(address_type="bech32")
+scriptpubkey = bytes.fromhex(test.nodes[0].getaddressinfo(dest_addr)['scriptPubKey'])
+
+# Determine minimum fee required for mempool acceptance.
+min_fee = int(test.nodes[0].getmempoolinfo()['mempoolminfee'] * 100000000)
+
+# Complete output which returns funds to Bitcoin Core wallet.
+dest_output= CTxOut(nValue=output_value-min_fee, scriptPubKey=scriptpubkey)
+tx_schnorr.vout = [dest_output]
+
+# Sign transaction with tweaked private key.
+# TODO: sig =
+hash_types = [0,1,2,3,0x81,0x82,0x83]
+sighash = TaprootSignatureHash(tx_schnorr, [output], hash_types[0])
+tweaked_sec = sec.add(taptweak)
+sig = tweaked_sec.sign_schnorr(sighash)
+
+# Construct transaction witness.
+witness = CScriptWitness()
+witness.stack.append(sig)
+witness_in = CTxInWitness()
+witness_in.scriptWitness = witness
+tx_schnorr.wit.vtxinwit.append(witness_in)
+
+# Serialize Schnorr transaction for broadcast.
+tx_schnorr_str = tx_schnorr.serialize().hex()
+
+# Test mempool acceptance.
+print(test.nodes[0].testmempoolaccept([tx_schnorr_str]))
+```
\ No newline at end of file
diff --git a/images/taptweak0.jpg b/images/taptweak0.jpg
new file mode 100644
index 000000000..739fbded5
Binary files /dev/null and b/images/taptweak0.jpg differ
diff --git a/images/taptweak0.svg b/images/taptweak0.svg
new file mode 100644
index 000000000..9030f7643
--- /dev/null
+++ b/images/taptweak0.svg
@@ -0,0 +1,114 @@
+
+
+
+
diff --git a/images/taptweak1.jpg b/images/taptweak1.jpg
new file mode 100644
index 000000000..dd1e667a7
Binary files /dev/null and b/images/taptweak1.jpg differ
diff --git a/images/taptweak1.svg b/images/taptweak1.svg
new file mode 100644
index 000000000..03904285d
--- /dev/null
+++ b/images/taptweak1.svg
@@ -0,0 +1,135 @@
+
+
+
+