taprootのアドレス生成の流れ
内部鍵とマークルツリーがあるので少しだけ生成の流れが変わる
必ずしもそうではないが、通常の送金は内部鍵(従来:P2WPKH)、複雑な条件はマークルツリーを使う(従来:P2WSH)
片方だけしか使わない場合でも内部鍵とマークルツリーに値を入れておく必要がある。
※ 使わない予定なのに値を入れておくのはデータ容量が増えて無駄になるかと思ったが、内部鍵とマークルツリーは楕円曲線上の点であり、加算が可能なので複数あってもデータ容量が変わることはなかった。
この記事の最初の流れを参考にbitcoinrbでのアドレス生成の流れを見ていく
code:送金先アドレスを作成
require 'bitcoin'
include Bitcoin::Opcodes
# testnetを使用
Bitcoin.chain_params = :testnet
# 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.
script_pubkey = builder.build
script_pubkey.to_addr
=> 'tb1p9uv58mst47h0r9zd8lm9hjlttcskq4wndxfceh8mjknd92mmflzspnsygf'
最初の方は内部鍵の生成とマークルツリーのリーフの作成をしているだけなのでスキップ
まずはbuilderでの流れを見ていく
code:builder
script_pubkey = builder.build
# tweak_public_key(公開鍵)を作成し、OP_1 と tweak_public_key のx軸だけをスクリプトに含める
def build
q = tweak_public_key
Bitcoin::Script.new << OP_1 << q.xonly_pubkey
end
# tweak_public_key は内部鍵とマークルツリーから作られる
# まずは引数について
## Bitcoin::Key.from_xonly_pubkey(internal_key) は 02 + internal_key.xonly_pubkey の値になる
def tweak_public_key
Taproot.tweak_public_key(Bitcoin::Key.from_xonly_pubkey(internal_key), merkle_root)
end
## merkle_root は @branches = leaves.each_slice(2).map.to_a リーフを元に二分木のマークルツリーを作成。この部分はよく使うはずのスクリプトをルートから近い位置におくなど工夫できるが、今回は左から順に自動で作成。
def merkle_root
parents = branches.map {|pair| combine_hash(pair)}
if parents.empty?
elsif parents.size == 1
else
parents = parents.each_slice(2).map { |pair| combine_hash(pair) } until parents.size == 1
end
parents.first.bth
end
# Taproot.tweak_public_key の部分を見ていく
# (ルートハッシュとInternal Keyからtweakを生成し、それを秘密鍵として、スクリプトツリーの公開鍵を計算する。)
# Internal Keyの公開鍵と2の公開鍵を加算してP2TRの公開鍵を計算する。
# 計算した公開鍵からP2TRのscriptPubkeyを生成する。
def tweak_public_key(internal_key, merkle_root)
t = tweak(internal_key, merkle_root)
key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES:compressed) Bitcoin::Key.from_point(key.to_point + internal_key.to_point)
end
# t = tweak(internal_key, merkle_root) のメソッドをみる。
# 引数の internal_key が鍵のオブジェクトかを確認
# マークルツリーはなければ空白を代入
# 内部鍵とマークルツリーをバイナリーに変換し足す。
def tweak(internal_key, merkle_root)
raise Error, 'internal_key must be Bitcoin::Key object.' unless internal_key.is_a?(Bitcoin::Key)
merkle_root ||= ''
t = Bitcoin.tagged_hash('TapTweak', internal_key.xonly_pubkey.htb + merkle_root.htb)
raise Error, 'tweak value exceeds the curve order' if t.bti >= ECDSA::Group::Secp256k1.order
t
end
def tagged_hash(tag, msg)
tag_hash = Digest::SHA256.digest(tag)
Digest::SHA256.digest(tag_hash + tag_hash + msg)
end
# tweak_public_key が求められ Bitcoin::Script.new << OP_1 << tweak_public_key.xonly_pubkey が生成される。