記事の内容
エンタープライズイーサリアムと呼ばれているQuorumに関する記事です。
Quorumの特徴的な機能の1つにPrivacy Manager(Tessera)という機能があります。
これはプライベートトランザクションに対するアクセス制御や暗号化データを他ノードへ送信する機能を有しています。
ただし、この機能はQuorumの標準機能ではなく、Quorumに追加してインストールする必要があります。
公式ドキュメントが若干分かり辛いのと、情報が少し古かったので、やってみたことをメモします。
参考
公式ドキュメント
こちらのPrivacy Managerのインストール部分を実施します
事前準備
JDKのインストール
この記事ではTesseraのバージョンは現時点の最新版である0.10.4を使用しますが、0.10.3以降のバージョンはJavaのバージョンが11以降である必要があります。
0.10.2以前はJava 8になります。
その為、この手順ではJDK 11をインストールしておく必要があります。
VMのメモリ
私はvagrantでCentOS7.4を動かしていましたが、デフォルトのメモリサイズ(1024MB)だと暗号化、復号処理でめちゃくちゃ時間がかかります。
4096MBだとサクサク進めることが出来ました。
Tesseraの環境構築手順
1.Quorumのインストールとtessera.jarのダウンロード
まずはQuorumのインストールをします。
$ git clone https://github.com/jpmorganchase/quorum.git
$ cd quorum
$ make all
$ export PATH=$(pwd)/build/bin:$PATH
次にtessera.jarをダウンロードします。
ダウンロード先はこちらになります。
tessera.jar
とりあえず、現時点の最新版jarをダウンロードします。
$ wget https://oss.sonatype.org/service/local/repositories/releases/content/com/jpmorgan/quorum/tessera-app/0.10.4/tessera-app-0.10.4-app.jar
$ mv tessera-app-0.10.4-app.jar tessera.jar
2.Keyの作成
tessera.jarの準備が出来たので、Keyを作成します。
※メモリサイズが小さいとめちゃくちゃ時間がかかります。私はメモリサイズ 1024MBで30分ほど時間がかかりました。
$ mkdir new-node-1t
$ cd new-node-1t
$ java -jar ../tessera.jar -keygen -filename new-node-1
Enter a password if you want to lock the private key or leave blank
Please re-enter the password (or lack of) to confirm
2020-04-08 02:20:52.712 [main] INFO com.quorum.tessera.nacl.jnacl.Jnacl - Generating new keypair...
2020-04-08 02:20:52.846 [main] INFO com.quorum.tessera.nacl.jnacl.Jnacl - Generated public key PublicKey[+fb03AOJiZNiJkHwP24J+Wn1XslaTqZRRewo+FBNsl0=] and private key REDACTED
2020-04-08 02:20:52.868 [main] INFO c.q.t.config.keys.KeyEncryptorImpl - Encrypting a private key
2020-04-08 02:49:29.354 [main] INFO c.q.t.config.keys.KeyEncryptorImpl - Private key encrypted
2020-04-08 02:49:29.534 [main] INFO c.q.t.k.generation.FileKeyGenerator - Newly generated private key has been encrypted
2020-04-08 02:49:32.675 [main] INFO c.q.t.k.generation.FileKeyGenerator - Saved public key to /home/quorum/new-node-1t/new-node-1.pub
2020-04-08 02:49:32.676 [main] INFO c.q.t.k.generation.FileKeyGenerator - Saved private key to /home/quorum/new-node-1t/new-node-1.key
処理が完了するとディレクトリに以下の2ファイルが作成されています。
$ ls
new-node-1.key new-node-1.pub
$ cat new-node-1.pub
+fb03AOJiZNiJkHwP24J+Wn1XslaTqZRRewo+FBNsl0=
$ cat new-node-1.key
{
"type" : "argon2sbox",
"data" : {
"snonce" : "Jv0b8BKkTUCT8G1uSuTUzOAvF3ZaO8E9",
"asalt" : "JzNHviN1FttUBBSKNfv6Lw==",
"sbox" : "nTMY1iBYF+0OXeB+qlSpmmuYC4extxsu3YL6pLDaqAE798fLhcAB+YEuTwxVRlXu",
"aopts" : {
"variant" : "i",
"iterations" : 10,
"memory" : 1048576,
"parallelism" : 4
}
}
}
3.コンフィグファイルを作成する
「yourpath」の部分を作業中のディレクトリに変更します
$ vi config.json
{
"useWhiteList": false,
"jdbc": {
"username": "sa",
"password": "",
"url": "jdbc:h2:/yourpath/new-node-1t/db1;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0",
"autoCreateTables": true
},
"serverConfigs":[
{
"app":"ThirdParty",
"enabled": true,
"serverAddress": "http://localhost:9081",
"communicationType" : "REST"
},
{
"app":"Q2T",
"enabled": true,
"serverAddress":"unix:/yourpath/new-node-1t/tm.ipc",
"communicationType" : "REST"
},
{
"app":"P2P",
"enabled": true,
"serverAddress":"http://localhost:9001",
"sslConfig": {
"tls": "OFF"
},
"communicationType" : "REST"
}
],
"peer": [
{
"url": "http://localhost:9001"
},
{
"url": "http://localhost:9003"
}
],
"keys": {
"passwords": [],
"keyData": [
{
"privateKeyPath": "/yourpath/new-node-1t/new-node-1.key",
"publicKeyPath": "/yourpath/new-node-1t/new-node-1.pub"
}
]
},
"alwaysSendTo": []
}
4.他のノードを作成する
他のノードを作成する場合は上記の2.、3.の手順を繰り返します。
$ mkdir new-node-2t
$ cd new-node-2t
$ java -jar ../tessera.jar -keygen -filename new-node-2
Enter a password if you want to lock the private key or leave blank
Please re-enter the password (or lack of) to confirm
2020-04-08 04:09:51.205 [main] INFO c.q.t.config.keys.KeyEncryptorImpl - Private key encrypted
2020-04-08 04:09:51.432 [main] INFO c.q.t.k.generation.FileKeyGenerator - Newly generated private key has been encrypted
2020-04-08 04:09:54.881 [main] INFO c.q.t.k.generation.FileKeyGenerator - Saved public key to /home/quorum/new-node-2t/new-node-2.pub
2020-04-08 04:09:54.882 [main] INFO c.q.t.k.generation.FileKeyGenerator - Saved private key to /home/quorum/new-node-2t/new-node-2.key
コンフィグファイルを作成する
ドキュメントだとnode1のconfig.jsonをコピーするようになっていますが、新規ファイルを作って、以下の内容を張り付けた方が良いかなと思います。
と言うのも、よく見るとyourpathの部分以外にもポート番号など変わっているところがあるので、単純にpathの修正をするだけの方が間違えにくいかなと思います。
$ vi config.json
{
"useWhiteList": false,
"jdbc": {
"username": "sa",
"password": "",
"url": "jdbc:h2:yourpath/new-node-2t/db1;MODE=Oracle;TRACE_LEVEL_SYSTEM_OUT=0",
"autoCreateTables": true
},
"serverConfigs":[
{
"app":"ThirdParty",
"enabled": true,
"serverAddress": "http://localhost:9083",
"communicationType" : "REST"
},
{
"app":"Q2T",
"enabled": true,
"serverAddress":"unix:/yourpath/new-node-2t/tm.ipc",
"communicationType" : "REST"
},
{
"app":"P2P",
"enabled": true,
"serverAddress":"http://localhost:9003",
"sslConfig": {
"tls": "OFF"
},
"communicationType" : "REST"
}
],
"peer": [
{
"url": "http://localhost:9001"
},
{
"url": "http://localhost:9003"
}
],
"keys": {
"passwords": [],
"keyData": [
{
"privateKeyPath": "/yourpath/new-node-2t/new-node-2.key",
"publicKeyPath": "/yourpath/new-node-2t/new-node-2.pub"
}
]
},
"alwaysSendTo": []
}
5.Tesseraノードの起動
ドキュメントに従いノードを2つ作ったのでそれぞれバックグラウンドで起動してみます。
$ java -jar ../tessera.jar --configfile config.json >> tessera.log 2>&1 &
[1] 15973
$ Enter
[1]+ Stopped java -jar ../tessera.jar --configfile config.json >> tessera.log 2>&1
プロセスが止まってしまいました。ログを確認してみます。
Password for key[0] missing or invalid.
Attempt 1 of 2. Enter a password for the key
パスワードの入力を求められてました。
パスワードのファイルに書いておくのはどうかと思いますが、config.jsonに入力してみます。
"keys": {
"passwords": ["password"],
"keyData": [
{
"privateKeyPath": "/home/quorum/new-node-2t/new-node-2.key",
"publicKeyPath": "/home/quorum/new-node-2t/new-node-2.pub"
}
]
},
"passwords"にKey作成時に設定したパスワードを入力し、再度ノードを起動してみます。
2020-04-08 06:35:58.992 [main] INFO c.q.t.config.keys.KeyEncryptorImpl - Decrypting private key
2020-04-08 07:04:53.553 [main] INFO c.q.t.config.keys.KeyEncryptorImpl - Decrypted private key
2020-04-08 07:04:57.438 [main] WARN c.q.t.c.c.NoUnmatchedElementsValidator - Ignoring unknown/unmatched json element: enabled
2020-04-08 07:04:57.466 [main] WARN c.q.t.c.c.NoUnmatchedElementsValidator - Ignoring unknown/unmatched json element: enabled
2020-04-08 07:04:57.472 [main] WARN c.q.t.c.c.NoUnmatchedElementsValidator - Ignoring unknown/unmatched json element: enabled
Config validation issue: keys.passwords For security reasons, passwords should not be provided directly in the config. Provide them in a separate file with "passwordFile" or at the CLI prompt during node startup.
エラーが出ました。パスワードは別ファイルに分けるかCLIから入力しろと怒られています。当たり前ですね。
tessera.jarの--helpを見てみましたが"passwordFile"に関するオプションが出てこなかったのでCLIから入力することにします。
$ java -jar ../tessera.jar --help
Usage:
Tessera private transaction manager for Quorum
tessera [OPTIONS] [COMMAND]
Description:
Start a Tessera node. Other commands exist to manage Tessera encryption keys
Options:
-configfile, --configfile <config>
Path to node configuration file
-o, --override KEY=VALUE
-pidfile, --pidfile <pidFilePath>
the path to write the PID to
Commands:
help Displays help information about the specified
command
admin Admin operations for a Tessera node
keygen, -keygen Generate Tessera encryption keys
keyupdate, -updatepassword Update the password for a key
一旦、「new-node-1t」、「new-node-2t」配下のconfig.jsonからpasswordを削除し、元の状態に戻します。
バックグラウンド実行だとパスワードの入力が出来ないので、起動方法を変えます。
コンソールを2つ起動し、「new-node-1t」、「new-node-2t」それぞれで実行します。
$ java -jar ../tessera.jar --configfile config.json
2つとも起動が完了するとノード1、ノード2で互いに通信が始まります。
正常に起動が完了していると作業中のディレクトリ配下にいくつかファイルが作成されています。
$ ls
config.json db1.mv.db db1.trace.db new-node-1.key new-node-1.pub tessera.log tm.ipc
この「tm.ipc」を使ってgethの起動を行います。
また、「new-node-1.pub」は次の工程で使いますので、中身を確認しておきます。
$ cat new-node-1.pub
+fb03AOJiZNiJkHwP24J+Wn1XslaTqZRRewo+FBNsl0=
6.Quorumノードの起動
公式ドキュメントだとここからの手順がかなり分かり辛いです。
cd ..
$vi startnode1.sh
... paste below
# !/bin/bash
PRIVATE_CONFIG=/yourpath/new-node-1t/tm.ipc nohup geth --datadir new-node-1 --nodiscover --verbosity 5 --networkid 31337 --raft --raftport 50000 --rpc --rpcaddr 0.0.0.0 --rpcport 22000 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft --emitcheckpoints --port 21000 >> node.log 2>&1 &
$vim startnode2.sh
... paste below
# !/bin/bash
PRIVATE_CONFIG=/yourpath/new-node-2t/tm.ipc nohup geth --datadir new-node-2 --nodiscover --verbosity 5 --networkid 31337 --raft --raftport 50001 --raftjoinexisting 2 --rpc --rpcaddr 0.0.0.0 --rpcport 22001 --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,raft --emitcheckpoints --port 21001 2>>node2.log &
$ chmod +x startnode1.sh startnode2.sh
$ ./startnode1.sh
$ ./startnode2.sh
シェルを2つ作成します。「yourpath」の部分は作成中の環境に合わせて修正します。
次にコントラクトデプロイ用のスクリプトを作成します。
a = eth.accounts[0]
web3.eth.defaultAccount = a;
// abi and bytecode generated from simplestorage.sol:
// > solcjs --bin --abi simplestorage.sol
var abi = [{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"initVal","type":"uint256"}],"payable":false,"type":"constructor"}];
var bytecode = "0x6060604052341561000f57600080fd5b604051602080610149833981016040528080519060200190919050505b806000819055505b505b610104806100456000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605157806360fe47b11460775780636d4ce63c146097575b600080fd5b3415605b57600080fd5b606160bd565b6040518082815260200191505060405180910390f35b3415608157600080fd5b6095600480803590602001909190505060c3565b005b341560a157600080fd5b60a760ce565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b6000805490505b905600a165627a7a72305820d5851baab720bba574474de3d09dbeaabc674a15f4dd93b974908476542c23f00029";
var simpleContract = web3.eth.contract(abi);
var simple = simpleContract.new(42, {from:web3.eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["+fb03AOJiZNiJkHwP24J+Wn1XslaTqZRRewo+FBNsl0="]}, function(e, contract) {
if (e) {
console.log("err creating contract", e);
} else {
if (!contract.address) {
console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");
} else {
console.log("Contract mined! Address: " + contract.address);
console.log(contract);
}
}
});
ここで注意が必要なのが、コード真ん中あたりに"privateFor"とあるので、そこに設定されている値を前の手順で確認した「new-node-1.pub」の値に更新します。
最後にGethのコンソールで動作確認をします。
$ geth attach new-node-1/geth.ipc
Welcome to the Geth JavaScript console!
instance: Geth/v1.8.18-stable-b9e886c4(quorum-v2.5.0)/linux-amd64/go1.11.5
coinbase: 0x7b752f197c4272419b2d5d7a158314049678a43c
at block: 0 (Thu, 01 Jan 1970 09:00:00 JST)
datadir: /home/quorum/fromscratch/new-node-1
modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 raft:1.0 rpc:1.0 txpool:1.0 web3:1.0
> personal.unlockAccount(eth.accounts[0])
Unlock account 0x7b752f197c4272419b2d5d7a158314049678a43c
Passphrase:
true
> loadScript("private-contract.js")
Contract transaction send: TransactionHash: 0x4a4b7655403ede633832c616aea1f2897e510ee14d5c3f17efaa6ed3d0b2e181 waiting to be mined...
true
この様にエラーがでなければ成功です。
トランザクションの情報も確認してみます。
eth.getTransaction("0x4a4b7655403ede633832c616aea1f2897e510ee14d5c3f17efaa6ed3d0b2e181")
{
blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
blockNumber: null,
from: "0x7b752f197c4272419b2d5d7a158314049678a43c",
gas: 4700000,
gasPrice: 0,
hash: "0x4a4b7655403ede633832c616aea1f2897e510ee14d5c3f17efaa6ed3d0b2e181",
input: "0x7f543e0f343deff9de4bcf6c09ffc6729ce978e496de4ae412a8613b0de7ae2a9065a64e31274e2cfbd469d7008a288aca23267b8540439ce8818c61f0cc6f79",
nonce: 0,
r: "0x2042bbe7edd4b3db938710ccca72cd25ef743b0bf1adfede5def2f8f921b96ae",
s: "0x104dcd9ef820c945f2fb00ee000bca2068e304136991636f8543b809ee1cafa8",
to: null,
transactionIndex: 0,
v: "0x25",
value: 0
}
おわりに
QuorumのPrivacy Manager(Tessera)の環境構築をしてみました。
ドキュメントに沿ってやってみましたが、まだまだどうやって使うのか、何が出来るのかが明確に理解出来ていません。
エンタープライズ領域でブロックチェーンを使う場合、Fabricと並びQuorumも選択肢として挙げられると思います。
そこでQuorumを使って何が出来るのか?Ethereumと明確にどう違うのか?というポイントをこれから理解を深めていきたいと考えてます。
何か有益な使い方などあればメモ程度でどんどんアウトプットしていきます。
※AzureのBlockchain Workbenchで環境を作ると標準でQuorumになり、Privacy Managerも使えるようです。