Hyperledger Besuの鍵管理/署名サービスEthSignerを使ってみた

こんにちは、日立ソリューションズの小島です。

私たちのチームでは、エンタープライズEthereumクライアントの[Hyperledger Besu](https://www.hyperledger.org/use/besu)に関する記事を投稿しています。
前回の記事では、Hyperledger Besuのサンプルの紹介・解説を行いました。

https://qiita.com/tanarchitect/items/4f4786512b8004ea1a2e

この記事では、Hyperledger Besuの特徴的なサービスの一つであるEthSignerについて紹介します。

# EthSignerとは？
[EthSigner](https://docs.ethsigner.consensys.net)は、Hyperledger Besuが有する鍵管理/署名サービスです。
EthSignerは、アプリケーションがブロックチェーンネットワークへ対してトランザクションを投げる際に中継を行います。
アプリケーションから受信したトランザクションの送信者に応じた鍵を選択し、受信したトランザクションへ署名を付与、ブロックチェーンネットワークへ中継します。


EthSignerを使用する場合のトランザクションのプロセスは以下の図の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1011535/858cd391-7e2d-bd3a-f2bb-525436057301.png)




従来のEthereum(Gethなど)では、アプリケーション側で鍵管理と署名を行っていましたが、Hyperledger BesuではEthSignerにその役割を任せることができます。
これにより、アプリケーション側では鍵管理と署名の手間から解放されます。
（EthSignerを使わずに、従来通りアプリケーション側で署名し、直接ブロックチェーンネットワークへトランザクションを投入することも可能です）

また鍵の保管場所として、EthSignerの動作している環境もしくはクラウドサービスを選択できます。
鍵をEthSignerの動作している環境に保存する際は、ファイルをV3 keystoreという形式で暗号化して保存します。

参考：　https://docs.ethsigner.consensys.net/en/stable/Concepts/Overview/

# 前提環境
この記事では以下の環境で動作させてみました。

|#  |項目  |バージョン  |
|---|---|---|
|1  | OS |Ubuntu 18.04 LTS  |
|2  | Node, npm|12.18.03, 6.14.4  |
|3  | Docker|19.03.12  |
|4  | docker-compose| 1.25.0 |

# 早速使ってみた
今回は、簡単に動作確認を行うため、[ethash](https://eth.wiki/en/concepts/ethash/ethash)(Proof of Work)を用いた最小構成のHyperledger Besuネットワークを作成します。


作成するネットワークの構成は以下の図の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1011535/faf67dd9-ea54-b7e5-600a-014d438299fb.png)


ブロックチェーンネットワークを構成する各ノードの説明は以下の表の通りです。

|#  |項目  |説明  |
|---|---|---|
|1  | bootnode | ブロックチェーンネットワークの起動と通信の取りまとめを行うノードです。 |
|2  | rpcnode|RPCリクエストを受け付けるノードです。今回は、EthSignerからの通信を受け付けます。 |
|3  | minernode|マイニングを行うノードです。  |

以下のように適当なワークフォルダを作成し、以降はこの下で作業を行います。

```
~$ mkdir ethsigner-trial
~$ cd ethsigner-trial
```

これ以降の手順で、以下のようなファイルを作成します。

```
.
├── ethsigner
│   ├── docker-compose.yaml
│   ├── keyfile
│   └── passwordfile
├── js
│   ├── createKey.js
│   ├── keyPair.js
│   ├── node_modules/
│   ├── package.json
│   ├── package-lock.json
│   └── sendTransaction.js
└── node
    ├── bootnode-privatekey
    ├── docker-compose.yaml
    └── genesis.json
```

### npmを用いたパッケージのインストール
npmを用いてパッケージをインストールします。特にethereumjs-walletは、バージョンに注意してください。

```
~/ethsigner-trial/js$ npm init -y
~/ethsigner-trial/js$ npm install ethereumjs-wallet@0.6.4
~/ethsigner-trial/js$ npm install web3@1.3.0
```

### 秘密鍵・公開鍵・アドレスの生成

はじめに、動作確認に使用するための、ethereumの秘密鍵・公開鍵・アドレスを生成します。
今回は、[ethereumjs-wallet](https://github.com/ethereumjs/ethereumjs-wallet)ライブラリ(v0.6.4)を用います。

```js:ethsigner-trial/js/keyPair.js
var Wallet = require('ethereumjs-wallet');
var addressData = Wallet.generate();
console.log("key: " + addressData.getPrivateKeyString().slice(2));
console.log("pub: " + addressData.getPublicKeyString().slice(2));
console.log("add: " + addressData.getAddressString().slice(2));
```

上記の`keyPair.js`を実行します。ここで生成した鍵ペアをbootnodeの鍵として用います。後で使うので控えておいてください。

```
~/ethsigner-trial/js$ node keyPair.js
key: 4322291ba51ce20c285206ef0756c35df615d573741f05db6b79b1c8c21b117e
pub: db69d24a1a18162e7ab56d59a8f8964a684c13f2d169e2db8754ef5db9d30131ec80674264745591843f19f920f35a418e6ddc2e06902f974219f93e350e1d87
add: b924fd952f22ad352e3c9b0b807859122535632a
```

もう一度`keyPair.js`を実行します。ここで生成したアドレスを[coinbase](https://besu.hyperledger.org/en/stable/Reference/CLI/CLI-Syntax/#miner-coinbase)(マイニング報酬を受け取るアドレス)に設定し、またEthSignerの署名のための鍵としても使用します。こちらも後で使うので控えておいてください。

```
~/ethsigner-trial/js$ node keyPair.js
key: c6a1a93a43128ea1acb63fb5874fdc221604c7163e6412973b0e2920918528db
pub: 2fd29b7362b88558b5e39e30e7a74752c96d446c9f4a08ec3c53f47a9a13d827d2f2eee3e909bdc67b3ee95574cfa63dfdecf0162c7f86b88c6b3e0d744ce208
add: 3bb6e43b428c55c2601ad15b71117280335744f8
```


### ブロックチェーンネットワークの設定・起動
それでは、Hyperledger Besuを用いたブロックチェーンネットワークの設定をしていきます。

まずは、bootnodeの秘密鍵ファイルを作成します。`bootnode-privatekey`というファイルに、先ほど一度目に作成した秘密鍵を貼り付けます。

```:ethsigner-trial/node/bootnode-privatekey
4322291ba51ce20c285206ef0756c35df615d573741f05db6b79b1c8c21b117e
```

<br>

次に、[genesisファイル](https://besu.hyperledger.org/en/stable/HowTo/Configure/Genesis-File/)を作成します。genesisファイルは、チェーン上の最初のブロックを定義します。

```json:ethsigner-trial/node/genesis.json
{
  "config": {
      "constantinoplefixblock": 0,
      "ethash": {
        "fixeddifficulty": 1000
      },
       "chainID": 2018
   },
  "nonce": "0x0",
  "gasLimit": "0x1000000",
  "difficulty": "0x10000"
  }
}
```
・`config`に`ethash`を指定することで、コンセンサスアルゴリズム`ethash`とします。
・`config`に`chainID`を指定します。この値を後ほどEthSignerの設定に用います。

<br>
次に、dockerのcomposeファイルを作成します。

```yaml:ethsigner-trial/node/docker-compose.yaml
---
version: '3.4'
services:
  bootnode:
    image: hyperledger/besu:21.10.2
    user: "0:0"
    command: [
      "--bootnodes",
      "--genesis-file=/config/genesis.json",
      "--p2p-host=172.16.239.11",
      "--data-path=/opt/besu/data",
      "--host-allowlist=all"]
    volumes:
      - ./bootnode-privatekey:/opt/besu/data/key
      - ./genesis.json:/config/genesis.json
    ports:
      - 30303:30303/tcp
      - 30303:30303/udp
    networks:
      besu-network:
        ipv4_address: 172.16.239.11


  minernode:
    image: hyperledger/besu:21.10.2
    user: "0:0"
    command: [
      "--bootnodes=enode://db69d24a1a18162e7ab56d59a8f8964a684c13f2d169e2db8754ef5db9d30131ec80674264745591843f19f920f35a418e6ddc2e06902f974219f93e350e1d87@172.16.239.11:30303",
      "--genesis-file=/config/genesis.json",
      "--p2p-host=172.16.239.12",
      "--data-path=/opt/besu/data",
      "--host-allowlist=all",
      "--miner-enabled=true",
      "--miner-coinbase=3bb6e43b428c55c2601ad15b71117280335744f8"]
    volumes:
      - ./genesis.json:/config/genesis.json
    depends_on:
      - bootnode
    networks:
      besu-network:
        ipv4_address: 172.16.239.12


  rpcnode:
    image: hyperledger/besu:21.10.2
    user: "0:0"
    command: [
      "--bootnodes=enode://db69d24a1a18162e7ab56d59a8f8964a684c13f2d169e2db8754ef5db9d30131ec80674264745591843f19f920f35a418e6ddc2e06902f974219f93e350e1d87@172.16.239.11:30303",
      "--genesis-file=/config/genesis.json",
      "--p2p-host=172.16.239.13",
      "--data-path=/opt/besu/data",
      "--host-allowlist=all",
      "--rpc-http-enabled=true",
      "--rpc-http-host=0.0.0.0",
      "--rpc-http-port=8545",
      "--rpc-http-cors-origins=all"]
    volumes:
      - ./genesis.json:/config/genesis.json
    depends_on:
      - bootnode
    ports:
      - 8545:8545
    networks:
      besu-network:
        ipv4_address: 172.16.239.13


networks:
  besu-network:
    external: true
```
・minernodeとrpcnodeの`command`の[--bootnodes](https://besu.hyperledger.org/en/stable/Reference/CLI/CLI-Syntax/#bootnodes)に`enode://<bootnodeの公開鍵>@<bootnodeのipアドレス>:<bootnodeのポート番号>`を指定します。`<bootnodeの公開鍵>`は、`keyPair.js`で1回目に生成した公開鍵です。
・minernodeの`command`の[--miner-coinbase](https://besu.hyperledger.org/en/stable/Reference/CLI/CLI-Syntax/#miner-coinbase)に、先ほど二度目に生成したアドレスを指定します。



<br>
それでは、必要なファイルがそろったので、ブロックチェーンネットワークを起動します。

以下のコマンドを実行し、subnetを指定して`besu-network`という名前のdockerネットワークの作成します。

```
~/ethsigner-trial/node$ docker network create besu-network --subnet=172.16.239.0/24
```

以下のコマンドを実行し、dockerイメージのpullとdockerコンテナの起動を行います。

```
~/ethsigner-trial/node$ docker-compose up -d
```

コンテナの起動を確認します。各コンテナの`STATUS`に`healthy`と表示されていれば、正常に起動しています。

```
~/ethsigner-trial/node$ docker ps
CONTAINER ID        IMAGE                      COMMAND                  CREATED             STATUS                    PORTS                                                               NAMES
20c6d33e703a        hyperledger/besu:21.10.2   "besu --bootnodes=en…"   40 seconds ago      Up 37 seconds (healthy)   8546-8547/tcp, 0.0.0.0:8545->8545/tcp, 30303/tcp                    node_rpcnode_1
a7f0314c7830        hyperledger/besu:21.10.2   "besu --bootnodes=en…"   40 seconds ago      Up 38 seconds (healthy)   8545-8547/tcp, 30303/tcp                                            node_minernode_1
7ac113832492        hyperledger/besu:21.10.2   "besu --bootnodes --…"   42 seconds ago      Up 40 seconds (healthy)   8545-8547/tcp, 0.0.0.0:30303->30303/tcp, 0.0.0.0:30303->30303/udp   node_bootnode_1
```

### EthSignerの設定・起動

それでは、EthSignerの設定をしていきます。
はじめに、EthSignerが署名に用いる秘密鍵を暗号化して鍵ファイルを生成し、暗号化に用いたパスワードをファイルに保存します。

まず、鍵の暗号化に用いるパスワードの記載されたテキストファイルを作成します。
今回は、`passwordfile`というファイル名にします。

```:ethsigner-trial/ethsigner/passwordfile
Password1
```

続いて、[web3.js](https://github.com/ChainSafe/web3.js)ライブラリ(v1.3.0)を用いて、V3 keystoreという形式で秘密鍵を暗号化します。
今回は、coinbaseの秘密鍵を暗号化します。そのために、以下の`createKey.js`スクリプトを用意します。

```js:ethsigner-trial/js/createKey.js

var Web3 = require('web3');
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); // rpcnodeエンドポイントを指定
var V3KeyStore = web3.eth.accounts.encrypt("c6a1a93a43128ea1acb63fb5874fdc221604c7163e6412973b0e2920918528db", "Password1");
console.log(JSON.stringify(V3KeyStore));
process.exit();

```
・web3jsのhttpproviderにはrpcnodeのエンドポイント(`http://localhost:8545`)を指定します。
・秘密鍵は、coinbaseの秘密鍵（`keyPair.js`で2回目に生成したもの）を指定してください。
・パスワードは、先ほどパスワードファイルに記載したパスワードを設定してください。



上記の`createKey.js`を実行し、鍵ファイルの内容をコンソールに出力します。出力される内容は実行する度に異なります。

```
~/ethsigner-trial/js$ node createKeyFile.js
{"version":3,"id":"aad7c3ed-1d37-4453-8d50-42a94885744e","address":"3bb6e43b428c55c2601ad15b71117280335744f8","crypto":{"ciphertext":"f3396008bb66688b85e2b802509cd352f0fd38831c365b8b054f29f4fd8934cb","cipherparams":{"iv":"0e1455d95bd2aa8bcf81c3db47baddbf"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"d1d2aa1f4ecced704f3fa94aa47cf6a27a2493820c7b5032cb72a566ef3742f5","n":8192,"r":8,"p":1},"mac":"f98564bc953d53c8401e1125e07def4c69a36d7acbd8daf0b8f9f8173d83e427"}}
```
出力された内容をコピーしてファイルに貼り付けます。今回は、`keyfile`というファイル名にします。
このファイルが、V3 keystoreファイルです。

```:ethsigner-trial/ethsigner/keyfile
{"version":3,"id":"aad7c3ed-1d37-4453-8d50-42a94885744e","address":"3bb6e43b428c55c2601ad15b71117280335744f8","crypto":{"ciphertext":"f3396008bb66688b85e2b802509cd352f0fd38831c365b8b054f29f4fd8934cb","cipherparams":{"iv":"0e1455d95bd2aa8bcf81c3db47baddbf"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"d1d2aa1f4ecced704f3fa94aa47cf6a27a2493820c7b5032cb72a566ef3742f5","n":8192,"r":8,"p":1},"mac":"f98564bc953d53c8401e1125e07def4c69a36d7acbd8daf0b8f9f8173d83e427"}}
```

参考：　https://docs.ethsigner.consensys.net/en/stable/Tutorials/Start-EthSigner/#create-password-and-key-files
<br>
続いてEthSigner用の`docker-compose.yaml`を作成します。

```yaml:ethsigner-trial/ethsigner/docker-compose.yaml

---
version: '3.4'
services:

  ethsigner:
    image: consensys/ethsigner:21.10.0
    command: [
      "--chain-id=2018",
      "--http-listen-host=0.0.0.0",
      "--downstream-http-port=8545",
      "--downstream-http-host=rpcnode",
      "file-based-signer",
      "-k",
      "/opt/ethsigner/keyfile",
      "-p",
      "/opt/ethsigner/passwordfile"]
    volumes:
      - ./passwordfile:/opt/ethsigner/passwordfile
      - ./keyfile:/opt/ethsigner/keyfile
    ports:
      - 18545:8545/tcp
    networks:
      besu-network:
        ipv4_address: 172.16.239.14


networks:
  besu-network:
    external: true

```

EthSigner用`docker-compose.yaml`に記載した[EthSignerのコマンドラインオプション](https://docs.ethsigner.consensys.net/en/stable/Reference/CLI/CLI-Syntax/)は以下の表の通りです。

|#  |オプション | 説明  |
|---|---|---|
|1  | --chain-id=2018 | 接続先ネットワークの[chain ID](https://besu.hyperledger.org/en/stable/Concepts/NetworkID-And-ChainID/)。genesisファイルに記載したものを指定。|
|2  | --http-listen-host=0.0.0.0|EthSignerのIPアドレス |
|3  | --downstream-http-port=8545|中継先(rpcnode)のポート番号 |
|4  | --downstream-http-host=rpcnode|中継先(rpcnode)のホスト名称  |
|5  | file-based-signer|ファイルベースの鍵を用いる設定  |
|6  | -k /opt/ethsigner/keyfile|暗号化された鍵ファイルを指定 |
|7  | -p /opt/ethsigner/passwordfile|暗号化された鍵に対するパスワードファイルを指定 |

今回は1つのEthSignerに対して[1つの鍵を設定](https://docs.ethsigner.consensys.net/en/stable/HowTo/Use-Single-Signer/)しましたが、1つのEthSignerに対して[複数の鍵を設定](https://docs.ethsigner.consensys.net/en/stable/HowTo/Use-Multiple-Signers/)することも可能です。その場合、EthSignerはトランザクションの送信者に応じた鍵を選択します。
<br>

これでEthSignerの設定ファイルがそろいました。
次に、`docker-compose.yaml`のあるディレクトリで以下のコマンドを実行し、dockerイメージのpullとdockerコンテナの起動を行います。

```
~/ethsigner-trial/ethsigner$ docker-compose up -d
```


コンテナの起動を確認します。

```
~/ethsigner-trial/ethsigner$ docker ps
CONTAINER ID        IMAGE                          COMMAND                  CREATED              STATUS                        PORTS                                                               NAMES
83d6de6a15c5        consensys/ethsigner:21.10.0    "/opt/ethsigner/bin/…"   21 seconds ago       Up 18 seconds                 0.0.0.0:18545->8545/tcp                                             ethsigner_ethsigner_1
20c6d33e703a        hyperledger/besu:21.10.2       "besu --bootnodes=en…"   About a minute ago   Up About a minute (healthy)   8546-8547/tcp, 0.0.0.0:8545->8545/tcp, 30303/tcp                    node_rpcnode_1
a7f0314c7830        hyperledger/besu:21.10.2       "besu --bootnodes=en…"   About a minute ago   Up About a minute (healthy)   8545-8547/tcp, 30303/tcp                                            node_minernode_1
7ac113832492        hyperledger/besu:21.10.2       "besu --bootnodes --…"   About a minute ago   Up About a minute (healthy)   8545-8547/tcp, 0.0.0.0:30303->30303/tcp, 0.0.0.0:30303->30303/udp   node_bootnode_1
```

これでEthSignerを含むブロックチェーンネットワークが立ち上がりました。

### EthSigner動作確認
それでは、EthSignerに対して、未署名のトランザクションを投げてみましょう。

今回のブロックチェーンネットワークでは、唯一のマイナーが大量のETHの報酬をもらいます。その報酬は、coinbaseに指定したアドレスへ貯められます。ブロックチェーンネットワークを起動してしばらくすると大量のETHを持っている状態になります。

そこで動作確認としては、[eth_sendTransaction](https://docs.ethsigner.consensys.net/en/stable/Reference/API-Methods/#eth_sendtransaction)を使用して、coinbaseに指定したアドレスから任意のアドレスへ10ETH送ります。
送金先アドレスには、任意のアドレスを設定してもらって構いませんが、新規に作成する場合は前述の`keyPair.js`を実行してください。

```js:ethsigner-trial/js/sendTransaction.js

var Web3 = require('web3');
var web3 = new Web3();

web3.setProvider(new web3.providers.HttpProvider('http://localhost:18545')); // EthSignerエンドポイントを指定

var account1 = '<送金先アドレス>';
var account2 = '3bb6e43b428c55c2601ad15b71117280335744f8'; // coinbaseアドレス

web3.eth.getBalance(account1, (error, balance) => {
  console.log("account1:" + web3.utils.fromWei(balance, "ether"));
});

var transaction = {
    from: account2,
    to: account1,
    value: web3.utils.toWei("10", "ether")
};

web3.eth.sendTransaction(transaction).then(() => {
  console.log("sendTransaction success");
  web3.eth.getBalance(account1, (error, balance) => {
    console.log("account1:" + web3.utils.fromWei(balance, "ether"));
  });
}).catch(e => {
  console.log(e);
});
```
・`account2`には、coinbaseのアドレス（`keyPair.js`で2回目に生成したもの）を指定してください。


上記の`sendTransaction.js`を実行します。

```
~/ethsigner-trial/js$ node sendTransaction.js
account1:0
sendTransaction success
account1:10
```
`account1`の残高が10ETH増えたため、10ETH送金のトランザクションが成功したことがわかります。
ここではEthSignerが、受け取ったトランザクションに署名を付与し、ブロックチェーンへ転送してくれました。

このように、Hyperledger Besuでは、EthSignerに鍵管理・署名のプロセスを任せることができます。

# 終わりに

さて、Hyperledger Besuの鍵管理・署名サービスであるEthSignerの紹介を行い、実際に動かしてみました。
EthSignerを使うことで、アプリケーションの鍵管理と署名の手間を省けることがわかりました。
非常に手軽で便利なサービスである一方、EthSignerに持たせる鍵ファイルやパスワードファイルのセキュリティ面を十分考慮する必要があると思いました。

また、今回動作確認に使用した最小構成のHyperledger Besuネットワークが参考になればと思います。
Hyperledger Besu関連の日本語の記事はまだまだ少ないので、今後も情報発信を行っていきたいです。