Author: freewind
Biyuan Project warehouse:
Making address: https://github.com/Bytom/bytom
Gitee address: https://gitee.com/BytomBlockchain/bytom
In the previous article, we looked at how keys, alias names, and passwords are transferred from the front end to the back end when registering from the browser’s dashboard. In this article, we will look at what happens when the background receives a request to create a key.
Since the problems in this article are more specific, we don’t need to break them down, so we’ll just start with the code.
Remember from the previous article what the configuration looked like for function points corresponding to the Web API that created the key?
In the api.buildHandler method:
api/api.go#L164-L244
func (a *API) buildHandler(a) {
// ...
ifa.wallet ! =nil {
// ...
m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey))
// ...
Copy the code
The path is /create-key, and the corresponding handler is a.pseudohsmCreateKey:
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) }Copy the code
It basically calls a.wallet.hsm.xcreate, let’s follow through:
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)
iferr ! =nil {
return nil, err
}
/ / 4.
h.cache.add(*xpub)
return xpub, err
}
Copy the code
The word HSM is used to refer to hardware-security-module, which is reserved for Hardware related modules.
The above code is divided into four parts:
- The first one that came in
alias
Parameters are normalized by removing white space from both sides and converting them to lowercase - check
cache
The private key and alias are one – to – one correspondence. This error can be used in the front end to remind the user to check or change to a new alias. - call
createChainKDKey
Generate the corresponding key and retrieve the returned public keyxpub
- Put the public key in the cache. It looks like the public key and alias are not the same thing, so why query alias earlier?
So let’s go to H.cache.hasalias and see:
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
}
Copy the code
Alias is a property of the public key (and of course of the corresponding private key). So we put the public key in the cache, and then we can query the alias.
How does the createChainKDKey in step 3 generate the key?
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)
iferr ! =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()))
iferr := 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
}
Copy the code
This code content is relatively clear, we can divide it into 4 steps, respectively:
- call
chainkd.NewXKeys
Generate a key. Among themchainkd
This corresponds to another package in the original code base"crypto/ed25519/chainkd"
From the name, is useded25519
Algorithm. If you still remember from the previous article “how to connect a node”, you will remember that when a new node is connected, you will use this algorithm to generate a pair of keys for the encryption communication of the second connection. It’s important to note, though, that both are trueed25519
Algorithm, but the code used last time was from a third-party library"github.com/tendermint/go-crypto"
. How it differs from this algorithm in detail is not yet clear, but will be studied at the appropriate time. And then incomingchainkd.NewXKeys(nil)
The parameters of thenil
, corresponding to the random number generator. If the transmission isnil
.NewXKeys
The default random number generator is used internally to generate random numbers and generate keys. The contents related to key algorithm are not discussed in this paper. - Generate a unique ID for the current key, which is later used to generate the file name and saved on the hard disk. Id uses the UUID and generates an example
62bc9340-f6a7-4d16-86f0-4be61920a06e
Such a globally unique random number - Save the key as a file on your hard drive. This is a lot of stuff, and I’ll talk about it in more detail.
- Group public key information together for use by the caller.
Let’s go over step 3 in more detail. Save the key to a file. First, the file name is generated. The keyFileName function corresponds to the following code:
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)
}
Copy the code
Note that the keyAlias argument here should actually be keyID, which is the UUID generated earlier. Writing alias is a bit misleading, PR#922 has been submitted. The generated file name is in the form UTC– 2018-05-07t06-20-46.270917000 Z–62bc9340-f6a7-4d16-86f0-4be61920a06e
After the file name is generated, it is placed in the appropriate directory through H.keyStore. JoinPath. If you are running OSX, it should be in your ~/Library/Bytom/keystore. If it is something else, you can use the following code to determine DefaultDataDir().
About the above save key file directory, exactly how to determine, in the code is actually a little round. But if you’re interested, I’m sure you’ll be able to find it on your own, so I won’t list it here. If you can’t find it, try these keywords: Pseudohsm.new (config.keysdir ()), os.expandenv (config.defaultDatadir ()), DefaultDataDir(), DefaultBaseConfig()
At the end of step 3, the keystore.storekey method is called to save it to a file. The code for this method is as follows:
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)
iferr ! =nil {
return err
}
return writeKeyFile(filename, keyjson)
}
Copy the code
A lot of work goes into EncryptKey, using the incoming key and other information to generate JSON information, which is then saved to the hard drive via writeKeyFile. Therefore, in your keystore directory, you will see your key file. They’re very important. Don’t delete them by mistake.
With a.wallet.hsm.xcreate finished, let’s go back to the last part of the a.pseudohsmCreateKey method. As you can see, when the key is successfully generated, a NewSuccessResponse(XPUB) is returned that returns information related to the public key to the front end. It is automatically converted to JSON format by jsonHandler and sent back over HTTP.
In this case, we’ll focus on what Bihara does internally after receiving the request through the Web API /create-key interface, and where it puts the key file. The key algorithms involved (such as ED25519) will be discussed in detail in future articles.