スクリーンショット_2019-08-23_19

Libraの新しいプログラミング言語moveのホワイトペーパー要約

Facebook Libraの新しいプログラミング言語であるMoveのテクニカルホワイトペーパーを要約してみました。

ブロックチェーンの情報は2017年のALISのICOから追っていました。

こんなの書いてたなぁ。。もう2年・・・

ブロックチェーンハッカソンなどに参加してブロックチェーンを使った事業作れないか考えたこともあったのですが、

①トランザクション処理能力の限界
②ボラリティが高すぎるトークンは安定利用出来ない

というハードルがあり、ブロックチェーンから一旦離れました。

そんな中、Libraは、この2点を解決し得るプラットホームになるのではないかな、とかなり期待しています。

Libraプラットホームが公開されるのは2020年になるとのことですが、今から出来る限り情報をキャッチアップしていきたいので、インプットのためにもこちらの記事を参考にテクニカルホワイトペーパーを要約していきます。(変なところがあったら是非とも連絡ください。)

-------------------------------------------------------------------------

Moveは、カスタムトランザクションとスマートコントラクトの実装に使用される実行可能なバイトコード言語です。バイトコードとは、仮想マシンが実行するために設計された、実行可能なプログラムをバイナリ表現で表したコードです。Javaが有名です。

Moveは、MoveのVM(バーチャルマシン)で直接実行できるバイトコード言語ですが、Solidity(Ethereumのスマートコントラクト言語)は、EVM(Ethereumバーチャルマシン)で実行する前にバイトコードにコンパイルする必要がある高級言語です。

(MoveはSolidityのように高級言語ではなく、バイトコードにすることによって、安全性を上げることを可能にしているようです。Ethereumでは過去Solidityで書いたスマートコントラクトがハッキングされて大問題が起こったことがあります。)

Moveの主な機能は、Rustに似ています。 Rustの値(value)は、一度に1つの名前にのみ割り当てることができます。別の名前に値を割り当てると、以前の名前ではアクセスできなくなります。普段、変数をコピーし放題している言語を扱っている人には、違和感を感じるでしょう。

たとえば、次のコードはエラーになります。

// invalid
let x = String::from("hello")
let y = x;
println("{}", x);

Rustにはgarbage collection(プログラムが動的に確保したメモリ領域のうち、不要になった領域を自動的に解放する機能)がないためです。変数が範囲外になると、変数が参照するメモリも割り当て解除されます。x変数にあった値はyに移ってしまい、もうx変数にはないためです。

オープンなシステムでデジタル資産への変換

現物の資産をデジタル資産でエンコード(変換)するのが難しい2つの理由があります。

⑴希少性:既存の資産の複製は禁止する必要があり、新しい資産の作成は特例である必要があります。
⑵アクセスコントロール:システムの参加者は、資産を保護できる必要があります。

この2つをシステム上で実現するために以下の提案を確認してみましょう。

提案#1:希少性とアクセスコントロールのない最も単純なルール

・G [K]:= n は、keyであるKにnという値をアップデートしたことを示しています。

・transaction <Alice,100>は、Aliceの口座残高を100に設定することを意味します。

上記の表現には深刻な問題があります。
・transaction <Alice,100>を自分で送信することにより、無限にコインを持つことができてしまいます。
・アリスがボブに送信するコインは、ボブも同じテクニックを使用して無限にコインを自分自身に送信できてしまいます。

提案#2:希少性を考慮に入れる

transaction script format <Ka, n, Kb>
if G[Ka] >= n then G[Ka] := G[Ka] - n  
                                G[Kb] := G[Kb] + n

ここで、Kaに格納されているコインの数は、転送が行われる前に少なくともnであることを示します。
ただし、これにより希少性の問題は解決しますが、誰がAliceのコインを送信したかを確認することが出来ません。

提案#3:希少性とアクセス制御の両方を考慮する

希少性とアクセス制御の両方を考慮する3番目
希少性チェックの前にデジタル署名メカニズムverify_sigを使用しします。つまり、アリスはプライベートキーを使用してトランザクションに署名し、彼女がコインの所有者であることを証明します。

既存のブロックチェーン言語

既存のブロックチェーン言語は、次の問題に直面しています(それらはすべてMoveで解決されています)。

