-
Notifications
You must be signed in to change notification settings - Fork 19
Taproot
bitcoinrb
supports following taproot related features.
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
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.
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
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)
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)>
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
The existing classes have been extended with the following Taproot-related features.
- Add
x_only_pubkey
method to return the x-corodinate only public key (hex format). - Add
from_xonly_pubkey
class method to generate aBitcoin::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
.
- 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.
-
sighash_for_input
andverify_input_sig
now support P2TR output.