Secure Commitment

Let's examine the cause of our insecure commitment first.

We have:

Q=P+tGQ = P + t \cdot G

The root cause is that nothing ties tt to PP.

Alice can pick any contract she wants, and then compute a PP' that makes it fit the same QQ.

Let's fix that.

The fix

One small change: put PP inside the hash.

t = hash(P || contract)Q = P + t·G

Now let's take the same scenario, where Alice wants to swap the contract.

She needs a new key PP' and a new tweak:

t=hash(Pcontract)t' = \text{hash}(P' \, || \, \text{contract}')

For her forgery to work she needs the same QQ, so she needs:

P+tG=P+tGP + t \cdot G = P' + t' \cdot G

She knows PP, tt, and the fake contract'. The only thing missing is PP'. So let's solve for it:

P=P+tGtGP' = P + t \cdot G - t' \cdot G

P=P+hash(Pcontract)Ghash(Pcontract)GP' = P + \text{hash}(P \, || \, \text{contract}) \cdot G - \text{hash}(P' \, || \, \text{contract}') \cdot G

Look at the last line.

PP' appears on both sides of the equation.

To compute PP' she first needs tt'. But tt' is a hash of PP'. So she needs PP' before she can get PP'.

Alice can't pick tt' and solve for PP'.

Her only other option is to guess: try random PP' values until one happens to work. That means breaking the hash function.

The commitment is now binding.

This is what taproot really does

This is exactly the shape taproot uses. The real tweak is

t = tagged_hash("TapTweak", P || merkle_root)

Suggest Edits