HD钱包: BIP32BIP44BIP39

Posted Chain_Rock

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HD钱包: BIP32BIP44BIP39相关的知识,希望对你有一定的参考价值。

钱包概念

        钱包是一个管理私钥的工具,钱包中的资产在链上。

        先来看几个基础的概念:

        种子:生成私钥的种子, 128,256 或 512 bit。

        私钥:一个32字节的数,用户持有,不能泄露。

        公钥:私钥经过椭圆曲线算法生成。

        地址:公钥通过hash函数生成。


BIP32 :为了避免管理一堆私钥的麻烦提出的分层推导方案

        根据一个随机数种子(seed)产生一个树状结构存储多组私钥和公钥,这样保存的时候,只需要保存一个种子就可以,私钥可以推导出来通过这种树状结构推导出来的秘钥,通常用路径来表示,每个级别之间用斜杠 / 来表示,由主私钥衍生出的私钥起始以“m”打头。

BIP44 :BIP32的分层路径定义规范

        BIP44则是为这个路径约定了一个规范的含义(也扩展了对多币种的支持),BIP44指定了包含5个预定义树状层级的结构:

        m / purpose' / coin_type' / account' / change / address_index

  • m : 固定
  • Purpose :固定值为44(或者 0x8000002C)
  • Coin type :代表币种,0代表比特币,60代表以太坊
  • Account :  账户索引,从0开始
  • Change:外部地址:0, 内部地址:1(一般为0)
  • address_index : 地址索引,从0开始,代表生成第几个地址

BIP39 :定义助记词

        助记词的生成:先生成一个128位随机数,再加上对随机数做的校验4位,得到132位的一个数,然后按每11位做切分,这样就有了12个二进制数,然后用每个数去查BIP39定义的单词表,这样就得到12个助记词。

        助记词推导种子:使用密钥拉伸(Key stretching)函数,PBKDF2基本原理是通过一个为随机函数(例如 HMAC 函数),把助记词明文和盐值作为输入参数,然后重复进行运算最终产生生成一个更长的(512 位)密钥种子。这个种子再构建一个确定性钱包并派生出它的密钥。

Node.js和NoSQL开发比特币加密货币应用程序(下)


在使用Node.js和NoSQL开发比特币加密货币应用程序(上)中,我们创建了HD钱包,它可以为给定的种子生成无限量的密钥,每个密钥代表一个用户钱包。我们将根据主种子创建每个包含钱包的用户帐户。下面我们接着来看如何进行交易、查询余额等重要功能如何实现。

我们要在这里改变一下。到目前为止,我们已经在NoSQL数据库中完成了面向帐户的操作。另一个重要方面是交易。例如,也许用户X为BTC存入一些美元货币,而用户Y进行提款。我们需要存储和查询该交易信息。

API端点函数将保存交易数据,但我们仍然可以查询它。

getAccountBalance(account) {
    var statement = "SELECT SUM(tx.satoshis) AS balance FROM " + this.bucket._name + " AS tx WHERE tx.type = 'transaction' AND tx.account = $account";
    var query = Couchbase.N1qlQuery.fromString(statement);
    return new Promise((resolve, reject) => {
        this.bucket.query(query, { "account": account }, (error, result) => {
            if(error) {
                reject({ "code": error.code, "message": error.message });
            }
            resolve({ "balance": result[0].balance });
        });
    });
}

给定一个帐户,我们希望获得特定用户的帐户余额。

等一下,让我们退后一步,因为我们不是已经创建了一些帐户余额功能吗?从技术上讲,我们做了,但这些功能用于检查钱包余额,而不是帐户余额。

这是我的一些经验变成灰色区域的地方。每次发送比特币时,都会收取费用,有时费用相当昂贵。当你存款时,将钱转入你的钱包并不符合成本效益,因为这将收取矿工费。然后你将被收取撤回甚至转账的费用。那时你已经失去了大部分的比特币。

再说一次,我不知道这是否真的如何运作,但这就是我为了避免各处收费而采取的方式。

回到我们的getAccountBalance函数。我们正在处理每笔交易的总和。存款具有正值,而转账和取款具有负值。将这些信息汇总在一起可以为你提供准确的数字,不包括你的钱包余额。稍后我们将获得一个钱包余额帐户。

鉴于我们对帐户余额知之甚少,我们可以尝试从钱包中创建一个交易:

createTransactionFromAccount(account, source, destination, amount) {
    return new Promise((resolve, reject) => {
        this.getAddressBalance(source).then(sourceAddress => {
            if(sourceAddress.balanceSat < amount) {
                return reject({ "message": "Not enough funds in account." });
            }
            this.getPrivateKeyFromAddress(account, source).then(keypair => {
                this.getAddressUtxo(source).then(utxo => {
                    var transaction = new Bitcore.Transaction();
                    for(var i = 0; i < utxo.length; i++) {
                        transaction.from(utxo[i]);
                    }
                    transaction.to(destination, amount);
                    this.addAddress(account).then(change => {
                        transaction.change(change.address);
                        transaction.sign(keypair.secret);
                        resolve(transaction);
                    }, error => reject(error));
                }, error => reject(error));
            }, error => reject(error));
        }, error => reject(error));
    });
}

同样地,假设我们想从我们的持有账户转账比特币:

createTransactionFromMaster(account, destination, amount) {
    return new Promise((resolve, reject) => {
        this.getAccountBalance(account).then(accountBalance => {
            if(accountBalance.balance < amount) {
                reject({ "message": "Not enough funds in account." });
            }
            var mKeyPairs = this.getMasterKeyPairs();
            var masterAddresses = mKeyPairs.map(a => a.address);
            this.getMasterAddressWithMinimum(masterAddresses, amount).then(funds => {
                this.getAddressUtxo(funds.address).then(utxo => {
                    var transaction = new Bitcore.Transaction();
                    for(var i = 0; i < utxo.length; i++) {
                        transaction.from(utxo[i]);
                    }
                    transaction.to(destination, amount);
                    var change = helper.getMasterChangeAddress();
                    transaction.change(change.address);
                    for(var j = 0; j < mKeyPairs.length; j ++) {
                        if(mKeyPairs[j].address == funds.address) {
                            transaction.sign(mKeyPairs[j].secret);
                        }
                    }
                    var tx = {
                        account: account,
                        satoshis: (amount * -1),
                        timestamp: (new Date()).getTime(),
                        status: "transfer",
                        type: "transaction"
                    };
                    this.insert(tx).then(result => {
                        resolve(transaction);
                    }, error => reject(error));
                }, error => reject(error));
            }, error => reject(error));
        }, error => reject(error));
    });
}

在我们进入API端点之前,我想重新尝试一些事情:

  • 我应该是在加密帐户文档中的关键数据。

  • 我没有广播任何交易,只创建它们。

使用Express Framework设计RESTful API端点

请记住,正如我们在开始时配置的那样,我们的端点将分为三个文件,这些文件充当分组。我们将从最小和最简单的端点组开始,这些端点比其他任何端点都更实用。

打开项目的routes/utility.js文件并包含以下内容:

const Bitcore = require("bitcore-lib");
const Mnemonic = require("bitcore-mnemonic");

module.exports = (app) => {

    app.get("/mnemonic", (request, response) => {
        response.send({
            "mnemonic": (new Mnemonic(Mnemonic.Words.ENGLISH)).toString()
        });
    });

    app.get("/balance/value", (request, response) => {
        Request("https://api.coinmarketcap.com/v1/ticker/bitcoin/").then(market => {
            response.send({ "value": "$" + (JSON.parse(market)[0].price_usd * request.query.balance).toFixed(2) });
        }, error => {
            response.status(500).send(error);
        });
    });

}

这里我们有两个端点,一个用于生成助记符种子,另一个用于获取比特币余额的法定值。这两者都不是真正必要的,但是在第一次启动时,生成种子值以便稍后保存在我们的配置文件中可能会很好。

现在打开项目的routes/account.js文件,以便我们处理帐户信息:

const Request = require("request-promise");
const Joi = require("joi");
const helper = require("../app").helper;

module.exports = (app) => {

    app.post("/account", (request, response) => { });

    app.put("/account/address/:id", (request, response) => { });

    app.get("/account/addresses/:id", (request, response) => { });

    app.get("/addresses", (request, response) => { });

    app.get("/account/balance/:id", (request, response) => { });

    app.get("/address/balance/:id", (request, response) => { });

}

请注意,我们正在从尚未启动的app.js文件中提取helper程序类。现在就跟它一起使用它以后会有意义,虽然它没什么特别的。

在创建帐户时,我们有以下内容:

app.post("/account", (request, response) => {
    var model = Joi.object().keys({
        firstname: Joi.string().required(),
        lastname: Joi.string().required(),
        type: Joi.string().forbidden().default("account")
    });
    Joi.validate(request.body, model, { stripUnknown: true }, (error, value) => {
        if(error) {
            return response.status(500).send(error);
        }
        helper.createAccount(value).then(result => {
            response.send(value);
        }, error => {
            response.status(500).send(error);
        });
    });
});

使用Joi我们可以验证请求正文并在错误时抛出错误。假设请求正文是正确的,我们可以调用createAccount函数在数据库中保存一个新帐户。

