• 地址
    • 前言
    • 源码
    • 类图
    • 流程图
    • 解读
      • 1.公共Api
      • 2.Hash地址
      • 3.别名地址
      • 4.注册用户名
      • 5.联系人列表
  • 总结
  • 链接
  • 参考

    地址

    前言

    上一篇,我们专门研究了《加密和验证》技术,明白了亿书对于加密解密技术的简单使用。我们说,加密货币处处都要用到加密解密技术,绝对不是一句空话,本篇文章我们就从最基础的加密货币地址开始,来学习和体会这句话的意义。

    源码

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

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

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

    类图

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

    accounts-class.png

    流程图

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

    解读

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

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

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

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

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

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

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

    wallet-add-username

    1.公共Api

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

    1. // 368行
    2. router.map(shared, {
    3. "post /open": "open",
    4. "get /getBalance": "getBalance",
    5. "get /getPublicKey": "getPublickey",
    6. "post /generatePublicKey": "generatePublickey",
    7. "get /delegates": "getDelegates",
    8. "get /delegates/fee": "getDelegatesFee",
    9. "put /delegates": "addDelegates",
    10. "get /username/get": "getUsername",
    11. "get /username/fee": "getUsernameFee",
    12. "put /username": "addUsername",
    13. "get /": "getAccount"
    14. });
    15. // 439行
    16. library.network.app.use('/api/accounts', router);

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

    1. // accounts
    2. get /api/accounts/ -> shared.getAccount //帐号主页
    3. post /api/accounts/open -> shared.open //登录
    4. get /api/accounts/getBalance -> shared.getBalance
    5. get /api/accounts/getPublicKey -> shared.getPublickey
    6. post /api/accounts/generatePublicKey -> shared.generatePublickey
    7. // username
    8. get /api/accounts/username/get -> shared.getUsername
    9. get /api/accounts/username/fee -> shared.getUsernameFee
    10. put /api/accounts/username -> shared.addUsername //注册用户名
    11. // delegates
    12. get /api/accounts/delegates -> shared.getDelegates
    13. get /api/accounts/delegates/fee -> shared.getDelegatesFee
    14. put /api/accounts/delegates -> shared.addDelegates
    15. // count 对应431行单独定义
    16. get /api/accounts/count -> privated.accounts
    17. // 另外两个是在debug或top环境下调试用的,暂且不表。
    18. ...

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

    2.Hash地址

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

    1. // 455行
    2. Accounts.prototype.generateAddressByPublicKey = function (publicKey) {
    3. var publicKeyHash = crypto.createHash('sha256').update(publicKey, 'hex').digest();
    4. var temp = new Buffer(8);
    5. for (var i = 0; i < 8; i++) {
    6. temp[i] = publicKeyHash[7 - i];
    7. }
    8. var address = bignum.fromBuffer(temp).toString() + 'L';
    9. if (!address) {
    10. throw Error("wrong publicKey " + publicKey);
    11. }
    12. return address;
    13. };

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

    1. // logic/block.js 20行
    2. privated.getAddressByPublicKey = function (publicKey) {
    3. var publicKeyHash = crypto.createHash('sha256').update(publicKey, 'hex').digest();
    4. var temp = new Buffer(8);
    5. for (var i = 0; i < 8; i++) {
    6. temp[i] = publicKeyHash[7 - i];
    7. }
    8. var address = bignum.fromBuffer(temp).toString() + "C";
    9. return address;
    10. }
    11. // 上面的方法,在312行dbRead函数里调用,
    12. generatorId: privated.getAddressByPublicKey(raw.b_generatorPublicKey),

    3.别名地址

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

    1. // modules/transactions.js 文件
    2. // 652行
    3. shared.addTransactions = function (req, cb) {
    4. var body = req.body;
    5. library.scheme.validate(body, {
    6. ...
    7. },
    8. // 685行
    9. required: ["secret", "amount", "recipientId"]
    10. }, function (err) {
    11. ...
    12. // 702行
    13. var isAddress = /^[0-9]+[L|l]$/g;
    14. if (isAddress.test(body.recipientId)) {
    15. query.address = body.recipientId;
    16. } else {
    17. query.username = body.recipientId;
    18. }
    19. library.balancesSequence.add(function (cb) {
    20. modules.accounts.getAccount(query, function (err, recipient) {
    21. ...
    22. // 717行
    23. var recipientId = recipient ? recipient.address : body.recipientId;
    24. var recipientUsername = recipient ? recipient.username : null;
    25. ...
    26. try {
    27. var transaction = library.logic.transaction.create({
    28. // 764行
    29. type: TransactionTypes.SEND,
    30. amount: body.amount,
    31. sender: account,
    32. recipientId: recipientId,
    33. recipientUsername: recipientUsername,
    34. keypair: keypair,
    35. requester: keypair,
    36. secondKeypair: secondKeypair
    37. });
    38. } catch (e) {
    39. return cb(e.toString());
    40. }
    41. ...

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

    4.注册用户名

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

    1. // 868行
    2. shared.addUsername = function (req, cb) {
    3. var body = req.body;
    4. library.scheme.validate(body, {
    5. type: "object",
    6. properties: {
    7. ...
    8. },
    9. // 890行
    10. required: ['secret', 'username']
    11. }, function (err) {
    12. // 896行
    13. var hash = crypto.createHash('sha256').update(body.secret, 'utf8').digest();
    14. var keypair = ed.MakeKeypair(hash);
    15. if (body.publicKey) {
    16. if (keypair.publicKey.toString('hex') != body.publicKey) {
    17. return cb("Invalid passphrase");
    18. }
    19. }
    20. library.balancesSequence.add(function (cb) {
    21. if (body.multisigAccountPublicKey && body.multisigAccountPublicKey != keypair.publicKey.toString('hex')) {
    22. modules.accounts.getAccount({publicKey: body.multisigAccountPublicKey}, function (err, account) {
    23. ...
    24. modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) {
    25. ...
    26. try {
    27. // 949行
    28. var transaction = library.logic.transaction.create({
    29. type: TransactionTypes.USERNAME,
    30. username: body.username,
    31. sender: account,
    32. keypair: keypair,
    33. secondKeypair: secondKeypair,
    34. requester: keypair
    35. });
    36. } catch (e) {
    37. return cb(e.toString());
    38. }
    39. modules.transactions.receiveTransactions([transaction], cb);
    40. });
    41. });
    42. } else {
    43. self.getAccount({publicKey: keypair.publicKey.toString('hex')}, function (err, account) {
    44. ...
    45. try {
    46. // 984行
    47. var transaction = library.logic.transaction.create({
    48. type: TransactionTypes.USERNAME,
    49. username: body.username,
    50. sender: account,
    51. keypair: keypair,
    52. secondKeypair: secondKeypair
    53. });
    54. } catch (e) {
    55. return cb(e.toString());
    56. }
    57. modules.transactions.receiveTransactions([transaction], cb);
    58. });
    59. }
    60. ...

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

    5.联系人列表

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

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

    contacts-class.png

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

    1. // modules/contacts.js文件
    2. // 198行
    3. router.map(shared, {
    4. "get /unconfirmed": "getUnconfirmedContacts",
    5. "get /": "getContacts",
    6. "put /": "addContact",
    7. "get /fee": "getFee"
    8. });

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

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

    对应方法的源码:

    1. // 406行
    2. shared.addContact = function (req, cb) {
    3. var body = req.body;
    4. library.scheme.validate(body, {
    5. ...
    6. // 431行
    7. required: ["secret", "following"]
    8. }, function (err) {
    9. ...
    10. // 448行
    11. var followingAddress = body.following.substring(1, body.following.length);
    12. var isAddress = /^[0-9]+[L|l]$/g;
    13. if (isAddress.test(followingAddress)) {
    14. query.address = followingAddress;
    15. } else {
    16. query.username = followingAddress;
    17. }
    18. library.balancesSequence.add(function (cb) {
    19. if (body.multisigAccountPublicKey && body.multisigAccountPublicKey != keypair.publicKey.toString('hex')) {
    20. ...
    21. try {
    22. var transaction = library.logic.transaction.create({
    23. type: TransactionTypes.FOLLOW,
    24. sender: account,
    25. keypair: keypair,
    26. secondKeypair: secondKeypair,
    27. contactAddress: followingAddress, // 511行
    28. requester: keypair
    29. });
    30. } catch (e) {
    31. return cb(e.toString());
    32. }
    33. modules.transactions.receiveTransactions([transaction], cb);
    34. });
    35. });
    36. ...

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

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

    1. shared.getContacts = function (req, cb) {
    2. // 362行
    3. modules.accounts.getAccount({address: query.address}, function (err, account) {
    4. ...
    5. async.series({
    6. contacts: function (cb) {
    7. // 372行
    8. if (!account.contacts.length) {
    9. return cb(null, []);
    10. }
    11. modules.accounts.getAccounts({address: {$in: account.contacts}}, ["address", "username"], cb);
    12. },
    13. followers: function (cb) {
    14. if (!account.followers.length) {
    15. return cb(null, []);
    16. }
    17. modules.accounts.getAccounts({address: {$in: account.followers}}, ["address", "username"], cb);
    18. }
    19. ...
    20. });
    21. });
    22. });
    23. };

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

    总结

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

    链接

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

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

    电子书阅读: http://bitcoin-on-nodejs.ebookchain.org

    参考

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