[Ethereum] ウォレット作成

## 概要
geth使ってパブリック・ネットワークのウォレットを作成しようと色々調べていたら、本番と同期した上でpersonalのapi開放しないと無理だったので、もっと敷居の低いwalletを作成する。

### 構成
```
──index.html

```

## やること
1. アドレス + private_keyで送金できるようにする
1. 環境を選択できるようにする
1. keystoreからprivate_keyを複合できるようにする
1. 本番と同期はしない（同期まで２日くらいかかるし数十GBの容量食うし爆音だしでいいことない）

## 接続先
https://infura.io/
必要な情報を送ると、パプリック・ropstenなどのエンドポイントをメールで送ってくれる。
personalのAPIが開放されていないため、送金などは証明書付きで行う必要がある。

## 作成

1.js
必要なファイルをcdnにする
https://qiita.com/takanorig/items/89db46120d2ec171e3d8
- keythereum
- ethereumjs-tx
- web3(ver 1.0)

```html
<script src="https://cdn.rawgit.com/ethereumjs/keythereum/622b3a4b/dist/keythereum.min.js"></script>
<script src="https://cdn.rawgit.com/ethereum/web3.js/1.0/dist/web3.min.js"></script>
<script src="https://cdn.rawgit.com/ethereumjs/browser-builds/2fb69a714afe092b06645286f14b94f41e5c062c/dist/ethereumjs-tx.js"></script>
```

※web3はver1.0（開発中）を利用するため、https://github.com/ethereum/web3.js　
のブランチを『1.0』に切り替えて使用している

### 機能


- アカウントを作成 + keystore作成 + ローカル保存 

```javascript

        async function generate_account() {
            ret = await web3.eth.accounts.create();
            s = web3.eth.accounts.encrypt(ret.privateKey, $("#apw").val());
            var blob = new Blob([JSON.stringify(s)], { "type" : "application/octet-stream" });
            url = URL.createObjectURL(blob);
            window.open(url);
            update_addr();
        }
```

- keystoreからprivateKeyの復元

```javascript

            $('#check').click(function(e) {
                var key = prompt('paste keystore(json_string)');
                var keyObj = JSON.parse(key);
                var password = prompt('password?');
                var privateKey=keythereum.recover(password, keyObj);

                console.log(privateKey.toString('hex'));

                $("#address").val(keyObj.address);
                $("#private_key").val(privateKey.toString('hex'));
            });
```

- 証明書付き送金

```javascript

    async function main() {
        // 送信元のアドレス (Walletの作成手順にあるwallet.getAddress()のメソッドによって得られたaddress)     
        const fromAddress = $("#address").val();
        // 送信先のアドレス
        const toAddress = $("#address_to").val();
        // 送信先のアドレス
        const gass_price = "0x" + Number($("#gass_price").val()).toString(16);
        // 送信先のアドレス
        const gass_limit = "0x" + Number($("#gass_limit").val()).toString(16);

        // 著名付きトランザクションのnonce値として必要なため、送信元アドレスのTransaction countを取得する
        const count = await web3.eth.getTransactionCount("0x" +fromAddress);
        const countHex = `0x` + count.toString(16);
        const money = "0x" + Number(web3.utils.toWei($('#money').val(), 'ether')).toString(16);
        //console.log(money);  
        // 上記のWallet作成手順にある、wallet.getPrivateKeyString()によって得られた、Private keyの値を設定する
        const privateKeyStr = $("#private_key").val();
        let privateKey = new EthJS.Buffer.Buffer(privateKeyStr, 'hex');

        // transactionのパラメータの設定
        const txParams = {
            nonce: countHex,
            // 適切なgasPriceとgasLimitを設定する
//    gasPrice: '0x09184e72a000',
//    gasLimit: '0x30000',
            gasPrice: gass_price,
            gasLimit: gass_limit,
            to: toAddress,
            // 送金する金額
            value: money
        };
        console.log(txParams);
        web3.eth.estimateGas(txParams,function(error, result){
            console.log("gasLimit:");
            console.log(result.toString(16));
        });
        // Transactionオブジェクトの生成
        const tx = new EthJS.Tx(txParams);
        // 秘密鍵での署名 
        tx.sign(privateKey);
        // Transactionオブジェクトをシリアライズして、16進数で表現
        const serializedTx = tx.serialize();
        const rawTx = '0x' + serializedTx.toString('hex');
        // 署名付きトランザクションの送信
        web3.eth.sendSignedTransaction(rawTx)
        .on('transactionHash', function(hash){
          console.log('transactionHash');
          web3.eth.getTransaction(hash).then(alertJSON);
        })
        .on('receipt', function(receipt){
          console.log('receipt');
        })
        .on('confirmation', function(confirmationNumber, receipt){
          console.log('confirmation');
        })
        .on('error', function(error){
          if (error.indexOf("Transaction was not mined within 50 blocks")) {
            return;
          }
          alert(error);
          console.log(error);
        }); 
    }
```
※web3のバグで50秒後に『Transaction was not mined within 50 blocks』のエラーが飛んでくるため、無視する。

