OROoro docs

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.

HeaderTypeDescription
X-HotkeystringYour SS58-encoded hotkey address.
X-TimestampstringCurrent Unix epoch timestamp (seconds).
X-NoncestringA unique identifier for this request (UUID recommended).
X-SignaturestringSR25519 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-446655440000

Step 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.

CheckFailure ResponseDetails
All four headers present401 UnauthorizedMissing any header rejects the request.
Timestamp within skew window401 UnauthorizedDefault allowed skew: 60 seconds. Configurable via TIMESTAMP_SKEW_SECONDS.
Nonce is unique401 UnauthorizedNonces are stored in Redis with a TTL. Replayed nonces are rejected.
SR25519 signature valid401 UnauthorizedSignature must verify against the provided hotkey and message.
Hotkey is registered403 ForbiddenThe hotkey must be registered on the configured Bittensor subnet. Registration status is cached in Redis (TTL: 300 seconds by default).
Hotkey has correct role403 ForbiddenMiner endpoints require a miner registration. Validator endpoints require a validator permit and minimum stake weight (40,000 on SN15).
Hotkey is not banned403 ForbiddenBanned hotkeys are rejected regardless of registration status.

Role-Based Access

Different endpoint groups require different registrations on the Bittensor network.

RoleRequired RegistrationEndpoint Prefix
MinerRegistered miner on subnet/v1/miner/*
ValidatorRegistered 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 signed

TypeScript: 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.

On this page