zoukankan      html  css  js  c++  java
  • 分层确定性钱包开发的代码实现(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;
                }
              });
        });
    }

     发送eth:

    function send_ether()
    {
        var    seed = document.getElementById("seed").value;
    
        if(!lightwallet.keystore.isSeedValid(seed))
        {
            document.getElementById("info").innerHTML = "Please enter a valid seed";
            return;
        }
    
        var password = Math.random().toString();
    
        lightwallet.keystore.createVault({
            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);
    
                    ks.passwordProvider = function (callback) {
                          callback(null, password);
                    };
    
                    var provider = new HookedWeb3Provider({
                          host: "http://localhost:8545",
                          transaction_signer: ks
                    }); //利用ks做交易的签署者。它调用ks的hasAddress方法和signTransactions方法
    var web3 = new Web3(provider);
    
                    var from = document.getElementById("address1").value;
                    var to = document.getElementById("address2").value;
                    var value = web3.toWei(document.getElementById("ether").value, "ether");
    
                    web3.eth.sendTransaction({
                        from: from,
                        to: to,
                        value: value,
                        gas: 21000
                    }, function(error, result){
                        if(error)
                        {    
                            document.getElementById("info").innerHTML = error;
                        }
                        else
                        {
                            document.getElementById("info").innerHTML = "Txn hash: " + result;
                        }
                    })
                }
              });
        });
    }

    测试:

     到项目根目录,执行 npm install 加载相关库。

    执行  node app.js 启动前端服务。端口8080.  访问 localhost:8080

     点击 generate new seed  ,输入2,生成两个账户。

    然后进入到一开始打开的geth客户端,通过 eth.accounts 查看geth下的账号。复制地址,通过eth.getBalance("XX") 来获取地址余额。

     

    转入一部分余额到上面生成的地址中,例如0xb4c7cf322956f0345b613f246d5d2f4ba03028f6.

     eth.sendTransaction({from: "0x8d1c1dd6f48c33c97924e5f310905e1822a6cbd0", to: "0xb4c7cf322956f0345b613f246d5d2f4ba03028f6", value: web3.toWei(100, "ether")})

     点击页面的 generate details 刷新,同样的seed会生成同样的账户。

    这个时候会发现账户多出100eth。然后可以测试钱包内地址,钱包对钱包外地址转账。

    项目giithub地址:https://github.com/figo050518/wallet

  • 相关阅读:
    SPOJ 694 (后缀数组) Distinct Substrings
    POJ 2774 (后缀数组 最长公共字串) Long Long Message
    POJ 3693 (后缀数组) Maximum repetition substring
    POJ 3261 (后缀数组 二分) Milk Patterns
    UVa 1149 (贪心) Bin Packing
    UVa 12206 (字符串哈希) Stammering Aliens
    UVa 11210 (DFS) Chinese Mahjong
    UVa (BFS) The Monocycle
    UVa 11624 (BFS) Fire!
    HDU 3032 (Nim博弈变形) Nim or not Nim?
  • 原文地址:https://www.cnblogs.com/gzhlt/p/10027796.html
Copyright © 2011-2022 走看看