摘要:如果传的是,就会在内部使用默认的随机数生成器生成随机数并生成密钥。使用的是,生成的是一个形如这样的全球唯一的随机数把密钥以文件形式保存在硬盘上。
作者:freewind
比原项目仓库:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockc...
在前一篇,我们探讨了从浏览器的dashboard中进行注册的时候,密钥、帐户的别名以及密码,是如何从前端传到了后端。在这一篇,我们就要看一下,当比原后台收到了创建密钥的请求之后,将会如何创建。
由于本文的问题比较具体,所以就不需要再细分,我们直接从代码开始。
还记得在前一篇中,对应创建密钥的web api的功能点的配置是什么样的吗?
在API.buildHandler方法中:
api/api.go#L164-L244
func (a *API) buildHandler() { // ... if a.wallet != nil { // ... m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey)) // ...
可见,其路径为/create-key,而相应的handler是a.pseudohsmCreateKey(外面套着的jsonHandler在之前已经讨论过,这里不提):
api/hsm.go#L23-L32
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--- 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)会在以后的文章中,进行详细的讨论。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/24182.html
摘要:下一步,将进入比原的节点也就是后端。它具体是怎么创建密钥的,这在以后的文章中将详细讨论。当我们清楚了在本文中,前后端数据是如何交互的,就很容易推广到更多的情景。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在前面一篇文章,我们粗略...
摘要:而本文将继续讨论,比原是如何通过接口来创建帐户的。把各信息打包在一起,称之为另外,在第处还是一个需要注意的。比原在代码中使用它保存各种数据,比如区块帐户等。到这里,我们已经差不多清楚了比原的是如何根据用户提交的参数来创建帐户的。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://git...
摘要:继续看生成地址的方法由于这个方法里传过来的是而不是对象,所以还需要再用查一遍,然后,再调用这个私有方法创建地址该方法可以分成部分在第块中主要关注的是返回值。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在比原的dashboard中...
摘要:所以本文本来是想去研究一下,当别的节点把区块数据发给我们之后,我们应该怎么处理,现在换成研究比原的是怎么做出来的。进去后会看到大量的与相关的配置。它的功能主要是为了在访问与的函数之间增加了一层转换。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlo...
摘要:启动直到进入所以我们首先需要知道,比原在源代码中是如何启动,并且一步步走进了的世界。后面省略了一些代码,主要是用来获取当前监听的实际以及外网,并记录在日志中。 比原是如何监听p2p端口的 我们知道,在使用bytomd init --chain_id mainnet/testnet/solonet初始化比原的时候,它会根据给定的chain_id的不同,使用不同的端口(参看config/t...
阅读 1573·2021-11-24 09:39
阅读 3021·2021-11-22 15:24
阅读 3065·2021-10-26 09:51
阅读 3252·2021-10-19 11:46
阅读 2869·2019-08-30 15:44
阅读 2189·2019-08-29 15:30
阅读 2519·2019-08-29 15:05
阅读 751·2019-08-29 10:55