# Key Generation Pipeline

[← Back to Cryptography I: Blockchain Security and Cryptographic Foundations](/core-dev-bootcamp/module04.md)

### Introduction

Now that we understand where randomness comes from, let's explore how rippled transforms that randomness into cryptographic keys. This chapter traces the complete key generation pipeline—from random bytes to secret keys, from secret keys to public keys, and from public keys to account addresses.

We'll examine both random key generation (for new accounts) and deterministic key generation (for wallet recovery), and understand why rippled supports two different cryptographic algorithms.

### The Two Paths to Key Generation

Rippled supports two approaches to key generation:

```
Path 1: Random Generation
    crypto_prng() → SecretKey → PublicKey → AccountID
    (Used for: New accounts, one-time keys)

Path 2: Deterministic Generation
    Seed → SecretKey → PublicKey → AccountID
    (Used for: Wallet recovery, multiple accounts from one seed)
```

### Random Key Generation

#### The Simple Case: `randomSecretKey()`

```cpp
// From src/libxrpl/protocol/SecretKey.cpp
SecretKey randomSecretKey()
{
    std::uint8_t buf[32];
    beast::rngfill(buf, sizeof(buf), crypto_prng());
    SecretKey sk(Slice{buf, sizeof(buf)});
    secure_erase(buf, sizeof(buf));
    return sk;
}
```

**Step-by-step breakdown:**

1. **Allocate buffer**: Create 32-byte buffer on stack
2. **Fill with randomness**: Use `crypto_prng()` to fill with random bytes
3. **Construct SecretKey**: Wrap bytes in `SecretKey` object
4. **Secure cleanup**: Erase temporary buffer from memory
5. **Return**: `SecretKey` object (move semantics, no copy)

#### Why 32 Bytes?

```cpp
// 32 bytes = 256 bits
std::uint8_t buf[32];

// This provides 2^256 possible keys
// That's approximately 10^77 combinations
// More than atoms in the observable universe!
```

**Security level:**

* 128-bit security requires 2^128 operations to break
* 256 bits provides 2^256 operations (overkill, but standard)
* Quantum computers reduce security by half (2^256 → 2^128)
* So 256 bits ensures long-term security even against quantum attacks

#### Generating a Complete Key Pair

```cpp
std::pair<PublicKey, SecretKey> randomKeyPair(KeyType type)
{
    // Generate random secret key
    SecretKey sk = randomSecretKey();

    // Derive public key from secret
    PublicKey pk = derivePublicKey(type, sk);

    return {pk, sk};
}
```

### Deterministic Key Generation from Seeds

#### What is a Seed?

A seed is a compact representation (typically 16 bytes) from which many keys can be derived:

```cpp
// Seed structure
class Seed
{
private:
    std::array<std::uint8_t, 16> buf_;  // 128 bits

public:
    // Construction, access, etc.
};
```

**Why seeds matter:**

* **Backup**: Remember one seed → recover all keys
* **Portability**: Move keys between wallets
* **Hierarchy**: Generate multiple accounts from one seed

#### Generating Keys from Seeds: The Interface

```cpp
std::pair<PublicKey, SecretKey>
generateKeyPair(KeyType type, Seed const& seed)
{
    switch (type)
    {
        case KeyType::secp256k1:
            return generateSecp256k1KeyPair(seed);

        case KeyType::ed25519:
            return generateEd25519KeyPair(seed);
    }
}
```

#### Ed25519: Simple Derivation

```cpp
// For ed25519, derivation is straightforward
case KeyType::ed25519: {
    // Hash the seed to get secret key
    auto const sk = generateSecretKey(type, seed);

    // Derive public key from secret
    return {derivePublicKey(type, sk), sk};
}

SecretKey generateSecretKey(KeyType::ed25519, Seed const& seed)
{
    // Simply hash the seed
    auto const secret = sha512Half_s(makeSlice(seed));
    return SecretKey{secret};
}
```

**Why this works:**