## 全体

```html

<html>
<head>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" type="text/css" media="all" />
</head>
<body>

<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdn.rawgit.com/ethereumjs/keythereum/622b3a4b/dist/keythereum.min.js"></script>
<script src="https://cdn.rawgit.com/ethereum/web3.js/1.0/dist/web3.min.js"></script>
<script src="https://cdn.rawgit.com/ethereumjs/browser-builds/2fb69a714afe092b06645286f14b94f41e5c062c/dist/ethereumjs-tx.js"></script>
<script type="text/javascript">
    var price = null;
    const web3 = new Web3();
    web3.setProvider(new web3.providers.HttpProvider('http://localhos:8545'));
    function setGasPrice(){
        web3.eth.getGasPrice(function(error, result){
            console.log(result.toString());
            $("#gass_price").val(result.toString());
        });
    }

    async function main() {
        // 送信元のアドレス (Walletの作成手順にあるwallet.getAddress()のメソッドによって得られたaddress)     
        const fromAddress = $("#address").val();
        // 送信先のアドレス
        const toAddress = $("#address_to").val();
        // 送信先のアドレス
        const gass_price = "0x" + Number($("#gass_price").val()).toString(16);
        // 送信先のアドレス
        const gass_limit = "0x" + Number($("#gass_limit").val()).toString(16);

        // 著名付きトランザクションのnonce値として必要なため、送信元アドレスのTransaction countを取得する
        const count = await web3.eth.getTransactionCount("0x" +fromAddress);
        const countHex = `0x` + count.toString(16);
        const money = "0x" + Number(web3.utils.toWei($('#money').val(), 'ether')).toString(16);
        //console.log(money);  
        // 上記のWallet作成手順にある、wallet.getPrivateKeyString()によって得られた、Private keyの値を設定する
        const privateKeyStr = $("#private_key").val();
        let privateKey = new EthJS.Buffer.Buffer(privateKeyStr, 'hex');

        // transactionのパラメータの設定
        const txParams = {
            nonce: countHex,
            // 適切なgasPriceとgasLimitを設定する
//    gasPrice: '0x09184e72a000',
//    gasLimit: '0x30000',
            gasPrice: gass_price,
            gasLimit: gass_limit,
            to: toAddress,
            // 送金する金額
            value: money
        };
        console.log(txParams);
        web3.eth.estimateGas(txParams,function(error, result){
            console.log("gasLimit:");
            console.log(result.toString(16));
        });
        // Transactionオブジェクトの生成
        const tx = new EthJS.Tx(txParams);
        // 秘密鍵での署名 
        tx.sign(privateKey);
        // Transactionオブジェクトをシリアライズして、16進数で表現
        const serializedTx = tx.serialize();
        const rawTx = '0x' + serializedTx.toString('hex');
        // 署名付きトランザクションの送信
        web3.eth.sendSignedTransaction(rawTx)
        .on('transactionHash', function(hash){
          console.log('transactionHash');
          web3.eth.getTransaction(hash).then(alertJSON);
        })
        .on('receipt', function(receipt){
          console.log('receipt');
        })
        .on('confirmation', function(confirmationNumber, receipt){
          console.log('confirmation');
        })
        .on('error', function(error){
          if (error.indexOf("Transaction was not mined within 50 blocks")) {
            return;
          }
          alert(error);
          console.log(error);
        }); 
    }
</script>
<div class="container">
    <h2>ローカル ウォレット</h2>
    <div class="panel panel-default">
        <div class="panel-body">
            <div class="row">
                <div class="col-sm-4"><label>送り主 / private_key</label></div>
                <div class="col-sm-8"><input type="text" id="address"/><input type="password" id="private_key"/></div>
            </div>
            <div class="row">
                <div class="col-sm-4"><label>送り先 / 金額(eth)</label></div>
                <div class="col-sm-8"><input type="text" id="address_to"/><input type="text" id="money" value="1"/></div>
            </div>
            <div class="row">
                <div class="col-sm-4"><label>gass price(wei) / gass limit</label></div>
                <div class="col-sm-8"><input type="text" id="gass_price" value="10000000000000"/><input type="text" id="gass_limit" value="196608"/></div>
            </div>
            <div class="row">
                <div class="col-sm-4"></div>
                <div class="col-sm-8"><input type="button" class="btn btn-primary active" id="go" value="送金"/></div>
            </div>
            <hr/>
            <div class="row">
                <div class="col-sm-4"><label>キーストア読み込み</label></div>
                <div class="col-sm-8"><input type="button" class="btn btn-primary active" id="check" value="keystore + password -> private_key"/></div>
            </div>
            <hr/>
            <div class="row">
                <div class="col-sm-4"><label>アカウント作成（パスワード入力）</label></div>
                <div class="col-sm-8">
                    <input type="password" id="apw" value=""/>
                    <input type="button" class="btn btn-primary active" id="create" value="登録"/>
                </div>
            </div>

            <div class="row">
                <div class="col-sm-4" id="keystore"></div>
            </div>

            <hr/>
<!-- 接続先。infura.ioに登録してエンドポイントを取得し、ここに貼り付ける。geth立ち上げて指定するのもあり -->
            <div class="row">
                <div class="col-sm-12">
                    <input type="radio" name=host value="https://mainnet.infura.io/xxxxxxxxxxxxxxx"> Ethereum<br/>
                    <input type="radio" name=host value="http://localhost:8545" checked="checked"> private_net(local)<br/>
                    <input type="radio" name=host value="https://ropsten.infura.io/xxxxxxxxxx"> test_net(Ropsten)<br/>
                </div>
            </div>
            <hr/>
            <div class="row">
                <div class="col-sm-12">mining : <label id="mining" /></label></div>
            </div>
        </div>
    </div>
    <hr/>
    <label> addr_list</label>
    <ul id="demo">
    </ul>

    <script type="text/javascript">
        function alertJSON(json) {
            alert(JSON.stringify(json));
        }
        function logDemoElements(elements) {
            ul = $("#demo");
            elements.forEach(function(val){
                ul.append('<li class="addr">' + val + "</li>");
            });
        }
        function update_addr() {
            $("#demo li").remove();
            web3.eth.getAccounts().then(logDemoElements);
        }
        function update_wallet() {
            $("#demo li").remove();
            web3.eth.getAccounts().then(logDemoElements);
        }

        async function generate_account() {
            ret = await web3.eth.accounts.create();
            s = web3.eth.accounts.encrypt(ret.privateKey, $("#apw").val());
            var blob = new Blob([JSON.stringify(s)], { "type" : "application/octet-stream" });
            url = URL.createObjectURL(blob);
            window.open(url);
            update_addr();
        }

        $(function() {
            $( 'input[name="host"]:radio' ).change( function() {
                console.log( $( this ).val()); // valueを表示  
                web3.setProvider(new web3.providers.HttpProvider($( this ).val()));
                update_addr();
                setGasPrice();
            });

            $('#go').click(function(e) {
                main();
            });
            $('#create').click(function(e) {
                generate_account();
            });

            $('#demo').on('click','.addr', function(){
                $("#address_to").val(this.innerText);
            });

            // アカウント作成
            $('#check').click(function(e) {
                var key = prompt('paste keystore(json_string)');
                var keyObj = JSON.parse(key);
                var password = prompt('password?');
                var privateKey=keythereum.recover(password, keyObj);

                console.log(privateKey.toString('hex'));

                $("#address").val(keyObj.address);
                $("#private_key").val(privateKey.toString('hex'));
            });
            $(document).ready(function(){
                update_addr();
                setGasPrice();
            });
        });
    </script>
</body>
</html>
```

<img width="803" alt="スクリーンショット 2018-03-12 19.14.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/236641/4686457b-92e6-101f-2d90-61423c61acd3.png">

- 送信元 / privateKey / 送信先 / 金額を入力して送金ができる
- キーストア読み込みボタンを押すと入力boxが出るので、keystore内のjsonをテキストに貼り付ける
  - ok押下後パスワードを聞かれるのでパスワード入力
  - 少し待つと送信元とprivateKeyのテキストボックスに値が入力される
- アカウント作成を行うと、keystoreファイルがダウンロードされる
- ラジオボタン選択で接続先を切り替える。接続先はinfura.ioに登録して取得なりGeth使って自分で立ち上げるなりする
- 自前のgethでノードを立ち上げた場合、そこで作成したアカウントはadd_listに表示される。選択すると送金先のテキストにアドレスをコピー

## 参考

geth consoleを使わずJavaScriptのみでブロックチェーン(Ethereum)を操作する方法
https://qiita.com/fukumame55/items/e631119877c4cd77d0a5