找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索

微学堂 NULS设计文档解读——账户模块

nuls 2020-3-14 10:37:59 显示全部楼层 阅读模式
账户.png 为什么要有账户模块

在区块链中,信息加密、数字签名和登录认证等应用场景,都需要使用非对称加密算法。非对称加密通常在加密和解密过程中使用两个非对称的密码,分别称为公钥和私钥。

非对称密钥对(公钥和私钥)具有两个特点:一是用其中一个密钥 (公钥或私钥) 加密信息后,只有另一个对应的密钥才能解开;二是公钥可向其他人公开,私钥则保密,其他人无法通过该公钥推算出相应的私钥。

在NULS2.0中,我们选择使用的非对称加密算法是椭圆曲线算法,通过椭圆曲线算法生成公钥和私钥之后,我们需要对公钥和私钥进行管理。

账户模块就是用于管理用户的公钥和私钥的。本质上,账户模块的所有功能都是基于公钥和私钥实现的,例如,基于公钥生成地址,基于密码和私钥生成Keystore,通过私钥进行交易签名等。

账户模块功能

账户模块功能体现了该模块的作用,也是开发该模块需要达到的目标。读者阅读本文档的目标是,理解账户模块具有哪些功能,以及这些功能的实现流程。

账户模块的功能主要分为四类:

  • 账户管理
  • 转账交易
  • 设置别名
  • 交易签名

下面我们将详细讲解以上四类功能的作用及实现流程。

账户管理

账户管理主要包含以下子功能:

  • 创建账户
  • 导入账户
  • 账户备份
  • 修改密码
  • 移除账户
  • 账户信息查询
创建账户功能

通过创建账户功能,用户可以获得一个NULS账户。

NULS账户中,最重要的属性就是NULS地址,理解NULS账户,要先从理解NULS地址开始。

NULS地址格式

一个NULS地址由多个部分组成,这有点像现实世界的地址,现实世界的地址通常会包含国家、省、市、区等多个组成部分,例如,中国北京市朝阳区朝阳公园南路XX号。

NULS地址(addressString)由前缀(prefix)、分隔符、链ID(chainId)、地址类型(addressType)、公钥摘要(pkh)、校验位(xor)组成。

addressType = prefix +分隔符+ Base58Encode(chainId+addressType+pkh+xor)
  • chainId,为当前链的链id,用于区分不同的区块链。chainId占2个字节,取值范围是1~65535,chainId是地址中非常重要的数据,是跨链操作的基础;
  • addressType,地址类型有三种取值,分别为 :1:普通地址,2:智能合约地址,3:多重签名地址。addressType占一个字节,取值范围为1~128;
  • prefix,前缀是为了便于识别、区分不同链的地址。目前,NULS提供了3种prefix的生成方案:
  • 默认前缀NULS和tNULS。NULS主网chainId为1,默认NULS主网(即chainId为1的链)地址以“NULS”为前缀;NULS核心测试网chainId为2,默认NULS核心测试网(即chainId为2的链)地址以"tNULS"为前缀;
  • 通过登记跨链设置前缀。 在登记跨链时,需要手动填写此链的前缀,系统会维护chainId和前缀的对应关系表,根据对应关系表生成相应的地址前缀;
  • 自动计算。NULS同构体系(通过ChainBox&ChainFactory开发搭建)的区块链,将按照如下的代码规则,自动计算生成一个地址前缀:
  //将chainId转换为字节数组,使用base58算法对字节数组进行计算,计算后全部转为大写字母  String prefix = Base58.encode(SerializeUtils.int16ToBytes(chainId)).toUpperCase();
  • pkh,公钥摘要。1)生成一个NULS地址,首先需要基于椭圆曲线算法获得一个公私钥对(ECKey)。NULS的椭圆曲线参数和比特币一样,使用secp256k1;2)ECKey与地址的关联关系就体现在这一部分,NULS的做法是先用Sha-256对公钥进行一次计算,得到的结果再通过 RIPEMD160进行一次计算得到20个字节的结果,就是pkh;
  • xor,校验位。NULS在生成字符串地址时,会增加一个字节的校验位,计算方式是对前面23个字节(chainId+type+pkh)进行异或运算。 校验位不参与序列化;
  • 分隔符,在前缀和Base58Encode(chainId+addressType+pkh+xor)之间,用一个小写字母进行分隔,便于从地址中提取chainId和验证地址类型及正确性。 小写字母的选择方式为,提供一个数组,按照字母表的顺序填充小写字母,根据prefix的长度来选择分隔的字母:
//前缀长度是几个字母,就选择第几个元素为分隔字母。//如前缀长度为2,则用b分隔,长度为3用c分隔,长度为4用d分隔,……String[] LENGTHPREFIX = new String[]{"", "a", "b", "c", "d"......"z"};

创建账户功能主要流程如下:

