> 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/secp256k1-sign.md).

# secp256k1\_sign

Sign messages on-chain using Mercury EVM's secp256k1 signing precompile.

## Overview

The secp256k1 signing precompile at address `0x69` creates ECDSA signatures on-chain. The message is hashed with the EIP-191 personal-sign prefix before signing, producing a 65-byte signature (r + s + v) compatible with `ecrecover` and standard Ethereum signature verification.

## Precompile Address

```
0x0000000000000000000000000000000000000069
```

## Input Encoding

| Field          | Size     | Description                                     |
| -------------- | -------- | ----------------------------------------------- |
| `secret_key`   | 32 bytes | secp256k1 private key                           |
| `message_hash` | 32 bytes | Keccak-256 hash of the EIP-191 prefixed message |

The input is the concatenation of `secret_key` (32 bytes) + `message_hash` (32 bytes), for a total of 64 bytes.

{% hint style="info" %}
You must hash the message yourself before calling the precompile. The precompile expects the EIP-191 prefixed message hash: `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)`
{% endhint %}

## Output Format

| Field | Size     | Description            |
| ----- | -------- | ---------------------- |
| `r`   | 32 bytes | Signature r component  |
| `s`   | 32 bytes | Signature s component  |
| `v`   | 1 byte   | Recovery ID (27 or 28) |

Total output: 65 bytes.

## Parameters

| Parameter      | Type       | Required | Description                                        |
| -------------- | ---------- | -------- | -------------------------------------------------- |
| `secret_key`   | `[u8; 32]` | Yes      | 32-byte secp256k1 private key                      |
| `message_hash` | `[u8; 32]` | Yes      | 32-byte keccak256 hash of EIP-191 prefixed message |

## Examples

### Basic Usage

```rust
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes, keccak256};
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);

    // Private key (32 bytes)
    let secret_key: [u8; 32] = [/* your private key */; 32];

    // Hash the message with EIP-191 prefix
    let message = "Hello, Seismic!";
    let prefix = format!("\x19Ethereum Signed Message:\n{}", message.len());
    let message_hash = keccak256(
        [prefix.as_bytes(), message.as_bytes()].concat(),
    );

    // Using convenience helpers
    let signature = precompiles::call::secp256k1_sign(&provider, &secret_key, message_hash.as_slice()).await?;
    println!("Signature (convenience): 0x{}", hex::encode(&signature));

    // Manual approach
    let sign_address: Address =
        "0x0000000000000000000000000000000000000069".parse()?;

    // Build precompile input: sk (32) + message_hash (32)
    let mut input = Vec::with_capacity(64);
    input.extend_from_slice(&secret_key);
    input.extend_from_slice(message_hash.as_slice());

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

    println!("Signature ({} bytes): 0x{}", result.len(), hex::encode(&result));

    Ok(())
}
```

### Extract Signature Components

```rust
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes, keccak256};
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 sign_address: Address =
        "0x0000000000000000000000000000000000000069".parse()?;

    let secret_key: [u8; 32] = [/* ... */; 32];
    let message = "Extract components";
    let prefix = format!("\x19Ethereum Signed Message:\n{}", message.len());
    let message_hash = keccak256(
        [prefix.as_bytes(), message.as_bytes()].concat(),
    );

    let mut input = Vec::with_capacity(64);
    input.extend_from_slice(&secret_key);
    input.extend_from_slice(message_hash.as_slice());

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

    // Signature is 65 bytes: r (32) + s (32) + v (1)
    let r = &signature[..32];
    let s = &signature[32..64];
    let v = signature[64];

    println!("r: 0x{}", hex::encode(r));
    println!("s: 0x{}", hex::encode(s));
    println!("v: {v}");

    Ok(())
}
```

### Sign Multiple Messages

