On a practical front, you can generate the Enode from the node key using Geth's bootnode utility:
> /usr/local/bin/bootnode -nodekeyhex 59233b25bfa4c214a8713e07a395a5d11478de10f36c6c80ba5369541f73bc44 -writeaddress
c636515b084e5dcfb39c0e00e3d0dd5b5c4ba7c04d9a4adc3aad4eea6ab25561f0fa09fce119e2aebdcfa34d02a3c8f551b814e31df6940fa937f16e0624fc40
The enode URL is then constructed like this:
enode://ENODE@IP:PORT
I.e.
enode://c636515b084e5dcfb39c0e00e3d0dd5b5c4ba7c04d9a4adc3aad4eea6ab25561f0fa09fce119e2aebdcfa34d02a3c8f551b814e31df6940fa937f16e0624fc40@127.0.0.1:30303
If you want to know the actual calculation, the source code from Geth works as follows.
bootnode executes this line
to process the input nodeKeyHex
from the command line parameter into nodeKey
:
nodeKey, err = crypto.HexToECDSA(*nodeKeyHex)
After this, nodeKey.PublicKey
is the Public Key (which is the enode) generated from the Private Key (which is the node key) using this function:
func toECDSA(d []byte, strict bool) (*ecdsa.PrivateKey, error) {
priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = S256()
if strict && 8*len(d) != priv.Params().BitSize {
return nil, fmt.Errorf("invalid length, need %d bits", priv.Params().BitSize)
}
priv.D = new(big.Int).SetBytes(d)
priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(d)
return priv, nil
}