> For the complete documentation index, see [llms.txt](https://docs.seismic.systems/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.seismic.systems/clients/alloy/precompiles/hkdf.md).

# hkdf

Derive cryptographic keys on-chain using Mercury EVM's HKDF precompile.

## Overview

The HKDF precompile at address `0x68` performs HKDF-SHA256 key derivation on input key material (IKM). It produces a uniformly distributed 32-byte derived key suitable for use as an AES-256 key or other cryptographic purpose.

{% hint style="info" %}
The `SeismicSignedProvider` uses this precompile internally to derive AES keys from ECDH shared secrets during the TEE key exchange. You can also call it directly for custom key derivation workflows.
{% endhint %}

## Precompile Address

```
0x0000000000000000000000000000000000000068
```

## Input Encoding

| Field | Size     | Description                          |
| ----- | -------- | ------------------------------------ |
| `ikm` | Variable | Input key material (arbitrary bytes) |

The input is the raw IKM bytes — no additional encoding is needed.

## Output Format

| Field         | Size     | Description             |
| ------------- | -------- | ----------------------- |
| `derived_key` | 32 bytes | HKDF-SHA256 derived key |

## Parameters

| Parameter | Type    | Required | Description                          |
| --------- | ------- | -------- | ------------------------------------ |
| `ikm`     | `&[u8]` | Yes      | Input key material (arbitrary bytes) |

## Examples

### Basic Usage

```rust
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes};
use alloy_rpc_types_eth::TransactionRequest;
use seismic_prelude::client::*;
use seismic_alloy_provider::precompiles;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let provider = SeismicProviderBuilder::new().connect_http(url);

    // Using convenience helpers
    let derived_key = precompiles::call::hkdf(&provider, b"my-input-key-material").await?;
    println!("Derived key (convenience): 0x{}", hex::encode(&derived_key));

    // Manual approach
    let hkdf_address: Address =
        "0x0000000000000000000000000000000000000068".parse()?;

    // Derive key from input material
    let ikm = b"my-input-key-material";
    let input = Bytes::from(ikm.to_vec());

    let result = provider
        .call(
            &TransactionRequest::default()
                .to(hkdf_address)
                .input(input.into()),
        )
        .await?;

    let derived_key: [u8; 32] = result[..32].try_into()?;
    println!("Derived key: 0x{}", hex::encode(derived_key));

    Ok(())
}
```

### Derive Multiple Keys by Context

```rust
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes};
use alloy_rpc_types_eth::TransactionRequest;
use seismic_prelude::client::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let provider = SeismicProviderBuilder::new().connect_http(url);

    let hkdf_address: Address =
        "0x0000000000000000000000000000000000000068".parse()?;

    let master_secret = b"shared-master-key";
    let contexts = [b"encryption" as &[u8], b"authentication", b"signing"];

    for ctx in &contexts {
        let mut ikm = master_secret.to_vec();
        ikm.extend_from_slice(ctx);

        let result = provider
            .call(
                &TransactionRequest::default()
                    .to(hkdf_address)
                    .input(Bytes::from(ikm).into()),
            )
            .await?;

        let key: [u8; 32] = result[..32].try_into()?;
        println!(
            "Key for {}: 0x{}",
            String::from_utf8_lossy(ctx),
            hex::encode(key),
        );
    }

    Ok(())
}
```

### Use as AES Key

```rust
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes};
use alloy_rpc_types_eth::TransactionRequest;
use seismic_prelude::client::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let provider = SeismicProviderBuilder::new().connect_http(url);

    // Step 1: Derive AES key via HKDF
    let hkdf_address: Address =
        "0x0000000000000000000000000000000000000068".parse()?;

    let ikm = b"my-master-secret";
    let hkdf_result = provider
        .call(
            &TransactionRequest::default()
                .to(hkdf_address)
                .input(Bytes::from(ikm.to_vec()).into()),
        )
        .await?;

    let aes_key: [u8; 32] = hkdf_result[..32].try_into()?;

    // Step 2: Use derived key for encryption
    let encrypt_address: Address =
        "0x0000000000000000000000000000000000000066".parse()?;

    let nonce = [0u8; 12];
    let plaintext = b"Encrypted with derived key";

    let mut encrypt_input = Vec::new();
    encrypt_input.extend_from_slice(&aes_key);
    encrypt_input.extend_from_slice(&nonce);
    encrypt_input.extend_from_slice(plaintext);

    let ciphertext = provider
        .call(
            &TransactionRequest::default()
                .to(encrypt_address)
                .input(Bytes::from(encrypt_input).into()),
        )
        .await?;

    println!("Ciphertext: 0x{}", hex::encode(&ciphertext));

    Ok(())
}
```

### Derive from ECDH Output

```rust
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes};
use alloy_rpc_types_eth::TransactionRequest;
use seismic_prelude::client::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let provider = SeismicProviderBuilder::new().connect_http(url);

    // Step 1: Perform ECDH
    let ecdh_address: Address =
        "0x0000000000000000000000000000000000000065".parse()?;

    let my_sk: [u8; 32] = [/* ... */; 32];
    let peer_pk: [u8; 33] = [/* ... */; 33];

    let mut ecdh_input = Vec::with_capacity(65);
    ecdh_input.extend_from_slice(&my_sk);
    ecdh_input.extend_from_slice(&peer_pk);

    let shared_secret = provider
        .call(
            &TransactionRequest::default()
                .to(ecdh_address)
                .input(Bytes::from(ecdh_input).into()),
        )
        .await?;

    // Step 2: Further derive key from shared secret + application context
    let hkdf_address: Address =
        "0x0000000000000000000000000000000000000068".parse()?;

    let mut ikm = shared_secret.to_vec();
    ikm.extend_from_slice(b"application-context-v1");

    let result = provider
        .call(
            &TransactionRequest::default()
                .to(hkdf_address)
                .input(Bytes::from(ikm).into()),
        )
        .await?;

    let derived_key: [u8; 32] = result[..32].try_into()?;
    println!("Derived key: 0x{}", hex::encode(derived_key));

    Ok(())
}
```

### Deterministic Key Derivation

```rust
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes};
use alloy_rpc_types_eth::TransactionRequest;
use seismic_prelude::client::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let provider = SeismicProviderBuilder::new().connect_http(url);

    let hkdf_address: Address =
        "0x0000000000000000000000000000000000000068".parse()?;

    // Same input always produces the same output
    let ikm = b"deterministic-input";
    let input = Bytes::from(ikm.to_vec());

    let key1 = provider
        .call(
            &TransactionRequest::default()
                .to(hkdf_address)
                .input(input.clone().into()),
        )
        .await?;

    let key2 = provider
        .call(
            &TransactionRequest::default()
                .to(hkdf_address)
                .input(input.into()),
        )
        .await?;

    assert_eq!(&key1[..32], &key2[..32]);
    println!("Deterministic key: 0x{}", hex::encode(&key1[..32]));

    Ok(())
}
```

## How It Works

1. **Encode parameters** — Passes input key material as-is
2. **Call precompile** — Issues an `eth_call` to address `0x68` with gas proportional to input length
3. **HKDF derivation** — Precompile performs HKDF-SHA256 extract and expand phases
4. **Return key** — Returns first 32 bytes of derived key material

## Gas Cost

Gas cost is calculated as:

```
sha256_base = 60
sha256_per_word = 12  // per 32-byte word
hkdf_expand = 120

// Extract phase (twice)
extract_cost = 2 * (sha256_base + (len(ikm) / 32) * sha256_per_word + 3000)

// Total
total_gas = extract_cost + hkdf_expand
```

For example:

| IKM Size  | Gas Cost |
| --------- | -------- |
| 32 bytes  | \~6144   |
| 64 bytes  | \~6168   |
| 128 bytes | \~6216   |

## Notes

* Uses HKDF-SHA256 from RFC 5869
* Always returns exactly 32 bytes regardless of input length
* Input key material can be any length
* The derivation is deterministic: same IKM always produces the same output
* Internally performs both HKDF-Extract and HKDF-Expand phases
* The derived key has uniform distribution suitable for cryptographic use

## Use Cases

* Derive encryption keys from shared secrets
* Convert non-uniform entropy into uniform keys
* Key separation: derive multiple keys from one master secret
* Post-process ECDH output for additional security

## Warnings

* **Not for password hashing** — Use proper password hashing algorithms (bcrypt, argon2) for passwords
* **Input entropy** — Output security depends entirely on input entropy
* **Deterministic** — Same input always yields the same output (no salt or randomness is added)

## See Also

* [Precompiles Overview](/clients/alloy/precompiles.md) — All precompile reference
* [ecdh](/clients/alloy/precompiles/ecdh.md) — Often used before HKDF to derive keys
* [aes-gcm-encrypt](/clients/alloy/precompiles/aes-gcm-encrypt.md) — Use derived keys for encryption
* [rng](/clients/alloy/precompiles/rng.md) — Generate random input material


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://docs.seismic.systems/clients/alloy/precompiles/hkdf.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