理解NULS地址的格式之后,下面介绍创建NULS账户的主要流程:

  • 1、生成公私钥对ECKey;
  • 2、生成公钥摘要pkh;
  • 3、将address=(chainId(2) + type(1) + PKH(20))序列化;
  • 4、根据序列化内容,生成校验位xor;
  • 5、添加前缀prefix和分隔符,生成字符串地址:
  • 固定前缀字符串地址
  addressString = prefix + 分隔符 + Base58Encode(address+xor)
  • 自动前缀字符串地址
  addressString = Base58Encode(chainId) + 分隔符 + Base58Encode(address+xor)

对于像比特币、以太坊,这样非NULS体系内的区块链,想要接入NULS生态,就需要有对应的NULS地址,与这些区块链上的每个原生地址形成映射关系。

NULS设计了一个地址转换协议,在NULS生态内,生成对应的映射地址:

address = Base58Encode(chainId+原始地址长度+原始地址+xor)

例如:比特币地址,在地址之前追加两个字节的chainId,之后跟随比特币的原始地址,地址解析方式根据链配置决定,确保任何一个地址都可以在NULS获得映射的地址。

导入账户

通过导入账户功能,用户可以在钱包中将私钥或Keystore导入,在输入密码后,登录到账户中。

导入私钥的主要流程如下:

  • 1、用户导入私钥,输入新密码;
  • 2、根据私钥,计算出公钥;
  • 3、参考创建账户功能中的流程,在节点本地生成账户,从而得到已有账户信息,完成登录;

导入Keystore的主要流程如下:

  • 1、用户导入Keystore,输入密码;
  • 2、根据Keystore和密码,计算得到私钥,再计算出公钥;
  • 3、参考创建账户功能中的流程,在节点本地生成账户,从而得到已有账户信息,完成登录。

这里有一个困惑:为何两种方式,都需要重新生成账户,而常见的系统,用账号和密码直接登录就可以?

这是因为区块链的去中心化导致的,因为对于每一个本地的节点来说,用户无论是导入私钥,还是Keystore,本地节点都不一定存储了已有账户的信息,所以通过两种方式导入账户,我们都是重新生成账户,得到已有账户的信息。

而中心化的系统中,每次使用账号密码登录,都是与中心的服务器数据在进行匹配,匹配成功,就会认为登录成功,不需要再生成新的账户。

账户备份

账户备份分为明文私钥备份和Keystore备份两种方式。

明文私钥备份主要流程如下:

  • 1、用户输入密码,发起明文私钥备份请求;
  • 2、密码验证通过,根据密码,将本地保存的加密后的私钥进行解密;
  • 3、生成明文私钥字符串,返回给用户。

Keystore备份主要流程如下:

  • 1、用户输入密码,发起Keystore备份请求;
  • 2、密码验证通过,计算得到账户的公钥;
  • 3、将本地保存的加密后的私钥、账户地址字符串、公钥,组装成Json字符串;
  • 4、生成Keystore文件,返回给用户。
修改密码

通过修改密码功能,存储用户账户数据的节点,将会根据新密码,对私钥进行加密。

修改密码主要流程如下:

  • 1、用户输入原密码和新密码,发起修改密码请求;
  • 2、原密码验证通过,使用原密码将加密的私钥解密;
  • 3、用新密码对私钥进行重新加密;
  • 4、将重新加密的私钥保存在本地。
移除账户

通过移除账户功能,用户可以将本地节点保存的账户数据删除。

移除账户功能主要流程如下:

  • 1、用户输入密码,发起移除账户请求;
  • 2、密码验证通过,删除本地存储的账户信息;
  • 3、操作成功,返会操作成功信息给前端。
账户信息查询

账户信息查询功能,是通过提供接口的形式,对外提供服务,让其他模块在实现相关功能时使用。用户通过账户信息查询功能提供的接口,可以查询到账户的地址、余额、别名等账户信息 。具体可以查询到的账户信息,可以参考账户模块提供的查询接口,接口参考[账户模块RPC-API接口文档] 。

转账交易

通过转账交易功能,用户可以发起一笔普通转账,将数字资产从一个账户转移到另一个账户。

理解转账交易功能,需要先理解交易协议,交易协议规定了一个交易需要包含哪些数据。以下是交易协议格式:

LenFieldsData TypeRemark
2typeuint16交易类型
4timeuint32时间,精确到秒
txDataVarByte业务数据
coinDataVarByte资产数据
remarkVarString备注
sigDataVarByte包含公钥和签名数据

CoinData的数据格式

交易协议中的CoinData,是交易的资产数据,由两个列表数据List<CoinFrom>、List<CoinTo>组成,NULS目前定义了一套通用的CoinData格式,具体如下:

froms://List<CoinFrom>格式,tos://List<CoinTo>格式

