剥开比原看代码10:比原是如何通过/create-key接口创建密钥的
作者:freewind
比原项目仓库:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
在前一篇,我们探讨了从浏览器的dashboard中进行注册的时候,密钥、帐户的别名以及密码,是如何从前端传到了后端。在这一篇,我们就要看一下,当比原后台收到了创建密钥的请求之后,将会如何创建。
由于本文的问题比较具体,所以就不需要再细分,我们直接从代码开始。
还记得在前一篇中,对应创建密钥的web api的功能点的配置是什么样的吗?
在API.buildHandler
方法中:
func (a *API) buildHandler() {
// ...
if a.wallet != nil {
// ...
m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey))
// ...
可见,其路径为/create-key
,而相应的handler是a.pseudohsmCreateKey
(外面套着的jsonHandler
在之前已经讨论过,这里不提):
func (a *API) pseudohsmCreateKey(ctx context.Context, in struct {
Alias string `json:"alias"`
Password string `json:"password"`
}) Response {
xpub, err := a.wallet.Hsm.XCreate(in.Alias, in.Password)
if err != nil {
return NewErrorResponse(err)
}
return NewSuccessResponse(xpub)
}
它主要是调用了a.wallet.Hsm.XCreate
,让我们跟进去:
blockchain/pseudohsm/pseudohsm.go#L50-L66
// XCreate produces a new random xprv and stores it in the db.
func (h *HSM) XCreate(alias string, auth string) (*XPub, error) {
// ...
// 1.
normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
// 2.
if ok := h.cache.hasAlias(normalizedAlias); ok {
return nil, ErrDuplicateKeyAlias
} // 3.
xpub, _, err := h.createChainKDKey(auth, normalizedAlias, false)
if err != nil {
return nil, err
}
// 4.
h.cache.add(*xpub)
return xpub, err
}
其中出现了HSM
这个词,它是指Hardware-Security-Module
,原来比原还预留了跟硬件相关的模块(暂不讨论)。
上面的代码分成了4部分,分别是:
- 首先对传进来的
alias
参数进行标准化操作,即去两边空白,并且转换成小写 - 检查
cache
中有没有,有的话就直接返回并报个相应的错,不会重复生成,因为私钥和别名是一一对应的。在前端可以根据这个错误提醒用户检查或者换一个新的别名。 - 调用
createChainKDKey
生成相应的密钥,并拿到返回的公钥xpub
- 把公钥放入cache中。看起来公钥和别名并不是同一个东西,那前面为什么可以查询alias呢?
所以我们进入h.cache.hasAlias
看看:
blockchain/pseudohsm/keycache.go#L76-L84
func (kc *keyCache) hasAlias(alias string) bool {
xpubs := kc.keys()
for _, xpub := range xpubs {
if xpub.Alias == alias {
return true
}
}
return false
}
通过xpub.Alias
我们可以了解到,原来别名跟公钥是绑定的,alias
可以看作是公钥的一个属性(当然也属于相应的私钥)。所以前面把公钥放进cache,之后就可以查询别名了。
那么第3步中的createChainKDKey
又是如何生成密钥的呢?
blockchain/pseudohsm/pseudohsm.go#L68-L86
func (h *HSM) createChainKDKey(auth string, alias string, get bool) (*XPub, bool, error) {
// 1.
xprv, xpub, err := chainkd.NewXKeys(nil)
if err != nil {
return nil, false, err
}
// 2.
id := uuid.NewRandom()
key := &XKey{
ID: id,
KeyType: "bytom_kd",
XPub: xpub,
XPrv: xprv,
Alias: alias,
}
// 3.
file := h.keyStore.JoinPath(keyFileName(key.ID.String()))
if err := h.keyStore.StoreKey(file, key, auth); err != nil {
return nil, false, errors.Wrap(err, "storing keys")
}
// 4.
return &XPub{XPub: xpub, Alias: alias, File: file}, true, nil
}
这块代码内容比较清晰,我们可以把它分成4步,分别是:
- 调用
chainkd.NewXKeys
生成密钥。其中chainkd
对应的是比原代码库中的另一个包"crypto/ed25519/chainkd"
,从名称上来看,使用的是ed25519
算法。如果对前面文章“如何连上一个比原节点”还有印象的话,会记得比原在有新节点连上的时候,就会使用该算法生成一对密钥,用于当次连接进行加密通信。不过需要注意的是,虽然两者都是ed25519
算法,但是上次使用的代码却是来自第三方库"github.com/tendermint/go-crypto"
的。它跟这次的算法在细节上究竟有哪些不同,目前还不清楚,留待以后合适的机会研究。然后是传入chainkd.NewXKeys(nil)
的参数nil
,对应的是“随机数生成器”。如果传的是nil
,NewXKeys
就会在内部使用默认的随机数生成器生成随机数并生成密钥。关于密钥算法相关的内容,在本文中并不探讨。 - 给当前密钥生成一个唯一的id,在后面用于生成文件名,保存在硬盘上。id使用的是uuid,生成的是一个形如
62bc9340-f6a7-4d16-86f0-4be61920a06e
这样的全球唯一的随机数 - 把密钥以文件形式保存在硬盘上。这块内容比较多,下面详细讲。
- 把公钥相关信息组合在一起,供调用者使用。
我们再详细讲一下第3步,把密钥保存成文件。首先是生成文件名,keyFileName
函数对应的代码如下:
blockchain/pseudohsm/key.go#L96-L101
// keyFileName implements the naming convention for keyfiles:
// UTC--<created_at UTC ISO8601>-<address hex>
func keyFileName(keyAlias string) string {
ts := time.Now().UTC()
return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), keyAlias)
}
注意这里的参数keyAlias
实际上应该是keyID
,就是前面生成的uuid。写成alias
有点误导,已经提交PR#922。最后生成的文件名,形如:UTC--2018-05-07T06-20-46.270917000Z--62bc9340-f6a7-4d16-86f0-4be61920a06e
生成文件名之后,会通过h.keyStore.JoinPath
把它放在合适的目录下。通常来说,这个目录是本机数据目录下的keystore
,如果你是OSX系统,它应该在你的~/Library/Bytom/keystore
,如果是别的,你可以通过下面的代码来确定DefaultDataDir()
关于上面的保存密钥文件的目录,到底是怎么确定的,在代码中其实是有点绕的。不过如果你对这感兴趣的话,我相信你应该能自行找到,这里就不列出来了。如果找不到的话,可以试试以下关键字:pseudohsm.New(config.KeysDir())
, os.ExpandEnv(config.DefaultDataDir())
, DefaultDataDir()
,DefaultBaseConfig()
在第3步的最后,会调用keyStore.StoreKey
方法,把它保存成文件。该方法代码如下:
blockchain/pseudohsm/keystore_passphrase.go#L67-L73
func (ks keyStorePassphrase) StoreKey(filename string, key *XKey, auth string) error {
keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
if err != nil {
return err
}
return writeKeyFile(filename, keyjson)
}
EncryptKey
里做了很多事情,把传进来的密钥及其它信息利用起来生成了JSON格式的信息,然后通过writeKeyFile
把它保存硬盘上。所以在你的keystore
目录下,会看到属于你的密钥文件。它们很重要,千万别误删了。
a.wallet.Hsm.XCreate
看完了,让我们回到a.pseudohsmCreateKey
方法的最后一部分。可以看到,当成功生成key之后,会返回一个NewSuccessResponse(xpub)
,把与公钥相关的信息返回给前端。它会被jsonHandler
自动转换成JSON格式,通过http返回过去。
在这次的问题中,我们主要研究的是比原在通过web api接口/create-key
接收到请求后,在内部做了哪些事,以及把密钥文件放在了哪里。其中涉及到密钥的算法(如ed25519
)会在以后的文章中,进行详细的讨论。
剥开比原看代码10:比原是如何通过/create-key接口创建密钥的的更多相关文章
- 剥开比原看代码11:比原是如何通过接口/create-account创建帐户的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码16:比原是如何通过/list-transactions显示交易信息的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码13:比原是如何通过/list-balances显示帐户余额的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码12:比原是如何通过/create-account-receiver创建地址的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码09:通过dashboard创建密钥时,前端的数据是如何传到后端的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码03:比原是如何监听p2p端口的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码08:比原的Dashboard是怎么做出来的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- Derek解读Bytom源码-protobuf生成比原核心代码
作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom ...
- Java多线程系列--“JUC原子类”03之 AtomicLongArray原子类
概要 AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray这3个数组类型的原子类的原理和用法相似.本章以AtomicLongArray对数 ...
随机推荐
- uvalive 3415 Guardian Of Decency
题意: 有一个老师想组织学生出去旅游,为了避免他们之间有情侣产生,他制定了一系列的条件,满足这些条件之一,那么这些人理论上就不会成为情侣: 身高相差40cm:性别相同:喜欢的音乐风格不同:最喜欢的运动 ...
- NSOperation、NSOperationQueue(III)
NSOperation.NSOperationQueue 常用属性和方法归纳 NSOperation 常用属性和方法 a. 取消操作方法 //可取消操作,实质是标记 isCancelled 状态. - ...
- 项目方说性能达到百万TPS,如何测试它的可信度?
项目方说性能达到百万TPS,如何测试它的可信度? 应用系统性能提升的关键在于运维端的接入管理模型(AAA,认证 Authentication.授权 Authorization.计费 Accountin ...
- golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息
golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放 ...
- HDU 1232 畅通工程 (并查集)
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇.省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可). ...
- [转载]PowerDesigner生成的ORACLE 建表脚本中去掉对象的双引号,设置大、小写
若要将 CDM 中将 Entity的标识符都设为指定的大小写,则可以这么设定: 打开cdm的情况下,进入Tools-Model Options-Naming Convention,把Name和Code ...
- docker local registry server gave HTTP response to HTTPS client
server gave HTTP response to HTTPS client报错是在insecure_registry中加入了http前缀,如果本地registry不是https的 就不要加任何 ...
- flask框架----上下文管理
一.上下文管理相关知识点: a.类似于本地线程 创建Local类: { 线程或协程唯一标识: { 'stack':[request],'xxx':[session,] }, 线程或协程唯一标识: { ...
- VisualSVN Server 服务器搭建 和 TortoiseSVN的配置和使用方法
摘自:https://blog.csdn.net/litaoshoujiao/article/details/8526136 一.VisualSVN Server的配置和使用方法[服务器端] 安装好V ...
- django 正向,反向
表名 ,foreignkey, 正向 obj.表名小写_set.all() 反向操作.