app.put("/account/address/:id", (request, response) => {
    helper.addAddress(request.params.id).then(result => {
        response.send(result);
    }, error => {
        return response.status(500).send(error);
    });
});

使用发送的帐户ID,我们可以调用我们的addAddress函数来对我们的文档使用子文档操作。

还不错吧?

app.get("/account/addresses/:id", (request, response) => {
    helper.getAddresses(request.params.id).then(result => {
        response.send(result);
    }, error => {
        response.status(500).send(error);
    });
});
app.get("/addresses", (request, response) => {
    helper.getAddresses().then(result => {
        response.send(result);
    }, error => {
        response.status(500).send(error);
    });
});
app.get("/account/balance/:id", (request, response) => {
    helper.getAddresses(request.params.id).then(addresses => helper.getWalletBalance(addresses)).then(balance => {
        helper.getAccountBalance(request.params.id).then(result => {
            response.send({ "balance": balance.balance + result.balance });
        }, error => {
            response.status(500).send({ "code": error.code, "message": error.message });
        });
    }, error => {
        response.status(500).send({ "code": error.code, "message": error.message });
    });
});

以上将调用我们的两个函数来获得余额,并将结果加在一起以获得一个巨大的余额。

帐户端点不是特别有趣。创建交易更令人兴奋。

打开项目的routes/transaction.js文件并包含以下内容:

const Request = require("request-promise");
const Joi = require("joi");
const Bitcore = require("bitcore-lib");
const helper = require("../app").helper;

module.exports = (app) => {

    app.post("/withdraw", (request, response) => { });

    app.post("/deposit", (request, response) => { });

    app.post("/transfer", (request, response) => { });

}

我们来看看存款端点:

app.post("/deposit", (request, response) => {
    var model = Joi.object().keys({
        usd: Joi.number().required(),
        id: Joi.string().required()
    });
    Joi.validate(request.body, model, { stripUnknown: true }, (error, value) => {
        if(error) {
            return response.status(500).send(error);
        }
        Request("https://api.coinmarketcap.com/v1/ticker/bitcoin/").then(market => {
            var btc = value.usd / JSON.parse(market)[0].price_usd;
            var transaction = {
                account: value.id,
                usd: value.usd,
                satoshis: Bitcore.Unit.fromBTC(btc).toSatoshis(),
                timestamp: (new Date()).getTime(),
                status: "deposit",
                type: "transaction"
            };
            helper.insert(transaction).then(result => {
                response.send(result);
            }, error => {
                response.status(500).send(error);
            });
        }, error => {
            response.status(500).send(error);
        });
    });
});

在我们验证输入后,我们使用CoinMarketCap检查美元比特币的当前值。使用响应中的数据,我们可以根据存入的美元金额计算出应该获得多少比特币。

创建数据库交易后,我们可以保存它,因为它是一个正数,它将在查询时返回正余额。

现在让我们说我们想从比特币中提取资金:

app.post("/withdraw", (request, response) => {
    var model = Joi.object().keys({
        satoshis: Joi.number().required(),
        id: Joi.string().required()
    });
    Joi.validate(request.body, model, { stripUnknown: true }, (error, value) => {
        if(error) {
            return response.status(500).send(error);
        }
        helper.getAccountBalance(value.id).then(result => {
            if(result.balance == null || (result.balance - value.satoshis) < 0) {
                return response.status(500).send({ "message": "There are not `" + value.satoshis + "` satoshis available for withdrawal" });
            }
            Request("https://api.coinmarketcap.com/v1/ticker/bitcoin/").then(market => {
                var usd = (Bitcore.Unit.fromSatoshis(value.satoshis).toBTC() * JSON.parse(market)[0].price_usd).toFixed(2);
                var transaction = {
                    account: value.id,
                    satoshis: (value.satoshis * -1),
                    usd: parseFloat(usd),
                    timestamp: (new Date()).getTime(),
                    status: "withdrawal",
                    type: "transaction"
                };
                helper.insert(transaction).then(result => {
                    response.send(result);
                }, error => {
                    response.status(500).send(error);
                });
            }, error => {
                response.status(500).send(error);
            });
        }, error => {
            return response.status(500).send(error);
        });
    });
});

类似的事件正在这里发生。在验证请求主体后,我们获得帐户余额并确保我们提取的金额小于或等于我们的余额。如果是,我们可以根据CoinMarketCap的当前价格进行另一次交易。我们将使用负值创建一个交易并将其保存到数据库中。

在这两种情况下,我们都依赖于CoinMarketCap,它在过去一直存在负面争议。你可能希望为交易选择不同的资源。

