Skip to content

Commit b4a701c

Browse files
committed
Contract commitment unsealing added to 2.2.3, 2.2.4.
1 parent 4223634 commit b4a701c

1 file changed

Lines changed: 114 additions & 44 deletions

File tree

2.2-taptweak.ipynb

Lines changed: 114 additions & 44 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: Modifying the tweak for a tweaked public key Q\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,40 @@
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 pays 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 pays 10 BTC to Bob' is committed to public key Q\n",
198+
"print(\"'Alice pays 10 BTC to Bob' appears to be committed to Q: {}\".format(\\\n",
199+
" P_key.tweak_add(sha256(\"Alice pays 10 BTC to Bob\".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",
203-
"\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'`."
209+
"However, note that is possible for Alice to modify this insecure commitment without changing the value of pub key Q.\n",
210+
"* The committed contract is changed to : `Alice pays 0.1 BTC to Bob`"
205211
]
206212
},
207213
{
@@ -210,37 +216,101 @@
210216
"metadata": {},
211217
"outputs": [],
212218
"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",
219+
"# Alice modifies the contract and produces an alternative tweak\n",
220+
"alternative_contract = \"Alice pays 0.1 BTC to Bob\"\n",
221+
"t2 = sha256(alternative_contract.encode('utf-8'))\n",
222+
"print(\"Tweak from original contract: {}\".format(t.hex()))\n",
223+
"print(\"Tweak from modified contract: {}\\n\".format(t2.hex()))\n",
224+
"\n",
225+
"# Alice modifies her original secret and public key\n",
226+
"# x` = x - t' + t\n",
227+
"x_int = x_key.as_int()\n",
228+
"t_int = int.from_bytes(t, \"big\") \n",
229+
"t2_int = int.from_bytes(t2, \"big\") \n",
230+
"x2_int = (x_int - t2_int + t_int) % SECP256K1_ORDER\n",
231+
"x2_key = ECKey().set(x2_int, True)\n",
232+
"P2_key = x2_key.get_pubkey()\n",
233+
"\n",
234+
"# The resulting tweaked public Q key remains the same\n",
235+
"Q2_key = P2_key.tweak_add(t2)\n",
236+
"print(\"Tweaked public key from original tweak t: {}\".format(Q_key.get_bytes().hex()))\n",
237+
"print(\"Tweaked public key from modified tweak t2: {}\\n\".format(Q2_key.get_bytes().hex()))\n",
238+
"\n",
239+
"# So Alice can still produce a valid signature for Q\n",
240+
"msg2 = sha256(b'I agree to the committed contract')\n",
241+
"sig2 = q_key.sign_schnorr(msg2)\n",
242+
"\n",
243+
"# Given P2, Q and the modified contract, \n",
244+
"# Alice can demonstrate the following to invalidate the original contract:\n",
245+
"\n",
246+
"# 1) The modified contract 'Alice pays 0.1 BTC to Bob' is appears to be committed to public key Q\n",
247+
"print(\"'Alice pays 0.1 BTC to Bob' appears to be committed to Q: {}\"\\\n",
248+
" .format(P2_key.tweak_add(sha256(\"Alice pays 0.1 BTC to Bob\".encode('utf-8'))) == Q_key))\n",
249+
"\n",
250+
"# 2) Alice is still the owner of the public key Q = P2 + t2*G\n",
251+
"print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig2, msg)))"
252+
]
253+
},
254+
{
255+
"cell_type": "markdown",
256+
"metadata": {},
257+
"source": [
258+
"#### Example 2.2.4 - Pay-to-contract: Tweaking the pubkey with `H(P|msg)`\n",
216259
"\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",
260+
"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",
261+
"\n",
262+
"* Alice can now no longer invalidate her previous contract commitment with Bob."
263+
]
264+
},
265+
{
266+
"cell_type": "code",
267+
"execution_count": null,
268+
"metadata": {},
269+
"outputs": [],
270+
"source": [
271+
"# Alice generates a key pair\n",
272+
"x_key, P_key = generate_key_pair()\n",
273+
"print(\"Private key: {}\\nPublic key: {}\\n\".format(x_key.secret, P_key.get_bytes().hex()))\n",
274+
"\n",
275+
"# Alice compute the tweak from H(P|msg)\n",
276+
"contract_bytes = \"Alice pays 10 BTC to Bob\".encode('utf-8')\n",
277+
"ss = P_key.get_bytes()\n",
278+
"ss += sha256(contract_bytes)\n",
221279
"t = sha256(ss)\n",
222280
"\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",
281+
"# Alice tweaks her key pair\n",
282+
"t_key = ECKey().set(t, True)\n",
283+
"T_key = t_key.get_pubkey()\n",
284+
"q_key = x_key + t_key\n",
285+
"Q_key = P_key + T_key\n",
286+
"print(\"Tweak scalar: {}\\nTweak point: {}\\n\".format(t_key.secret, T_key.get_bytes().hex()))\n",
227287
"\n",
228-
"privkey_tweaked = privkey + tweak\n",
229-
"pubkey_tweaked = pubkey + tweak_point\n",
288+
"# Alice signs a valid message\n",
289+
"msg = sha256(b'I agree to the committed contract')\n",
290+
"sig = q_key.sign_schnorr(msg)\n",
230291
"\n",
231-
"# Sign message and verify signature\n",
232-
"msg = sha256(b'msg')\n",
233-
"sig = privkey_tweaked.sign_schnorr(msg)\n",
292+
"# Given pubkey1, pubkey_tweaked and the contract, Bob can verify the following:\n",
234293
"\n",
235-
"assert pubkey_tweaked.verify_schnorr(sig, msg)\n",
236-
"print(\"Success!\")"
294+
"# 1) The contract 'Alice pays 10 BTC to Bob' is committed to public key Q\n",
295+
"print(\"'Alice pays 10 BTC to Bob' is committed to Q: {}\"\\\n",
296+
" .format(P_key.tweak_add(sha256(P_key.get_bytes() + sha256(\"Alice pays 10 BTC to Bob\".encode('utf-8')))) == Q_key))\n",
297+
"\n",
298+
"# 2) Alice is the owner of the public key P + t*G = Q\n",
299+
"print(\"Alice has produced a valid signature for Q: {}\".format(Q_key.verify_schnorr(sig, msg)))"
300+
]
301+
},
302+
{
303+
"cell_type": "markdown",
304+
"metadata": {},
305+
"source": [
306+
"**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."
237307
]
238308
},
239309
{
240310
"cell_type": "markdown",
241311
"metadata": {},
242312
"source": [
243-
"## Spending a taproot output along the key path\n",
313+
"## Part 2) Spending a taproot output along the key path\n",
244314
"\n",
245315
"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",
246316
"\n",

0 commit comments

Comments
 (0)