Quorum
特徴:金融機関のニーズを満たす
基本的にはEthereumと同じだが以下の3点が異なり、特徴を構成
アカウントベース、ステートのやりとり
秘匿性:Private Stateの実装 ←Tomato.icon<ここが大事
PrivateとPublicとの二つのStateに分離することで実現
暗号化技術
Ethereumの基本的なBlock/Transactionの仕組みは維持
ファイナリティとスケーラビリティ:Raft-based Consensusアルゴリズムの採用
即時ファイナリティ、短期間のブロック生成間隔を実現
ノード管理:管理者が参加ノード、関係ノードとをそれぞれ制限
Quorum中身わいわい
What is Quorum??
三つのコンポーネントで構成
Quorum Node
gethの派生形として開発
gethとの相違点:
コンセンサス方法がRAFT, or Istanbul BFT
Tomato.icon<今回は、解説等が一番進んでいる且つ早いRAFTを取り上げる
State trieがpublicとprivateとに分けられている
Tomato.icon<privateの部分がquorumの特徴の一つ
Private Transactionにゼロ知識証明を利用
Gasが存在するものの、Gas代は排除
Constellation/Tessera - Transaction Manager:Private Transactionの司令塔
秘匿データの読み取り機能:
Enclaveに暗号化、解読化を指示
private Transactionの場合相手方との情報交換の指令所として機能
Constellation/Tessera - Enclave:暗号/解読部隊
暗号化、解読を実働する部隊
秘密鍵の生成、管理を行う
暗号化、解読とを独立に実行
秘密鍵はvirtural HSMで管理
https://gyazo.com/466d1b6027a67024b02c590930b193ac
Transaction Processing & Privacy
種類:privateTransactionとpublicTransactionとで構成
publicTransaction:Quorum参加コミュニティ内では誰からでもみられるトランザクション
通常のethereum(standard ethereum transaction)と同様の方法でTransfer実行
TransactionとStateとがそれぞれのnodeでvalidation, updateされる
例:マーケットデータの更新、リファレンスデータの更新 等
privateTransaction:指定された者のみ閲覧可能なトランザクション
function privateFor:この関数で設定された公開鍵を持つユーザのみ閲覧可能になる
プロセスは下記で紹介
Private Transactionsプロセス特徴:
トランザクション送付前にコンテンツを暗号化された内容に変換される
これは、Enclaveによって実行される
Transactionをnodeへ伝播させる前に取引相手型transaction managerへ暗号化されたpayloadを送付する
Private transactionの内容を事前にoff-chainで送付して結果だけprivate stateに記録しよう!というのが一言で表したprivate transactionの特徴
この可能性に対応するためにpublicTransactionとprivateTransactionとは別のtrieで保存される
後者はgloballyに同期していなくても成立するように設計
→storageRoot RPCの役割。詳細は下記
Private Transactionプロセス詳細:
https://github.com/jpmorganchase/quorum-docs/raw/master/images/QuorumTransactionProcessing.JPG
前提:PartyAがBに送付。Cは今回関係ないノード
1:AがトランザクションをAのQuorumノードへ送付する
AとBのpublic keyを入力
2:ノードはpayloadへ積み重ねるようにtransferManagerへ連絡
3:Enclaveはsenderとトランザクションが正しいか判定
transferManagerの指示に従って行動
4:EnclaveはまずAの公開鍵をチェック&暗号化
チェックに通れば、送付へ
チェック方法:
1:generating a symmetric key and a random Nonce
2:encrypting the Transaction payload and Nonce with the symmetric key from i.
3:calculating the SHA3-512 hash of the encrypted payload from ii.
4:iterating through the list of Transaction recipients, in this case Parties A and B, and encrypting the symmetric key from i. with the recipient's public key (PGP encryption)
5:returning the encrypted payload from step ii., the hash from step iii. and the encrypted keys (for each recipient) from step iv. to the Transaction Manager
5:transferManagerは4の結果出てきた暗号化されたpayloardと暗号化鍵をpayloadに入れる
その後、BのtransferManagerへ送付
Bは受け取ったのち、Aへその旨を返答
mosa_siru.icon< 注意:この時点ではprivate stateは更新してないよ
mosa_siru.icon < ここがリトライだのノード再構築でもう一度やりなおせるのかが気になる
Tomato.icon<Transactionとして伝播する前にprivateの相手方に出来レース的な形で握っておく、というイメージ?
Tomato.icon<なんでこのプロセスをTransaction前にやっちゃうんだろう、、、
Tomato.icon<先に握っておくという事実が後戻りできない世界では大切
Tomato.icon<どうやって実現するのか。普通に送るだけ説
6:AのtransferMangerはQuorum Nodeにhash化されたpayloadを納入
その際、署名のvvalueを37か38に設定
周囲のノードにこれはprivateTransferであることを示すため
7:Quorum全体のノードへノードが更新されたことを通知
これは、Ethereumの通常の伝播方法と同様
8:当該トランザクションが含まれたブロックがネットワーク全体に伝播
Tomato.icon<ブロック生成方法については下記参照
9:各ノードはブロック内のトランザクションをチェック
vが37,38担っているトランザクションはprivateであると検知
自身に係るトランザクションかを判断
自身に関係ないトランザクションだった場合:10へ
10:privateTransactionに関係しないノードの対処法
notAReceipientを受け取ったのち、検証をスルー
そのばあい、自身のprivate stateは更新しない
11:AとBの対応
自身のトランザクションをnodeが見つけるとmanagerはEnclaveに解読を命じる
Enclave内にある鍵を利用して解読、元々のpayload復活
その情報をtransactionManagerへ返答
12:private Stateの更新
AとBのtransactionManagerは元々のpayloadを獲得
それらをEVMへコントラクト実行を命令
EVMの命令に基づいて各Node内のprivate stateが更新される
Private stateの確認方法のお話し
ギモン:「どうやってPrivate stateの証明をするのん?」
誤った理解:「一部のregulatorや管理者がPrivate Stateも全部持っていてそこが証明するんだよ」
Tomato.icon<私も最初そういう理解だったし、whitepaper v1.0もそういう書き方だったポヨ
akinama.icon<な、なんだってー
正しい理解:「storageRoot RPCによって証明するんや!」
詳細は以下で説明
regulator等がprivate stateを全部持っているのは、第三者執行や1node間のデータ移動時の証明のため
Tomato.icon<詳細はwhitepaperにも書いていなくてやや不安丸
storageRoot RPCとは:private transactionの相手方に対して対象のstate rootを返答する機能
ブロックに暗号化されたprivate transactionと、private trieとが入っていることを利用
storageRoot RPCを投げると指定ブロックの対象nodeのprivate stateを返答する
関係者は互いにstateの中身を知っているからそれで突合すればOKってわけ
Tomato.icon<validationはなされずとも、ブロックの持つ不可変性を利用、、、?
Tomato.icon<Transactionに関係するプレイヤーと関係しないプレイヤーによって確認方法違う説
Consensus
種類は大きく三つ用意:
Raft-based Consensus:
Istanbul BFT Consensus:
Clique POA Consensus:
Raft-based Consensus
一番詳細に解説されており、これが推奨されていると推測
特徴:
ファイナリティが即時に完了
ブロック生成間隔の速さ:50ms
ファイナリティの即時性:nodeの階層化により実現
nodeを階層化:LeaderとFollowerとに分離
Leaderのみがブロック生成を行うように設定
block生成タイミング:Followerがブロックを承認した後にblockが生成
フォークの可能性が無いのでファイナリティが即時達成
ブロック生成間隔の速さへの対応:speculate mintingという方法でレイテンシに対応
詳細プロセス:
minter(leader)の役割詳細
Transactionが届いたら自分のとらんざくしょんプールに入れる
Blockに順次Transactionを入れていく
followerの役割
leaderからのappendblockのメッセージに対して承認する(承認?)
過半数必要
疑問:Tomato.icon<leader nodeの選定方法、交代方法
Tomato.icon<交代時のごにゃごにゃに対応するふにゃふにゃ
Security & Network permissions
Network Permissions: 各ノードがどのノードと繋がることができるかを管理もあるらしい
現在:各ノードで--permissionedこまんどでかんりされている
ブロック作成ノードと承認ノードとの役割分担も可能
<data-dir>/permissioned-nodes.json:各ノードどのノードとつながれるかが載っている
例:
code:permissioned(json)
[
"enode://remoteky1@ip1:port1",
"enode://remoteky1@ip2:port2",
"enode://remoteky1@ip3:port3",
]
実際に7nodes example を触ってみた
やり方
Raftモードで起動 RaftでもIBFTでも動いた
ターミナルを3つ開き、private transaction作るやつ、private transaction受け取るやつ、部外者と使い分ける
作るやつ: docker exec -it quorum-examples_node1_1 geth attach /qdata/dd/geth.ipc
部外者: docker exec -it quorum-examples_node4_1 geth attach /qdata/dd/geth.ipc
受け取るやつ: docker exec -it quorum-examples_node7_1 geth attach /qdata/dd/geth.ipc
作るやつからloadScript('/examples/private-contract.js')を実行
Dockerでノード起動してると思うので、./{consensus}-init.shや./{consensus}-start.shは実行不要
以下、検証結果
private transactionで部外ノードが見えるもの/見えないもの
eth.getTransaction、eth.getTransactionReceiptを関連ノード、部外ノードそれぞれで叩いてみて比べた
見える
Transactionの送付者
fromで送信者のアドレスがわかる
署名情報
v,r,sがわかる
コントラクト実行回数
nonceがわかる
private transactionであること
vからわかる
0x25または0x26ならプライベート (10進数で37or38)
コントラクトアドレス
contractaddressがわかる
見えない
トークン送付量
private.get()を叩いても部外ノードは0を返答するのみ
送付先などコントラクトに付属する内容
eth.getTransactionのinputデータ
inputは暗号化されたデータのHash値が返答される
tomato.icon<Quorumの中に聞いてみたら仮説は当たってた
https://gyazo.com/9eebf7ce0eb0af6c688fdcc1be682806
多分 SHA3-512 hash of the encrypted payloadのこと
Constellation/Tesseraに格納されたデータを検索する時のキーとしてこのinputが使われる
public transactionのinputはpublic ethereumと同様にencodeされたinput data
public/private transaction共に、どのノードでgetTransactionしても同じデータが返答される
このinputデータはQuorumのJSON-RPC APIを利用することで復号できる
code:curl at Node1
{"jsonrpc":"2.0","id":1,"result":"0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029000000000000000000000000000000000000000000000000000000000000002a"}
この結果は暗号化&ハッシュ化する前のデータとなっており、ABIとあわせてdecodeすれば元のデータに変換できる
なお、秘匿化共有先のQuorumノードのAPIを叩いても復号できるが、部外者のノードのAPIをコールした場合は復号データを得られない
code:curl at Node7
{"jsonrpc":"2.0","id":1,"result":"0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029000000000000000000000000000000000000000000000000000000000000002a"}
code:curl at Node4
{"jsonrpc":"2.0","id":1,"result":"0x"}
private transaction実行例
code:eth.getTransactionの結果(どのノードでも同じ結果が得られる)
eth.getTransaction("0x8c027dd895a5f02b02791961d5c0d38892284dd9d6e1630cf4e015a21b3e5f37")
{
blockHash: "0x5f4b17fa4a02a4ef107dd3c8bbc12dfe679aab806733b418ac5a057aca91f49e",
blockNumber: 1,
from: "0xed9d02e382b34818e88b88a309c7fe71e65f419d",
gas: 4700000,
gasPrice: 0,
hash: "0x8c027dd895a5f02b02791961d5c0d38892284dd9d6e1630cf4e015a21b3e5f37",
input: "0x8dcff39f526d3e893656f7351ec9c781ec9550cf34b7492457d2a2e2d2eee1d9a010f4bd37135b16f3a9eb7902aa91651f56369cd4228deeb343acf29276dfea",
nonce: 0,
r: "0xcea2e4efad9f9a7adcc9c8723db1d7ca1e46c0fcc8481c786c3e2a388c5a0f2b",
s: "0x35cca7d1170effd6843cbb5120b88297adb8f60347bb6c9b99ca2dadb13675a9",
to: null,
transactionIndex: 0,
v: "0x26",
value: 0
}
code:eth.getTransactionReceiptの結果(どのノードでも同じ結果が得られる)
eth.getTransactionReceipt("0x8c027dd895a5f02b02791961d5c0d38892284dd9d6e1630cf4e015a21b3e5f37")
{
blockHash: "0x5f4b17fa4a02a4ef107dd3c8bbc12dfe679aab806733b418ac5a057aca91f49e",
blockNumber: 1,
contractAddress: "0x1932c48b2bf8102ba33b4a6b545c32236e342f34",
cumulativeGasUsed: 0,
from: "0xed9d02e382b34818e88b88a309c7fe71e65f419d",
gasUsed: 0,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "0x1",
to: null,
transactionHash: "0x8c027dd895a5f02b02791961d5c0d38892284dd9d6e1630cf4e015a21b3e5f37",
transactionIndex: 0
}
この時得られるinputは暗号化されたデータのハッシュ値であり、decodeできない。
public transaction実行例
code:eth.getTransactionの結果(どのノードでも同じ結果が得られる)
eth.getTransaction('0x84cefc3aab8ce5797dc73c70db604e5c8830fc7c2cf215876eb34fff533e2725')
{
blockHash: "0xdba79ca8696961db05b79a91d08e28314e9fffc3d9742a143f833c7c61dd8bed",
blockNumber: 2,
from: "0xed9d02e382b34818e88b88a309c7fe71e65f419d",
gas: 4700000,
gasPrice: 0,
hash: "0x84cefc3aab8ce5797dc73c70db604e5c8830fc7c2cf215876eb34fff533e2725",
input: "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029000000000000000000000000000000000000000000000000000000000000002a",
nonce: 1,
r: "0x54185138f0f2ec188948db3fe7e65c519c5505e2658736a050dbfb4b8968ec9a",
s: "0x27ca597ef9fa51cea64d2077fdbb6d769e662b8f09b306ebe39fcf83ccde791b",
to: null,
transactionIndex: 0,
v: "0x37",
value: 0
}
code:eth.getTransactionReceiptの結果(どのノードでも同じ結果が得られる)
eth.getTransactionReceipt('0x84cefc3aab8ce5797dc73c70db604e5c8830fc7c2cf215876eb34fff533e2725')
{
blockHash: "0xdba79ca8696961db05b79a91d08e28314e9fffc3d9742a143f833c7c61dd8bed",
blockNumber: 2,
contractAddress: "0x1349f3e1b8d71effb47b840594ff27da7e603d17",
cumulativeGasUsed: 144806,
from: "0xed9d02e382b34818e88b88a309c7fe71e65f419d",
gasUsed: 144806,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "0x1",
to: null,
transactionHash: "0x84cefc3aab8ce5797dc73c70db604e5c8830fc7c2cf215876eb34fff533e2725",
transactionIndex: 0
}
inputをdecodeすると以下のようなデータが得られた
code:decoded_input
{
"method": null,
"types": [
"uint256"
],
"inputs": [
{
"_hex": "0x60ce565b6040518082815260200191505060405180910390f35b60005481565b"
}
],
"names": [
"initVal"
]
}
公開鍵の在りか
docker exec -it quorum-examples_node1_1 /bin/shとかでログインして、以下コマンドで公開鍵を見れる。
code:cat /qdata/tm/tm.pub
BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=/ #
ノード4の公開鍵はoNspPPgszVUFw0qmGFfWwh1uxVUXgvBxleXORHj07g8
お気持ち
Ethereumの理解の深さがモノをいうのがQuorum感
ギモン
Tomato.icon<複数人に対してprivate transaction送ることができるの?
Tomato.icon<できます!by Quorum中の人
Tomato.icon<後から第三者が秘密に入りたいといった場合に関係性再構築できるの?
Tomato.icon<今の所できない!今後更新予定!by Quorum 中の人
Tomato.icon<ゼロ知識の秘匿化の範囲, そもそもゼロ知識?
Tomato.icon<金額もできるし、アドレスそのもののマスキングも可能になった。最近ニュースになったよね!
https://gyazo.com/f6c05078be4ebc1b45201c3a6f4ffa7b
https://gyazo.com/c568b3059f04ca4f30784c4d673e0691
Source