Author: freewind
Compared to the original project warehouse:
GitHub Address: Https://github.com/Bytom/bytom
Gitee Address: Https://gitee.com/BytomBlockc ...
In the previous article, we explored how the key, account alias, and password were uploaded from the front end to the backend when registering from the browser's dashboard. In this article, we'll look at how it will be created when the request to create the key is received in the background than the original.
As this article is more specific, so there is no need to subdivide, we start directly from the code.
Remember in the previous article, what is the configuration of the function point for the Web API that created the key?
In the API.buildHandler
method:
api/api.go#l164-l244
func (a *API) buildHandler() { // ... if a.wallet != nil { // ... m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey)) // ...
Visible, its path is /create-key
, and the corresponding handler is a.pseudohsmCreateKey
(outside the cover jsonHandler
has been discussed before, here does not mention):
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)}
It's mostly called a.wallet.Hsm.XCreate
, let's go in:
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}
The word appears, HSM
it means Hardware-Security-Module
that the original is also reserved for hardware-related modules (not discussed).
The above code is divided into 4 parts, namely:
- First, standardize the incoming
alias
parameters, that is, go blank on both sides, and convert to lowercase
- Check that
cache
there is no, some words will be directly returned and reported a corresponding error, will not be repeated generation, because the private key and alias is one by one corresponding. The front end can be used to alert users to check or change a new alias based on this error.
- Called
createChainKDKey
to generate the corresponding key and get the returned public keyxpub
- Put the public key into the cache. It seems that the public key and the alias are not the same thing, so why is it possible to query alias before?
So let's go h.cache.hasAlias
look at:
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
as we can see, the original alias is bound to the public key and can be thought of as alias
a property of the public key (and, of course, the corresponding private key). So the public key is placed in the cache before the alias can be queried.
So how does the 3rd step generate the key? 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}
This code is quite clear, we can divide it into 4 steps, namely:
- Invokes the
chainkd.NewXKeys
generate key. It chainkd
corresponds to another package in the original code base, "crypto/ed25519/chainkd"
and from the name, the algorithm is used ed25519
. If you have an impression that the previous article "How to connect to a previous node", you will remember that the algorithm generates a pair of keys that are used to encrypt the communication when the connection is connected. However, it is important to note that although both are ed25519
algorithms, the last code used was from a third-party library "github.com/tendermint/go-crypto"
. It is different from the details of this algorithm, it is not clear, left to the appropriate opportunity to study. Then there is the incoming chainkd.NewXKeys(nil)
parameter, which corresponds to the nil
"random number generator". If nil
so, NewXKeys
random numbers are generated internally using the default random number generator and the key is generated. The relevant content of the key algorithm is not discussed in this paper.
- Generates a unique ID for the current key, which is later used to generate the file name, saved on the hard disk. The ID uses the UUID, which generates a
62bc9340-f6a7-4d16-86f0-4be61920a06e
globally unique random number that is shaped like this.
- Save the key as a file on your hard disk. This piece of content is more, detailed below.
- The public key-related information is combined for use by the caller.
Let's talk about the 3rd step in detail and save the key as a file. The first is to generate the file name, keyFileName
the corresponding code of the function is as follows:
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)}
Note that the parameters here should actually be the UUID that was keyAlias
keyID
generated earlier. Written alias
somewhat misleading, has been submitted to pr#922. The last generated file name, such as:UTC--2018-05-07T06-20-46.270917000Z--62bc9340-f6a7-4d16-86f0-4be61920a06e
After the file name is generated, h.keyStore.JoinPath
it is placed in the appropriate directory. Generally speaking, this directory is the native data directory keystore
, if you are the OSX system, it should be in your ~/Library/Bytom/keystore
, if anything else, you can use the following code to determine Defaultdatadir ()
About the above to save the key file directory, exactly how to determine, in the code is actually a bit around. But if you are interested in this, I believe you should be able to find it yourself, this is not listed here. If you can't find it, try the following keywords:,, pseudohsm.New(config.KeysDir())
, os.ExpandEnv(config.DefaultDataDir())
DefaultDataDir()
DefaultBaseConfig()
At the end of the 3rd step, the method is called to keyStore.StoreKey
save it as a file. The method code 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) if err != nil { return err } return writeKeyFile(filename, keyjson)}
EncryptKey
Has done a lot of things, using incoming keys and other information to generate JSON-formatted information, and then writeKeyFile
save it on the hard disk. So in your keystore
directory, you will see the key file that belongs to you. They are important, so don't delete them by mistake.
a.wallet.Hsm.XCreate
After reading, let's go back to a.pseudohsmCreateKey
the last part of the method. As you can see, when a key is successfully generated, it returns one NewSuccessResponse(xpub)
that returns the information associated with the public key to the front end. It will be jsonHandler
automatically converted into JSON format and returned to the past via HTTP.
In this case, we focused on /create-key
what was done internally, and where the key file was placed, after the request was received via the Web API interface. The algorithm that involves the key (for example ed25519
) will be discussed in detail in a later article.