1.資産の間接的な表現。資産はintを使用してエンコードされますが、intの値は資産と同じではありません。実際、Bitcoin / Ether / StrawCoinを表す型や値は存在しません。これにより、アセットを使用するプログラムを作成するのが難しくなり、エラーが発生しやすくなります。アセットの受け渡し方法のパターンには、特別な言語サポートが必要です。

2.希少性は拡張できません。言語は、1つの希少な資産のみを表します。さらに、希少性の保護は、言語に直接ハードコーディングされています。資産を作成したいプログラマーは、言語のサポートなしで、希少性を慎重に再実装する必要があります。(ハードコードとは、ソフトウェア開発の際に、特定の動作環境を決め打ちして、その環境を前提とした処理やデータをソースコードの中に直に記述すること。ハードコードされたデータなどはプログラムの完成後、実行時などに容易に変更することはできないため、後から利用者などの手で変更する必要が無いと判断されたデータがハードコードされることが多い。)

これらはまさにEthereumスマートコントラクトの問題です。 ERC-20トークンなどのアセットは、intを使用して値と総供給量を表します。新しいトークンが作成されるたびに、スマートコントラクトコードは希少性(この場合は総供給量)に達しているかどうかを手動で確認する必要があります。

さらに、資産の問題の間接的な表現により、複製、再利用、資産の損失などの深刻なバグが発生する可能性が高くなります。

3.アクセスコントロールが柔軟ではありません。 そのモデルが要求する唯一のアクセスコントロールポリシーは、公開鍵に基づく署名スキームです。 希少性保護と同様に、アクセスコントロールポリシーは言語に深く組み込まれています。 プログラマーがアクセスコントロールポリシーを定義できるように言語を拡張する方法は明らかになっていません。

これは、スマートコントラクトがアクセスコントロールを行うための公開鍵と秘密鍵の暗号化の言語サポートを持たないEthereumにも当てはまります。 開発者は、OnlyOwner(Zeppelinのパッケージ)を使用するなど、アクセスコントロールを手動で記述する必要があります。

特に、Etherをスマートコントラクトに転送するには、動的ディスパッチ(動的にメソッドを呼び出すこと)が必要です。これにより、リエントラントの脆弱性として知られる新しいバグが発生しました。

※「ディスパッチ」は「呼び出すメソッドを決定する」こと。それが「動的」ということは、呼び出すメソッドが実行時に決定されることを言っています。
※動的ディスパッチの特徴
・コードが膨張しないが低速な関数の呼び出しが必要
・正確な型は実行時になって初めて判明する
・インライン化(コンパイラによる最適化手法)ができない
※リエントラントとは
複数の呼び出し元から同時に使用されても問題がないように設計されている関数、サブルーチンのことである。呼び出し元が自分自身の場合もある。

ここでの動的ディスパッチとは、コンパイル時(静的)ではなく、実行時(動的)にコード実行ロジックが決定されることを意味します。 したがって、Solidityでは、コントラクトAがコントラクトBの関数を呼び出すと、コントラクトBはコントラクトAの設計者が予期していなかったコードを実行できるため、リエントラントの脆弱性につながる可能性があります )

Moveのデザインのゴール

1.First-Class Resources

Moveのモジュール/リソース/プロシジャー間の関係は、オブジェクト指向プログラミングのクラス/オブジェクト/メソッド間の関係に似ています。
Moveモジュールは、他のブロックチェーン言語のスマートコントラクトに似ています。 モジュールは、宣言されたリソースを作成、破棄、および更新するためのルールをエンコードするリソースタイプとプロシージャを宣言します。

モジュール/リソース/プロシジャーは、Moveの専門用語の一部です。 この記事の後半でこれらを説明します。

2.柔軟性

Moveは、トランザクションスクリプトによりLibraに柔軟性を与えます。 各Libraトランザクションには、メインプロシジャーであるトランザクションスクリプトが含まれています。
スクリプトは、表現力豊かな1回限りの動作(特定の受信者への支払いなど)または再利用可能な動作(再利用可能なロジックをカプセル化する単一のプロシージャの呼び出し)を実行できます。

上記から、Moveのトランザクションスクリプトは、1回限りの振る舞いと再利用可能な振る舞いが可能であるため、柔軟性が向上することがわかりますが、Ethereumは再利用可能な振る舞い(単一のスマートコントラクトメソッドを呼び出す)しか実行できません。 「再利用可能」と名付けられた理由は、スマートコントラクトを複数回実行できるからです。

