1. 程式人生 > >分成確定性錢包開發的程式碼實現(HD錢包服務)

分成確定性錢包開發的程式碼實現(HD錢包服務)

HD Wallets的全稱是Hierachical Deterministic Wallets, 對應中文是 分層確定性錢包。

這種錢包能夠使用一組助記詞來管理所有的賬戶的所有幣種,在比特幣的BIP32提案中提出,通過種子來生成主私鑰,然後派生海量的子私鑰和地址。種子很長,為了方便記錄,轉換為一組單詞記錄,這是BIP39提出的。

生成錢包地址的基本流程:1 生成一組助記詞 2 助記詞轉化成種子(通過PBKDF2) 3 種子生成根私鑰(通過HMAC-SHA512) 4 通過根私鑰生成子私鑰

 

本文的目的是帶著讀者用程式碼實現一個HD錢包開發。

開發過程中要用到兩個第三方庫,一個是Hooked-Web3-Provider,一個是LightWallet。

Hooked-Web3-Provider使用HTTP與geth通訊,可以使用祕鑰來簽署呼叫交易sendTransaction的例項,因此不需要建立交易資料部分。直接呼叫sendTransaction完成生成交易資料,傳送交易,廣播給全網。

LightWallet是一個實現BIP32、BIP39和BIP44的HD錢包。
LightWallet提供API來建立和簽署交易,或者使用LightWallet生成的地址和金鑰加密和解密資料。它的主要目的是為Hooked-Web3-Provider提供一個簽名提供方。它的名稱空間有四個,即keystore、signing、encryption和txutils。

signing、encryption和txutils三個名稱空間分別用來簽名,非對稱加密,生成交易,它們的名字大概能反應出各自的功能。keystore名稱空間用來生成種子,keystor,這是一個儲存加密種子和祕鑰的物件。keystore對於其中發現的地址可以自動簽名。

HD 錢包中的金鑰是用"路徑"命名的,且每個級別之間用斜槓(/)字元來表示。由主私鑰衍生出的私鑰起始以"m"打頭。因此,第一個母金鑰生成的子私鑰是 m/0。第一個公共鑰匙是 M/0。第一個子金鑰的子金鑰就是 m/0/1,以此類推。

金鑰的"祖先"是從右向左讀,直到你達到了衍生出的它的主金鑰。舉個例 子,識別符號 m/x/y/z 描述的是子金鑰 m/x/y 的第 z 個子金鑰。而子金鑰 m/x/y 又是 m/x 的第 y 個子金鑰。m/x 又是 m 的第 x 個子金鑰。

 

程式碼實現

1. 啟動geth網路

假設已經安裝好geth。使用命令啟動geth網路:

geth --networkid 15 --dev --dev.period 1 --rpc --rpcapi "db,eth,net,web3,miner,personal"   --rpccorsdomain "*" --rpcaddr "0.0.0.0" --rpcport "8545"   console 2>>log

 

這個命令指定了networkid是15,當然這是隨便取的。 --dev --dev.period 1  --dev是開發網路,但是geth後面的版本中為了方便開發,如果不加--dev.period 1 不會自動挖礦。 --rpcaddr "0.0.0.0" 這樣設定是為了讓所有的網路都能連上geth。 指定埠8545. console表明啟動玩登入到控制檯。

 

2.構建前端

專案程式碼結構(參考《區塊鏈專案開發指南》):

app.js內容如下:

var express = require("express");  
var app = express();  

app.use(express.static("public"));

app.get("/", function(req, res){
    res.sendFile(__dirname + "/public/html/index.html");
})

app.listen(8080);

構建前端的一個node服務。