* SHA-512-Half is a one-way function
* Same seed always produces same secret key
* Different seeds produce uncorrelated secret keys
* No special validation needed (all 32-byte values are valid ed25519 keys)

#### Secp256k1: Complex Derivation

```cpp
// For secp256k1, need to handle curve order constraint
case KeyType::secp256k1: {
    detail::Generator g(seed);
    return g(0);  // Generate the 0th key pair
}
```

**Why more complex?**

Not all 32-byte values are valid secp256k1 secret keys. The value must be:

* Greater than 0
* Less than the curve order (a large prime number)

```cpp
// secp256k1 curve order
// Any secret key must be: 0 < key < order
static const uint256 CURVE_ORDER =
    "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141";
```

#### The Generator Class

```cpp
class Generator
{
private:
    Seed seed_;

public:
    explicit Generator(Seed const& seed) : seed_(seed) {}

    // Generate the n-th key pair
    std::pair<PublicKey, SecretKey> operator()(std::uint32_t ordinal)
    {
        // Derive root key from seed
        SecretKey rootKey = deriveRootKey(seed_, ordinal);

        // Derive public key
        PublicKey publicKey = derivePublicKey(KeyType::secp256k1, rootKey);

        return {publicKey, rootKey};
    }
};
```

#### Deriving the Root Key

```cpp
SecretKey deriveRootKey(Seed const& seed, std::uint32_t ordinal)
{
    // Try up to 128 times to find valid key
    for (int i = 0; i < 128; ++i)
    {
        // Create buffer: seed (16 bytes) + ordinal (4 bytes)
        std::array<std::uint8_t, 20> buf;

        // Copy seed
        std::copy(seed.data(), seed.data() + 16, buf.begin());

        // Append ordinal (big-endian)
        buf[16] = (ordinal >> 24) & 0xFF;
        buf[17] = (ordinal >> 16) & 0xFF;
        buf[18] = (ordinal >>  8) & 0xFF;
        buf[19] = (ordinal >>  0) & 0xFF;

        // Hash it
        auto const candidate = sha512Half(makeSlice(buf));

        // Check if valid secp256k1 secret key
        if (isValidSecretKey(candidate))
            return SecretKey{candidate};

        // Not valid, increment ordinal and try again
        ++ordinal;
    }

    // Should never reach here (probability ~ 1 in 2^128)
    Throw<std::runtime_error>("Failed to derive key from seed");
}

bool isValidSecretKey(uint256 const& candidate)
{
    // Must be in range: 0 < candidate < CURVE_ORDER
    return candidate > 0 && candidate < CURVE_ORDER;
}
```

**Why this loop?**

The probability that a random 256-bit value is >= CURVE\_ORDER is approximately 1 in 2^128.\
This is so unlikely that we almost never need a second try, but the code handles it correctly.

**Incrementing ordinal:**\
If the first hash isn't valid, we increment the ordinal and try again. This ensures:

* Deterministic behavior (same seed always produces same result)
* Eventually finds a valid key (extremely high probability on first try)
* No bias in the resulting key distribution

### Public Key Derivation

#### For Secp256k1

```cpp
PublicKey derivePublicKey(KeyType::secp256k1, SecretKey const& sk)
{
    secp256k1_pubkey pubkey_imp;

    // Perform elliptic curve point multiplication: PublicKey = SecretKey × G
    secp256k1_ec_pubkey_create(
        secp256k1Context(),
        &pubkey_imp,
        reinterpret_cast<unsigned char const*>(sk.data()));

    // Serialize to compressed format
    unsigned char pubkey[33];
    std::size_t len = sizeof(pubkey);
    secp256k1_ec_pubkey_serialize(
        secp256k1Context(),
        pubkey,
        &len,
        &pubkey_imp,
        SECP256K1_EC_COMPRESSED);  // 33 bytes: prefix + X coordinate

    return PublicKey{Slice{pubkey, len}};
}
```

**Compressed vs Uncompressed:**

```
Uncompressed: 0x04 | X (32 bytes) | Y (32 bytes) = 65 bytes  
Compressed:   0x02/0x03 | X (32 bytes) = 33 bytes

Prefix byte indicates Y parity:  
- 0x02: Y is even  
- 0x03: Y is odd
```

