diff --git a/2.2-taptweak.ipynb b/2.2-taptweak.ipynb index f906331eb..960104781 100644 --- a/2.2-taptweak.ipynb +++ b/2.2-taptweak.ipynb @@ -22,13 +22,20 @@ "source": [ "# 2.2 TapTweak\n", "\n", - "* Tweaking the Public Key\n", - "* Commitment Schemes with Tweaks\n", - "* Spending a (tweaked) taproot output along the key path\n", + "* Part 1: Tweaking the public key and commitment schemes with tweaks\n", + "* Part 2: Spending a (tweaked) taproot output along the key path\n", + "* Part 3 (Case Study): Contract commitments\n", "\n", "The linear property of bip-schnorr means that we can encode a commitment into a public key, and then reveal that commitment when signing with the private key. We do that by _tweaking_ the private key with the commitment, and using the associated _tweaked_ pubkey. When signing, we can reveal that the original private key was tweaked by the commitment.\n", "\n", - "## Part 1: Tweaking the Public Key\n", + "In part 1, we'll learn about how private/public key pairs can be tweaked, and how we can use that to create a secure commitment scheme. In part 2, we'll create a segwit v1 output and spend it along the key path, using a tweaked private and public key. Part 3 of this chapter is a case study, showing how pay-to-contract with tweaked keys can be used instead of OP_RETURN outputs to create timestamped commitments." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 1: Tweaking the public key\n", "\n", "Instead of using our original public key as the witness program, we use a tweaked public key.\n", "\n", @@ -113,10 +120,10 @@ "c_map, agg_pubkey = generate_musig_key([pubkey1, pubkey2])\n", "\n", "# Apply challenge factors to keys\n", - "privkey1_c = privkey1.mul(c_map[pubkey1])\n", - "privkey2_c = privkey2.mul(c_map[pubkey2])\n", - "pubkey1_c = pubkey1.mul(c_map[pubkey1])\n", - "pubkey2_c = pubkey2.mul(c_map[pubkey2])\n", + "privkey1_c = privkey1 * c_map[pubkey1]\n", + "privkey2_c = privkey2 * c_map[pubkey2]\n", + "pubkey1_c = pubkey1 * c_map[pubkey1]\n", + "pubkey2_c = pubkey2 * c_map[pubkey2]\n", "\n", "# Tweak musig public key\n", "# Method: ECPubKey.tweak_add()\n", @@ -152,7 +159,7 @@ "\n", "![test](images/taptweak0.jpg)\n", "\n", - "Instead, the committed value must first be hashed with the untweaked public key point. **This commitment scheme is called pay-to-contract. It does not allow the modification of a committed value for a given public key point Q.** " + "Instead, the committed value must first be hashed with the untweaked public key point. This commitment scheme is called *pay-to-contract*. **It does not allow the modification of a committed value for a given public key point Q.**" ] }, { @@ -192,14 +199,14 @@ "msg = sha256(b'I agree to the committed contract')\n", "sig = q_key.sign_schnorr(msg)\n", "\n", - "# Given P, Q and the contract, Bob believes he can verify the following:\n", - "\n", - "# 1) The contract 'Alice agrees to pay 10 BTC to Bob' is committed to public key Q\n", - "print(\"'Alice agrees to pay 10 BTC to Bob' appears to be committed to Q: {}\".format(\\\n", - " P_key.tweak_add(sha256(contract.encode('utf-8'))) == Q_key))\n", + "# Bob can verify that sig is a valid signature for the public key Q:\n", + "verify_sig = Q_key.verify_schnorr(sig, msg)\n", + "print(\"Alice has produced a valid signature for Q: {}\".format(verify_sig))\n", "\n", - "# 2) Alice is the owner of the public key Q = P + t*G\n", - "print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig, msg)))" + "# Alice provides the untweaked public key P to Bob.\n", + "# Bob believes he can verify that the signature committed to the tweak t:\n", + "verify_tweak = P_key.tweak_add(sha256(contract.encode('utf-8'))) == Q_key\n", + "print(\"The signature appears to commit to '{}': {}\".format(contract, verify_tweak))" ] }, { @@ -224,33 +231,25 @@ "print(\"Tweak from original contract: {}\".format(t.hex()))\n", "print(\"Tweak from modified contract: {}\\n\".format(t2.hex()))\n", "\n", - "# Alice modifies her original secret and public key\n", - "# x` = x - t' + t\n", + "# Alice modifies her original private key and public key\n", + "# x2 = x - t2 + t\n", "x_int = x_key.as_int()\n", "t_int = int.from_bytes(t, \"big\") \n", "t2_int = int.from_bytes(t2, \"big\") \n", - "x2_int = (x_int - t2_int + t_int) % SECP256K1_ORDER\n", - "x2_key = ECKey().set(x2_int, True)\n", - "P2_key = x2_key.get_pubkey()\n", - "\n", - "# The resulting tweaked public Q key remains the same\n", - "Q2_key = P2_key.tweak_add(t2)\n", - "print(\"Tweaked public key from original tweak t: {}\".format(Q_key.get_bytes().hex()))\n", - "print(\"Tweaked public key from modified tweak t2: {}\\n\".format(Q2_key.get_bytes().hex()))\n", + "x2_key, P2_key = generate_key_pair((x_int - t2_int + t_int) % SECP256K1_ORDER)\n", "\n", - "# So Alice can still produce a valid signature for Q\n", + "# Alice can still produce a valid signature for Q\n", "msg2 = sha256(b'I agree to the committed contract')\n", "sig2 = q_key.sign_schnorr(msg2)\n", "\n", - "# Given P2, Q and the modified contract, \n", - "# Alice can demonstrate the following to invalidate the original contract:\n", + "# Bob can verify that sig is a valid signature for the public key Q:\n", + "verify_sig = Q_key.verify_schnorr(sig, msg)\n", + "print(\"Alice has produced a valid signature for Q: {}\".format(verify_sig))\n", "\n", - "# 1) The modified contract 'Alice agrees to pay 0.1 BTC to Bob' appears to be committed to public key Q\n", - "print(\"'Alice agrees to pay 0.1 BTC to Bob' appears to be committed to Q: {}\"\\\n", - " .format(P2_key.tweak_add(sha256(alternative_contract.encode('utf-8'))) == Q_key))\n", - "\n", - "# 2) Alice is still the owner of the public key Q = P2 + t2*G\n", - "print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig2, msg)))" + "# Alice claims that P2 is the untweaked public key.\n", + "# Bob believes he can verify that the signature committed to the tweak t:\n", + "verify_tweak = P2_key.tweak_add(sha256(alternative_contract.encode('utf-8'))) == Q_key\n", + "print(\"The signature appears to commit to '{}': {}\".format(alternative_contract, verify_tweak))" ] }, { @@ -272,7 +271,7 @@ "source": [ "#### Example 2.2.5 - Pay-to-contract: Tweaking the pubkey with `H(P|msg)`\n", "\n", - "In this example, we demonstrate a _secure_ commitment scheme called pay-to-contract. The private key is tweaked with the scalar `H(P|c)`. Since `P` appears both inside and outside the hash, it isn't possible to solve for a different `c` by modifying `x'`.\n", + "In this example, we demonstrate a _secure_ commitment scheme called pay-to-contract. The private key is tweaked with the scalar `H(P|c)`. Since `P` appears both inside and outside the hash, it isn't possible to solve for a different contract `c` by modifying `x`.\n", "\n", "* Alice can now no longer invalidate her previous contract commitment with Bob." ] @@ -294,38 +293,29 @@ "t = sha256(ss)\n", "\n", "# Alice tweaks her key pair\n", - "t_key = ECKey().set(t, True)\n", - "T_key = t_key.get_pubkey()\n", - "q_key = x_key + t_key\n", - "Q_key = P_key + T_key\n", - "print(\"Tweak scalar: {}\\nTweak point: {}\\n\".format(t_key.secret, T_key.get_bytes().hex()))\n", + "Q_key = P_key.tweak_add(t)\n", + "q_key = x_key.add(t)\n", + "print(\"Tweaked private key: {}\\nTweaked public key: {}\\n\".format(q_key.secret, Q_key.get_bytes().hex()))\n", "\n", "# Alice signs a valid message\n", "msg = sha256(b'I agree to the committed contract')\n", "sig = q_key.sign_schnorr(msg)\n", "\n", - "# Given pubkey1, pubkey_tweaked and the contract, Bob can verify the following:\n", - "\n", - "# 1) The contract 'Alice agrees to pay 10 BTC to Bob' is committed to public key Q\n", - "print(\"'Alice agrees to pay 10 BTC to Bob' is committed to Q: {}\"\\\n", - " .format(P_key.tweak_add(sha256(P_key.get_bytes() + sha256(contract.encode('utf-8')))) == Q_key))\n", + "# Bob can verify that sig is a valid signature for the public key Q:\n", + "verify_sig = Q_key.verify_schnorr(sig, msg)\n", + "print(\"Alice has produced a valid signature for Q: {}\".format(verify_sig))\n", "\n", - "# 2) Alice is the owner of the public key P + t*G = Q\n", - "print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig, msg)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Note: A pay-to-contract commitment does not require the schnorr signature scheme.** Since we are tweaking both private and public keys with the same tweak, the key pair remains valid for any compatible signature scheme, such as ECDSA." + "# Alice provides the untweaked public key P to Bob.\n", + "# Bob believes he can verify that the signature committed to the tweak t:\n", + "verify_tweak = P_key.tweak_add(sha256(P_key.get_bytes() + sha256(contract.encode('utf-8')))) == Q_key\n", + "print(\"The signature commits to '{}': {}\".format(contract, verify_tweak))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Part 2: Spending a taproot output along the key path\n", + "## Part 2: Spending a (tweaked) taproot output along the key path\n", "\n", "In this exercise, we'll create a segwit v1 output that sends to a tweaked public key. We'll then spend that output along the key path using the tweaked private key.\n", "\n", @@ -457,7 +447,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Part 3 (Case Study): Contract Commitments\n", + "## Part 3 (Case Study): Contract commitments\n", "\n", "Alice currently commits contracts with Bob to unspendable OP_RETURN outputs, which contain 32B proof-of-existence commitments. Although this is a standard output with a zero amount, several disadvantages remain:\n", "\n",