Develop with pleasure!

福岡でCloudとかBlockchainとか。

Schnorr署名とOP_CATを使ったCovenants

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署名は以下の手順で生成される。

  1. 一時鍵R = kGを生成
  2. s = k + e * xを計算
  3. (R.x, s)が署名データ

ここでeは、公開鍵PとRおよびトランザクションデータ(署名対象のメッセージ)から生成されるハッシュ値

署名データは、スタック上にプッシュされるデータであるため、この署名データからeを計算することができれば、スクリプトトランザクションデータを間接的に参照することができる。そして↑の計算式と、トリックを使うとeの値を計算することができる。

検証するユーザーはkの値は知らない(kの値が分かると秘密鍵xもバレる)ので、単純に考えるとsからeを計算することはできないけど、例えば、kxの値が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`

このスクリプトは、次のように実行される。()内は実行後のスタックの状態

  1. スタックにsとGをプッシュする(<s> <G>
  2. OP_2DUPはスタック上の2つの要素を複製する(<s> <G> <s> <G>
  3. OP_SWAPは、上位2つの要素を入れ替える(<s> <G> <G> <s>
  4. OP_CATは、上位2つのデータを連結する(<s> <G> <G><s>
    この<G><s>は↑のSchnorr署名の(R.x s)となる。
  5. OP_SWAPは、上位2つの要素を入れ替える(<s> <G><s> <G>
  6. OP_CHECKSIGは、署名検証を行う。この場合、公開鍵Gに対して署名値<Gs>を検証する。
  7. 最終的に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で行うとかかな。