3.安全性

Moveの実行可能形式は、アセンブラよりも高レベルでありながらソース言語よりも低レベルの型付きバイトコードです。 バイトコードは、バイトコード検証によってリソース、型、およびメモリの安全性についてチェーン上でチェックされ、その後、バイトコードインタープリターによって直接実行されます。 これにより、Moveは、ソースコンパイラを信頼できるコンピューティングベースに追加したり、コンパイルのコストをトランザクション実行のクリティカルパスに追加したりすることなく、ソース言語に関連付けられた安全性を保証できます。

これは、Moveがバイトコード言語であるための非常に優れたデザインです。 ソースからSolidityのようなバイトコードにコンパイルする必要がないため、コンパイラで起こりうる障害や攻撃を心配する必要はありません。

4.検証可能性

私たちのアプローチは、可能な限り軽量のオンチェーン検証を実行することですが、オフチェーン静的検証ツールをサポートするようにMove言語を設計しています。

ここから、Moveはオンチェーン検証作業を行うよりも静的な検証を実行することを好むことがわかります。 しかしながら、彼らの論文の最後に述べたように、検証ツールはまだ作成出来ていないようです。

3.モジュール性。 Moveモジュールは、データを抽象化し、リソースの重要な操作をローカライズ(??)します。 モジュールの型に対して作成したプログラムがモジュール外のコードによって変更されないことを保証します。

これも非常によく考えられたデータ抽象化設計です。 つまり、スマートコントラクトのデータは、契約範囲内でのみ変更でき、外部からの他の契約は変更できません。

Moveの概要

トランザクションスクリプトは、モジュール外の悪意のあるプログラマーが、モジュールのリソースの書き換えなどを行えないようになっています。

このセクションでは、プログラミング言語を記述する際に実際にどのモジュール、リソース、およびプロシジャーが使用されるかについての例を紹介します。

1.Peer-to-Peer Payment Transaction Script

public main(payee: address, amount: u64) {
   let coin: 0x0.Currency.Coin = 0x0.Currency.withdraw_from_sender(copy(amount));
   0x0.Currency.deposit(copy(payee), move(coin));
}

これは決めた量のコインが送り先のアドレスに移動するスクリプトを表しています。

0x0: アドレス
Currency: モジュール名
Coin: リソースタイプ
withdraw_from_sender: プロシジャー
copy(): 値をもう一度使うことが出来ます
move(): 値をもう一度使うことは出来ません

コードをさらに詳しく

最初のステップで、送信者は、0x0.Currencyに保存されているモジュールからwithdraw_from_senderという名前のプロシジャーを呼び出します。
2番目のステップでは、送信者はCoinリソースの値を0x0.Currencyモジュールの入金手順に移動することにより、受取人に資金を転送します。

次に拒絶される3つの例を挙げます。

1. move(coin)をcopy(coin)に変更して通貨を複製する

リソース値は移動のみ可能です。 リソース値を複製しようとすると(上記の例でcopy(coin)を使用するなど)、バイトコード検証時にエラーが発生します。

coinはリソースの値のため、moveしか使えません。

2. move(coin)を2回書くことで通貨を再利用する

行0x0.Currency.deposit(copy(some_other_payee)、move(coin))を上記の例に追加すると、送信者はコインを2回「支出」することができます。 Moveはこのプログラムをエラーにします。

3.move(coin)をしないでコインを失う

リソースの移動に失敗すると(たとえば、上記の例のmove(coin)を含む行を削除することにより)、バイトコード検証エラーが生じます。 これにより、プログラマーが誤って(または意図的に)リソースを追跡できなくなるのを防ぎます。

Currencyモジュールについて

各アカウントには、0個以上のモジュール(四角形で表示)と1つ以上のリソース値(シリンダーで表示)を含めることができます。 たとえば、アドレス0x0のアカウントには、モジュール0x0.Currencyとタイプ0x0.Currency.Coinのリソース値が含まれています。 アドレス0x1のアカウントには、2つのリソースと1つのモジュールがあります。 アドレス0x2のアカウントには2つのモジュールと1つのリソース値があります。