```rust
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes, keccak256};
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 sign_address: Address =
        "0x0000000000000000000000000000000000000069".parse()?;

    let secret_key: [u8; 32] = [/* ... */; 32];
    let messages = ["Message 1", "Message 2", "Message 3"];

    for msg in &messages {
        let prefix = format!("\x19Ethereum Signed Message:\n{}", msg.len());
        let message_hash = keccak256(
            [prefix.as_bytes(), msg.as_bytes()].concat(),
        );

        let mut input = Vec::with_capacity(64);
        input.extend_from_slice(&secret_key);
        input.extend_from_slice(message_hash.as_slice());

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

        println!(
            "Signature for '{}': 0x{}...",
            msg,
            hex::encode(&signature[..20]),
        );
    }

    Ok(())
}
```

### Sign Structured Data

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

#[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 sign_address: Address =
        "0x0000000000000000000000000000000000000069".parse()?;

    let secret_key: [u8; 32] = [/* ... */; 32];

    // Sign structured data as JSON
    let context = json!({
        "action": "transfer",
        "amount": 1000,
        "recipient": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
    });
    let message = serde_json::to_string(&context)?;

    let prefix = format!("\x19Ethereum Signed Message:\n{}", message.len());
    let message_hash = keccak256(
        [prefix.as_bytes(), message.as_bytes()].concat(),
    );

    let mut input = Vec::with_capacity(64);
    input.extend_from_slice(&secret_key);
    input.extend_from_slice(message_hash.as_slice());

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

    println!("Context signature: 0x{}", hex::encode(&signature));

    Ok(())
}
```

## How It Works

1. **Hash message** — You apply the EIP-191 personal-sign prefix and keccak256 hash:

   ```
   prefix = "\x19Ethereum Signed Message:\n" + len(message)
   message_hash = keccak256(prefix + message)
   ```
2. **Encode parameters** — Concatenate the 32-byte private key and 32-byte message hash
3. **Call precompile** — Issues an `eth_call` to address `0x69` with 3000 gas
4. **Sign hash** — Precompile performs ECDSA signing on the secp256k1 curve
5. **Return signature** — Returns 65-byte signature (r + s + v)

## Gas Cost

Fixed gas cost: **3000 gas**

The cost is constant regardless of message length (since the message is hashed before signing).

## EIP-191 Message Hashing

The precompile expects the standard EIP-191 personal-sign message hash:

```rust
let prefix = format!("\x19Ethereum Signed Message:\n{}", message.len());
let message_hash = keccak256([prefix.as_bytes(), message.as_bytes()].concat());
```

This ensures compatibility with standard Ethereum message signing (e.g., MetaMask's `personal_sign`).

## Signature Format

The returned signature is 65 bytes:

| Component | Offset | Size     | Description            |
| --------- | ------ | -------- | ---------------------- |
| `r`       | 0      | 32 bytes | Signature r value      |
| `s`       | 32     | 32 bytes | Signature s value      |
| `v`       | 64     | 1 byte   | Recovery ID (27 or 28) |

This format is compatible with `ecrecover` and standard Ethereum signature verification.

## Notes

* Uses secp256k1 curve (same as Ethereum)
* You must hash the message with EIP-191 prefix before calling the precompile
* Signatures are non-deterministic (random `k` value)
* Compatible with MetaMask and other Ethereum wallets
* Can be verified on-chain using the `ecrecover` precompile

## Warnings

* **Private key security** — Never expose or log private keys
* **Message format** — Ensure messages are hashed correctly with EIP-191 prefix before sending to the precompile
* **Signature malleability** — Standard ECDSA signatures are malleable (use EIP-2098 compact signatures if needed)
* **Non-deterministic** — Multiple signatures of the same message will differ due to random `k` values

## See Also

* [Precompiles Overview](/clients/alloy/precompiles.md) — All precompile reference
* [ecdh](/clients/alloy/precompiles/ecdh.md) — ECDH key exchange
* [EIP-191](https://eips.ethereum.org/EIPS/eip-191) — Signed data standard


---

# 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/secp256k1-sign.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.
