Skip to content

Taproot

shigeyuki azuchi edited this page Nov 16, 2021 · 9 revisions

bitcoinrb supports following taproot related features.

Build P2TR output

Bitcoin::Taproot::SimpleBuilder makes it easy to build P2TR output.

Taproot's scriptPubkey consists of the following items.

Q = P + H(P||S)*G

where P is the internal public key and S is the tweak calculated from the markle root of the script tree.

And scriptPubkey is:

OP_1 <Q>

For example, a PT2R with three public keys and three unlock conditions that require the signature of each public key can be constructed as follows:

require 'bitcoin'

include Bitcoin::Opcodes

# Create internal key
internal_key = Bitcoin::Key.new(priv_key: '98d2f0b8dfcaa7b29933bc78e8d82cd9d7c7a18ddc128ce2bc9dd143804f36f4')

# Create three locking scripts
key1 = Bitcoin::Key.new(priv_key: 'fd0137b05e26f40f8900697b690e11b2eba8abbd0f53c421148a22646b15f96f')
key2 = Bitcoin::Key.new(priv_key: '3b0ce9ef75031f5a1d6679f017fdd8d77460ecdcac1a24d482e1465e1768e22c')
key3 = Bitcoin::Key.new(priv_key: 'df94bce0533b3ff0c6b8ca16d6d2ce08b01350792cb350146cfaba056d5e4bfa')
leaf1 = Bitcoin::Taproot::LeafNode.new(Bitcoin::Script.new << key1.xonly_pubkey << OP_CHECKSIG)
leaf2 = Bitcoin::Taproot::LeafNode.new(Bitcoin::Script.new << key2.xonly_pubkey << OP_CHECKSIG)
leaf3 = Bitcoin::Taproot::LeafNode.new(Bitcoin::Script.new << key3.xonly_pubkey << OP_CHECKSIG)

# Build P2TR using internal public key and three locking scripts.
builder = Bitcoin::Taproot::SimpleBuilder.new(internal_key.xonly_pubkey, [leaf1, leaf2, leaf3])
script_pubkey = builder.build
script_pubkey.to_addr
=> 'tb1p9uv58mst47h0r9zd8lm9hjlttcskq4wndxfceh8mjknd92mmflzspnsygf'

In this case, the script tree would be constructed as follows:

      N0
   /     \
  N1      C
 /  \
A    B

Spends P2TR output

Let's try to use the P2TR UTXO that we created in the previous section. P2TR UTXO can be unlocked by either key-path or script-path.

key-path spending

In key-path, you can use internal key to unlock. In this case, as witness, we provide a valid Schnorr signature for the public key in the scriptPubkey of P2TR, and SimpleBuilder supports derivation of the private key corresponding to this public key.

# Derive private key to use sign
key = builder.tweak_private_key(internal_key)

Then, you can use this private key to sign the Tx.

# Create Tx
tx = Bitcoin::Tx.new
tx.in << Bitcoin::TxIn.new(out_point: Bitcoin::OutPoint.from_txid('9b5dbbe79a8938b9527b0a5f12c9be695ca1dac4e4267529a228c380c0b232bd', 1))
tx.out << Bitcoin::TxOut.new(value: 90_000, script_pubkey: script_pubkey)

# Calculate sighash
prevouts = [Bitcoin::TxOut.new(value: 100_000, script_pubkey: script_pubkey)]
sighash = tx.sighash_for_input(0, sig_version: :taproot, prevouts: prevouts, hash_type: Bitcoin::SIGHASH_TYPE[:default])

# Calculate schnorr signature
sig = key.sign(sighash, algo: :schnorr)

# Set signature to input(If hash_type is not default, hash_type must also be given at the end).
tx.in[0].script_witness.stack << sig

# Output tx payload.
tx.to_hex

Generate tweaked key

If you need to generate tweaked public key and private key from internal key and merkle root, you can use following methods:

merkle_root = '5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21'

internal_private_key = Bitcoin::Key.new(priv_key: 'your private key')

# Generate tweaked private key
tweaked_private_key = Bitcoin::Taproot.tweak_private_key(internal_private_key, merkle_root)

internal_public_key = Bitcoin::Key.new(pubkey: 'your public key')

