DEV Community

Oghenovo Usiwoma
Oghenovo Usiwoma

Posted on

More on Taproot

In a previous article, I gave a brief intro Taproot and shared some examples utilizing Taproot addresses.
In this article, I intend to share more information about taproot addresses and share one more example which I neglected in my previous article. I'll share:

  • Public Key Tweaking, what it is, why we do it and how we do it
  • How Taproot addresses are created
  • How to generate the private key required to sign a spending transaction from a Taproot address.
  • How to spend a Taproot output that has a script-tree without using any leaf scripts with bitcoinjs

Public Key Tweaking

What is Public key Tweaking

According to a workshop on taproot by Bitcoin Optech, you can tweak a public key by adding a value (the tweak) to it. This way, you can still spend the tweaked public key if you know the original private key and the tweak. It is used in taproot to commit data or scripts.

Why do we tweak keys

We tweak keys in taproot to commit to data or scripts that can be used for different spending conditions. This way, we can make complex transactions look like simple ones and save space and fees.

Why didn't we tweak before Taproot

Before taproot, we used a different type of digital signatures called ECDSA. ECDSA did not allow for easy tweaking. Taproot uses a new type of signatures called Schnorr that enable tweaking and other benefits.

How does a tweak work under the hood

A tweak is positive scalar value t where:

0 < t < SECP256K1_ORDER

In bitcoin cryptography, we use the elliptic curve secp256k1 and SECP256K1_ORDER is the number of points on the curve

A point, the Tweak point, is obtained from this tweak scalar value by multiplying it by the generator point:

T = t * G

The generator point is a special point that can produce any other point in its subgroup by multiplying it by some integer

This tweak point is added to the x-coordinate of the original public key to get the tweaked public key. Prepend a parity bit to this tweaked public key to indicate if it is positive or negative.

In taproot, we get the tweak value by concatenating the x-coordinate of the public key and a commitment to the script path to spend and hashing it. To spend a taproot output, you can sign with the tweaked private key (private key + tweak value) or provide a proof of the script path spend.

Creating a Taproot address

Taproot allows you to create addresses that can be spent by a single public key or by scripts. It does this by allowing you to add data to a public key by tweaking it.

To create a P2TR (Pay-to-Taproot) address for a single public key, you need to:

  1. Check that the y-coordinate of your public key is even, if not, negate the public key. Taproot requires that the y coordinate of the public key is even.
  2. extract the x-coordinate (bytes 1 to 33) of your public key (which is a point on an Elliptic Curve)
  3. tweak the public key with this x-only part of the public key.
  4. hash the x-coordinate of the tweaked public key with SHA256 and RIPEMD to get a 20-byte address.
  5. Prepend the version byte 0x51(indicates that the taproot address is a native SegWit output with version 1) to the address to get a 21-byte witness program.
  6. Encode the witness program with Bech32m encoding.

To create an address with multiple spending conditions (in the form of scripts), we arrange our scripts into a Merkle Tree, where each node is a hash of its children. The root of this tree is then used to tweak a public key and an address can be generated from this tweaked public key in the same steps (from step 4) defined above.

Even if you don't need multiple spending conditions for your address, BIP341 requires that you tweak the original public key with an unspendable script path which is what we do in step 3

Tweaking private keys for signing

To sign a transaction input spending a Taproot output, you need to tweak the private key for the original public key using the same tweak value that generated the address. If the public key was negated (see step 1 above) you must also negate the private key. The tweaked private key will create a valid Schnorr signature for the tweaked public key. This works because of the mathematical properties of Schnorr signatures and elliptic curves.

Spending a Taproot output with a script tree via the key path using bitcoinjs

In the Taptree example from my previous article. It is possible to spend the Taproot output without using the script tree.

// ...

const script_p2tr = payments.p2tr({
    internalPubkey: toXOnly(keypair.publicKey),
    scriptTree,
    network
});

// ...

const key_spend_psbt = new Psbt({ network });
key_spend_psbt.addInput({
    hash: utxos[0].txid,
    index: utxos[0].vout,
    witnessUtxo: { value: utxos[0].value, script: script_p2tr.output! },
    tapInternalKey: toXOnly(keypair.publicKey),
    tapMerkleRoot: script_p2tr.hash
});
key_spend_psbt.addOutput({
    address: "mohjSavDdQYHRYXcS3uS6ttaHP8amyvX78", // faucet address
    value: utxos[0].value - 150
});
// We need to create a signer tweaked by script tree's merkle root
const tweakedSigner = tweakSigner(keypair, { tweakHash: script_p2tr.hash });
key_spend_psbt.signInput(0, tweakedSigner);
key_spend_psbt.finalizeAllInputs();

tx = key_spend_psbt.extractTransaction();
console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`);
txid = await broadcast(tx.toHex());
console.log(`Success! Txid is ${txid}`);
Enter fullscreen mode Exit fullscreen mode

Thank you for reading!

Top comments (3)

Collapse
 
ildarmgt profile image
ILDAR MUSIN • Edited

wasn't tweaking supposed to prevent one from spending without following the script tree? "nothing up my sleeves" concept? is this possible because in original example pubkey leaf was signed using non-tweaked original key? How to fix it to only be spendable by script like you would want in many multiparty constructions? Thanks!

found my answer later, standard seem to be to use unspendable internal public key as recommended in BIP which is SHA256(uncompressedDER(GENERATOR_POINT)) or 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0

Collapse
 
andredevjs profile image
nosfter • Edited

If you created the address using

const p2pktr = payments.p2tr({
  pubkey: toXOnly(tweakedSigner.publicKey),
  network
});
const p2pktr_addr = p2pktr.address ?? "";
Enter fullscreen mode Exit fullscreen mode

That means you can decode that address to make it kind of a pubkey

Buffer.from(bech32m.fromWords(bech32m.decode(p2pktr_addr).words))
Enter fullscreen mode Exit fullscreen mode

Question is, how do you prove that the p2pktr_addr was created using the keypair you used to generate the tweakedSigner?
Is it decoding the address an actual pub? Cause it can't be used to verify a schnorr signature

Collapse
 
cftpra profile image
cftpra

Hi, thanks for your article, I have a doubt on this:

**_To create a P2TR (Pay-to-Taproot) address for a single public key, you need to:

Check that the y-coordinate of your public key is even, if not, negate the public key. Taproot requires that the y coordinate of the public key is even.
_**

What is the meaning of that? I used a x-only value where y-coordinate was odd and it seem to work, can you explain that better?