|
22 | 22 | "source": [ |
23 | 23 | "# 2.2 TapTweak\n", |
24 | 24 | "\n", |
25 | | - "* Tweaking the Public Key\n", |
26 | | - "* Commitment Schemes with Tweaks\n", |
27 | | - "* Spending a (tweaked) taproot output along the key path\n", |
| 25 | + "* Part 1: Tweaking the public key and commitment schemes with tweaks\n", |
| 26 | + "* Part 2: Spending a (tweaked) taproot output along the key path\n", |
| 27 | + "* Part 3 (Case Study): Contract commitments\n", |
28 | 28 | "\n", |
29 | 29 | "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", |
30 | 30 | "\n", |
31 | | - "## Part 1: Tweaking the Public Key\n", |
| 31 | + "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." |
| 32 | + ] |
| 33 | + }, |
| 34 | + { |
| 35 | + "cell_type": "markdown", |
| 36 | + "metadata": {}, |
| 37 | + "source": [ |
| 38 | + "## Part 1: Tweaking the public key\n", |
32 | 39 | "\n", |
33 | 40 | "Instead of using our original public key as the witness program, we use a tweaked public key.\n", |
34 | 41 | "\n", |
|
156 | 163 | "\n", |
157 | 164 | "\n", |
158 | 165 | "\n", |
159 | | - "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.** " |
| 166 | + "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.**" |
160 | 167 | ] |
161 | 168 | }, |
162 | 169 | { |
|
196 | 203 | "msg = sha256(b'I agree to the committed contract')\n", |
197 | 204 | "sig = q_key.sign_schnorr(msg)\n", |
198 | 205 | "\n", |
199 | | - "# Given P, Q and the contract, Bob believes he can verify the following:\n", |
200 | | - "\n", |
201 | | - "# 1) The contract 'Alice agrees to pay 10 BTC to Bob' is committed to public key Q\n", |
202 | | - "print(\"'Alice agrees to pay 10 BTC to Bob' appears to be committed to Q: {}\".format(\\\n", |
203 | | - " P_key.tweak_add(sha256(contract.encode('utf-8'))) == Q_key))\n", |
| 206 | + "# Bob can verify that sig is a valid signature for the public key Q:\n", |
| 207 | + "verify_sig = Q_key.verify_schnorr(sig, msg)\n", |
| 208 | + "print(\"Alice has produced a valid signature for Q: {}\".format(verify_sig))\n", |
204 | 209 | "\n", |
205 | | - "# 2) Alice is the owner of the public key Q = P + t*G\n", |
206 | | - "print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig, msg)))" |
| 210 | + "# Alice provides the untweaked public key P to Bob.\n", |
| 211 | + "# Bob believes he can verify that the signature committed to the tweak t:\n", |
| 212 | + "verify_tweak = P_key.tweak_add(sha256(contract.encode('utf-8'))) == Q_key\n", |
| 213 | + "print(\"The signature appears to commit to '{}': {}\".format(contract, verify_tweak))" |
207 | 214 | ] |
208 | 215 | }, |
209 | 216 | { |
|
228 | 235 | "print(\"Tweak from original contract: {}\".format(t.hex()))\n", |
229 | 236 | "print(\"Tweak from modified contract: {}\\n\".format(t2.hex()))\n", |
230 | 237 | "\n", |
231 | | - "# Alice modifies her original secret and public key\n", |
232 | | - "# x` = x - t' + t\n", |
| 238 | + "# Alice modifies her original private key and public key\n", |
| 239 | + "# x2 = x - t2 + t\n", |
233 | 240 | "x_int = x_key.as_int()\n", |
234 | 241 | "t_int = int.from_bytes(t, \"big\") \n", |
235 | 242 | "t2_int = int.from_bytes(t2, \"big\") \n", |
236 | | - "x2_int = (x_int - t2_int + t_int) % SECP256K1_ORDER\n", |
237 | | - "x2_key = ECKey().set(x2_int, True)\n", |
238 | | - "P2_key = x2_key.get_pubkey()\n", |
239 | | - "\n", |
240 | | - "# The resulting tweaked public Q key remains the same\n", |
241 | | - "Q2_key = P2_key.tweak_add(t2)\n", |
242 | | - "print(\"Tweaked public key from original tweak t: {}\".format(Q_key.get_bytes().hex()))\n", |
243 | | - "print(\"Tweaked public key from modified tweak t2: {}\\n\".format(Q2_key.get_bytes().hex()))\n", |
| 243 | + "x2_key, P2_key = generate_key_pair((x_int - t2_int + t_int) % SECP256K1_ORDER)\n", |
244 | 244 | "\n", |
245 | | - "# So Alice can still produce a valid signature for Q\n", |
| 245 | + "# Alice can still produce a valid signature for Q\n", |
246 | 246 | "msg2 = sha256(b'I agree to the committed contract')\n", |
247 | 247 | "sig2 = q_key.sign_schnorr(msg2)\n", |
248 | 248 | "\n", |
249 | | - "# Given P2, Q and the modified contract, \n", |
250 | | - "# Alice can demonstrate the following to invalidate the original contract:\n", |
| 249 | + "# Bob can verify that sig is a valid signature for the public key Q:\n", |
| 250 | + "verify_sig = Q_key.verify_schnorr(sig, msg)\n", |
| 251 | + "print(\"Alice has produced a valid signature for Q: {}\".format(verify_sig))\n", |
251 | 252 | "\n", |
252 | | - "# 1) The modified contract 'Alice agrees to pay 0.1 BTC to Bob' appears to be committed to public key Q\n", |
253 | | - "print(\"'Alice agrees to pay 0.1 BTC to Bob' appears to be committed to Q: {}\"\\\n", |
254 | | - " .format(P2_key.tweak_add(sha256(alternative_contract.encode('utf-8'))) == Q_key))\n", |
255 | | - "\n", |
256 | | - "# 2) Alice is still the owner of the public key Q = P2 + t2*G\n", |
257 | | - "print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig2, msg)))" |
| 253 | + "# Alice claims that P2 is the untweaked public key.\n", |
| 254 | + "# Bob believes he can verify that the signature committed to the tweak t:\n", |
| 255 | + "verify_tweak = P2_key.tweak_add(sha256(alternative_contract.encode('utf-8'))) == Q_key\n", |
| 256 | + "print(\"The signature appears to commit to '{}': {}\".format(alternative_contract, verify_tweak))" |
258 | 257 | ] |
259 | 258 | }, |
260 | 259 | { |
|
276 | 275 | "source": [ |
277 | 276 | "#### Example 2.2.5 - Pay-to-contract: Tweaking the pubkey with `H(P|msg)`\n", |
278 | 277 | "\n", |
279 | | - "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", |
| 278 | + "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", |
280 | 279 | "\n", |
281 | 280 | "* Alice can now no longer invalidate her previous contract commitment with Bob." |
282 | 281 | ] |
|
298 | 297 | "t = sha256(ss)\n", |
299 | 298 | "\n", |
300 | 299 | "# Alice tweaks her key pair\n", |
301 | | - "t_key = ECKey().set(t, True)\n", |
302 | | - "T_key = t_key.get_pubkey()\n", |
303 | | - "q_key = x_key + t_key\n", |
304 | | - "Q_key = P_key + T_key\n", |
305 | | - "print(\"Tweak scalar: {}\\nTweak point: {}\\n\".format(t_key.secret, T_key.get_bytes().hex()))\n", |
| 300 | + "Q_key = P_key.tweak_add(t)\n", |
| 301 | + "q_key = x_key.add(t)\n", |
| 302 | + "print(\"Tweaked private key: {}\\nTweaked public key: {}\\n\".format(q_key.secret, Q_key.get_bytes().hex()))\n", |
306 | 303 | "\n", |
307 | 304 | "# Alice signs a valid message\n", |
308 | 305 | "msg = sha256(b'I agree to the committed contract')\n", |
309 | 306 | "sig = q_key.sign_schnorr(msg)\n", |
310 | 307 | "\n", |
311 | | - "# Given pubkey1, pubkey_tweaked and the contract, Bob can verify the following:\n", |
312 | | - "\n", |
313 | | - "# 1) The contract 'Alice agrees to pay 10 BTC to Bob' is committed to public key Q\n", |
314 | | - "print(\"'Alice agrees to pay 10 BTC to Bob' is committed to Q: {}\"\\\n", |
315 | | - " .format(P_key.tweak_add(sha256(P_key.get_bytes() + sha256(contract.encode('utf-8')))) == Q_key))\n", |
| 308 | + "# Bob can verify that sig is a valid signature for the public key Q:\n", |
| 309 | + "verify_sig = Q_key.verify_schnorr(sig, msg)\n", |
| 310 | + "print(\"Alice has produced a valid signature for Q: {}\".format(verify_sig))\n", |
316 | 311 | "\n", |
317 | | - "# 2) Alice is the owner of the public key P + t*G = Q\n", |
318 | | - "print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig, msg)))" |
319 | | - ] |
320 | | - }, |
321 | | - { |
322 | | - "cell_type": "markdown", |
323 | | - "metadata": {}, |
324 | | - "source": [ |
325 | | - "**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." |
| 312 | + "# Alice provides the untweaked public key P to Bob.\n", |
| 313 | + "# Bob believes he can verify that the signature committed to the tweak t:\n", |
| 314 | + "verify_tweak = P_key.tweak_add(sha256(P_key.get_bytes() + sha256(contract.encode('utf-8')))) == Q_key\n", |
| 315 | + "print(\"The signature commits to '{}': {}\".format(contract, verify_tweak))" |
326 | 316 | ] |
327 | 317 | }, |
328 | 318 | { |
329 | 319 | "cell_type": "markdown", |
330 | 320 | "metadata": {}, |
331 | 321 | "source": [ |
332 | | - "## Part 2: Spending a taproot output along the key path\n", |
| 322 | + "## Part 2: Spending a (tweaked) taproot output along the key path\n", |
333 | 323 | "\n", |
334 | 324 | "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", |
335 | 325 | "\n", |
|
461 | 451 | "cell_type": "markdown", |
462 | 452 | "metadata": {}, |
463 | 453 | "source": [ |
464 | | - "## Part 3 (Case Study): Contract Commitments\n", |
| 454 | + "## Part 3 (Case Study): Contract commitments\n", |
465 | 455 | "\n", |
466 | 456 | "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", |
467 | 457 | "\n", |
|
0 commit comments