はじめに
近年、ブロックチェーンあるいはBitcoinなどの暗号資産の取引について耳にすることが増えてきている、という方も多いのではないでしょうか?今後はコイン的なものだけではなく、例えば不動産の権利など、更に様々なモノのデジタル化が進み、ネットワーク上で取引されるようになっていくかもしれません。
デジタルの資産(以下、アセット)をネットワークを介して単純に交換しようとするとすると、例えば、もしも相手が裏切ったときには、先に交換対象を提供した人は損してしまいます。相手が裏切るリスクを避けるために、仲介者を介して交換する方法も考えられますが、その場合でも、仲介者が裏切って持ち逃げしたら、一方もしくは双方とも損をすることになります。
そのようなリスクを回避し、信頼関係がない相手と安全に交換する方法として、Atomic Swapというものがあります。アトミックな交換、つまり、交換が成功するか、交換がなかったことになるか、のどちらかになるというものです。交換対象を拠出したのに、相手からはもらえないままという中途半端な状態では終わらないというものです。Atomic Swapでは交換相手が裏切ろうとしても、事前に決めていたタイミングになれば、相手に拠出していたものを取り戻すことができますので、信頼できない相手との交換を安全に行えます。仲介者も不要です。
Atomic Swapは、最新の話題というものではありませんが、ブロックチェーンの活用方法を考える上で、大変参考になる、上手く考えられたものですので、ざっくりとでも知っておくと良いと思います。しかしながら、ブロックチェーンに少し興味を持ち始めた方などが、実際に動かしてその仕組みを試してみたいと思っても、環境を準備したり、プログラムの書き方を把握したりと、お手軽にというわけにはいきません。
そこでAtomic Swapという言葉を聞いたことがない、あるいは、どんなことかまでは知らないといった方が、概要を把握することに役立つかもしれない、環境とプログラムのちょっとしたサンプルをこちらにつくってみました。
きちんとした説明はインターネットなどにいろいろとあると思いますので、そちらをご参照いただくとして、ここでは、ささっと、このようなことというイメージをつかめるようにするのと、ある環境を前提にした場合に動作するモノをつくるときに少しでも円滑に進められるようにするための一助となればと思っています。そのため、説明が網羅的ではなく、また、厳密には違うということがあるかもしれないことを予めご了承ください。ブロックチェーンに関連することに、本格的に取り組む前の1ステップになればと思っています。
環境
Atomic Swapではブロックチェーンをベースに異なるアセットの交換を扱いますが、その動作を試す環境として、今回は、独自に定義した複数のアセットを扱える、Elementsというプロダクトを使用します。これはBitcoin Coreをベースに様々な機能を拡張したもので、独自のアセットを扱う以外にも、やりとりするアセットの種類や量を秘匿する機能などがあります(今回、使用していません)。なお、このサンプルでは簡易的に1つのブロックチェーン上で、やりとりを再現しています。
また、その環境を使用しての交換のやりとりは、Jupyter Notebook上に記載し、順を追って動かしていけるようにしています。交換の当事者(Alice, Bob)、アセットの発行者(Charie)の各ElementsノードとJupyter NotebookのノードをDockerコンテナとして構成しています。サンプル環境の構築手順はこちらをご参照ください。
Elementsにおけるアセットの所有権の移転も、Bitcoinと同様、トランザクションを介して行います。所有権の移転のおおまかな流れは、移転元の人が「施錠されている未使用のアセット(UTXO)」の「錠前」を「鍵」を使用して外し(アンロック)、「新たな所有者のみが解除できる錠前」に付け替える(ロック)、というような操作をトランザクションとして作成し、それをノードに送信し、その後ブロックに取り込まれることで完了するというものになります。
シナリオ
サンプルプログラムで扱う交換のやりとりは下記の3種類です。
-
a. Aliceが持っているアセットABC(量:30)とBobが持っているアセットXYZ(量:20)を正常に交換する。(交換成立)
-
b. Aliceはアセットを拠出したものの、Bobが交換対象を拠出しないため、Aliceは拠出していたアセットを取り戻す。(交換不成立)
-
c. Bobはアセットを拠出したものの、Aliceがそれを受け取らないため、Bobは拠出していたアセットを取り戻す。(交換不成立)
シナリオbとcは冒頭で述べたような裏切りなどが起こり、シナリオaのようにはやりとりが進まず、途中で取り戻すことになるケースです。
シナリオa
下図①〜④の順にAliceとBobは交互にトランザクションをブロードキャストしあい、交換が成功するケースです。
- Aliceが最初にアセットを渡すとき(Tx1)、Aliceだけが知っている秘密の値Sのハッシュ値を送り、BobがBobの署名だけではなく、その**ハッシュ値の元になる値(プリイメージ)**も提示できたら、Aliceが渡すTx1のアセットをアンロックできるようにします。これによりBobは、AliceにBobのアセットを拠出しないまま、Aliceが拠出したアセット持ち逃げできないようになります。
- ①のあとBobが何もしないと、Aliceが拠出したアセットは、Bobに持ち逃げされなくはなるものの、Aliceが取り戻すこともできなくなります。そこで、ある時点以降にAliceが取り戻せるように、ある時間やブロック高などが経過したらアンロックできるようにもします。その場合、Alice以外の人にとられないよう、Aliceの署名も必要となるようにします。
- ②にて、Bobは交換対象のアセットを拠出します(Tx2)。ロックのパターンはTx1と同様です。
- ③にて、Aliceは秘密の値Sを使用して、Tx2に含まれるアセットXYZのロックを解除します。AliceがTx3をブロードキャストすると値Sは公開されることになります。
- ③のあと、BobはTx3から値Sを知ることができるため、Tx4により、Tx1のアセットABCをアンロックできます。
※ 注意点
相手がトランザクションを送り返さないときに取り戻せるように、時間やブロック高などによるロックをかけていますが(図中のNやMの箇所)、この大小関係には注意が必要です。今回の場合は、N > M である必要があります。N <= Mとしてしまうと、下記のようにAliceが全部のアセットを取得できてしまいます。
1. AliceがTx1を送信、BobがTx2を送信
(その後、Nは経過したが、Mは経過していないとする)
2. この時点でNを経過しているので、AliceはTx1を引き戻せるのでそうする
(Mを経過していないので、BobはまだTx2を引き戻せない)
3. 一方でAliceはいつでもTx2のアセットを入手できるので、Tx3を送信し、Bob拠出のTx2のアセットを入手
(Aliceに全部とられてしまう)
シナリオb
Bobがアセットを拠出しないため、交換が不成立になるケースです。Bobがアセットを拠出しないので、Aliceは自身が拠出していたアセットを取り戻します。
- ①のあとの時点では、Bobはまだハッシュ前の値Sを知らないため、AliceがブロードキャストしたアセットABCをアンロックできません。そしてTx1のアンロック条件に指定されたタイミングになれば、AliceはTx1のアセットを取り戻すことができます。
シナリオc
Aliceがアセットを受け取らないため、交換が不成立になるケースです。Aliceがアセットを受け取らないので、Bobは自身が拠出していたアセットを取り戻します。
- Tx2のアンロック条件に指定されたタイミングになれば、BobはTx2のアセットを取り戻すことができます。
シナリオを実現するための仕組み
このようなやりとりは、トランザクションのアウトプットに、先に述べたようなロックをかけることで実現します。ロックにはScriptというものを使用します。今回は、BIP0199に記載されているScriptのパターンを使用します。
ロック
細かくわけると3種類のロックが登場します。1つ1つはシンプルな機能ですが、それらを上手に組み合わせて、アトミックな交換を実現しているんですね。
- 公開鍵によるロック
- 指定された公開鍵に対応する秘密鍵を使用して作成した、トランザクションの署名を提示できたら解除
- ハッシュロック
- 指定されたハッシュ値の元の値(プリイメージ)を提示できたら解除
- タイムロック
- 指定されたタイミングになったら解除
Script
ScriptはOPコードという命令に相当するものと、いわゆるデータに相当するもので構成されます。トランザクションのアウトプット単位にScriptを使用してロックをかけます。Aliceがはじめにアセットを拠出するときには、次のようなScriptを使用し、ロックを掛けます。
AliceがBobに送るTx1に含まれるアセットに対するロックのScriptのパターン
OP_IF
OP_HASH160 {プリイメージのハッシュ値} OP_EQUALVERIFY OP_DUP OP_HASH160 {相手の公開鍵のHASH160} …(条件1)
<ハッシュロック> <公開鍵によるロック>
OP_ELSE
{タイムアウトN} OP_CHECKSEQUENCEVERIFY OP_DROP OP_DUP OP_HASH160 {自分の公開鍵のHASH160} …(条件2)
<タイムロック> <公開鍵によるロック>
OP_ENDIF
OP_EQUALVERIFY
OP_CHECKSIG
(なお、サンプルコードでは変換処理を端折るため、OP_xxの表記ではなく、16進表記にしています)
OP_IFの後とOP_ELSEの後に、それぞれロックの条件があります。
- 条件1は交換がうまくいくときに使われる条件です。ハッシュ化する前のプリイメージを知っていて、かつ、交換相手の公開鍵に対応する秘密鍵による署名があれば、ロックを解除できるという条件です。ある時点までは、どちらの人も片方ずつしか知らないため、このロックを解除できません。
- 条件2は交換がうまくいかないときに使われる条件です。相手が裏切ろうとするなど、後続のやりとりが行われなかったときに、自身にアセットを取り戻せるようにするための条件です。指定のタイミング以降、自身だけが知っている秘密鍵を使用して作成した署名を使うことでロックを解除できます。
Bobがアセットを拠出するときのScriptも、Aliceが拠出するときと大枠は同じです。(OPコードは同じで、データが異なります)
タイムアウト
タイムアウトの値は、ブロック高 または タイム、相対値 または 絶対値 で指定します。サンプルでは、ブロック高・相対値(当該トランザクションがブロックに取り込まれてから指定ブロック経過後にロックが解除される)としています。タイムアウトとして指定する値が500,000,000未満のときはブロック高として解釈されます。相対値か絶対値かについては、OPコードにより決まります。OP_CHECKSEQUENCEVERIFYを指定した場合には相対値として解釈されます。
データ長
Scriptは最終的にはバイトにシリアライズされます。OP_CODEはこちらのように変換します。データの場合は値そのものだけでなく、そのバイト長(リトルエンディアン)をデータの前につける必要があります。例えば、プリイメージや公開鍵のハッシュはHASH160しているので、そのハッシュ値のバイト長は20バイトになり、16進数としては14を指定することになります。今回のScriptでは下記のように変換することになります。
Scriptのシリアライズの例
OP_IF
63
OP_HASH160 {シークレット値のハッシュ値} OP_EQUALVERIFY OP_DUP OP_HASH160 {相手の公開鍵のHASH160}
a9 14 {ハッシュ値} 88 76 a9 14 {ハッシュ値}
OP_ELSE
67
{タイムアウト 8 block} OP_CHECKSEQUENCEVERIFY OP_DROP OP_DUP OP_HASH160 {自分の公開鍵のHASH160}
58 b2 75 76 a9 14 {ハッシュ値}
OP_ENDIF
68
OP_EQUALVERIFY
88
OP_CHECKSIG
ac
また、Script内のデータのバイト長を指定するとき、その大きさによっては、OP_PUSHDATA1などのOPコードをつける必要があります。データのバイト長が76(0x4c)〜255(0xff)のときはOP_PUSHDATA1、256〜520バイトのときはOP_PUSHDATA2を指定します。例えば、バイト長が100(0x64)のときには、OP_PUSHDATA1のような目印がないと、その値がOPコードの値(OP_NOTIF:0x64)なのか、バイト長なのか区別がつかなくなってしまいます。
あと、Script内でバイト長ではなくデータ本体として数値を使うときも、少し注意した方がよい点があります。今回、タイムアウト値として8ブロックなどの小さな値を使用していますが、16以下の数値を使用する場合には、OPコード OP_8(0x58)やOP_16(0x60)で指定します。
Elements
サンプルではElementsを使用していますが、そのことについても少し書いておきます。(バージョン0.14.1)
独自アセットの追加
コマンドを実行してアセットを発行するのですが、各ノードとも、すぐにそれが使えるようにはなりません。各Elementsノードの設定ファイルに新しいアセットの情報を追加して、ノードを再起動する必要があります。このあたりでアセットの発行まわりのことをしています。
トランザクションの構成
Elementsのトランザクションの構成は、今回のように、Confidential Transaction/Confidential AssetのようなElements独自の機能を使わないとき、Bitcoinと似た構成ではありますが、異なる点もありますので、トランザクションを自身で組み立てたりするときには考慮が必要です。
おわりに
これまでみてきたような仕組みにより、ネットワークを介して、信用できない相手とであっても、安全にやりとりができるようになることで、さまざまな領域でのやりとり・取引において、大きな変化がもたらされるかもしれません。
こういった仕組みに対する理解を深めたり、その仕組みをベースとする何かをつくりはじめてみる際に、少し詰まることがあったときなどに、汎用的ではなく荒いつくりではあるものの、動作するモノという点で、このサンプルが何らかのお役に立てばと思います。