最后,我们有转账:

app.post("/transfer", (request, response) => {
    var model = Joi.object().keys({
        amount: Joi.number().required(),
        sourceaddress: Joi.string().optional(),
        destinationaddress: Joi.string().required(),
        id: Joi.string().required()
    });
    Joi.validate(request.body, model, { stripUnknown: true }, (error, value) => {
        if(error) {
            return response.status(500).send(error);
        }
        if(value.sourceaddress) {
            helper.createTransactionFromAccount(value.id, value.sourceaddress, value.destinationaddress, value.amount).then(result => {
                response.send(result);
            }, error => {
                response.status(500).send(error);
            });
        } else {
            helper.createTransactionFromMaster(value.id, value.destinationaddress, value.amount).then(result => {
                response.send(result);
            }, error => {
                response.status(500).send(error);
            });
        }
    });
});

所有这些都基于我们之前创建的功能。

通过端点,我们可以专注于引导我们的应用程序并得出结论。

引导Express Framework应用程序

现在我们有两个文件保持不受示例的影响。我们还没有添加配置或驱动逻辑来引导我们的端点。

打开项目的config.json文件,并包含以下内容:

{
    "mnemonic": "manage inspire agent october potato thought hospital trim shoulder round tired kangaroo",
    "host": "localhost",
    "bucket": "bitbase",
    "username": "bitbase",
    "password": "123456"
}

记住这个文件非常敏感。考虑将其锁定或甚至使用不同的方法。如果种子被暴露,则可以毫不费力地获得所有用户帐户和交换帐户的每个私钥。

现在打开项目的app.js文件并包含以下内容:

const Express = require("express");
const BodyParser = require("body-parser");
const Bitcore = require("bitcore-lib");
const Mnemonic = require("bitcore-mnemonic");
const Config = require("./config");
const Helper = require("./classes/helper");

var app = Express();

app.use(BodyParser.json());
app.use(BodyParser.urlencoded({ extended: true }));

var mnemonic = new Mnemonic(Config.mnemonic);
var master = new Bitcore.HDPrivateKey(mnemonic.toHDPrivateKey());

module.exports.helper = new Helper(Config.host, Config.bucket, Config.username, Config.password, master);

require("./routes/account.js")(app);
require("./routes/transaction.js")(app);
require("./routes/utility.js")(app);

var server = app.listen(3000, () => {
    console.log("Listening at :" + server.address().port + "...");
});

我们正在做的是初始化Express,加载配置信息以及链接我们的路由。module.exports.helper变量是我们的单例,将在每个其他JavaScript文件中使用。

结论

你刚刚了解了如何使用Node.js和Couchbase作为NoSQL数据库来构建自己的加密货币交易。我们涵盖了很多,从生成HD钱包到创建具有复杂数据库逻辑的端点。

我不能强调这一点。我是加密货币爱好者,在金融领域没有真正的经验。我分享的东西应该有效,但可以做得更好。不要忘记加密密钥并确保种子安全。测试你的工作,知道自己正在做什么。

如果你想下载此项目,请在GitHub上查看。如果你想分享关于该主题的见解,经验等,请在评论中分享。社区可以努力创造伟大的东西!

如果你是Golang的粉丝,我在之前的教程中创建了一个类似的项目。

文章来自官方博客:http://blog.hubwiz.com。您也可以直接点击左下角的“阅读原文”进行访问。

汇智网的教程已经覆盖以下编程技术,发送相应的文字会获取对应教程的信息:

区块链以太坊EOS比特币Tendermint、Node.jsMongoDB、JavaScript、C、C#、PHP、Python、AngularjsIonic、React、UML、redis、mySQL、Nginx、CSS、HTML、Bootstrap、Flask、Gulp、Mocha、Git、Meteor、Canvas、zebra、Typescript、Material Design Lite、ECMAScript、Elasticsearch、Mongoose、jQuery、d3.js、django、cheerioSVG、phoneGap、jQueryMobile、Saas、YAML、Vue.js、webpack、Firebird、jQuery Easy UI、ruby、asp.net、c++、Express......



以上是关于HD钱包: BIP32BIP44BIP39的主要内容,如果未能解决你的问题,请参考以下文章

HD钱包: BIP32BIP44BIP39

分成确定性钱包开发的代码实现(HD钱包服务)

1.16. BIP39协议:使用助记词生成确定性钱包

(转)以太坊 钱包 创建 导入 Keystore

官方发布 | BSV区块链协会与CapitalSavvy联合推出区块链创新计划(BIP)

官方发布 | BSV区块链协会与CapitalSavvy联合推出区块链创新计划(BIP)