《Nodejs开发加密货币》之十八:地址

前言

上篇我们介绍了亿书的共识机制,是入门部分的补充,这篇开始介绍包括共识机制在内的加密货币的核心技术实现。另外,DPOS共识机制,首先需要通过用户注册为受托人,然后才会有一系列其他的操作,因此要从用户帐号开始。而对于亿书这款加密货币而言,用户帐号本质就是加密货币的地址。本篇,我们就来好好研究它。

源码

account.js https://github.com/Ebookcoin/ebookcoin/blob/master/logic/account.js

accounts.js https://github.com/Ebookcoin/ebookcoin/blob/master/modules/accounts.js

contacts.js https://github.com/Ebookcoin/ebookcoin/blob/master/modules/contacts.js

类图

地址主要通过modules/accounts.js模块处理,类图如下:

流程图

这里的逻辑并不复杂,其本质就是一种交易,所以,我们将在《交易》那篇提供详细的流程图。

解读

计算机软件是人类活动的模拟和程序化,也就是大家所谓的“虚拟化”。在设计的时候,都会设想一个角色(Role)代替人类,负责完成要开发的各类操作。这个角色,通常在开发中被定义为用户(User),用户看到并可操作的就是用户帐号,在此基础上,才能进行权限认证,记录和管理与用户有关的各类操作。

比特币里的用户角色仅仅就是一个比特币地址,该地址是通过Hash算法进行加密处理的字符串,因此我们叫它Hash地址。同时,基于真实网络的复杂性,对于IP地址的追踪也不容易,所以比特币的匿名性很好(因为压根就没有给你暴露名字的机会)。

亿书作为一款加密货币产品,自然也提供了类似的Hash地址。并基于该地址,扩展提供了其他功能,比如“别名地址”。原因有三个:

  1. 本质需要。版权保护应用,必然要明确版权所有人,实名信息是基本要求,如果再进行完全的匿名操作,显然有点不合适,属于跟自己过不去。当然,普通阅读用户不需要实名,亿书允许保持足够的匿名性。
  2. 用户需要。复杂的字符串地址不适合人类脑记,很多人在最初接触比特币的时候,非常不习惯,经常弄混、忘记自己的比特币地址就是很好的证明。
  3. 产品需要。说到交互功能,比特币除了交易之外,是没有什么交互的。而作为面向普通用户的亿书,要提供基本写作、团队协作、自出版等极具个性化的功能,交互功能被摆在突出位置,充满个性化的用户名是必须的。

具体操作时,可以实现下面的需求,详见 亿书白皮书

  1. 用户可以注册一个用户名,它相当于是用户帐户的一个别名;
  2. 用户名都是唯一的;
  3. 注册后无法更改或删除;
  4. 用户名可作为支付地址,所以称为别名地址,类似于人们常用的支付宝帐号,其它用户可以直接向该用户的用户名付款,用户不再需要记下一长串的加密货币地址;
  5. 用户可以维护一个联系人列表。

这些操作和信息,都可以在客户端里完成,如下图所示:

1.公共Api

modules/accounts.js 368行的代码, 如下:

// 368行
router.map(shared, {
  "post /open": "open",
  "get /getBalance": "getBalance",
  "get /getPublicKey": "getPublickey",
  "post /generatePublicKey": "generatePublickey",
  "get /delegates": "getDelegates",
  "get /delegates/fee": "getDelegatesFee",
  "put /delegates": "addDelegates",
  "get /username/get": "getUsername",
  "get /username/fee": "getUsernameFee",
  "put /username": "addUsername",
  "get /": "getAccount"
});

// 439行
library.network.app.use(‘/api/accounts‘, router);

前面,我们分析过,这里的router是helpers/router.js的一个实例。上述代码,最终会在439行的调用中,映射为公共Api,并分别对应shared中的方法,如:

// accounts
get /api/accounts/ -> shared.getAccount //帐号主页

post /api/accounts/open -> shared.open //登录
get /api/accounts/getBalance -> shared.getBalance
get /api/accounts/getPublicKey -> shared.getPublickey
post /api/accounts/generatePublicKey -> shared.generatePublickey

// username
get /api/accounts/username/get -> shared.getUsername
get /api/accounts/username/fee -> shared.getUsernameFee
put /api/accounts/username -> shared.addUsername //注册用户名

