slides
Tezos: 008 Edo protocol
DaiLambda, Inc.
Jun FURUSE/古瀬 淳
Tezos Tokyo Meetup, 2021-02-26

自己紹介

古瀬 淳 @camlspotter / @camloeba

ダイラムダ株式会社CEO

25年くらい OCaml を書いている

会社紹介: ダイラムダ株式会社

Tezosブロックチェーン研究開発

  • コア開発: ノードやプロトコルの実装
  • アプリケーションへのコンサル業務

相場はやりません。

東山三条に新オフィスオープン予定!

Agenda

Tezosとは

Tezosの歴史

Edoプロトコル技術紹介

  • Ticket
  • BLS12-381
  • Sapling
  • 匿名送金

Tezosとは

Tezos

第三世代パブリックブロックチェーン

Proof of Stake

オンチェーンガバナンス

形式検証を重視

関数型言語 OCaml による実装

スマートコントラクト

Proof of Stake

トークン保有量に比例したブロック生成権

Proof of Work のようなマイニング資源を必要としない

Tezos の LPoS: 「ベーキング」

Rasberry Pi 4 でさえ可能

生成権を委託し報酬を得ることもできる

手数料が高騰しない

オンチェーンガバナンス

オンチェーン投票により信任を得たハードフォーク

新技術をスムーズに投入する

プロトコル変更をコードとして提案

自動的にP2P配布

プロトコル上で採択するか投票

採択されれば自動的に移行

形式検証

ソフトウェアテストは重要だが完璧ではない
形式検証を使って数理的に安全性を証明していく

最新のソフトウェア検証技術
型システム、モデル検査、定理証明
多方面に検証を適用
プロトコル、ノード、、コンパイラ、
スマートコントラクト…

OCaml

関数型プログラミング言語

形式検証と相性が良い
静的型システム、Coq, F*
金融での使用例
高頻度取引、デリバティブモデリングでの使用実績
Tezosの実装に使用
75万行

スマートコントラクト

ブロックチェーン上でのプログラムの自動実行

Michelson: TezosのスマートコントラクトVM

「高レベル」スタックマシン

可変長整数
No overflow
静的型
“Never goes wrong” ™️
データ型
pair, sum, option, list, map

Tezos スマートコントラクト

マルチシグコントラクト in Michelson

parameter (or (unit %default)
              (pair %main
                 (pair :payload
                    (nat %counter) # counter, used to prevent replay attacks
                    (or :action    # payload to sign, represents the requested action
                       (lambda %operation unit (list operation))
                       (pair %change_keys           # change the keys controlling the multisig
                          (nat %threshold)          # new threshold
                          (list %keys key))))       # new list of keys
                 (list %sigs (option signature)))); # signatures

storage (pair (nat %stored_counter) (pair (nat %threshold) (list %keys key))) ;

code
  {
    UNPAIR ;
    IF_LEFT
      { # Default entry point: do nothing
        # This entry point can be used to send tokens to this contract
        DROP ; NIL operation ; PAIR }
      { # Main entry point
        # Assert no token was sent:
        # to send tokens, the default entry point should be used
        PUSH mutez 0 ; AMOUNT ; ASSERT_CMPEQ ;
        SWAP ; DUP ; DIP { SWAP } ;
        DIP
          {
            UNPAIR ;
            # pair the payload with the current contract address, to ensure signatures
            # can't be replayed accross different contracts if a key is reused.
            DUP ; SELF ; ADDRESS ; CHAIN_ID ; PAIR ; PAIR ;
            PACK ; # form the binary payload that we expect to be signed
            DIP { UNPAIR @counter ; DIP { SWAP } } ; SWAP
          } ;

        # Check that the counters match
        UNPAIR @stored_counter; DIP { SWAP };
        ASSERT_CMPEQ ;

        # Compute the number of valid signatures
        DIP { SWAP } ; UNPAIR @threshold @keys;
        DIP
          {
            # Running count of valid signatures
            PUSH @valid nat 0; SWAP ;
            ITER
              {
                DIP { SWAP } ; SWAP ;
                IF_CONS
                  {
                    IF_SOME
                      { SWAP ;
                        DIP
                          {
                            SWAP ; DIIP { DUUP } ;
                            # Checks signatures, fails if invalid
                            { DUUUP; DIP {CHECK_SIGNATURE}; SWAP; IF {DROP} {FAILWITH} };
                            PUSH nat 1 ; ADD @valid } }
                      { SWAP ; DROP }
                  }
                  {
                    # There were fewer signatures in the list
                    # than keys. Not all signatures must be present, but
                    # they should be marked as absent using the option type.
                    FAIL
                  } ;
                SWAP
              }
          } ;
        # Assert that the threshold is less than or equal to the
        # number of valid signatures.
        ASSERT_CMPLE ;
        # Assert no unchecked signature remains
        IF_CONS {FAIL} {} ;
        DROP ;

        # Increment counter and place in storage
        DIP { UNPAIR ; PUSH nat 1 ; ADD @new_counter ; PAIR} ;

        # We have now handled the signature verification part,
        # produce the operation requested by the signers.
        IF_LEFT
          { # Get operation
            UNIT ; EXEC
          }
          {
            # Change set of signatures
            DIP { CAR } ; SWAP ; PAIR ; NIL operation
          };
        PAIR }
  }

