Develop with pleasure!

福岡でCloudとかBlockchainとか。

Ordinal InscriptionのDEX「OpenOrdex」でのPSBTの利用

Bitcoin上でNFTを取引可能にするOrdinalのDEX「OpenOrdex」がリリースされてたのでみてみる↓

github.com

Dockerfileが提供されてるので、ビルドして実行するとローカル環境にOrdinalのDEXが起動する↓

Inscriptionの取得

Inscriptionは、Inscription numberを入力することで指定できる。Collectionページにリストされてるコレクションは、↓のリポジトリから取得してるみたい。

github.com

自分のInscriptionをコレクションに追加したい場合は、↑にPRしてねと。

Inscriptionの画像は、直接トランザクションをパースしてる訳ではなく、https://ordinals.com/にアクセスしてるみたい。

PSBTを利用したトラストレスな購入

OpenOrdexのメリットは、自分のローカル環境にDEX機能を起動でき、トラストレスにInscriptionの購入が可能なところで、それにPSBTを利用してる。PSBTは、ウォレット間や複数のユーザー間でトランザクションを完成させるために必要な共通データのフォーマットを定義した仕様↓

Inscriptionの購入フローは以下の通り。

  1. 購入したいInscriptionを選択し、Buy Inscriptionを押す。
  2. 購入者のアドレスを入力する。この時、指定したアドレスにダミーUTXO(=1,000 sat以下のUTXO)がない場合、↓のようにダミーUTXOを作成するPSBTが提示される(指定したアドレスが持つUTXOをインプットとして、1,000 satのダミーUTXOとお釣りのUTXOを持つトランザクション。どうしてこれが必要なのかは後述)。

ダミーUTXOを作成するため、↑のPSBTをファイナライズしてTxをブロードキャストする。lndをウォレットとして使ってる場合は、以下のコマンドでPSBTに対して署名済みのトランザクションを作れる。

$ lncli wallet psbt finalize <↑のPSBT>
{
    "psbt": "<入力したPSBT>",
    "final_tx": "<ファイナライズされたトランザクション>"
}

あとは、ファイナライズされたTxをブロードキャストすればいい。

ダミーUTXOが作成できたら、↑の購入ページをリロードして、再度Buy Inscriptionを押す。すると今度は購入用のPSBTが表示される↓

このPSBTのトランザクションは、以下のような構成になっている(どんなデータか簡単に確認したい場合は、bitcoin-clidecodepsbtコマンド使うといい)。

  • インプット:以下の3つ
    • ↑で作成したダミーUTXO
    • Inscriptionが現在所有されている売り手のUTXO
    • ダミーUTXOと同じアドレスを持つ支払いに使用するUTXO
  • アウトプット:以下の4つ
    • 購入者のInscriptionのUTXO
    • 売り手のアドレスへの支払い
    • 購入者の将来使用可能な新しいダミーUTXO
    • 支払いのお釣り

そして、PSBTのインプットデータとして、各インプットのUTXOが参照するトランザクションデータに加えて、2つめのインプットについては、Inscriptionの売り手の署名データを含む以下のキーのPSBTインプットが提供されている。

  • PSBT_IN_TAP_INTERNAL_KEY:P2TRアウトプットを構成する際に使用したTaprootの内部キー。
  • PSBT_IN_TAP_KEY_SIG:P2TRアウトプットをアンロックするのに必要な署名。
  • PSBT_IN_FINAL_SCRIPTWITNESS:P2TRアウトプットをアンロックするのに必要なすべてのwitnessデータ(今回の場合はPSBT_IN_TAP_KEY_SIGと同じ)。
  • PSBT_IN_SIGHASH_TYPE:インプットに署名する際に使用されるSIGHASHタイプ。

この時、PSBT_IN_SIGHASH_TYPEの値は0x83で、SIGHASH_SINGLE | SIGHASH_ANYONECANPAYで署名されていることが分かる。

SIGHASH_SINGLEは、全インプットとそのインプットと同じインデックスのアウトプットを保護するSIGHASHタイプだが、これにSIGHASH_ANYONECANPAYを組み合わせてるので、このインプットと、同じインデックスのアウトプットを保護するSIGHASHタイプになる。↑の場合、インプットの売り手のInscriptionと、アウトプットの売り手のアドレスへの支払いを保護する(他の部分は他の署名者によって変更可能)。

