|
10 | 10 | "\n", |
11 | 11 | "import util\n", |
12 | 12 | "from test_framework.address import program_to_witness\n", |
13 | | - "from test_framework.key import ECKey, SECP256K1_ORDER, generate_key_pair, generate_schnorr_nonce\n", |
| 13 | + "from test_framework.key import ECKey, ECPubKey, SECP256K1_ORDER, generate_key_pair, generate_schnorr_nonce\n", |
14 | 14 | "from test_framework.messages import CTxInWitness, sha256\n", |
15 | 15 | "from test_framework.musig import generate_musig_key, aggregate_schnorr_nonces, sign_musig, aggregate_musig_signatures\n", |
16 | | - "from test_framework.script import TaprootSignatureHash, SIGHASH_ALL_TAPROOT" |
| 16 | + "from test_framework.script import CTransaction, OP_RETURN, SIGHASH_ALL_TAPROOT, TaprootSignatureHash" |
17 | 17 | ] |
18 | 18 | }, |
19 | 19 | { |
|
453 | 453 | "test.shutdown()" |
454 | 454 | ] |
455 | 455 | }, |
| 456 | + { |
| 457 | + "cell_type": "markdown", |
| 458 | + "metadata": {}, |
| 459 | + "source": [ |
| 460 | + "## Part 3 (Case Study): Contract Commitments\n", |
| 461 | + "\n", |
| 462 | + "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", |
| 463 | + "\n", |
| 464 | + "* Committing data to an OP_RETURN output requires an additional output with a zero amount, resulting in a higher transaction fees.\n", |
| 465 | + "* The OP_RETURN output reveals the presence of a data commitment to any on-chain observer. This reduces the privacy of Alice's commitments.\n", |
| 466 | + "\n", |
| 467 | + "In this chapter, we'll show how Alice can move her contract commitments to public key tweaks to reduce fees and improve the privacy of her commitments." |
| 468 | + ] |
| 469 | + }, |
| 470 | + { |
| 471 | + "cell_type": "markdown", |
| 472 | + "metadata": {}, |
| 473 | + "source": [ |
| 474 | + "### Committing contract data to an OP_RETURN output\n", |
| 475 | + "\n", |
| 476 | + "We'll first show Alice's current setup: An OP_RETURN script containing commitment data." |
| 477 | + ] |
| 478 | + }, |
| 479 | + { |
| 480 | + "cell_type": "markdown", |
| 481 | + "metadata": {}, |
| 482 | + "source": [ |
| 483 | + "#### Example 2.2.10: Create the contract commitment" |
| 484 | + ] |
| 485 | + }, |
| 486 | + { |
| 487 | + "cell_type": "code", |
| 488 | + "execution_count": null, |
| 489 | + "metadata": {}, |
| 490 | + "outputs": [], |
| 491 | + "source": [ |
| 492 | + "contract_bytes = \"Alice pays 10 BTC to Bob\".encode('utf-8')\n", |
| 493 | + "commitment_bytes = sha256(contract_bytes)\n", |
| 494 | + "print(\"The contract commitment is: {}\".format(commitment_bytes.hex()))" |
| 495 | + ] |
| 496 | + }, |
| 497 | + { |
| 498 | + "cell_type": "markdown", |
| 499 | + "metadata": {}, |
| 500 | + "source": [ |
| 501 | + "#### Example 2.2.11: Create and broadcast a transaction with an OP_RETURN output\n", |
| 502 | + "\n", |
| 503 | + "We now construct a OP_RETURN output which contains the commitment data of Alice's contract with Bob, and then add it to a transaction with a regular P2WPKH output. This way, the commitment can be done more efficiently, by sharing transaction data with another spendable output." |
| 504 | + ] |
| 505 | + }, |
| 506 | + { |
| 507 | + "cell_type": "code", |
| 508 | + "execution_count": null, |
| 509 | + "metadata": {}, |
| 510 | + "outputs": [], |
| 511 | + "source": [ |
| 512 | + "# Start node\n", |
| 513 | + "test = util.TestWrapper()\n", |
| 514 | + "test.setup()\n", |
| 515 | + "node = test.nodes[0]\n", |
| 516 | + "\n", |
| 517 | + "# Construct a transaction with two outputs:\n", |
| 518 | + "# Output 0) Alice's destination address\n", |
| 519 | + "# Output 1) OP_RETURN with Alice's commitment\n", |
| 520 | + "address_alice = node.getnewaddress(address_type=\"bech32\")\n", |
| 521 | + "op_return_tx = node.generate_and_send_coins(address_alice, data=commitment_bytes.hex())\n", |
| 522 | + "\n", |
| 523 | + "# Confirm details of the OP_RETURN output\n", |
| 524 | + "data_output = op_return_tx.vout[1]\n", |
| 525 | + "print(\"The OP_RETURN output script is: {}\".format(data_output.scriptPubKey.hex()))\n", |
| 526 | + "print(\"The OP_RETURN output value is: {}\".format(data_output.nValue))\n", |
| 527 | + "\n", |
| 528 | + "# Note the total weight of the transaction with a dedicated OP_RETURN commitment output\n", |
| 529 | + "op_return_tx_hex = op_return_tx.serialize().hex()\n", |
| 530 | + "print(\"The total transaction weight is: {}\\n\".format(node.decoderawtransaction(op_return_tx_hex)['weight']))" |
| 531 | + ] |
| 532 | + }, |
| 533 | + { |
| 534 | + "cell_type": "markdown", |
| 535 | + "metadata": {}, |
| 536 | + "source": [ |
| 537 | + "### Committing contract data with the pay-to-contract scheme\n", |
| 538 | + "\n", |
| 539 | + "Next, we will commit Alice's contract to a spendable pay-to-pubkey output with the pay-to-contract commitment scheme." |
| 540 | + ] |
| 541 | + }, |
| 542 | + { |
| 543 | + "cell_type": "markdown", |
| 544 | + "metadata": {}, |
| 545 | + "source": [ |
| 546 | + "#### _Programming Exercise 2.2.12:_ Generate segwit v1 address for a pay-to-contract public key\n", |
| 547 | + "\n", |
| 548 | + "Commit the contract to Alice's public key with the pay-to-contract commitment scheme, and then generate the corresponding segwit v1 address." |
| 549 | + ] |
| 550 | + }, |
| 551 | + { |
| 552 | + "cell_type": "code", |
| 553 | + "execution_count": null, |
| 554 | + "metadata": {}, |
| 555 | + "outputs": [], |
| 556 | + "source": [ |
| 557 | + "# Generate a key pair\n", |
| 558 | + "privkey, pubkey = generate_key_pair()\n", |
| 559 | + "\n", |
| 560 | + "# Generate the pay-to-contract tweak\n", |
| 561 | + "contract_bytes = \"Alice pays 10 BTC to Bob\".encode('utf-8')\n", |
| 562 | + "tweak_private = # TODO: implement\n", |
| 563 | + "tweak_point = # TODO: implement\n", |
| 564 | + "\n", |
| 565 | + "# Tweak Alice's key pair with the pay-to-contract tweak\n", |
| 566 | + "tweaked_pubkey = # TODO: implement\n", |
| 567 | + "tweaked_privkey = # TODO: implement\n", |
| 568 | + "\n", |
| 569 | + "# Generate the segwit v1 address\n", |
| 570 | + "tweaked_pubkey_data = # TODO: implement\n", |
| 571 | + "tweaked_pubkey_program = # TODO: implement\n", |
| 572 | + "version = 1\n", |
| 573 | + "address = program_to_witness(version, tweaked_pubkey_program)\n", |
| 574 | + "print(\"Address encoding the segwit v1 output: \", address)" |
| 575 | + ] |
| 576 | + }, |
| 577 | + { |
| 578 | + "cell_type": "markdown", |
| 579 | + "metadata": {}, |
| 580 | + "source": [ |
| 581 | + "#### Example 2.2.13: Create a transaction with the Bitcoin Core wallet sending funds to the segwit v1 address\n" |
| 582 | + ] |
| 583 | + }, |
| 584 | + { |
| 585 | + "cell_type": "code", |
| 586 | + "execution_count": null, |
| 587 | + "metadata": {}, |
| 588 | + "outputs": [], |
| 589 | + "source": [ |
| 590 | + "test = util.TestWrapper()\n", |
| 591 | + "test.setup()\n", |
| 592 | + "node = test.nodes[0]\n", |
| 593 | + "\n", |
| 594 | + "# Generate coins and send coins to segwit v1 address containing the pay-to-contract public key\n", |
| 595 | + "tx = node.generate_and_send_coins(address)\n", |
| 596 | + "print(\"Transaction {}, output 0\\nSent to {}\\n\".format(tx.hash, address))\n", |
| 597 | + "print(\"Transaction weight with pay-to-contract: {}\".format(node.decoderawtransaction(tx.serialize().hex())['weight']))\n", |
| 598 | + "print(\"Transaction weight with OP_RETURN: {}\\n\".format(node.decoderawtransaction(op_return_tx_hex)['weight']))" |
| 599 | + ] |
| 600 | + }, |
| 601 | + { |
| 602 | + "cell_type": "markdown", |
| 603 | + "metadata": {}, |
| 604 | + "source": [ |
| 605 | + "#### _Programming Exercise 2.2.14:_ Verify that the contract between Alice and Bob is committed correctly\n", |
| 606 | + "\n", |
| 607 | + "Extract the witness program from the segwit v1 output and verify the pay-to-contract commitment." |
| 608 | + ] |
| 609 | + }, |
| 610 | + { |
| 611 | + "cell_type": "code", |
| 612 | + "execution_count": null, |
| 613 | + "metadata": {}, |
| 614 | + "outputs": [], |
| 615 | + "source": [ |
| 616 | + "# Fetch output from tx\n", |
| 617 | + "v1_output = tx.vout[0]\n", |
| 618 | + "\n", |
| 619 | + "# Extract witness program from the output script\n", |
| 620 | + "program = # TODO: implement\n", |
| 621 | + "\n", |
| 622 | + "# Reconstruct pay-to-contract public key\n", |
| 623 | + "tweaked_pubkey_bytes = # TODO: implement\n", |
| 624 | + "tweaked_pubkey = # TODO: implement\n", |
| 625 | + "\n", |
| 626 | + "# Verify pay-to-contract commitment is correct\n", |
| 627 | + "contract_bytes = \"Alice pays 10 BTC to Bob\".encode('utf-8')\n", |
| 628 | + "ss = pubkey.get_bytes()\n", |
| 629 | + "ss += sha256(contract_bytes)\n", |
| 630 | + "t = sha256(ss)\n", |
| 631 | + "assert pubkey.tweak_add(t) == tweaked_pubkey\n", |
| 632 | + "print(\"Contract commitment is correct!\")" |
| 633 | + ] |
| 634 | + }, |
| 635 | + { |
| 636 | + "cell_type": "markdown", |
| 637 | + "metadata": {}, |
| 638 | + "source": [ |
| 639 | + "#### Example 2.2.15: Construct a CTransaction to spend the pay-to-contract public key\n", |
| 640 | + "\n", |
| 641 | + "Unlike an OP_RETURN output, the tweaked pay-to-contract public key can be spent like a regular, untweaked public key. An on-chain observer cannot determine whether a commitment was made to the public key." |
| 642 | + ] |
| 643 | + }, |
| 644 | + { |
| 645 | + "cell_type": "code", |
| 646 | + "execution_count": null, |
| 647 | + "metadata": {}, |
| 648 | + "outputs": [], |
| 649 | + "source": [ |
| 650 | + "# Create a spending transaction, which sends funds back to the wallet\n", |
| 651 | + "spending_tx = test.create_spending_transaction(tx.hash)\n", |
| 652 | + "print(\"Spending transaction:\\n{}\\n\".format(spending_tx))\n", |
| 653 | + "\n", |
| 654 | + "# Create sighash for ALL (0x00)\n", |
| 655 | + "sighash = TaprootSignatureHash(spending_tx, [tx.vout[0]], SIGHASH_ALL_TAPROOT, input_index=0)\n", |
| 656 | + "\n", |
| 657 | + "# Create a valid transaction signature for the tweaked public key\n", |
| 658 | + "sig = tweaked_privkey.sign_schnorr(sighash)\n", |
| 659 | + "print(\"Signature of tweaked keypair is {}\\n\".format(sig.hex()))\n", |
| 660 | + "\n", |
| 661 | + "# Construct transaction witness\n", |
| 662 | + "spending_tx.wit.vtxinwit.append(CTxInWitness([sig]))\n", |
| 663 | + "\n", |
| 664 | + "# Test mempool acceptance\n", |
| 665 | + "assert node.test_transaction(spending_tx)\n", |
| 666 | + "print(\"Success!\")" |
| 667 | + ] |
| 668 | + }, |
| 669 | + { |
| 670 | + "cell_type": "markdown", |
| 671 | + "metadata": {}, |
| 672 | + "source": [ |
| 673 | + "#### _Shutdown TestWrapper_" |
| 674 | + ] |
| 675 | + }, |
| 676 | + { |
| 677 | + "cell_type": "code", |
| 678 | + "execution_count": null, |
| 679 | + "metadata": {}, |
| 680 | + "outputs": [], |
| 681 | + "source": [ |
| 682 | + "# Shutdown\n", |
| 683 | + "test.shutdown()" |
| 684 | + ] |
| 685 | + }, |
456 | 686 | { |
457 | 687 | "cell_type": "markdown", |
458 | 688 | "metadata": {}, |
|
461 | 691 | "\n", |
462 | 692 | "- Learned how to tweak a public/private key pair with a value.\n", |
463 | 693 | "- Created an _insecure_ commitment scheme (by tweaking the keys with the raw commitment value) and a _secure_ commitment scheme (by tweaking with a hash of the commitment and the public key).\n", |
464 | | - "- Sent coins to a segwit v1 output with a tweaked public key, and later spent that output by signing with the tweaked private key." |
| 694 | + "- Sent coins to a segwit v1 output with a tweaked public key, and later spent that output by signing with the tweaked private key.\n", |
| 695 | + "- Improved cost and privacy of a contract commitment by moving it from an unspendable OP_RETURN output to a pay-to-contract public key." |
465 | 696 | ] |
466 | 697 | } |
467 | 698 | ], |
|
0 commit comments