**Why compress?**

* Saves 32 bytes per public key
* Given X, only two possible Y values exist
* Prefix bit tells us which one

#### For Ed25519

```cpp
PublicKey derivePublicKey(KeyType::ed25519, SecretKey const& sk)
{
    unsigned char buf[33];
    buf[0] = 0xED;  // Type prefix marker

    // Derive public key using Ed25519 algorithm
    ed25519_publickey(sk.data(), &buf[1]);

    return PublicKey(Slice{buf, sizeof(buf)});
}
```

**Simpler than secp256k1:**

* No compression needed (Ed25519 public keys are naturally 32 bytes)
* No serialization complexity
* Just prepend type marker (0xED)

### Account ID Generation

Once we have a public key, we derive the account ID:

```cpp
AccountID calcAccountID(PublicKey const& pk)
{
    ripesha_hasher h;
    h(pk.data(), pk.size());
    return AccountID{static_cast<ripesha_hasher::result_type>(h)};
}
```

#### RIPESHA: Double Hashing

```cpp
class ripesha_hasher
{
private:
    openssl_sha256_hasher sha_;

public:
    void operator()(void const* data, std::size_t size)
    {
        // First: SHA-256
        sha_(data, size);
    }

    operator result_type()
    {
        // Get SHA-256 result
        auto const sha256_result =
            static_cast<openssl_sha256_hasher::result_type>(sha_);

        // Second: RIPEMD-160 of SHA-256
        ripemd160_hasher ripe;
        ripe(sha256_result.data(), sha256_result.size());
        return static_cast<result_type>(ripe);
    }
};
```

**The pipeline:**

```
Public Key (33 bytes)
       ↓
   SHA-256
       ↓
  32-byte digest
       ↓
  RIPEMD-160
       ↓
  20-byte Account ID
```

**Why double hash?**

1. **Defense in depth**: If one hash is broken, the other provides protection
2. **Compactness**: 20 bytes is shorter than 32 bytes
3. **Quantum resistance**: Even if quantum computers break elliptic curve crypto, they can't reverse the hash to get the public key

### Address Encoding

The final step is encoding the account ID as a human-readable address:

```cpp
std::string toBase58(AccountID const& accountID)
{
    return encodeBase58Token(
        TokenType::AccountID,
        accountID.data(),
        accountID.size());
}
```

**Result:**

```
Account ID (20 bytes): 0x8B8A6C533F09CA0E5E00E7C32AA7EC323485ED3F  
Address:               rN7n7otQDd6FczFgLdlqtyMVrn3LNU8B4C
```

We'll explore Base58Check encoding in detail in Chapter 7.

### Complete Key Generation Examples

#### Example 1: Random Ed25519 Key

```cpp
// Generate random ed25519 key pair
auto [publicKey, secretKey] = randomKeyPair(KeyType::ed25519);

// Derive account ID
AccountID accountID = calcAccountID(publicKey);

// Encode as address
std::string address = toBase58(accountID);

std::cout << "Public Key: " << strHex(publicKey) << "\n";
std::cout << "Account ID: " << strHex(accountID) << "\n";
std::cout << "Address:    " << address << "\n";
```

#### Example 2: Deterministic Secp256k1 Key

```cpp
// Create seed from passphrase (EXAMPLE ONLY - don't do this in production!)
Seed seed = generateSeedFromPassphrase("my secret passphrase");

// Generate deterministic key pair
auto [publicKey, secretKey] = generateKeyPair(KeyType::secp256k1, seed);

// Same seed always produces same keys
auto [publicKey2, secretKey2] = generateKeyPair(KeyType::secp256k1, seed);
assert(publicKey == publicKey2);
assert(secretKey == secretKey2);

// Derive account
AccountID accountID = calcAccountID(publicKey);
std::string address = toBase58(accountID);

std::cout << "Address: " << address << "\n";
```

#### Example 3: Multiple Accounts from One Seed