つまり、このInscriptionを指定額で販売すること(指定額を受け取ること)にはコミットしていて、その原資や所有者については任意に設定可能な署名データになっている。一応↑のようなトランザクション構成になっているけど、他の部分は変更しても売り手の署名は有効なまま。

PSBTには売り手の署名があるので、あとは買い手がPSBTをファイナライズ(自分の署名を追加)すれば購入トランザクションが完成する。トランザクションブロードキャストして実際に買ってみた

※ 今回は実験用に購入したのでlndのウォレット使ってるけど、リアルにInscriptionが欲しい場合は、うっかり失わないようordとかでちゃんと管理する必要がある。

Nostrを利用したInscriptionの販売

購入したInscriptionは、DEXで販売することもできる。List Inscription For Saleボタンを押すと、売値と受け取るアドレスを入力するウィンドウが表示されるので、希望額とアドレスを入力すると、ここでもPSBTが生成される。このPSBTは、InscriptionのUTXOをインプットとして、指定額を指定したアドレスに送信するトランザクションを構成するもの。そして、PSBTのPSBT_IN_SIGHASH_TYPEの値は先程と同じ、0x83(SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)。このPSBTに署名してPublishすると、Nostrのリレーサーバーに販売情報と署名済みのPSBTが配信される。実際にローカルでPublish後、https://openordex.org/を確認すると公開した情報が確認できた。

↑の購入時に提示されるPSBTの販売者の署名は、このPSBTデータから作られている。販売用のPSBTは1インプット、1アウトプットのトランザクションだが、この署名を別のトランザクションに埋め込んでも、インプットとアウトプットのインデックスが同じである限り(販売用のPSBTではインデックス=0で、↑の購入用のPSBTではインデックス=1になる)署名は有効なまま。

Ordinal Theory

Ordinal Inscriptionのベースになっているのが、Bitcoinでマイニングされたすべてのsatoshiに一意の番号を割り当てるOrdinal Theory。Ordinal Inscriptionの所有者の移転もこのルールに従って行われる。

↑の購入Txを見ると分かるけど、インプットのコインが、

  1. ダミーUTXO:1,000 sat
  2. InscriptionのUTXO:6,696 sat
  3. 購入資金のUTXO:46,678

そして、アウトプットのコインが、

  1. Inscriptionの移転先:7,696 sat
  2. 支払い:10,000 sat
  3. 新しいダミーUTXO:1,000 sat
  4. お釣り:30,190 sat

となっている。最初2つのインプット額=先頭のアウトプット(移転先)の額になっているのが分かる。これはOrdinal Theoryで2つのインプットのsatoshiが最初のアウトプットに割り当てられていることを示すもので、これによりInscriptionの所有者が先頭のアウトプットになっているのが分かる。

最初はどうしてダミーUTXOを用意するのが疑問だったけど、

  • 販売用のPSBTのように売り手のInscriptionのインプットと支払い受取のアウトプットが最初に来るとInscriptionの所有者が移転できない
  • SIGHASH_SINGLEで保護可能なのは同一インデックスのインプットとアウトプットなので、支払い受取のアウトプットを1つ後ろにすると、インプットのInscriptionも1つ後ろにずらす必要がある
  • アウトプットの先頭はInscriptionの購入者のUTXOとなるので、販売資金をインプットの先頭に配置できない(インプットの最初2つ分が、先頭のアウトプットに移転されるので)

といった理由からなんだろう。Bitcoinの既存のSIGHASHタイプはそんなに柔軟ではないので、こういう制約が出てくるけど、インプットと任意のインデックスのアウトプットをマッピング可能なSIGHASH_GROUP とかが導入されると、ダミーUTXOは無くせそう。

でもこれ、InscriptionのUTXOが保持している金額は、購入者に渡るのでそれを見越して販売価格設定する必要があるな。あと後から販売価格変えようと思っても、すでに公開したPSBT持ってる人がいたら前の価格で購入される。あと、Inscriptionが購入される度に、ダミーUTXOの価格分、InscriptionのUTXOが保持する金額も増えていくんじゃ?

以上がOpenOrdexでのPSBTの利用方法。誰もトラストすることなく、Inscriptionの販売、購入が可能で、トランザクション手数料以外の手数料を支払う必要もない。それにしても、PSBTとNostrのリレーサーバーを上手く利用してDEX作ってるの面白いな。