index.html  頁面:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <link rel="stylesheet" href="/css/bootstrap.min.css">
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="col-md-6 offset-md-3">
                    <br>
                    <div class="alert alert-info" id="info" role="alert">
                          Create or use your existing wallet.
                    </div>
                    <form>
                        <div class="form-group">
                            <label for="seed">Enter 12-word seed</label>
                            <input type="text" class="form-control" id="seed">
                        </div>
                        <button type="button" class="btn btn-primary" onclick="generate_addresses()">Generate Details</button>
                        <button type="button" class="btn btn-primary" onclick="generate_seed()">Generate New Seed</button>
                    </form>
                    <hr>
                    <h2 class="text-xs-center">Address, Keys and Balances of the seed</h2>
                    <ol id="list">
                    </ol>
                    <hr>
                    <h2 class="text-xs-center">Send ether</h2>
                    <form>
                        <div class="form-group">
                            <label for="address1">From address</label>
                            <input type="text" class="form-control" id="address1">
                        </div>
                        <div class="form-group">
                            <label for="address2">To address</label>
                            <input type="text" class="form-control" id="address2">
                        </div>
                        <div class="form-group">
                            <label for="ether">Ether</label>
                            <input type="text" class="form-control" id="ether">
                        </div>
                        <button type="button" class="btn btn-primary" onclick="send_ether()">Send Ether</button>
                    </form>
                </div>
            </div>
        </div>

           <script src="/js/web3.min.js"></script>
           <script src="/js/hooked-web3-provider.min.js"></script>
        <script src="/js/lightwallet.min.js"></script>
        <script src="/js/main.js"></script>
    </body>
</html>

重點在main.js

generate_seed函式:
function generate_seed()
{
    var new_seed = lightwallet.keystore.generateRandomSeed();  //生成一個隨機的種子

    document.getElementById("seed").value = new_seed;   //放到頁面

    generate_addresses(new_seed);
}

var totalAddresses = 0;

function generate_addresses(seed)
{
    if(seed == undefined)
    {
        seed = document.getElementById("seed").value;
    }

    if(!lightwallet.keystore.isSeedValid(seed))  //判斷種子是否是有效的種子
    {
        document.getElementById("info").innerHTML = "Please enter a valid seed";
        return;
    }

    totalAddresses = prompt("How many addresses do you want to generate");  //使用者輸入想生成的賬戶的個數

    if(!Number.isInteger(parseInt(totalAddresses)))   //確保輸入是一個數字
    {
        document.getElementById("info").innerHTML = "Please enter valid number of addresses";
        return;
    }

    var password = Math.random().toString();   //隨機生成一個密碼   這個密碼可以由使用者輸入,也可以自動生成。這裡為了方便,提升體驗,自動生成一個。

    lightwallet.keystore.createVault({     
//     使用createVault方法建立keystore例項。createVault用一個物件和
// 一個回撥函式作為引數。物件可以有4種屬性:password、seedPharse、
// salt和hdPathString。password是必選項,其他的都是可選項。如果不提
// 供seedPharse,它會生成和使用一個隨機seed。拼接salt與password,以
// 提高對稱金鑰加密技術的安全性,因為攻擊者不僅要找到password還得
// 找到salt。如果不提供salt,它就會隨機生成。keystore名稱空間儲存未加
// 密的salt。hdPathString用於為keystore名稱空間提供預設衍生路徑,即生
// 成地址、簽署交易等。如果不提供衍生路徑,則使用該衍生路徑。如果
// 不提供hdPathString,則預設值為m/0'/0'/0'。這個衍生路徑的預設目的是
// 簽名。可以建立新的衍生路徑或者使用keystore例項的
// addHdDerivationPath()方法重寫當前衍生路徑的purpose。還可以使用
// keystore例項的setDefaultHdDerivationPath()方法改變預設衍生路徑。
// 最後,一旦keystore名稱空間被建立,就通過回撥函式返回例項。所
// 以,這裡僅用keyword和seed建立了一個keystore。

        password: password,
          seedPhrase: seed
    }, function (err, ks) {
          ks.keyFromPassword(password, function (err, pwDerivedKey) {  //使用這個方法生成對應數量的地址和祕鑰
            if(err)
            {
                document.getElementById("info").innerHTML = err;
            }
            else
            {
                ks.generateNewAddress(pwDerivedKey, totalAddresses);
                var addresses = ks.getAddresses();    
                
                var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));  //建立web3和本地的連線,如果是遠端把localhost改成對應的ip即可。
                var html = "";

                for(var count = 0; count < addresses.length; count++)
                {
                    var address = addresses[count];
                    var private_key = ks.exportPrivateKey(address, pwDerivedKey);  //使用該方法解碼和檢索地址私鑰。
                    var balance = web3.eth.getBalance("0x" + address);

                    html = html + "<li>";
                    html = html + "<p><b>Address: </b>0x" + address + "</p>";
                    html = html + "<p><b>Private Key: </b>0x" + private_key + "</p>";
                    html = html + "<p><b>Balance: </b>" + web3.fromWei(balance, "ether") + " ether</p>";
                    html = html + "</li>";
                }

                document.getElementById("list").innerHTML = html;
            }
          });
    });
}