Skip to content

Commit 06f1231

Browse files
committed
Add contract commitment unsealing to 2.2.3, 2.2.4
1 parent 2e635c0 commit 06f1231

2 files changed

Lines changed: 136 additions & 51 deletions

File tree

2.2-taptweak.ipynb

Lines changed: 134 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"\n",
2929
"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",
3030
"\n",
31-
"## Tweaking the Public Key\n",
31+
"## Part 1: Tweaking the Public Key\n",
3232
"\n",
3333
"Instead of using our original public key as the witness program, we use a tweaked public key.\n",
3434
"\n",
@@ -152,16 +152,20 @@
152152
"\n",
153153
"![test](images/taptweak0.jpg)\n",
154154
"\n",
155-
"Instead, the committed value must first be hashed with the untweaked public key point. **This prevents modification of both untweaked secret and tweak for a given tweaked pubkey point Q.**"
155+
"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.** "
156156
]
157157
},
158158
{
159159
"cell_type": "markdown",
160160
"metadata": {},
161161
"source": [
162-
"#### Example 2.2.3: modifying the tweak for a tweaked public key Q\n",
162+
"#### Example 2.2.3: Tweaking a public key Q with commitment data\n",
163163
"\n",
164-
"In this example we demonstrate an insecure commitment scheme. Simply tweaking the private key with a value `c` allows the pubkey equation `Q = x'G + c'G` to be solved for any `c'` by modifying `x'`."
164+
"In this example we demonstrate an insecure commitment scheme. The committed value `c` can be trivially modified to `c'`. The public key point equation `Q = x'G + c'G` still holds and is equal to the same point Q. An alternative secret `x` can also be solved for.\n",
165+
"\n",
166+
"First, we commit a contract between Alice and Bob and then demonstrate how this unsafe commitment can be changed.\n",
167+
"\n",
168+
"* The initial committed contract is: `Alice pays 10 BTC to Bob`"
165169
]
166170
},
167171
{
@@ -170,38 +174,42 @@
170174
"metadata": {},
171175
"outputs": [],
172176
"source": [
173-
"# Generate a key pair\n",
174-
"x, P = generate_key_pair()\n",
175-
"print(\"Private key: {}\\nPublic key: {}\\n\".format(x.secret, P.get_bytes().hex()))\n",
177+
"# Alice generates a key pair\n",
178+
"x_key, P_key = generate_key_pair()\n",
179+
"print(\"Private key: {}\\nPublic key: {}\\n\".format(x_key.secret, P_key.get_bytes().hex()))\n",
176180
"\n",
177-
"# Tweak the public key\n",
178-
"t = random.randrange(1, SECP256K1_ORDER)\n",
179-
"print(\"Tweak: {}\".format(t))\n",
180-
"Q = P.tweak_add(t)\n",
181+
"# Alice generates the tweak from the contract\n",
182+
"contract = \"Alice agrees to pay 10 BTC to Bob\"\n",
183+
"t = sha256(contract.encode('utf-8'))\n",
184+
"print(\"Tweak from original contract: {}\\n\".format(t.hex()))\n",
185+
"\n",
186+
"# Alice tweaks her key pair\n",
187+
"Q_key = P_key.tweak_add(t)\n",
188+
"q_key = x_key.add(t)\n",
189+
"print(\"Tweaked private key: {}\\nTweaked public key: {}\\n\".format(q_key.secret, Q_key.get_bytes().hex()))\n",
181190
"\n",
182-
"# Create a fake tweak\n",
183-
"t2 = random.randrange(1, SECP256K1_ORDER)\n",
184-
"print(\"Tweak 2: {}\\n\".format(t2))\n",
191+
"# Alice produces a valid signature for this tweaked public key\n",
192+
"msg = sha256(b'I agree to the committed contract')\n",
193+
"sig = q_key.sign_schnorr(msg)\n",
185194
"\n",
186-
"# Solve: x` = x - t' + t\n",
187-
"x_int = x.as_int()\n",
188-
"x2_int = (x_int - t2 + t) % SECP256K1_ORDER\n",
195+
"# Given P, Q and the contract, Bob believes he can verify the following:\n",
189196
"\n",
190-
"x2_key = x = ECKey().set(x2_int, True)\n",
191-
"P2 = x2_key.get_pubkey()\n",
192-
"Q2 = P2.tweak_add(t2)\n",
197+
"# 1) The contract 'Alice agrees to pay 10 BTC to Bob' is committed to public key Q\n",
198+
"print(\"'Alice agrees to pay 10 BTC to Bob' appears to be committed to Q: {}\".format(\\\n",
199+
" P_key.tweak_add(sha256(contract.encode('utf-8'))) == Q_key))\n",
193200
"\n",
194-
"print(\"Tweaked pubkey for x tweaked by t: {}\".format(Q.get_bytes().hex()))\n",
195-
"print(\"Tweaked pubkey for x2 tweaked by t2: {}\".format(Q2.get_bytes().hex()))"
201+
"# 2) Alice is the owner of the public key Q = P + t*G\n",
202+
"print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig, msg)))"
196203
]
197204
},
198205
{
199206
"cell_type": "markdown",
200207
"metadata": {},
201208
"source": [
202-
"#### Example 2.2.4 - Tweaking the pubkey with `H(P|msg)`\n",
209+
"#### Example 2.2.4: Modifying the commitment tweak of public key Q\n",
203210
"\n",
204-
"In this example, we demonstrate a _secure_ commitment scheme. 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'`."
211+
"However, note that is possible for Alice to modify this insecure commitment without changing the value of pub key `Q`.\n",
212+
"* The committed contract is changed to : `Alice pays 0.1 BTC to Bob`"
205213
]
206214
},
207215
{
@@ -210,48 +218,125 @@
210218
"metadata": {},
211219
"outputs": [],
212220
"source": [
213-
"# Key pair generation\n",
214-
"privkey1, pubkey1 = generate_key_pair()\n",
215-
"print(\"Private key: {}\\nPublic key: {}\\n\".format(x.secret, pubkey.get_bytes().hex()))\n",
221+
"# Alice modifies the contract and produces an alternative tweak\n",
222+
"alternative_contract = \"Alice agrees to pay 0.1 BTC to Bob\"\n",
223+
"t2 = sha256(alternative_contract.encode('utf-8'))\n",
224+
"print(\"Tweak from original contract: {}\".format(t.hex()))\n",
225+
"print(\"Tweak from modified contract: {}\\n\".format(t2.hex()))\n",
226+
"\n",
227+
"# Alice modifies her original secret and public key\n",
228+
"# x` = x - t' + t\n",
229+
"x_int = x_key.as_int()\n",
230+
"t_int = int.from_bytes(t, \"big\") \n",
231+
"t2_int = int.from_bytes(t2, \"big\") \n",
232+
"x2_int = (x_int - t2_int + t_int) % SECP256K1_ORDER\n",
233+
"x2_key = ECKey().set(x2_int, True)\n",
234+
"P2_key = x2_key.get_pubkey()\n",
235+
"\n",
236+
"# The resulting tweaked public Q key remains the same\n",
237+
"Q2_key = P2_key.tweak_add(t2)\n",
238+
"print(\"Tweaked public key from original tweak t: {}\".format(Q_key.get_bytes().hex()))\n",
239+
"print(\"Tweaked public key from modified tweak t2: {}\\n\".format(Q2_key.get_bytes().hex()))\n",
240+
"\n",
241+
"# So Alice can still produce a valid signature for Q\n",
242+
"msg2 = sha256(b'I agree to the committed contract')\n",
243+
"sig2 = q_key.sign_schnorr(msg2)\n",
244+
"\n",
245+
"# Given P2, Q and the modified contract, \n",
246+
"# Alice can demonstrate the following to invalidate the original contract:\n",
247+
"\n",
248+
"# 1) The modified contract 'Alice agrees to pay 0.1 BTC to Bob' appears to be committed to public key Q\n",
249+
"print(\"'Alice agrees to pay 0.1 BTC to Bob' appears to be committed to Q: {}\"\\\n",
250+
" .format(P2_key.tweak_add(sha256(alternative_contract.encode('utf-8'))) == Q_key))\n",
251+
"\n",
252+
"# 2) Alice is still the owner of the public key Q = P2 + t2*G\n",
253+
"print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig2, msg)))"
254+
]
255+
},
256+
{
257+
"cell_type": "markdown",
258+
"metadata": {},
259+
"source": [
260+
"#### Summary of 2.2.3, 2.2.4: Insecure practice of tweaking a public key with commitment data\n",
261+
"\n",
262+
"We have demonstrated how a simple key tweak with commitment data does not work as a commitment scheme.\n",
263+
"* Tweaking the original public key `P` with commitment data hides the commitment.\n",
264+
"* However, the original public key `P` can be recomputed (`P2`) for any modified commitment, without altering the tweaked public key `Q`.\n",
265+
"\n",
266+
"To any observer, both original and modified \"commitments\" appear to be valid for the same public key `Q`."
267+
]
268+
},
269+
{
270+
"cell_type": "markdown",
271+
"metadata": {},
272+
"source": [
273+
"#### Example 2.2.5 - Pay-to-contract: Tweaking the pubkey with `H(P|msg)`\n",
274+
"\n",
275+
"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",
216276
"\n",
217-
"# Compute the tweak from H(P|msg)\n",
218-
"commitment = b'commitment'\n",
219-
"ss = sha256(pubkey.get_bytes())\n",
220-
"ss += sha256(commitment)\n",
277+
"* Alice can now no longer invalidate her previous contract commitment with Bob."
278+
]
279+
},
280+
{
281+
"cell_type": "code",
282+
"execution_count": null,
283+
"metadata": {},
284+
"outputs": [],
285+
"source": [
286+
"# Alice generates a key pair\n",
287+
"x_key, P_key = generate_key_pair()\n",
288+
"print(\"Private key: {}\\nPublic key: {}\\n\".format(x_key.secret, P_key.get_bytes().hex()))\n",
289+
"\n",
290+
"# Alice computes the tweak from H(P|msg)\n",
291+
"contract = \"Alice agrees to pay 10 BTC to Bob\"\n",
292+
"ss = P_key.get_bytes()\n",
293+
"ss += sha256(contract.encode('utf-8'))\n",
221294
"t = sha256(ss)\n",
222295
"\n",
223-
"# Determine tweak point\n",
224-
"tweak = ECKey().set(t, True)\n",
225-
"tweak_point = tweak.get_pubkey()\n",
226-
"print(\"Tweak scalar: {}\\nTweak point: {}\\n\".format(tweak.secret, tweak_point.get_bytes().hex()))\n",
296+
"# Alice tweaks her key pair\n",
297+
"t_key = ECKey().set(t, True)\n",
298+
"T_key = t_key.get_pubkey()\n",
299+
"q_key = x_key + t_key\n",
300+
"Q_key = P_key + T_key\n",
301+
"print(\"Tweak scalar: {}\\nTweak point: {}\\n\".format(t_key.secret, T_key.get_bytes().hex()))\n",
227302
"\n",
228-
"privkey_tweaked = privkey + tweak\n",
229-
"pubkey_tweaked = pubkey + tweak_point\n",
303+
"# Alice signs a valid message\n",
304+
"msg = sha256(b'I agree to the committed contract')\n",
305+
"sig = q_key.sign_schnorr(msg)\n",
230306
"\n",
231-
"# Sign message and verify signature\n",
232-
"msg = sha256(b'msg')\n",
233-
"sig = privkey_tweaked.sign_schnorr(msg)\n",
307+
"# Given pubkey1, pubkey_tweaked and the contract, Bob can verify the following:\n",
234308
"\n",
235-
"assert pubkey_tweaked.verify_schnorr(sig, msg)\n",
236-
"print(\"Success!\")"
309+
"# 1) The contract 'Alice agrees to pay 10 BTC to Bob' is committed to public key Q\n",
310+
"print(\"'Alice agrees to pay 10 BTC to Bob' is committed to Q: {}\"\\\n",
311+
" .format(P_key.tweak_add(sha256(P_key.get_bytes() + sha256(contract.encode('utf-8')))) == Q_key))\n",
312+
"\n",
313+
"# 2) Alice is the owner of the public key P + t*G = Q\n",
314+
"print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig, msg)))"
315+
]
316+
},
317+
{
318+
"cell_type": "markdown",
319+
"metadata": {},
320+
"source": [
321+
"**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."
237322
]
238323
},
239324
{
240325
"cell_type": "markdown",
241326
"metadata": {},
242327
"source": [
243-
"## Spending a taproot output along the key path\n",
328+
"## Part 2: Spending a taproot output along the key path\n",
244329
"\n",
245-
"In this exercise, we'll create a segwit version 1 output that sends to a tweaked public key. We'll them spend that output along the key path using the tweaked private key.\n",
330+
"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",
246331
"\n",
247-
"Such as spend does not reveal the committed tweak to the observer and is indistinguishable any other key path spend."
332+
"Such as spend does not reveal the committed tweak to the observer and is indistinguishable from any other key path spend."
248333
]
249334
},
250335
{
251336
"cell_type": "markdown",
252337
"metadata": {},
253338
"source": [
254-
"#### _Programming Exercise 2.2.5:_ Construct taproot output with tweaked public key"
339+
"#### _Programming Exercise 2.2.6:_ Construct taproot output with tweaked public key"
255340
]
256341
},
257342
{
@@ -284,7 +369,7 @@
284369
"cell_type": "markdown",
285370
"metadata": {},
286371
"source": [
287-
"#### Example 2.2.6: Start Bitcoin Core node and send coins to the taproot address\n",
372+
"#### Example 2.2.7: Start Bitcoin Core node and send coins to the taproot address\n",
288373
"\n",
289374
"Only run setup once, or after a clean shutdown."
290375
]
@@ -309,7 +394,7 @@
309394
"cell_type": "markdown",
310395
"metadata": {},
311396
"source": [
312-
"#### Example 2.2.7: Construct `CTransaction` and populate inputs\n",
397+
"#### Example 2.2.8: Construct `CTransaction` and populate inputs\n",
313398
"\n",
314399
"We use the `create_spending_transaction(node, txid)` convenience function."
315400
]
@@ -329,7 +414,7 @@
329414
"cell_type": "markdown",
330415
"metadata": {},
331416
"source": [
332-
"#### _Programming Exercise 2.2.8:_ Spend taproot output with key path"
417+
"#### _Programming Exercise 2.2.9:_ Spend taproot output with key path"
333418
]
334419
},
335420
{

solutions/2.2-taptweak-solutions.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"cell_type": "markdown",
6565
"metadata": {},
6666
"source": [
67-
"#### _Programming Exercise 2.2.5:_ Construct taproot output with tweaked public key"
67+
"#### _Programming Exercise 2.2.6:_ Construct taproot output with tweaked public key"
6868
]
6969
},
7070
{
@@ -98,7 +98,7 @@
9898
"cell_type": "markdown",
9999
"metadata": {},
100100
"source": [
101-
"#### _Programming Exercise 2.2.8:_ Spend taproot output with key path"
101+
"#### _Programming Exercise 2.2.9:_ Spend taproot output with key path"
102102
]
103103
},
104104
{

0 commit comments

Comments
 (0)