2019.10.04

Ledger のハードウェアウォレットでトランザクションにサインする

こんにちは。次世代システム研究室のT.M. です。

はじめに

ブロックチェーンを利用するためには、秘密鍵をきちんと管理する必要があります。秘密鍵を管理する良い方法の一つとして、ハードウェアウォレットで管理する、という方法があります。ハードウェアウォレットとして、主要なものとしてTrezor とLedger があります。それらのハードウェアウォレットの使い方としては、各ウォレットを利用するためのソフトウェアを用いることが多いかと思います。また、Ethereum ならばMetaMask を用いる、という方法もあります。しかし、アプリケーションの要件により、それらの利用ができない場合は、ハードウェアウォレットを操作するアプリケーションを開発する必要があります。本稿では、ハードウェアウォレットを操作するアプリケーションの開発方法を説明するために、Ledger Nano S を用いてEthereum の任意のトランザクションにサインをするアプリケーションをNode.js で実装する。

秘密鍵作成

Ledger Nano S 初期設定を行い、Ethereum で利用できる秘密鍵を作成する必要があるが、本稿の目的ではなく、様々なサイトで解説されているので、省略する。

Ledger Nano S へのアクセス

最初にLedger Nano S へアクセスできることを確認するために、ウォレットにある秘密鍵のアドレスを取得する。

Transport 作成

USB によりアクセスするためのTransport のライブラリをインストールする。
npm install @ledgerhq/hw-transport-node-hid regenerator-runtime
import 'regenerator-runtime';
import Transport from '@ledgerhq/hw-transport-node-hid';
const transport = await Transport.create()
ポイントとしては、@ledgerhq/hw-transport-node-hid のimport より前にregenerator-runtime をimport しておかないとエラーが発生します。Transport の作成は以上です。

Ethereum API オブジェクト作成

ハードウェアウォレットによりEthereum API を叩くためのオブジェクトを作成する。
npm install @ledgerhq/hw-app-eth
import Eth from "@ledgerhq/hw-app-eth";
const eth = new Eth(transport)
Ethereum API オブジェクトの作成は以上です。

getAddress 実行

ハードウェアウォレットで管理されている秘密鍵のアドレスを取得する。
const address = await eth.getAddress("44'/60'/0'/0/0")
引数にBIP 32のパスを指定することでアドレスを取得することができる。

トランザクションにサイン

任意のトランザクションにサインを行う。

トランザクション作成

ethereumjs-tx を用いてトランザクションを作成する。
npm install ethereumjs-tx
import { Transaction } from 'ethereumjs-tx';
const transaction = new Transaction({ gasLimit: 100000, gasPrice: 20000000000, to: '0x0000000000000000000000000000000000000001', nonce: 0, value: 0, data: '0x' });
to をコントラクトアドレス、data に実行するコントラクトの関数のエンコードしたものを入れると、コントラクトのトランザクションになります。また、gasLimit を21000、to をEOA のアドレス、value を送金したいETH にすると、ETH を送金するトランザクションになります。

RawTransaction 作成

トランザクションにサインをするためには、16進数のRawTransaction に変換する必要があります。
import { encode } from 'rlp';
const rawTransaction = encode(transaction.raw.slice(0, -3)).toString('hex');
ポイントとしては、後ろから3つのオブジェクトを削除したものをRLP でencode することです。transaction.raw には、サイン、つまりv, r, s が含まれており、そのままencode してしまうと、v, r, s がnull でencode されてしまい、正しいRawTransaction ではありません。そこで、transaction.raw の後ろから3つのオブジェクトを削除します。

サイン

ハードウェアウォレットでサインをする。
const { v, r, s } = await eth.signTransaction("44'/60'/0'/0/0", rawTransaction);
transaction.v = `0x${v}`;
transaction.r = `0x${r}`;
transaction.s = `0x${s}`;
const signedTransaction = `0x${transaction.serialize().toString('hex'))`;
signTransaction の第一引数はBIP32のパスであり、第二引数はサインをしたいトランザクションです。サインの結果として、v, r, s が返ってくるので、0x をつけて、トランザクションに入れれば良い。

さいごに

基本的にハードウェアウォレットはハードウェアウォレット用のアプリケーションを利用するのが良い。しかし、要件によっては自作する必要がある。ただ、自作されることが少ないため、情報が少なく、バグがあったり、ドキュメントに詳細に書かれていないところがあったりして、開発に詰まる点が多い。本稿が助けになれば、嬉しく思います。

次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。インフラ設計、構築経験者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

皆さんのご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