型があるので手書きできる

コントラクト記述高級言語

Michelsonへとコンパイルする高級言語(たち)

from better-call.dev
SmartPy:
Python eDSL
Lorentz
Haskell eDSL
LIGO
Multi syntax (OCamlish/Reasonish/Pascalish)

SCaml

弊社が開発したコンパイラ

OCaml 完全下位互換
OCaml がわかる人なら書ける
dune, Merlin, ocaml-lsp が使える
Tezosの新機能をすぐ足せる
SCaml. Not Scam.
https://scamlang.dev/

SCaml

マルチシグコントラクト in SCaml

open SCaml

type storage =
  { stored_counter : nat
  ; threshold : nat
  ; keys : key list
  }

type parameter =
  { payload : payload
  ; sigs : signature option list
  }

and payload =
  { counter : nat
  ; action : action
  }

and action =
  | Transfer of transfer
  | Delegate of key_hash option
  | Change_keys of change_keys

and transfer =
  { amount : tz
  ; dest : unit contract
  }

and change_keys =
  { threshold : nat
  ; keys : key list
  }

let main parameter storage =
  (* pair the payload with the current contract address, to ensure signatures
     can't be replayed accross different contracts if a key is reused. *)
  let signature_target =
    Obj.pack ( parameter.payload
             , Contract.address Contract.self
             , (match (Obj.unpack (Obj.pack (Chain_id "NetXdQprcVkpaWU")) : SCaml.chain_id option) with None -> failwith "failed" | Some x -> x)
               (* Global.get_chain_id () varies in different networks *)
             )
  in
  (* Check that the counters *)
  assert (storage.stored_counter = parameter.payload.counter);
  (* Compute the number of valid signatures *)
  let nsigs = Loop.left (fun (nsigs, keys, sigs) ->
      match keys, sigs with
      | [], [] -> Right nsigs
      | key::keys, Some sig_::sigs ->
          (* Checks signatures, fails if invalid *)
          assert (Crypto.check_signature key sig_ signature_target);
          Left (nsigs +^ Nat 1, keys, sigs)
      | key::keys, None::sigs ->
          Left (nsigs, keys, sigs)
      | _ ->
          (* There were fewer signatures in the list
             than keys. Not all signatures must be present, but
             they should be marked as absent using the option type.
          *)
          assert false
    ) (Nat 0, storage.keys, parameter.sigs)
  in
  (* Assert that the threshold is less than or equal to the
     number of valid signatures.
  *)
  assert (storage.threshold <= nsigs);
  (* Increment counter and place in storage *)
  let storage = { storage with stored_counter = storage.stored_counter +^ Nat 1 } in
  (* We have now handled the signature verification part,
     produce the operation requested by the signers. *)
  match parameter.payload.action with
  | Transfer { amount ; dest } -> (* Transfer tokens *)
      [ Operation.transfer_tokens () amount dest ], storage
  | Delegate key_hash_opt -> (* Change delegate *)
      [ Operation.set_delegate key_hash_opt], storage
  | Change_keys { threshold ; keys } -> (* Change set of signatures *)
      [], { storage with threshold; keys }

普通に副作用のないOCamlのコード。

Tezosの歴史

Tezosの歴史

2014-08
ホワイトペーパー発表
2017-03
Tezos財団創設
2017-07
ICOで250億円調達
2018-09
Mainnet開始

Tezos プロトコルの歴史

2018-09: 003: 開始バージョン

2019-05: 004 Athens: PoS参加条件引下げ、ガスリミット引上げ

2019-10: 005 Babylon: 合意アルゴリズム Emmy+、投票方式の改良

2020-03: 006 Carthge: スマコンVM強化

2020-11: 007 Delphi: ガスコスト大幅削減

2021-02: 008 Edo: Ticket, BLS12_381, Sapling, 匿名送金

2021-0?: 009 F???: ???

Tezos 008 Edo

2021-02-13 (日本時間14日早朝)に移行

  • Ticket
  • BLS12-381 (ゼロ知識証明)
  • Sapling と匿名送金

Ticket

Ticket

偽造できない「保存量」の発行が可能

  • スマートコントラクトが自由に発行できる
  • Ticket には発行者情報が付属、他人は偽造できない
  • 量を保存する分割(split)、統合(join)しかできない

通貨や認証システムを簡単に間違いなく作れる

よくある Fungible Token 実装

  • 各ユーザーの所持量を管理テーブル
  • 発行総量を変更する特殊操作: Mint/Burn
  • 所有者移転操作: 発行総量の保存チェック

Ticket による Fungible Token

  • 所持量管理テーブルは不要
  • Mint: ticket 値を生成するだけ
  • Burn: もらった ticket を破棄するだけ
  • 発行総量を保存しない移転操作は自動的に失敗する

Ticket による Fungible Token

  • Mint: ticket を発行しユーザーコントラクトに送付
  • ユーザーコントラクトはもらった ticket を保存
  • ユーザーコントラクトは使用したい分をSPLITし発行者に送る
      手元には残高のticketが残る。偽造できない
  • Burn: 発行者は受け取った ticket を破棄して
      額面分のサービスをユーザーに対して行う
  • ユーザーコントラクトは ticket の一部を他人に譲渡できる

Ticket 応用

量保存性が必要な数値ならいろんなものに使える

FT
UTXO 的なものを実装できる
NFT
NFT の ID を乗せることができる。別ID の ticket は join できない。
投票
投票用紙の ticket を有権者に配って、返事付きで返してもらう

Ticket型と関連命令の追加

線形性がある型: α ticket

TICKET : α -> nat -> α ticket
(** Ticket の複製は不可能 (型エラーになる) *) 

READ_TICKET : α ticket -> (address * α * nat) * α ticket
(** 発行者のアドレスを返す *)

SPLIT_TICKET : α ticket -> (nat * nat) 
               -> (α ticket * α ticket) option
(** 分割が正しくない場合は失敗する (None) *)

JOIN_TICKET : α ticket -> α ticket -> α ticket option
(** 発行者が異なると失敗する (None) *)

Ticket型の線型性

線形型: 一度しか使用できない値を表す型

Michelsonはスタック言語なので線形型そのものでは無い

  • スタック上で DUP できない
  • TICKET 命令でしか生成できない
    定数は無い (定数をPUSHできると偽造可能)
  • ブロックチェーン外にやり取りできない

高級言語では線形型が現れてくる:

type linear α ticket

val READ_TICKET : α ticket -> (address * α * nat) * α ticket
(** 発行者のアドレスを返す 
    線型性のため、同じticketを返す *)

BLS12-381 とゼロ知識証明

楕円曲線

楕円曲線上の格子点を「数」(素体 \(G\))として使う

整数\(a\)\(G\)の生成元 \(P\) から点 \(aP\) は簡単に計算できるが、\(aP\) から \(a\) を計算するのは困難。


双線形写像

二つの楕円曲線上の点を取る「都合のいい」関数 \(e(aP, bQ)\)

\[e(aP, bQ) = e(P,Q)^{ab}\]

係数を左右に振り替えることができる。ペアリング検査

ペアリング検査による公開鍵署名

 秘密鍵: \(\color{red}s\)

 公開鍵: \({\color{blue}p} = {\color{red}{s}}P\)

 メッセージ: \(\color{blue}m\)

 署名: \({\color{blue}{\sigma}} = {\color{red}s}{\color{blue}m}Q\)    ← \(\color{red}s\) を知っていないと作れない

\(e(P, {\color{blue}\sigma})\)\(e({\color{blue}p}, {\color{blue}{m}}Q)\) を比較して同じなら:

\[e(P, {\color{blue}\sigma}) = e(P, {\color{red}s}{\color{blue}m}Q) = e({\color{red}s}P, {\color{blue}m}Q) = e({\color{blue}p}, {\color{blue}{m}}Q)\]

\(\color{red}s\)を知っている人の署名だとわかる

BLS12-381

匿名コイン Zcash の Sapling に使われている楕円曲線と
双線形写像の実装

二つの素体 \(G_1\)\(G_2\) を使う (\(\#G_1 \ll \#G_2\))

009 Edo から Tezos/Michelson で使用可能に

BLS12-381 in Tezos

bls12_381_g1: \(G_1\) の型
bls12_381_g2: \(G_2\) の型
bls12_381_fr: 係数の型
計算
INT : bls12_381_fr から int への型変換
ADD MUL NEG : \(G_1, G_2\) と係数の計算に対応
ペアリング検査命令
PAIRING_CHECK
 : (bls12_381_g1 * bls_12_381_g2) list -> bool \(e(p_1,q_1)*e(p_2,q_2)*..*e(p_n,q_n) \stackrel{?}= 1\)

\(e(p_1,q_1) = e(p_2,q_2) ~~\Leftrightarrow~~ e(p_1,q_q) * e(-p_2,q_2) = 1\)

BLS12-381 による署名検査

open SCaml
open BLS12_381

let [@entry] main _param _storage =
  let g1_one (* G1 の生成元 P *) = G1Bytes "0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1" in
  let g2_one (* G2 の生成元 Q *) = G2Bytes "0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801" in
  let s (* 秘密鍵 *) = FrBytes "0x012345" in
  let p (* 公開鍵 = sP *) = let open G1 in g1_one * s in
  let m (* メッセージ *) = FrBytes "0x789012" in
  let mQ = let open G2 in g2_one * m in
  let sg (* 署名 = smQ *) = let open G2 in mQ * s in
  let a1 (* (P, sg) *) = (g1_one, sg) in
  let a2 (* (-p, mQ) *) = (G1.(~-) p, mQ) in
  (* ペアリング検査 e(P,-sg) & e(-p, mQ) = 1 *)
  assert (BLS12_381.pairing_check [a1; a2]);
  [], ()

チェーンは検査に特化している。データ作成の便利な命令はない。 本来はオフチェーンでやるべきこと。

BLS12-381 とゼロ知識証明

ゼロ知識証明

ある知識を知っていることを、
その知識そのものを公開せずに証明する。

例: 公開鍵署名

「自分はメッセージ \(m\) に対してこの署名 \(\sigma\)
 作成できる秘密鍵 \(s\) を知っている」

例: 匿名通貨

「自分は残高が \(n\) 以上あるUTXOの秘密鍵 \(s\) を持っている」
のでその一部 \(n\) を送金してほしい

BLS12-381 とゼロ知識証明

Tezos/Michelsonは証明の検証のみ提供

検証器とそのパラメータ、そこに提供する証明データの
作成は提供されていない

ZoKrates を利用可能

ゼロ知識証明ツールキット

  • 証明したい性質を Python 風プログラムとして入力、
    検証器コード(Solidity)とパラメータを得る
  • 性質を満たすデータを入力、証明データを得る

簡単な変更で Tezos/Michelson に使用可能

ZoKrates for Tezos

「ある数の平方根を知っている」

# ZoKrates での仕様
def main(private field a, field b) -> bool:
  return a * a == b

ZoKrates -c bls12_381 -s g16 使ってコンパイル

$ zokrates compile -c bls12_381 -i root.zok
$ zokrates setup -s g16
$ zokrates export-verifier -s g16

\(337^2 = 113569\) の証明を作る

$ zokrates compute-witness -a 337 113569
$ zokrates generate-proof -s g16

出力データを Tezos/Michelson 向けに変換

ZoKrates for Tezos

「ある数の平方根を知っている」証明検証器

(* Tezos/Michelson *)
open SCaml

let [@entry] main
    ( ( ( input_x : bls12_381_fr), ( input_y : bls12_381_fr) ),
      ( ( proof_a : bls12_381_g1 ), ( proof_b : bls12_381_g2 ), ( proof_c : bls12_381_g1 ) ) )
    ()
  =
  let vk_a = G1Point ( "0x169d7633e3da4d413bf1918c412fc54c548ddf641a423f47b61ca883c0ba1b85f5ee13dd63d7c1661cc4fe2ca38f00e1", "0x065dbcfb2123f8258ae2b3cf92035485f621e55d433b1f251ad37c02ae2b3ec6a1658ae23bbc77649878ec0871a6d8f1" ) in
  let vk_b = G2Point (( "0x1116f52efd0f128a0bd6be9042bec761332408e765609caae2b6f7805ab3287143a9bafeb94a7cdd6635fd1ee293c2cf", "0x16c58ffdfec9c8b7a4d3826e32a40f99e97bd237067971e474438078e8bca6ccbbee0870bef905972fe879030273dd67" ), ( "0x0dd3791f5adb64150c2e7082f23a214b7ef5aa97fd903e648637deb13c156061788756f74b75545c3453e10822012e6a", "0x037bef7f92e32e639cd632611077b121db404705da6a507b9b8d8adc08a38eba27b2d31b7b22e95a96e22e26660162d3" )) in
  let vk_gamma = G2Point (( "0x1889d7bcf405c1932c7cc66ea8d353b34844769b50da863b3f0f11079371d2e6b7c10031e270761f136e2f4e52f16224", "0x00e3114ca5fa1c0af741154f059c94a4d2647481ba6071b97317587a937e21f048e5f85191ba2fbd0f3929121485f4e2" ), ( "0x16872517c4811f11bba424dc64fdb3e6f27455e736d369d04de53a3e51eb36d4be6686a07f8be5352c138aceea7c6357", "0x1182c943f4672b1293f79699f6747d874e805f57e8211aae940d3e31693619054e2be22cb99b177cfc092db80c1f7896")) in
  let vk_delta = G2Point ( ( "0x095f3f088b18f2ab4e44df666560e101f4c83da739ce074527deb3fde2fcb25e2a19fe2a0f7c4f546ca7f904575875b9", "0x1394d62459f5e72373706504085111cae6afb88121e8f5707a9ff82daf97d300b9a20fc52a6ac7149dac1626d1e4d0ae" ), ( "0x0d108a1585684a6ccbc94bdcb37c133579866e5dabb75c77178927d38f59a687015995d7c6d7fb536ee9f76f7e8081b2", "0x16ec4958b2b639d08f0c1bbd4af8e9e9289280e84b7308d790da4707ceefe61c78b661fbd76f8be9bd365ddd9a5f0b25" )) in
  let vk_gamma_a = G1Point ( "0x07f1710668da99b2b45e6392d5cd89db7a1d34eff180ead6c886d51bca063f8111a179640d5d2dfc209e0a92783fea07", "0x0b5a9436d5c1be84f88a4127c9a02d307c5858df52ce76ae0b6fbb5c0d855438419e06a1c37f633b306e9da44eaf345e" ) in
  let vk_gamma_b = G1Point ( "0x18c1bf32977afa48e224b2209e9df000ea072af1cf06efc9e83cf9b156ff577e154da015b077626e2bab7a8300a087ea", "0x18dbc6bb77f98da0fc7e492c0624fd30b513ef5cfb54f2ed052216485052ba8a117f83a63e925b735fb6fe7c4c88e364") in
  let vk_gamma_c = G1Point ( "0x102bae3361383e2cef7d4aed348743201f6fd5fd25f4400d0888451a5556cd980f311510e6457e36fe8f330947269539", "0x0cf8968b8d779e53c04632588e402ae5884cc49ee3988159ede6a292fb3b9cd1071eec4bf3fffe51b7325184cadeab1d") in
  (* Compute vk_x as
     (vk_gamma_b * input_x) + (vk_gamma_c * input_y) + vk_gamma_a
  *)
  let vk_x =
    let open BLS12_381.G1 in
    vk_gamma_b * input_x + vk_gamma_c * input_y + vk_gamma_a
  in
  let list =
      let (~-) = BLS12_381.G1.(~-) in
      [ (proof_a, proof_b);
        (~- vk_x, vk_gamma);
        (~- proof_c, vk_delta);
        (~- vk_a, vk_b) ]
    in
    assert (BLS12_381.pairing_check list);
    [], ()

詳しくは 記事 を。

Sapling

Sapling

Zcash の Sapling protocol を導入

スマートコントラクトでゼロ知識証明を使った
匿名トランザクションが可能に。

Tezos本体が匿名コインになるわけでは無い!!

Shielded tez

sapling_contract.tz: 1 shielded tez = 1 tez の匿名コイン

コントラクト内のトランザクションは匿名化

このコントラクトをデプロイすればオレオレ匿名 tez を作れる

tezos-client sapling コマンド

Sapling トランザクションを作成してコントラクトに適用

# 匿名アカウント作成
$ tezos-client sapling gen key <zname>

# 使用するスマートコントラクトを指定
$ tezos-client sapling use key <zname> for contract <contract>

# 匿名アドレスを作成
$ tezos-client sapling gen address <zname>

# 通常アカウントから匿名アドレスへの送金 1 tez = 1 shielded tez
$ tezos-client sapling shield <amount> from <name> to <zaddress>

# 匿名アドレスの残高照会
$ tezos-client sapling get balance for <zname> in contract <contract>

# 匿名送金の作成
$ tezos-client sapling forge transaction <amount> from <zname> to <zaddress> \
    using <contract>

# 送金命令の送信
$ tezos-client sapling submit <file> from <account> using <contract>

GUIウォレットへの対応が待たれる

Sapling in Michelson

type n sapling_state (* 状態。 n は整数定数 *)
type n sapling_transaction (* 状態変更 *)

val SAPLING_EMPTY_STATE n : n sapling_state
(** 空の状態 *)

val SAPLING_VERIFY_UPDATE n : 
      n sapling_transaction -> n sapling_state -> (int * n sapling_state) option
(** State の元で transaction が valid であれば、
    transaction を適用して新しい state を返す *)

Michelson レベルでは Sapling 関連データは抽象化され、
検証命令しか存在しない。

Transaction のためのゼロ知識証明はオフチェーンで作成。
ex. tezos-client sapling コマンド

Sapling の応用

Sapling 命令は Shielded tez のためだけではなく、
匿名トランザクションを使った FT dApps の実現に利用できる

Sapling in SCaml

module Sapling : sig
  type 'sz state = 'sz sapling_state
  type 'sz transaction = 'sz sapling_transaction
  val empty_state : 'sz -> 'sz state
  val verify_update : 'sz transaction -> 'sz state -> (int * 'sz state) option
end

Michelson の型では 'sz には整数が来る。
でも、定数だし、依存型というわけでもない。

多層バリアントを使って数値から型への変換を行う。
サイズ 8 の空 state を作るには:

Saplint.empty_state `n8 : [> `n8] state

Tezos 008 Edo

主な新機能:

  • Ticket
  • BLS12-381
  • Sapling

他にも Keccak, SHA3 命令の追加、 PVSS などある

Tezos 009 F???

ベーキングアカウント

コントラクト呼び出し規約変更

ガス量最適化

などなど。現在プロトコル改定提案に向けてテスト中。

求人情報(予定)

プロジェクトマネージメントできる方

PM経験 + 英語 + OCaml + 関西圏

詳細は近日中に https://dailambda.jp

Tezos: 008 Edo protocol
DaiLambda, Inc.
Jun FURUSE/古瀬 淳
Tezos Tokyo Meetup, 2021-02-26