// delegates
get /api/accounts/delegates -> shared.getDelegates
get /api/accounts/delegates/fee -> shared.getDelegatesFee
put /api/accounts/delegates -> shared.addDelegates

// count 对应431行单独定义
get /api/accounts/count -> private.accounts

// 另外两个是在debug或top环境下调试用的,暂且不表。
...

这里的delegates(受托人)api,以及后面的debug环境下的api,暂且不提。通盘浏览这些公开接口信息,我们知道,可以直接浏览的信息主要包括余额(balance)、公钥(publicKey)、用户名(username)及修改用户名需要花费的费用(fee),以及受托人及其费用等,可以产生公钥和添加用户名。但是,没有删除和修改用户名的功能,所以一旦注册了用户名,想要修改,只能重新注册一个。

2.Hash地址

比特币地址是使用前缀来区分的,比如:1开头的地址就是我们实际使用的地址,3开头的地址是测试地址。而亿书,使用后缀来区分,通常以L结尾。代码在modules/accounts.js文件里:

// 455行
Accounts.prototype.generateAddressByPublicKey = function (publicKey) {
    var publicKeyHash = crypto.createHash(‘sha256‘).update(publicKey, ‘hex‘).digest();
    var temp = new Buffer(8);
    for (var i = 0; i < 8; i++) {
        temp[i] = publicKeyHash[7 - i];
    }

    var address = bignum.fromBuffer(temp).toString() + ‘L‘;
    if (!address) {
        throw Error("wrong publicKey " + publicKey);
    }
    return address;
};

另一个类似的地址,就是区块链的以c结尾的块地址,用来标注generatorId,代码与上面的基本一致:

// logic/block.js 20行
private.getAddressByPublicKey = function (publicKey) {
    var publicKeyHash = crypto.createHash(‘sha256‘).update(publicKey, ‘hex‘).digest();
    var temp = new Buffer(8);
    for (var i = 0; i < 8; i++) {
        temp[i] = publicKeyHash[7 - i];
    }

    var address = bignum.fromBuffer(temp).toString() + "C";
    return address;
}

// 上面的方法,在312行dbRead函数里调用,
generatorId: private.getAddressByPublicKey(raw.b_generatorPublicKey),

3.别名地址

用户名相当于地址别名,就像支付宝帐号,所以白皮书说“别名地址”。我们知道,只有在转移支付的时候,才会用到地址(接受地址和发送地址),所以体现把用户名当作地址的逻辑代码,自然要在处理“资金转移”的交易代码里,看看modules/transactions.js文件,具体如下:

// modules/transactions.js 文件
// 652行
shared.addTransactions = function (req, cb) {
    var body = req.body;
    library.scheme.validate(body, {
        ...
        },
    // 685行
        required: ["secret", "amount", "recipientId"]
    }, function (err) {
    ...

    // 702行
        var isAddress = /^[0-9]+[L|l]$/g;
        if (isAddress.test(body.recipientId)) {
            query.address = body.recipientId;
        } else {
            query.username = body.recipientId;
        }

    library.balancesSequence.add(function (cb) {
            modules.accounts.getAccount(query, function (err, recipient) {
                ...
        // 717行
                var recipientId = recipient ? recipient.address : body.recipientId;
                var recipientUsername = recipient ? recipient.username : null;

                            ...

                            try {
                                var transaction = library.logic.transaction.create({
                  // 764行
                  type: TransactionTypes.SEND,
                                    amount: body.amount,
                                    sender: account,
                                    recipientId: recipientId,
                                    recipientUsername: recipientUsername,
                                    keypair: keypair,
                                    requester: keypair,
                                    secondKeypair: secondKeypair
                                });
                            } catch (e) {
                                return cb(e.toString());
                            }
                        ...

上述代码,实现的就是资金转账的功能(后台编码的交易类型TransactionTypes.SEND,见764行),毫无疑问,必须提供三个参数”secret”, “amount”, “recipientId”(见685行)。从702行可知,其中recipientId 可以是字符串地址,也可以是用户名。从764行以后的代码还可以了解到,交易时的recipientIdrecipientUsername字段都保存在数据库里了。

4.注册用户名

亿书默认不提供别名地址,需要用户注册。从上面的Api,很容易找到“注册用户名”的源码方法shared.addUsername,如下:

// 868行
shared.addUsername = function (req, cb) {
    var body = req.body;
    library.scheme.validate(body, {
        type: "object",
        properties: {
      ...
        },

    // 890行
        required: [‘secret‘, ‘username‘]
    }, function (err) {
      // 896行
        var hash = crypto.createHash(‘sha256‘).update(body.secret, ‘utf8‘).digest();
        var keypair = ed.MakeKeypair(hash);

        if (body.publicKey) {
            if (keypair.publicKey.toString(‘hex‘) != body.publicKey) {
                return cb("Invalid passphrase");
            }
        }

        library.balancesSequence.add(function (cb) {
            if (body.multisigAccountPublicKey && body.multisigAccountPublicKey != keypair.publicKey.toString(‘hex‘)) {
                modules.accounts.getAccount({publicKey: body.multisigAccountPublicKey}, function (err, account) {
          ...

                    modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) {
            ...
                        try {
              // 949行
                            var transaction = library.logic.transaction.create({
                                type: TransactionTypes.USERNAME,
                                username: body.username,
                                sender: account,
                                keypair: keypair,
                                secondKeypair: secondKeypair,
                                requester: keypair
                            });
                        } catch (e) {
                            return cb(e.toString());
                        }
                        modules.transactions.receiveTransactions([transaction], cb);
                    });
                });
            } else {
                self.getAccount({publicKey: keypair.publicKey.toString(‘hex‘)}, function (err, account) {
                    ...

                    try {
            // 984行
                        var transaction = library.logic.transaction.create({
                            type: TransactionTypes.USERNAME,
                            username: body.username,
                            sender: account,
                            keypair: keypair,
                            secondKeypair: secondKeypair
                        });
                    } catch (e) {
                        return cb(e.toString());
                    }
                    modules.transactions.receiveTransactions([transaction], cb);
                });
            }

        ...

分析代码949行和984行,可以了解到,所谓的注册用户名,实质上就是提交了一个TransactionTypes.USERNAME类型的交易。890行代码,说明该Api必须两个参数,用户要提供明文密码(secret)和用户名(username)。

5.联系人列表

亿书具备社交功能,维护了一个联系人列表。与传统中心化软件不同的是,加密货币系统里,处处是交易,用户关注其他用户的行为,也是一项交易(内部的交易类型为TransactionTypes.FOLLOW)。

这项功能的源码在文件modules/contacts.js里,类图如下:

与其他模块文件一样,我们可以非常清晰的看到该文件提供的公共Api,如下:

// modules/contacts.js文件
// 198行
router.map(shared, {
  "get /unconfirmed": "getUnconfirmedContacts",
  "get /": "getContacts",
  "put /": "addContact",
  "get /fee": "getFee"
});

这些Api很简单,我们重点关注其中两个Api:

put /api/contacts -> shared.addContact //添加关注功能
get /api/contacts -> shared.getContacts //获得列表

对应方法的源码:

// 406行
shared.addContact = function (req, cb) {
    var body = req.body;
    library.scheme.validate(body, {
        ...
    // 431行
        required: ["secret", "following"]
    }, function (err) {
        ...

    // 448行
        var followingAddress = body.following.substring(1, body.following.length);
        var isAddress = /^[0-9]+[L|l]$/g;
        if (isAddress.test(followingAddress)) {
            query.address = followingAddress;
        } else {
            query.username = followingAddress;
        }

        library.balancesSequence.add(function (cb) {
            if (body.multisigAccountPublicKey && body.multisigAccountPublicKey != keypair.publicKey.toString(‘hex‘)) {
        ...
                        try {
                            var transaction = library.logic.transaction.create({
                                type: TransactionTypes.FOLLOW,
                                sender: account,
                                keypair: keypair,
                                secondKeypair: secondKeypair,
                                contactAddress: followingAddress, // 511行
                                requester: keypair
                            });
                        } catch (e) {
                            return cb(e.toString());
                        }
                        modules.transactions.receiveTransactions([transaction], cb);
                    });
                });
            ...

431行,添加关注需要两个参数”secret”和”following”,这里”following”其实就是用户的帐号地址或用户名(见448行)。然后,经过一系列验证之后,写入数据库的contactAddress字段(见511行),一个关注的操作过程就完成了。

然后,用户通过客户端浏览自己的联系人列表,需要用到另一个Api,对应的方法是shared.getContacts,如下:

shared.getContacts = function (req, cb) {
      // 362行
        modules.accounts.getAccount({address: query.address}, function (err, account) {
            ...

            async.series({
                contacts: function (cb) {
          // 372行
                    if (!account.contacts.length) {
                        return cb(null, []);
                    }
                    modules.accounts.getAccounts({address: {$in: account.contacts}}, ["address", "username"], cb);
                },
                followers: function (cb) {
                    if (!account.followers.length) {
                        return cb(null, []);
                    }
                    modules.accounts.getAccounts({address: {$in: account.followers}}, ["address", "username"], cb);
                }
            ...
            });
        });
    });
};

这段代码最重要的部分,就是362行modules.accounts.getAccount方法的调用,获得对应地址的用户帐号(account)实例,联系人都保存在该实例的account.contacts里。

总结

读完代码,可以发现,代码逻辑非常简单,仅仅相当于两个基本功能,一个是生成加密货币的Hash地址,另一个是通过交易模块扩展和关联其他功能。其中,Hash地址是基础,在整个亿书的开发设计中,无处不在,签名、验证和交易,以及区块链等需要它。接下来我们介绍签名和验证,请看下一篇:签名和多重签名

链接

本系列文章即时更新,若要掌握最新内容,请关注下面的链接

本源文地址: https://github.com/imfly/bitcoin-on-nodejs

首发区块链俱乐部: http://chainclub.org

亿书官方网站: http://ebookchain.org

亿书开发QQ群: 185046161 (亿书为开源项目,欢迎各界小伙伴参与)

参考

亿书白皮书 http://ebookchain.org/ebookchain.pdf

时间: 2024-10-10 01:03:02

《Nodejs开发加密货币》之十八:地址的相关文章

《Nodejs开发加密货币》之二十六:轻松从Js文件生成UML类图

前言 上一篇<函数式编程入门经典>,罗嗦了很长,很多小伙伴看得云里雾里.这里提供一个实例,让大家切身感受函数式编程的奥妙和趣味.当然,仅仅为了举例而写代码就没有什么意义了,本书提供的例子都是承担了某项任务的具体项目或工具,这个例子自然也不能例外. 本书用到了大量的Uml类图,经常有小伙伴问我用什么工具画的.说实话,前几篇是我个人一点点手工整理的,但后来就感觉在浪费生命,作为程序员,怎么可能容忍这样的事情反复发生.所以,就有了 js2uml(见参考)这个小工具.只不过,当初目的单一,仅仅使用正则

《Nodejs开发加密货币》之十五:加密货币就是货币

这是一篇加密货币的入门文章,是对<Nodejs开发加密货币>的入门指南部分的补充,主要写给那些没有接触过比特币.加密货币的小伙伴,接下来的内容,都将与加密货币相关. 前言 "加密货币就是货币"听起来挺"白痴的".想想背后的意思,言外之意就是"加密货币可能不是货币",就非常值得玩味了.事实上,在我接触的很多朋友当中,一开始认为后者的更多.包括我自己,也是经过探究一段时间之后,才认定这个结论的. 惯性定律不仅存在于物质世界,也存在于人类的

《Nodejs开发加密货币》之十:三张图让你全面掌握加密解密技术

关于 <Nodejs开发加密货币>,是一个加密货币产品的详细开发文档,涉及到使用Nodejs开发产品的方方面面,从前端到后台.从服务器到客户端.从PC到移动.加密解密等各个环节.代码完全开源.文章免费分享. 相关资源见 http://ebookchain.org QQ交流群: 185046161 前言 加密解密技术,涉及面很广,这里,把前人的研究成果汇总起来,通过图表的形式来帮助记忆和筛选,方便日后使用.内容主要包括两个方面,一个是场景与算法,一个是Nodejs的相关模块或组件.共三张脑图,具

《Nodejs开发加密货币》之十二:静态网站开发全景扫描

前言 在前面的入门部分,介绍了Nodejs在前端开发中的应用,并通过具体项目说明了Nodejs在比特币客户端领域被广泛应用.当时为了介绍Nodejs入门技术,一切都是从头创建,没有引入前端框架.但在具体的项目实践中,前端是有框架可以选择的,效率和体验会有明显提升. 具体到前端框架,我的选择是Ember.js.Ember给开发带来一种飞一般的感觉,如果问前端框架哪家强,我会毫不犹豫的说Ember.(具体为什么,网上仍然争论不休,本文不做讨论) 本文重点介绍静态网站的类型,亿书官网的技术选型,以及在

《Nodejs开发加密货币》之十六:利益,魔鬼与天使的共同目标

前言 上篇文章<加密货币就是货币>吸引了很多小伙伴关注,非常感谢.同时说明,很多小伙伴因为对加密货币不了解(或者有误解),所以才会敬而远之,错失良机. 这篇文章,继续上一篇,仍然通过直白的语言来讲解技术.涉及到的内容包括未来趋势,应用场景和风险提示,让我们更直观地理解币圈里的一些概念,比如:智能合约等. 利益,主宰着人类行为 人活着到底是为了什么?我们每个人可能都问过自己这个问题.我们有时候踌躇满志,想要拥有一切.有时候又高尚地低下头,崇尚与世无争,无忧无虑.但在纷繁复杂的真实世界里,我们总会

《Nodejs开发加密货币》之十七:共识机制,可编程的利益转移规则

本文是关于加密货币入门文章的最后一篇.加密货币入门文章主要针对开发人员,从理论层面描述加密货币的架构思路,共计3篇.本文标题在真正写作的时候作了修改,没有延续上文最后的提示<机制,左右社会未来的根源>.写作本文时,比特币遭遇疯涨,当前是3876元/比特币. 前言 前面的文章中,我们说过,加密货币都是去中心化的,去中心化的基础就是P2P节点众多,那么如何吸引用户加入网络成为节点,有那些激励机制?同时,开发的重点是让多个节点维护一个数据库,那么如何决定哪个节点写入?何时写入?一旦写入,又怎么保证不

《Nodejs开发加密货币》之二十一:交易

题外话:这篇文章,耗费了我大量精力,用UML表达javascript类及流程本来就不是什么容易的事情,用来描述加密货币交易这种验证逻辑非常多的代码更难,加之Nodejs的回调在这些代码里嵌套很深,所以如何把异步调用变成人类容易理解的顺序调用,也做了一番取舍,时间不知不觉就过了一星期. 所幸,赶在比特币减半的今天完成并发布这篇文章,也算在区块链火热的今天,<Nodejs开发加密货币>走到了一个关键节点:触及了加密货币的灵魂和腹地.动辄几千一枚的比特币等加密货币可能会消亡,但是背后的技术却蓬勃发展

《Nodejs开发加密货币》之八:一个精巧的p2p网络实现

发布本文时,比特币价格 ¥2909.92 / $448.29 .为什么一个凭空设计出来的加密货币如此受追捧?为什么微软.IBM等巨头纷纷进入?为什么尝试了解比特币的技术人员,都会被深深吸引?它到底有什么诱人之处?<Nodejs开发加密货币>,让我们一起探索其背后的密码. <Nodejs开发加密货币>,目的是提供加密货币(亿书币)的详尽开发文档,涉及到使用Nodejs开发产品的方方面面,从前端到后台.从服务器到客户端.从PC到移动.从IO密集型到计算密集型.从中心化到去中心化.加密解

《Nodejs开发加密货币》之七:入口程序app.js解读

入口程序app.js解读 发布本文时,比特币价格 ¥2873.95 / $443.95 .为什么一个凭空设计出来的加密货币如此受追捧?为什么微软.IBM等巨头纷纷进入?为什么尝试了解比特币的技术人员,都会被深深吸引?它到底有什么诱人之处?<Nodejs开发加密货币>,让我们一起探索其背后的密码. <Nodejs开发加密货币>,目的是提供加密货币(亿书币)的详尽开发文档,涉及到使用Nodejs开发产品的方方面面,从前端到后台.从服务器到客户端.从PC到移动.从IO密集型到计算密集型.

《Nodejs开发加密货币》之九:在Nodejs中使用加密解密技术

关于 <Nodejs开发加密货币>,是一个加密货币产品的详细开发文档,涉及到使用Nodejs开发产品的方方面面,从前端到后台.从服务器到客户端.从PC到移动.加密解密.区款链等各个环节.代码完全开源.文章免费分享. 相关资源见 http://ebookchain.org QQ交流群: 185046161 前言 加密解密技术在加密货币开发中的作用不言而喻.但技术本身并不是什么新鲜事,重要的是如果没有前面的P2P网络,和后面要介绍的区块链,单独的加解密显然没有那么神奇,加密货币也不会成为无需验证.