Ethereum Public Key, Private Key, and Address Conversion Basics

·

Understanding the cryptographic foundation of Ethereum is essential for developers, security analysts, and blockchain enthusiasts. At the heart of Ethereum's security model lies elliptic curve cryptography (ECC), specifically the secp256k1 curve. This system enables secure generation of private keys, public keys, and wallet addresses—each playing a critical role in transaction signing and identity verification.

In this guide, we’ll walk through how these components are generated and interconverted using real code examples based on the Go Ethereum library (geth). We'll maintain clarity while diving into technical depth, ensuring both beginners and experienced users gain value.


🔐 Private Key Generation

The private key is the cornerstone of Ethereum account security. It’s a randomly generated 256-bit (32-byte) number, typically represented as a 64-character hexadecimal string (without the 0x prefix).

Here’s how to generate one using Go:

key, err := crypto.GenerateKey()
if err != nil {
    t.Fatalf("failed to generate key: %s", err)
}
privateKeyHex := hex.EncodeToString(crypto.FromECDSA(key))
fmt.Println("Private key (no 0x):", privateKeyHex)

Example Output:

b1fb9a42d8478cf19bbc1cb4e75625ced1728c8de8691845f546b2ad84a7d379

This private key must be kept absolutely secret. Anyone with access to it can control the associated Ethereum account.

👉 Learn how secure wallet systems manage private keys


💾 Storing Your Private Key Securely

Once generated, you may want to persist the private key locally for later use:

err := crypto.SaveECDSA("privatekey", key)
if err != nil {
    log.Error("Failed to save private key:", "err", err)
}

This saves the key in an unencrypted file—not recommended for production environments. For enhanced security, always encrypt private keys using standards like keystore files with PBKDF2 or scrypt.


🌐 Public Key Derivation

From the private key, we derive the public key using elliptic curve multiplication. The full public key is 65 bytes long, starting with 0x04 (uncompressed format), followed by two 32-byte values representing the x and y coordinates on the curve.

To print it:

publicKeyBytes := crypto.FromECDSAPub(&key.PublicKey)
fmt.Println("Public key (no 0x):", hex.EncodeToString(publicKeyBytes))

Example Output:

0425b775a01b5df335cd71170f6a16d8b43704e68b8eb87a8e6ebfd3deafbfc1151d76bbe078002ffb7caaca06441b1c3976c3ca3b1e1fda9cf0f4591d799758e4

🧮 Compressing the Public Key

To save space, Ethereum supports compressed public keys, which are only 33 bytes. These start with 0x02 or 0x03 (depending on the parity of the y-coordinate), followed by the 32-byte x-coordinate.

While Go’s crypto package doesn’t expose compression directly, you can implement it manually:

func CompressPubkey(pubKey *ecdsa.PublicKey) []byte {
    return crypto.CompressPubkey(pubKey)
}

compressed := CompressPubkey(&key.PublicKey)
fmt.Println("Compressed public key:", hex.EncodeToString(compressed))

Decompression is also possible:

decompressedKey, _ := crypto.DecompressPubkey(compressed)

🏁 Generating an Ethereum Address

An Ethereum address is derived from the last 20 bytes of the Keccak-256 hash of the public key (excluding the 0x04 prefix).

Here’s how it works:

address := crypto.PubkeyToAddress(key.PublicKey)
fmt.Println("Ethereum Address:", address.Hex())

Output Example:

0x703c4b2bD70c169f5717101CaeE543299Fc946C7

Note: Ethereum addresses are case-insensitive but often displayed with mixed-case checksums via EIP-55 to prevent typos.


🔁 Converting String-Based Private Keys to Addresses

You can recover an address from an existing private key string:

privHex := "8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a"
key, _ := crypto.HexToECDSA(privHex)
address := crypto.PubkeyToAddress(key.PublicKey)
fmt.Println("Recovered Address:", address.Hex())

This is useful when importing accounts from backup phrases or external wallets.

👉 Explore tools that simplify key management securely


📦 Bytes to Address and Hash Conversion

Sometimes, raw byte data needs conversion into Ethereum types:

// Convert bytes to address (padded to 20 bytes)
addr := common.BytesToAddress([]byte("ethereum"))
fmt.Println("Byte-derived Address:", addr.Hex())

// Convert bytes to hash (padded to 32 bytes)
hash := common.BytesToHash([]byte("topic1"))
fmt.Println("Byte-derived Hash:", hash.Hex())

Output:

Address: 0x000000000000000000000000657468657265756D
Hash:    0x00...746f70696331

These conversions are commonly used in smart contract interactions and event parsing.


✍️ Recovering Public Keys from Signatures

One of Ethereum’s powerful features is the ability to recover the signer’s public key from a message and its signature, enabling trustless authentication.

Step-by-step Process:

  1. Hash the message using Keccak-256.
  2. Sign it with the private key.
  3. Use Ecrecover or SigToPub to retrieve the public key.
msg := crypto.Keccak256([]byte("foo"))
sig, _ := crypto.Sign(msg, key1)

// Method 1: Using Ecrecover
recoveredPub, _ := crypto.Ecrecover(msg, sig)
pubKey, _ := crypto.UnmarshalPubkey(recoveredPub)
recoveredAddr := crypto.PubkeyToAddress(*pubKey)

// Method 2: Direct SigToPub
recoveredPub2, _ := crypto.SigToPub(msg, sig)
recoveredAddr2 := crypto.PubkeyToAddress(*recoveredPub2)

fmt.Println("Original Address:   ", addrtest.Hex())
fmt.Println("Recovered Address:  ", recoveredAddr.Hex())
fmt.Println("Recovered via SigToPub:", recoveredAddr2.Hex())

All three addresses should match—proving signature validity without revealing the private key.


🧩 Core Keywords for SEO and Technical Clarity

To align with search intent and improve discoverability, here are the core keywords naturally integrated throughout this article:

These terms reflect what developers and learners typically search for when exploring Ethereum cryptography.


❓ Frequently Asked Questions (FAQ)

Q: How long is an Ethereum private key?

A: An Ethereum private key is 32 bytes (256 bits) and represented as a 64-character hexadecimal string.

Q: Can I derive a private key from a public key or address?

A: No. Due to the one-way nature of elliptic curve multiplication, deriving a private key from a public key or address is computationally infeasible—this ensures security.

Q: What is the purpose of compressing a public key?

A: Compression reduces the size from 65 to 33 bytes, saving bandwidth and storage. The original point can still be reconstructed during verification.

Q: Why does the public key start with 0x04?

A: The 0x04 prefix indicates an uncompressed public key. Compressed keys use 0x02 or 0x03.

Q: Is it safe to store a private key in plain text?

A: Absolutely not. Never store unencrypted private keys in files, version control, or memory dumps. Use encrypted keystores or hardware wallets instead.

Q: How is an Ethereum address created from a public key?

A: The address is the rightmost 20 bytes of the Keccak-256 hash of the public key (excluding the 0x04 prefix).


🧑‍💻 Complete Working Code Example

Create a file file_test.go under a test package:

package test

import (
    "encoding/hex"
    "fmt"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/log"
    "testing"
)

func TestGenerateKey(t *testing.T) {
    key, err := crypto.GenerateKey()
    if err != nil {
        t.Fatalf("Failed to generate key: %s", err)
    }

    fmt.Println("Private Key (no 0x):", hex.EncodeToString(crypto.FromECDSA(key)))
    
    if err := crypto.SaveECDSA("privatekey", key); err != nil {
        log.Error("Failed to save key", "err", err)
    }

    fmt.Println("Public Key (no 0x):", hex.EncodeToString(crypto.FromECDSAPub(&key.PublicKey)))

    accKey, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
    addr := crypto.PubkeyToAddress(accKey.PublicKey)
    fmt.Println("Address:", addr.String())

    msg := crypto.Keccak256([]byte("foo"))
    sig, _ := crypto.Sign(msg, accKey)

    recoveredPub, _ := crypto.SigToPub(msg, sig)
    recoveredAddr := crypto.PubkeyToAddress(*recoveredPub)
    fmt.Println("Recovered Address:", recoveredAddr.String())
}

Run with:

go test -v

👉 Get started with secure blockchain development practices