Verify Signature
Every EigenAI API response includes a cryptographic signature field that proves the response was generated by the EigenAI Operator (that is, EigenLabs). You can verify this signature to ensure authenticity and integrity of the response. When signature verification succeeds, you have cryptographic proof that:
- The response was generated by Eigenlab’s EigenAI operator(that is, the holder of the signing private key).
- The response has not been modified after signing.
- The specific model, prompt, and output are authentic.
What is signed?
The signature covers a specific message constructed from four components concatenated together with no separators:
chain_id + model_id + prompt + output
Where:
chain_id: Network identifier (1 for mainnet, 11155111 for Sepolia testnet)model_id: Themodelfield from the API responseprompt: Allcontentfields from the original request’s messages array, concatenatedoutput: Allcontentfields from the API response’s choices array, concatenated
You need to store your original request, the API response, and the chainID of the network queried to verify the signature.
Steps to verify
The steps to verify the signature are:
- Extract the prompt from the original request
- Extract the output from the response
- Construct the message
- Verify the signature
- Compare addresses.
1. Extract the prompt
Concatenate all request.messages[].content fields, in order, with no separators.
Request messages: [{"content": "Hello"}, {"content": "World"}]
Prompt: "HelloWorld"
2. Extract the output
Concatenate all response.choices[].message.content fields, with no separators.
Response choices: [{"message": {"content": "AI response here"}}]
Output: "AI response here"
3. Construct the message
Build the verification message by concatenating the four components with no separators, spaces, or delimiters:
{chain_id}{model_id}{prompt}{output}
1gpt-oss-120b-f16HelloWorldAI response here
4. Verify the signature
You can verify the signature using either standard libraries (recommended) or manually.
Using standard libraries
- Rust (using alloy)
- JavaScript (using ethers)
- Python (using eth-account)
use alloy_primitives::hex;
use alloy_signer::Signature;
// Parse the signature (65 bytes: r, s, v)
let signature_bytes = hex::decode(response_signature)?;
let signature = Signature::try_from(signature_bytes.as_slice())?;
// Recover the signer address
let recovered = signature.recover_address_from_msg(message.as_bytes())?;
import { verifyMessage } from 'ethers';
// Recover the signer address
const recovered = verifyMessage(message, '0x' + signature);
from eth_account.messages import encode_defunct
from eth_account import Account
# Encode and verify
message_obj = encode_defunct(text=message)
signature_bytes = bytes.fromhex(signature)
recovered = Account.recover_message(message_obj, signature=signature_bytes)
Manual verification
If you prefer not to use libraries or want to understand the process, expand for the manual verification process:
-
Add Ethereum Signed Message Prefix
Prepend the standard Ethereum prefix to your message:
"\x19Ethereum Signed Message:\n" + message_length + messageExample:
"\x19Ethereum Signed Message:\n42" + "1gpt-oss-120b-f16HelloWorldAI response here" -
Hash with Keccak256
Compute the Keccak256 hash of the prefixed message. This produces a 32-byte hash.
-
Parse the Signature
The signature is 65 bytes encoded as 130 hex characters:
- Bytes 0-31 (chars 0-63): r component
- Bytes 32-63 (chars 64-127): s component
- Byte 64 (chars 128-129): v component (recovery ID)
-
Perform ECDSA public key recovery
Using the secp256k1 elliptic curve:
- Take the 32-byte hash from step 2
- Take the r, s, v values from step 3
- Use ECDSA recovery algorithm to extract the public key.
The recovery process uses the mathematical relationship between the signature components, the hash, and the original public key on the secp256k1 curve.
-
Derive Ethereum address from public key
- Take the recovered 64-byte uncompressed public key (x and y coordinates)
- Hash it with Keccak256 (produces 32 bytes)
- Take the last 20 bytes
- Prepend "0x" for standard Ethereum address format
5. Compare addresses
Check that the recovered address matches the expected EigenAI signer:
| Environment | Chain ID | Signer Address |
|---|---|---|
| Mainnet | 1 | 0x7053bfb0433a16a2405de785d547b1b32cee0cf3 |
| Sepolia Testnet | 11155111 | 0xB876f1301b39c673554EE0259F11395565dCd295 |
The ECDSA signer can be looked up onchain in the KeyRegistrar contract using the Operator address. The deployed KeyRegistrar
addresses for Mainnet and Sepolia testnet are listed in the eigenlayer-contracts repository.