子アカウントとはなんでしょうか?
前回までは何も無いところからアカウントを生成しました。
今回は親アカウントの情報(秘密鍵)を頼りに子アカウントを生成してみます。
以前にも説明したことがあるのですが、
nanowalletに学ぶ、JavaScriptだけでBIP32準拠のNEMアカウントを生成する方法
この方法を用いれば、秘密鍵を覚える必要もなく芋づる式にアカウントを生成していくことが可能です。
今回は最新のライブラリ構成と後日完成すると思われるXEMBook-sdkの設計思想を考慮して解説していきます。
まずはいつも通りの雛形です。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<!-- Crypto -->
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="nacl-fast.js"></script>
<script src="keyPair.js"></script>
<!-- Utils -->
<script src="sha256.js"></script>
<script src="bitcoinjs-min.js"></script>
<!-- app -->
<script>
//ここにロジックを挿入していきます。
window.document.write("NEMアカウントを生成しました。<br>Private Key:" + privateKey + "<br>Public Key:" + account.publicKey.toString() + "<br>ADDRESS:" + address );
console.log(privateKey);
console.log(account.publicKey.toString());
console.log(address);
</script>
</body>
</html>
今回は新たに sha256.jsとbitcoinjs-min.js というライブラリを使用します。そうです、ビットコインのライブラリを使用します。つまりビットコインで定められたルール(BIP32)と同じ方法で子アカウントを生成するので、その手法が今後がらりと変わることは考えにくいということです。ビットコインはもはや暗号資産界の歴史の一部となりつつあるようです。
ここに挿入するのは以下のようなロジックです。
var MASTER_KEY = "NXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var networkId = 0x68000000; //MAIN_NET
var masterHash = getMasterHash(MASTER_KEY,"login_password");
var privateKeyBytes = getChildKeyBytes(masterHash,networkId,"m/0");
var privateKey = Crypto.util.bytesToHex(privateKeyBytes.toByteArrayUnsigned());
var account = new KeyPair(privateKey);
var address = toAddress(account.publicKey.toString(), networkId);
必要となる情報は親となる秘密鍵(MASTER_KEY)、ログインパスワード(NEM Walletの場合)、ネットワークID、そして階層(m/0) となります。
親秘密鍵とログインパスワードでマスターとなるハッシュ値を生成し[getMasterHash]、
そこから任意のネットワーク(ここではMAIN_NET)に対して親から指定階層に対してのアカウントを生成します[getChildKeyBytes]。
ちなみにNEM Walletで(m/0)の場所に作成されるアカウントは委任ハーベストに使用され、m/1 からが子アカウントとなっているようです。
ではそれぞれの関数でどんな処理を行っているのか見てみましょう。
var pk_SHA3_25000 = void 0;
for (var i = 0; i < 25000; ++i) {
pk_SHA3_25000 = CryptoJS.SHA3(MASTER_KEY, {
outputLength: 256
});
}
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA3, "login_password");
hmac.update(pk_SHA3_25000);
var hash = hmac.finalize();
getMasterHashでは秘密鍵を25000回ほどぐちゃぐちゃにして256バイトのハッシュ値に膨張させています。
そして、その後ログインパスワードをシードにHMACという手法でハッシュ値を作り直しています。
今回は親には何回でも同じものを再現できるように手がかりを残しつつハッシュ化しています。つまりエントロピー等は使用しません。
次にgetChildKeyBytesを見ていきましょう。
var il = Crypto.util.hexToBytes(hash.toString().slice(0, 64));
var ir = Crypto.util.hexToBytes(hash.toString().slice(64, 128));
// Create BIP32 object
var gen_bip32 = new BIP32();
// Set BIP32 object properties
gen_bip32.eckey = new Bitcoin.ECKey(il);
gen_bip32.eckey.pub = gen_bip32.eckey.getPubPoint();
gen_bip32.eckey.setCompressed(true);
gen_bip32.eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(gen_bip32.eckey.pub.getEncoded(true));
gen_bip32.has_private_key = true;
gen_bip32.chain_code = ir;
gen_bip32.child_index = 0;
gen_bip32.parent_fingerprint = Bitcoin.Util.hexToBytes("00000000");
// BIP32 version by wallet network
gen_bip32.version = 0x68000000;
gen_bip32.depth = 99;
gen_bip32.build_extended_public_key();
gen_bip32.build_extended_private_key();
var result = void 0;
result = gen_bip32.derive("m/0");
var privkeyBytes = result.eckey.priv.toByteArrayUnsigned();
while (privkeyBytes.length < 32) {
privkeyBytes.unshift(0);
};
ちょっと難しくなってきましたね。
作成したマスターハッシュ値の128バイト分をさらに左(il)と右(ir)に2分割します。
BIP32に必要なデータをセットしていきます。
ネットワークID(0x68000000)や階層(m/0)ですね。そのほかはデフォルトといった感じです。
最後に全体を32バイトになるように0埋めして完成です。
生成された子アカウントの秘密鍵はバイト値の配列として出力され、
これからいつも通りの秘密鍵、公開鍵、アドレスに変換することが可能です。
今回、privateKeyではなくprivkeyBytesのバイト配列として秘密鍵を取り出しました。ここにXEMBookの一つの想いがあります。
JavaScriptソースコードの至るところにprivateKeyという変数が放置されるのは、望ましく無い事態が発生するように思います。
マルウェアに感染したPCがメモリダンプされれば、秘密鍵をむき出しにしたソースコードは送金額を書き換えられたり送金先を変更されたり様々な危険性が出てきます。もちろんマルウェアにやられる方も悪いし、秘密鍵をバイト配列で保有してもやられる時はやられます。
マルチシグ運用が最強ではありますが、使う側がまだその段階ではありません。
その可能性を少しでも低くする努力を続けることがサービス提供者としては必要なことだと思います。
XEMBook-sdkはソースコード中の秘密鍵露出を出来るだけ減らし、かつ直感的にどんなソースコードにも組み込みやすいライブラリを目指します。