Ethereum private/public keys, addresses and PEM certificates

Public key to Ethereum address

A public key must be 64 bytes long (65 bytes with the constant 0x04 prefix). An Ethereum address is made of 20 bytes, it is commonly represented by adding the 0x prefix. In order to derive it, one should take the keccak-256 hash (SHA3-256) of the hexadecimal form of a public key (without the 0x04 prefix), then keep only the last 20 bytes. See this blog post for further details. Example:

# Generate the hash and take the address part
> cat pub | keccak-256sum -x -l | tr -d ' -' | tail -c 41

Caveat

An implementation of the SHA3 (Keccak) is not easy to obtain on every platform. Most popular implementation is maandree/sha3sum based on maandree/libkeccak. However, it requires a compilation of the source code.

Python 3.6 hashlib provides an implementation of this algorithm.

Ethereum private to public key

A private key must be 32 bytes long. To obtain a public key, the secp256k1 generator point G must be multiplied by the secret key (which is the private key). An algorithmic description of the elliptic curve point multiplication can be found here. An implementation in Python derived from this thread:

pubkey
#! /usr/bin/env python

import sys

def sk_to_pk(sk):
    """
    Derive the public key of a secret key on the secp256k1 curve.

    Args:
        sk: An integer representing the secret key (also known as secret
          exponent).

    Returns:
        A coordinate (x, y) on the curve repesenting the public key
          for the given secret key.

    Raises:
        ValueError: The secret key is not in the valid range [1,N-1].
    """
    # base point (generator)
    G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
         0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)

    # field prime
    P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F

    # order
    N = (1 << 256) - 0x14551231950B75FC4402DA1732FC9BEBF

    # check if the key is valid
    if not(0 < sk < N):
        msg = "{} is not a valid key (not in range [1, {}])"
        raise ValueError(msg.format(hex(sk), hex(N-1)))

    # addition operation on the elliptic curve
    # see: https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Point_addition
    # note that the coordinates need to be given modulo P and that division is
    # done by computing the multiplicative inverse, which can be done with
    # x^-1 = x^(P-2) mod P using fermat's little theorem (the pow function of
    # python can do this efficiently even for very large P)
    def add(p, q):
        px, py = p
        qx, qy = q
        if p == q:
            lam = (3 * px * px) * pow(2 * py, P - 2, P)
        else:
            lam = (qy - py) * pow(qx - px, P - 2, P)
        rx = lam**2 - px - qx
        ry = lam * (px - rx) - py
        return rx % P, ry % P

    # compute G * sk with repeated addition
    # by using the binary representation of sk this can be done in 256
    # iterations (double-and-add)
    ret = None
    for i in xrange(256):
        if sk & (1 << i):
            if ret is None:
                ret = G
            else:
                ret = add(ret, G)
        G = add(G, G)

    return ret

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print 'Usage: %s <private key>' % sys.argv[0]
        sys.exit(1)

    key_str = sys.argv[1]
    if len(key_str) != 64:
        print 'Private key must contain 64 hex characters'
        sys.exit(1)

    key = long(key_str, 16)
    px, py = sk_to_pk(key)
    print '04%064x%064x' % (px, py)

PEM private key and certificate from hex private/public keys

secp256k1 private key in PEM format (including the public key) can be constructed by openssl with an intermediate step over a DER format key expressed in ASN.1 syntax (openssl nconf):

key.asn1
asn1 = SEQUENCE:seq_section

[seq_section]
version    = INTEGER:01
privateKey = FORMAT:HEX,OCT:<private key hex>
parameters = EXPLICIT:0,OID:secp256k1
publicKey  = EXPLICIT:1,FORMAT:HEX,BITSTR:<public key hex>

A PEM file can be then obtained by:

> openssl asn1parse -noout -genconf privkey.asn1 -out privkey.der
> openssl ec -inform DER -in privkey.der -out privkey.pem

PEM key check

> openssl ec -noout -text -in privkey.pem

PEM certificate from PEM key

> openssl req -new -nodes -key privkey.pem -x509 -days 365 -out certificate.pem -subj "/CN=localhost"

Check certificate

> openssl x509 -noout -text -in certificate.pem