```cpp
Seed seed = /* ... */;

// Create generator
detail::Generator gen(seed);

// Generate multiple accounts
auto [pub0, sec0] = gen(0);
auto [pub1, sec1] = gen(1);
auto [pub2, sec2] = gen(2);

// Each has different address
AccountID acc0 = calcAccountID(pub0);
AccountID acc1 = calcAccountID(pub1);
AccountID acc2 = calcAccountID(pub2);

std::cout << "Account 0: " << toBase58(acc0) << "\n";
std::cout << "Account 1: " << toBase58(acc1) << "\n";
std::cout << "Account 2: " << toBase58(acc2) << "\n";
```

### Key Type Detection

#### Public Key Type Detection

```cpp
std::optional<KeyType> publicKeyType(Slice const& slice)
{
    if (slice.size() != 33)
        return std::nullopt;

    // Check first byte
    switch (slice[0])
    {
        case 0x02:
        case 0x03:
            return KeyType::secp256k1;
        case 0xED:
            return KeyType::ed25519;
        default:
            return std::nullopt;
    }
}
```

#### Automatic Algorithm Selection

```cpp
Buffer sign(PublicKey const& pk, SecretKey const& sk, Slice const& m)
{
    // Automatically detect which algorithm to use
    auto const type = publicKeyType(pk.slice());

    switch (*type)
    {
        case KeyType::ed25519:
            return signEd25519(pk, sk, m);
        case KeyType::secp256k1:
            return signSecp256k1(pk, sk, m);
    }
}
```

### The secp256k1 Context

```cpp
secp256k1_context const* secp256k1Context()
{
    // Thread-local context for performance
    static thread_local std::unique_ptr<
        secp256k1_context,
        decltype(&secp256k1_context_destroy)>
    context{
        secp256k1_context_create(
            SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY),
        &secp256k1_context_destroy
    };

    return context.get();
}
```

### Security Considerations

#### Secret Key Storage

```cpp
// ❌ WRONG
void badExample() {
    SecretKey sk = randomSecretKey();
}

// ✅ CORRECT
void goodExample() {
    SecretKey sk = randomSecretKey();
}
```

#### Key Validation

```cpp
bool validateKeys(PublicKey const& pk, SecretKey const& sk)
{
    auto derived = derivePublicKey(publicKeyType(pk).value(), sk);
    return derived == pk;
}
```

#### Seed Protection

```cpp
class Seed {
    ~Seed() {
        secure_erase(buf_.data(), buf_.size());
    }
};
```

### Performance Characteristics

#### Key Generation Speed

```
Ed25519:
- Secret key generation: ~50 µs
- Public key derivation:  ~50 µs
- Total: ~100 µs

Secp256k1:
- Secret key generation:  ~50 µs
- Public key derivation:  ~100 µs
- Total: ~150 µs
```

#### Caching Considerations

```cpp
std::vector<std::pair<PublicKey, SecretKey>> generateKeys(int count)
{
    std::vector<std::pair<PublicKey, SecretKey>> keys;
    keys.reserve(count);

    for (int i = 0; i < count; ++i) {
        keys.push_back(randomKeyPair(KeyType::ed25519));
    }

    return keys;
}
```

### Summary

Key generation in rippled involves:

1. **Randomness**: Cryptographically secure random bytes from `crypto_prng()`
2. **Secret keys**: 32 bytes of random or deterministically-derived data
3. **Public keys**: Derived via one-way function
4. **Account IDs**: Double hash (SHA-256 + RIPEMD-160) of public keys
5. **Addresses**: Base58Check encoding of account IDs

**Two algorithms:**

* **secp256k1** – complex, validated, widely used
* **ed25519** – simpler, faster, preferred

**Two approaches:**

* **Random**: Maximum security, requires backup of each key
* **Deterministic**: One seed recovers many keys, convenient for wallets

In the next chapter, we'll see how these keys are used to create and verify digital signatures—the mathematical proof of authorization.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.xrpl-commons.org/core-dev-bootcamp/module04/key-generation-pipeline.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