# Generate tweaked public key
tweaked_public_key = Bitcoin::Taproot.tweak_public_key(internal_public_key, merkle_root)

# Calculate tweak value itself
tweak = Bitcoin::Taproot.tweak(internal_public_key, merkle_root)

script-path spending

When unlocking using script-path, provide as witness the script to be used for unlocking, the internal public key, and a proof that the script is included in the tree. In addition to that, the elements needed to unlock the script.

The proof is composed of the parity bit that represents the even/odd Y-coordinate of the P2TR public key, the internal public key, and the leaf version, which together form the Control Block, which is set to witness.

In this case, we will use leaf2 from above to unlock it. To create a transaction that unlocks using script-path:

# Create Tx
tx = Bitcoin::Tx.new
tx.in << Bitcoin::TxIn.new(out_point: Bitcoin::OutPoint.from_txid('3cad3075b2cd448fdae11a9d3bb60d9b71acf6a279df7933dd6c966f29e0469d', 1))
tx.out << Bitcoin::TxOut.new(value: 90_000, script_pubkey: script_pubkey)

# Calculate sighash
prevouts = [Bitcoin::TxOut.new(value: 100_000, script_pubkey: script_pubkey)]
opts = {leaf_hash: leaf2.leaf_hash} # script pathではleaf hashにもコミットするためオプションで渡す
sighash = tx.sighash_for_input(0, sig_version: :tapscript, prevouts: prevouts, hash_type: Bitcoin::SIGHASH_TYPE[:default], opts: opts)

# Calculate schnorr signature
sig = key2.sign(sighash, algo: :schnorr)

# Set items to need unlock to witness
## Set leaf2 unlock item(signature)
tx.in[0].script_witness.stack << sig
## Set leaf2
tx.in[0].script_witness.stack << leaf2.script.to_payload
## Set control block that prove leaf2 is included in the tree.
tx.in[0].script_witness.stack << builder.control_block(leaf2)

# Output tx payload.
tx.to_hex

SimpleBuilder generates a Control Block that contains a proof to provide the leaf hash of the script to be used in the #leaf_hash method and to prove that the script is included in the script tree in the #control_block method. This Control Block is a concatenation of the following data:

<parity bit representing even/odd Y-coordinate of P2TR public key + leaf version of script> + <Internal public key> + <Proof (hash of sibling nodes from the leaf node to the root of the tree)>

Run P2TR script using the script interpreter.

Bitcoin::ScriptInterpreter currently supports P2TR script execution.

# Create tx checker where prevouts is the set of `Bitcoin::TxOut` objects that the Tx inputs refer to.
checker = Bitcoin::TxChecker.new(tx: tx, input_index: index, prevouts: prevouts)

# Initialize interpreter.
i = Bitcoin::ScriptInterpreter.new(checker: checker)

# Run interpreter
i.verify_script(tx.in[index].script_sig, prevouts[index].script_pubkey, tx.in[index].script_witness)
=> true

If you simply want to verify the signature of Tx, you can do so using Tx#verify_input_sig.

tx.verify_input_sig(index, prevouts[index].script_pubkey, amount: prevouts[index].value, prevouts: prevouts)
=> true

If you are evaluating Taproo-related scripts in versions earlier than v1.0.0, you need to use the following flags:

flags = STANDARD_SCRIPT_VERIFY_FLAGS | 
  SCRIPT_VERIFY_TAPROOT |
  SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION |
  SCRIPT_VERIFY_DISCOURAGE_UNKNOWN_ANNEX | 
  SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS |
  SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE

Extension of existing classes

The existing classes have been extended with the following Taproot-related features.

Bitcoin::Key

  • Add x_only_pubkey method to return the x-corodinate only public key (hex format).
  • Add from_xonly_pubkey class method to generate a Bitcoin::Key object from x-only public key.
  • sign method supports the :algo keyword argument to specify the signature algorithm, and generates a Schnoor signature with :shcnorr.

Bitcoin::Script

  • Add pt2r? method to check whether the script is a P2TR scriptPubkey or not.
  • to_addr method now returns Bech32m address if the script is P2TR.

Bitcoin::Tx

  • sighash_for_input and verify_input_sig now support P2TR output.