CoinFrom[70]:

address:  //byte[24] 账户地址  assetsChainId://uint16 资产发行链的assetsId: //uint16 资产amount:  //uint128,转出数量nonce  : //byte[8] 交易顺序号,前一笔交易的hash的后8个字节locked : //byte 是否是锁定状态(locktime:-1),1代表锁定,0代表非锁定

CoinTo[68]:

address:  //byte[24],目标地址assetsChainId://uint16 资产发行链的assetsId: //uint16 资产amount :  //uint128,转账金额lockTime://uint32,解锁高度或解锁时间,-1为永久锁定

在交易协议中,我们规定普通转账的交易类型(type)取值为2,因为转账交易不涉及业务数据,所以业务数据(txData)取值为空,其他数据会根据具体转账交易的情况对数据进行填充。

转账交易功能的主要流程如下:

  • 1、用户填写转账金额,输入密码,发起交易;
  • 2、账户模块根据交易协议数据格式,填充对应转账交易数据;
  • 3、计算交易数据的Hash值,对交易Hash进行签名(签名过程见下文),广播交易。
设置别名

通过设置别名功能,用户可以为账户设置一个自己想要的别名。

因为设置别名,需要将账户的别名数据上链,所以该功能将会触发一笔交易。设置别名交易的数据格式,遵循前面提到的交易协议,只是交易类型(type)、业务数据(txData)取值的不同。

type: 3 //设置别名交易的类型为3txData:{    address:  //VarByte 设置别名的地址    alias:   //VarByte 别名字符串转成的字节数组,用UTF-8解码}
  • alias(别名)数据格式
LenFieldsData TypeRemark
24addressbyte[]设置别名的地址
32aliasbyte[]别名字符串转成的字节数组,用UTF-8解码

设置别名功能的主要流程如下:

  • 1、用户输入别名,输入密码,发起设置别名请求;
  • 2、进行相关数据验证,具体包括:
1、别名格式合法性验证2、主网上是否已经存在此别名3、该地址是否已经设置过别名
  • 3、账户模块根据交易协议数据格式,填充对应转账交易数据;(注:设置别名交易需要烧毁一个Token单位,所以交易目标地址是一个黑洞地址,其他交易的操作本质上与转账交易没有差别。)
  • 4、计算交易数据的Hash值,对交易Hash进行签名(签名过程见下文),广播交易。
  • 5、在本地对别名交易进行业务验证,包括主网上是否已经存在此别名、该地址是否已经设置过别名,验证通过,等待其他节点对该交易的处理结果;
  • 6、如果其他节点验证该交易通过,则进行提交操作,在节点本地保存该交易,否则,执行回滚。
交易签名

通过交易签名功能,账户模块会使用私钥,对通过交易数据计算得到的交易Hash进行签名。

签名后的交易被认为获得了用户的授权,在签名完成后,交易才会被广播到NULS网络中,其他节点会验证交易的正确性。

交易签名功能的主要流程如下:

  • 1、组装交易数据,计算交易数据的Hash值;
  • 2、通过密码和Keystore,获得私钥,对交易Hash进行签名。
其他Java特有的设计
  • Account对象设计

该表存储时使用的key:

NULS同构体系:chainId+type+hash160

NULS异构体系:chainId+length+address

`字段名称``type``说明`
chainIdshort链ID
addressString账户地址(Base58(address)+Base58(chainId))
aliasString账户别名
statusInteger是否默认账户(不保存)
pubKeybyte[]公匙
priKeybyte[]私匙-未加密
encryptedPriKeybyte[]已加密私匙
extendbyte[]扩展数据
remarkString备注
createTimelong创建时间
  • Address对象设计(不持久化存储)
`字段名称``type``说明`
chainIdshort链ID
addressTypebyte地址类型
hash160byte[]公匙hash
addressBytesbyte[]地址字节数组
  • Alias对象设计

该表存储时使用的key:

address和alias分别作为key存储,别名数据存储两份

需要按照不同的链分别创建不同的别名表

`字段名称``type``说明`
addressbyte[]账户地址
aliasString账户别名
账户模块启动时需要依赖的模块

账户模块在启动时,需要依赖以下三个模块:

1、交易模块:因为账户模块需要提供转账交易和设置别名两个功能,这两个功能是涉及到交易,需要在交易模块中,进行注册;

2、网络模块:账户模块需要通过网络模块对外发送消息,需要在网络模块中注册自己需要发送的消息类型;

3、协议升级模块:协议升级模块会自动检测是否有协议升级,账户模块在启动时,会将转账交易和设置别名交易的协议注册到协议升级模块中。



项目网站:https://nuls.io
个人微信:nulsio,加入NULS社群
官方QQ群:474789123
您需要登录后才可以回帖 登录 | 立即注册

  • 4 关注
  • 3 粉丝
  • 521 帖子