• 分层确定性钱包开发的代码实现(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

  • 相关阅读:
    java-以月为单位,得到一年中某一个月份的范围
    计算两个时间段相差几个月(包含相差的哪些月份)
    单个进程最大线程数
    Dell PowerEdge R720内存安装原则
    Java [parms/options] range -b 100 -c 10 -i 100 -t 300 -s 180
    PhysicalDrive
    classpath和环境变量设置
    MySQL正则表达式
    MySQL模式匹配(LIKE VS REGEXP)
    ubuntu为什么没有/etc/inittab文件? 深究ubuntu的启动流程分析
  • 原文地址:https://www.cnblogs.com/gzhlt/p/10027796.html
Copyright © 2020-2023  润新知