Covenantsは、Bitcoinのコインの用途(送り先など)を制限するための仕組み。これまでCovenantsを導入する仕組みの提案がいくつかあったけど、2021年1月にAndrew PoelstraがSchnorr署名とOP_CAT
を利用したCovenantsの構成を発表しているので、その内容について見てみる↓
https://www.wpsoftware.net/andrew/blog/cat-and-schnorr-tricks-i.html
もともと、Elementsで実装されているスタック上のアイテムの署名検証を行うOP_CHECKSIGFROMSTACK
opcodeとOP_CHECKSIG
を組み合わせてCovenantsを構成する方法は以前発表されていたけど、OP_CHECKSIGFROMSTACK
を使わず、Schnorr署名とOP_CAT
で同様のことを行う構成みたい。
Schnorr署名とOP_CAT
OP_CAT
は元々Bitcoinに実装されていて、2010年に削除されたopcode(なので、現在利用はできない)。機能は、スタックから2つの要素をピックアップし、それらを連結して結果をスタックにプッシュするというもの。
基本的にBitcoinのScriptの実行環境では、スクリプト内でトランザクションデータにアクセスできない。ただ、Schnorr署名とOP_CATを利用したトリックを使うと、スクリプト内でトランザクションデータにアクセス(正確にはそのハッシュ)できる。
ある鍵ペア P = xGがあったとして、Schorr署名は以下の手順で生成される。
- 一時鍵R = kGを生成
- s = k + e * xを計算
- (R.x, s)が署名データ
ここでe
は、公開鍵PとRおよびトランザクションデータ(署名対象のメッセージ)から生成されるハッシュ値。
署名データは、スタック上にプッシュされるデータであるため、この署名データからeを計算することができれば、スクリプトでトランザクションデータを間接的に参照することができる。そして↑の計算式と、トリックを使うとeの値を計算することができる。
検証するユーザーはk
の値は知らない(k
の値が分かると秘密鍵x
もバレる)ので、単純に考えるとsからeを計算することはできないけど、例えば、k
とx
の値が1だとするとどうだろう?sの値は
s = 1 + e
になる。この場合k = 1ということはR = Gで、x = 1であればP = Gである(Gは楕円曲線のベースポイント)。そしてsの値はeに1を加算した値になる。
つまり、RとPを↑のように固定して、そのようなPとRに対して署名検証すれば、スタック上でsを使ってトランザクションハッシュにアクセスできるようになる。
そんな検証を適用する具体的なスクリプトが↓
<s> <G> OP_2DUP OP_SWAP OP_CAT OP_SWAP OP_CHECKSIG`
このスクリプトは、次のように実行される。()内は実行後のスタックの状態
- スタックにsとGをプッシュする(
<s> <G>
) OP_2DUP
はスタック上の2つの要素を複製する(<s> <G> <s> <G>
)OP_SWAP
は、上位2つの要素を入れ替える(<s> <G> <G> <s>
)OP_CAT
は、上位2つのデータを連結する(<s> <G> <G><s>
)
この<G><s>
は↑のSchnorr署名の(R.x s)となる。OP_SWAP
は、上位2つの要素を入れ替える(<s> <G><s> <G>
)OP_CHECKSIG
は、署名検証を行う。この場合、公開鍵Gに対して署名値<Gs>
を検証する。- 最終的にsがスタックに残る。
最後のOP_CHECKSIG
の検証により、このスクリプトを実行しているトランザクションがsの元となったデータと等しいことが保証される。これによりスクリプトで指定したデータ(s)通りのトランザクションになることを強制する。
ただ↑のままだと余分な1があるので、これに対処する必要がある。まずeの元になるトランザクションの値を微妙に変えながら(nLocktimeを変更するとか)、eの最下位バイトが01
で終わるハッシュ値を探す。eがそんなハッシュ値になると、sはそれに1を加算した値になるので、sの最下位バイトは02
になる。そして↑のスクリプトでは最下位バイトを省略したs値をプッシュし、<02> OP_CAT
のスクリプトを加えて、スクリプトで最下位バイトを補うようにするみたい。
TXID参照問題とANYPREVOUT
↑のスキームでBitcoinでCovenantsが可能になるように思えるが、実は現状ではワークしない。↑では、Schnorr署名のs値を利用してトランザクションハッシュとして強制することで、トランザクションに制約を加える仕組みになっている。
つまり、scriptPubkeyの一部としてsを提供する必要があるんだけど、そのようなsを計算する場合に、そのUTXOを参照する際のTXIDを含める必要があり、sを作るためにsが必要になる循環参照が発生する。
この問題を回避するための1つの方法は、以前から提案され最近名称が更新されたSIGHASH_ANYPREVOUT
の導入。SIGHASH_ANYPREVOUT
は、署名対象のメッセージ=sighashの計算からインプットが参照するOutPointを除外するというもの。つまり署名がインプットが参照するUTXOのTXIDにコミットしなくなるため、これを利用すると↑の循環参照問題は解決する。
もしくは、最初に書いたElementsでやっているようなトランザクションデータの組み立て自体をScriptで行うとかかな。