・トランザクションスクリプトの実行は、オールオアナッシング
・モジュールはグローバルステイトに公開されて存在し続ける
・グローバルステイトはアカウントアドレスからアカウントへのマップとして構造化される
・アカウントには、特定のタイプのリソース値を最大1つ、特定の名前のモジュールを最大1つ含めることができます(アドレス0x0のアカウントには、追加の0x0.Currency.CoinリソースまたはCurrencyという名前の別のモジュールを含めることはできません)
・宣言モジュールのアドレスは、型の一つです(0x0.Currency.Coinと0x1.Currency.Coinは、互換的に使用できない別個の型です)
・プログラマーは、カスタムラッパーリソースを定義することにより、アカウント内の特定のリソースタイプの複数のインスタンスを保持できます。

Coinリソースの宣言

module Currency {
     resource Coin { value: u64}
}

・Coinは、64ビット符号なし整数(uint)の単一フィールド値を持つ構造体です。
・Coinモジュールのプロシジャーのみが、Coinの値を作成または破棄できます。
・他のモジュールとトランザクションスクリプトは、モジュールによって公開されているパブリックプロシジャーを介してのみ値フィールドを書き込みまたは参照できます。

Depositの解釈

public deposit(payee: address, to_deposit: Coin) {
   let to_deposit_value: u64 = Unpack<Coin>(move(to_deposit));
   let coin_ref: &mut Coin = BorrowGlobal<Coin>(move(payee));
   let coin_value_ref: &mut Coin = BorrowGlobal<Coin>(move(payee));
   let coin_value: u64 = *move(coin_value_ref);
   RejectUnless(copy(coin_value)) >= copy(amount);
   let new_coin: Coin = Pack<coin>(move(amount));
   return move(new_coin);
}
このプロシジャーでは、入力としてCoinリソースを受け取り、それを受取人のアカウントに保存されているCoinリソースと組み合わせます。
1.入力コインを破棄し、その値を記録します。
2.受取人のアカウントの下に保存されている一意のCoinリソースへの参照を取得します。
3.受取人のコインを、手続きに渡されたコイン分増やします。

・Unpack、BorrowGlobalは組み込みの手順です
・Unpack <T>は、タイプTのリソースを削除する唯一の方法です。タイプTのリソースを入力として受け取り、それを破棄し、リソースのフィールドにバインドされた値を返します。
・BorrowGlobal <T>はアドレスを入力として受け取り、そのアドレスで発行されたTの一意のインスタンスへの参照を返します
・&mut Coinは、CoinではなくCoinリソースへの可変参照です

withdraw_from_senderの解釈
public withdraw_from_sender(amount: u64): Coin {
   let transaction_sender_address: address = GetTxnSenderAddress();
   let coin_ref: &mut Coin = BorrowGlobal<coin>(move(transaction_sender_address));
   let coin_value_ref: &mut u64 = *move(coin_value_ref);
   *move(coin_value_ref) = move(coin_value) - copy(amount);
   let new_coin: Coin = Pack<Coin>(move(amount));
   return move(new_coin);
}
このプロシジャーは、
1.送信者のアカウントで公開されたタイプCoinの一意のリソースへの参照を取得します。
2.参照されるコインの値を入力値分減らします。
3.価値のある新しいコインを作成して返します。

・depositは誰でも呼び出すことができますが、withdraw_from_senderは、コインの所有者のみが呼び出すことができるアクセスコントロールを持っています。
・GetTxnSenderAddressはSolidityのmsg.senderに似ています。msg.senderについては以下の記事が参考になるかと!

・RejectUnlessはSolidityの要求に似ています。 このチェックが失敗した場合、現在のトランザクションスクリプトの実行は停止し、実行した操作はいずれもグローバルステートに適用されません。
・組み込みプロシージャでもあるPack <T>は、タイプTの新しいリソースを作成します。
・Unpack <T>と同様に、Pack <T>はリソースTの宣言モジュール内でのみ呼び出すことができます。

-----------------------------------------------------------------------------

以上になります。めちゃくちゃ難しいですね。Rustの基礎文法勉強して、moveの文法を勉強する手順で進めていきたいと思います。

強い方は、こちらの論文を原文で読んで僕に教えてください。

少しずつ勉強していきます!


この記事が気に入ったらサポートをしてみませんか?