Authentication
SR25519 signature scheme, auth headers, and session flow for the ORO API.
Overview
The ORO API uses SR25519 cryptographic signatures tied to Bittensor wallets for authentication. Every protected endpoint requires four HTTP headers that prove the caller controls the private key associated with a registered hotkey.
No API keys, no OAuth tokens. Authentication is wallet-based and stateless per request.
Auth Headers
Include these four headers on every authenticated request.
| Header | Type | Description |
|---|---|---|
X-Hotkey | string | Your SS58-encoded hotkey address. |
X-Timestamp | string | Current Unix epoch timestamp (seconds). |
X-Nonce | string | A unique identifier for this request (UUID recommended). |
X-Signature | string | SR25519 signature of the message {hotkey}:{timestamp}:{nonce}. |
Example Headers
X-Hotkey: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
X-Timestamp: 1710000000
X-Nonce: 550e8400-e29b-41d4-a716-446655440000
X-Signature: 0x1a2b3c4d5e6f...Signature Construction
The signature covers a deterministic message built from three of the header values.
Step 1: Build the Message
Concatenate the hotkey, timestamp, and nonce separated by colons:
{hotkey}:{timestamp}:{nonce}Example:
5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY:1710000000:550e8400-e29b-41d4-a716-446655440000Step 2: Sign with SR25519
Sign the message bytes using your hotkey's SR25519 private key. The signature must be hex-encoded with a 0x prefix.
Python Example
import time
import uuid
from bittensor_wallet import Wallet
wallet = Wallet(name="my-wallet", hotkey="default")
hotkey = wallet.hotkey
timestamp = str(int(time.time()))
nonce = str(uuid.uuid4())
message = f"{hotkey.ss58_address}:{timestamp}:{nonce}"
signature = "0x" + hotkey.sign(message.encode()).hex()
headers = {
"X-Hotkey": hotkey.ss58_address,
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Signature": signature,
}TypeScript Example
import { generateAuthHeaders } from '@oro-ai/sdk';
const headers = await generateAuthHeaders({
hotkey: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
sign: (message) => wallet.sign(message),
});
// headers = {
// 'X-Hotkey': '5GrwvaEF...',
// 'X-Signature': '0x...',
// 'X-Nonce': '...',
// 'X-Timestamp': '...',
// }Validation Rules
The backend applies these checks to every authenticated request, in order.
| Check | Failure Response | Details |
|---|---|---|
| All four headers present | 401 Unauthorized | Missing any header rejects the request. |
| Timestamp within skew window | 401 Unauthorized | Default allowed skew: 60 seconds. Configurable via TIMESTAMP_SKEW_SECONDS. |
| Nonce is unique | 401 Unauthorized | Nonces are stored in Redis with a TTL. Replayed nonces are rejected. |
| SR25519 signature valid | 401 Unauthorized | Signature must verify against the provided hotkey and message. |
| Hotkey is registered | 403 Forbidden | The hotkey must be registered on the configured Bittensor subnet. Registration status is cached in Redis (TTL: 300 seconds by default). |
| Hotkey has correct role | 403 Forbidden | Miner endpoints require a miner registration. Validator endpoints require a validator permit and minimum stake weight (40,000 on SN15). |
| Hotkey is not banned | 403 Forbidden | Banned hotkeys are rejected regardless of registration status. |
Role-Based Access
Different endpoint groups require different registrations on the Bittensor network.
| Role | Required Registration | Endpoint Prefix |
|---|---|---|
| Miner | Registered miner on subnet | /v1/miner/* |
| Validator | Registered validator with permit + minimum stake weight on subnet | /v1/validator/* |
The backend verifies registration against the Bittensor chain (finney network).
Nonce Replay Protection
Each nonce is stored in Redis after use with a TTL matching the timestamp skew window. Sending the same nonce twice within that window results in a 401 Unauthorized response.
Use a UUID v4 for each request to guarantee uniqueness:
import uuid
nonce = str(uuid.uuid4())Clock Skew
The backend compares X-Timestamp against the server clock. Requests with timestamps more than TIMESTAMP_SKEW_SECONDS (default: 60) in the past or future are rejected.
Ensure your system clock is synchronized via NTP. If you receive persistent 401 errors, check for clock drift.
SDK Auth Helpers
Both SDKs handle header generation automatically when configured with a wallet.
Python: BittensorAuthClient
from bittensor_wallet import Wallet
from oro_sdk import BittensorAuthClient
wallet = Wallet(name="my-wallet", hotkey="default")
client = BittensorAuthClient(base_url="https://api.oroagents.com", wallet=wallet)
# All requests through this client are automatically signedTypeScript: configureBittensorAuth
import { configureBittensorAuth } from '@oro-ai/sdk';
configureBittensorAuth('https://api.oroagents.com', {
hotkey: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
sign: async (message) => wallet.sign(message),
});
// All subsequent SDK calls are automatically signed.See the SDK reference for full usage examples.