Skip to content

Commit

Permalink
Add mini-case study: OP_RETURN vs pay-to-contract
Browse files Browse the repository at this point in the history
  • Loading branch information
jachiang committed Nov 6, 2019
1 parent 7125edf commit b259f1d
Show file tree
Hide file tree
Showing 2 changed files with 302 additions and 3 deletions.
237 changes: 234 additions & 3 deletions 2.2-taptweak.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
"\n",
"import util\n",
"from test_framework.address import program_to_witness\n",
"from test_framework.key import ECKey, SECP256K1_ORDER, generate_key_pair, generate_schnorr_nonce\n",
"from test_framework.key import ECKey, ECPubKey, SECP256K1_ORDER, generate_key_pair, generate_schnorr_nonce\n",
"from test_framework.messages import CTxInWitness, sha256\n",
"from test_framework.musig import generate_musig_key, aggregate_schnorr_nonces, sign_musig, aggregate_musig_signatures\n",
"from test_framework.script import TaprootSignatureHash, SIGHASH_ALL_TAPROOT"
"from test_framework.script import CTransaction, OP_RETURN, SIGHASH_ALL_TAPROOT, TaprootSignatureHash"
]
},
{
Expand Down Expand Up @@ -453,6 +453,236 @@
"test.shutdown()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 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",
"* Committing data to an OP_RETURN output requires an additional output with a zero amount, resulting in a higher transaction fees.\n",
"* 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",
"\n",
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Committing contract data to an OP_RETURN output\n",
"\n",
"We'll first show Alice's current setup: An OP_RETURN script containing commitment data."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Example 2.2.10: Create the contract commitment"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"contract_bytes = \"Alice pays 10 BTC to Bob\".encode('utf-8')\n",
"commitment_bytes = sha256(contract_bytes)\n",
"print(\"The contract commitment is: {}\".format(commitment_bytes.hex()))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Example 2.2.11: Create and broadcast a transaction with an OP_RETURN output\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Start node\n",
"test = util.TestWrapper()\n",
"test.setup()\n",
"node = test.nodes[0]\n",
"\n",
"# Construct a transaction with two outputs:\n",
"# Output 0) Alice's destination address\n",
"# Output 1) OP_RETURN with Alice's commitment\n",
"address_alice = node.getnewaddress(address_type=\"bech32\")\n",
"op_return_tx = node.generate_and_send_coins(address_alice, data=commitment_bytes.hex())\n",
"\n",
"# Confirm details of the OP_RETURN output\n",
"data_output = op_return_tx.vout[1]\n",
"print(\"The OP_RETURN output script is: {}\".format(data_output.scriptPubKey.hex()))\n",
"print(\"The OP_RETURN output value is: {}\".format(data_output.nValue))\n",
"\n",
"# Note the total weight of the transaction with a dedicated OP_RETURN commitment output\n",
"op_return_tx_hex = op_return_tx.serialize().hex()\n",
"print(\"The total transaction weight is: {}\\n\".format(node.decoderawtransaction(op_return_tx_hex)['weight']))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Committing contract data with the pay-to-contract scheme\n",
"\n",
"Next, we will commit Alice's contract to a spendable pay-to-pubkey output with the pay-to-contract commitment scheme."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### _Programming Exercise 2.2.12:_ Generate segwit v1 address for a pay-to-contract public key\n",
"\n",
"Commit the contract to Alice's public key with the pay-to-contract commitment scheme, and then generate the corresponding segwit v1 address."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Generate a key pair\n",
"privkey, pubkey = generate_key_pair()\n",
"\n",
"# Generate the pay-to-contract tweak\n",
"contract_bytes = \"Alice pays 10 BTC to Bob\".encode('utf-8')\n",
"tweak_private = # TODO: implement\n",
"tweak_point = # TODO: implement\n",
"\n",
"# Tweak Alice's key pair with the pay-to-contract tweak\n",
"tweaked_pubkey = # TODO: implement\n",
"tweaked_privkey = # TODO: implement\n",
"\n",
"# Generate the segwit v1 address\n",
"tweaked_pubkey_data = # TODO: implement\n",
"tweaked_pubkey_program = # TODO: implement\n",
"version = 1\n",
"address = program_to_witness(version, tweaked_pubkey_program)\n",
"print(\"Address encoding the segwit v1 output: \", address)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Example 2.2.13: Create a transaction with the Bitcoin Core wallet sending funds to the segwit v1 address\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"test = util.TestWrapper()\n",
"test.setup()\n",
"node = test.nodes[0]\n",
"\n",
"# Generate coins and send coins to segwit v1 address containing the pay-to-contract public key\n",
"tx = node.generate_and_send_coins(address)\n",
"print(\"Transaction {}, output 0\\nSent to {}\\n\".format(tx.hash, address))\n",
"print(\"Transaction weight with pay-to-contract: {}\".format(node.decoderawtransaction(tx.serialize().hex())['weight']))\n",
"print(\"Transaction weight with OP_RETURN: {}\\n\".format(node.decoderawtransaction(op_return_tx_hex)['weight']))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### _Programming Exercise 2.2.14:_ Verify that the contract between Alice and Bob is committed correctly\n",
"\n",
"Extract the witness program from the segwit v1 output and verify the pay-to-contract commitment."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Fetch output from tx\n",
"v1_output = tx.vout[0]\n",
"\n",
"# Extract witness program from the output script\n",
"program = # TODO: implement\n",
"\n",
"# Reconstruct pay-to-contract public key\n",
"tweaked_pubkey_bytes = # TODO: implement\n",
"tweaked_pubkey = # TODO: implement\n",
"\n",
"# Verify pay-to-contract commitment is correct\n",
"contract_bytes = \"Alice pays 10 BTC to Bob\".encode('utf-8')\n",
"ss = pubkey.get_bytes()\n",
"ss += sha256(contract_bytes)\n",
"t = sha256(ss)\n",
"assert pubkey.tweak_add(t) == tweaked_pubkey\n",
"print(\"Contract commitment is correct!\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Example 2.2.15: Construct a CTransaction to spend the pay-to-contract public key\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create a spending transaction, which sends funds back to the wallet\n",
"spending_tx = test.create_spending_transaction(tx.hash)\n",
"print(\"Spending transaction:\\n{}\\n\".format(spending_tx))\n",
"\n",
"# Create sighash for ALL (0x00)\n",
"sighash = TaprootSignatureHash(spending_tx, [tx.vout[0]], SIGHASH_ALL_TAPROOT, input_index=0)\n",
"\n",
"# Create a valid transaction signature for the tweaked public key\n",
"sig = tweaked_privkey.sign_schnorr(sighash)\n",
"print(\"Signature of tweaked keypair is {}\\n\".format(sig.hex()))\n",
"\n",
"# Construct transaction witness\n",
"spending_tx.wit.vtxinwit.append(CTxInWitness([sig]))\n",
"\n",
"# Test mempool acceptance\n",
"assert node.test_transaction(spending_tx)\n",
"print(\"Success!\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### _Shutdown TestWrapper_"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Shutdown\n",
"test.shutdown()"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -461,7 +691,8 @@
"\n",
"- Learned how to tweak a public/private key pair with a value.\n",
"- 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",
"- Sent coins to a segwit v1 output with a tweaked public key, and later spent that output by signing with the tweaked private key."
"- 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",
"- Improved cost and privacy of a contract commitment by moving it from an unspendable OP_RETURN output to a pay-to-contract public key."
]
}
],
Expand Down
68 changes: 68 additions & 0 deletions solutions/2.2-taptweak-solutions.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,74 @@
"assert node.test_transaction(spending_tx)\n",
"print(\"Success!\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### _Programming Exercise 2.2.12:_ Generate segwit v1 address for a pay-to-contract public key"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Generate a key pair\n",
"privkey, pubkey = generate_key_pair()\n",
"\n",
"# Generate the pay-to-contract tweak\n",
"contract_bytes = \"Alice pays 10 BTC to Bob\".encode('utf-8')\n",
"ss = pubkey.get_bytes()\n",
"ss += sha256(contract_bytes)\n",
"t = sha256(ss)\n",
"tweak_private = ECKey().set(t, True)\n",
"tweak_point = tweak_private.get_pubkey()\n",
"\n",
"# Tweak Alice's key pair with the pay-to-contract tweak\n",
"tweaked_pubkey = pubkey + tweak_point\n",
"tweaked_privkey = privkey + tweak_private\n",
"\n",
"# Generate the segwit v1 address\n",
"tweaked_pubkey_data = tweaked_pubkey.get_bytes()\n",
"tweaked_pubkey_program = bytes([tweaked_pubkey_data[0] & 1]) + tweaked_pubkey_data[1:]\n",
"version = 1\n",
"address = program_to_witness(version, tweaked_pubkey_program)\n",
"print(\"Address encoding the segwit v1 output: \", address)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### _Programming Exercise 2.2.14:_ Verify that the contract between Alice and Bob is committed correctly"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Fetch output from tx\n",
"v1_output = tx.vout[0]\n",
"\n",
"# Extract witness program from the output script\n",
"program = v1_output.scriptPubKey[2:]\n",
"\n",
"# Reconstruct pay-to-contract public key\n",
"tweaked_pubkey_bytes = bytes([program[0] + 2]) + program[1:]\n",
"tweaked_pubkey = ECPubKey().set(tweaked_pubkey_bytes)\n",
"\n",
"# Verify pay-to-contract commitment is correct\n",
"contract_bytes = \"Alice pays 10 BTC to Bob\".encode('utf-8')\n",
"ss = pubkey.get_bytes()\n",
"ss += sha256(contract_bytes)\n",
"t = sha256(ss)\n",
"assert pubkey.tweak_add(t) == tweaked_pubkey\n",
"print(\"Contract commitment is correct!\")"
]
}
],
"metadata": {
Expand Down

0 comments on commit b259f1d

Please sign in to comment.