Traditional node auth uses API keys: the server has a key, the client provides it in a header, the server checks if it matches. This works, but it requires the server to store secrets. If the control plane is breached, all node keys are compromised.
Infernet Protocol uses a different approach: each node has a secp256k1 keypair, and every request is signed with the private key. The control plane stores only public keys. Verifying a request requires only the public key and the signature — no secrets are stored anywhere except on the node itself.
This is the same cryptographic foundation used by Nostr (hence “Nostr-style”), Bitcoin, and Ethereum. The security properties are well-understood.
When you run infernet setup, a secp256k1 keypair is
generated for your node:
Private key (hex): stored in ~/.infernet/keys/node.key (mode 600)
Public key (hex): registered with the control plane
The public key is derived deterministically from the private key. The private key never leaves your machine.
In Nostr notation:
Private key: nsec1... (bech32-encoded private key)
Public key: npub1... (bech32-encoded public key)
You can view your node’s public key:
infernet status | grep "Public key"
# Public key: npub1abc123def456...Every request the daemon makes to the control plane carries an
X-Infernet-Auth header. This header contains a signed proof
that the request was made by the holder of the private key.
Format:
X-Infernet-Auth: v1.<signature>.<nonce>.<timestamp>
Where: - v1 — protocol version -
<signature> — hex-encoded Schnorr signature -
<nonce> — 16-byte random hex value, unique per
request - <timestamp> — Unix timestamp in seconds
The signature covers:
message = SHA256(
method + // "POST"
"\n" +
path + // "/api/v1/heartbeat"
"\n" +
body_sha256 + // SHA256 of request body, or empty string
"\n" +
nonce + // same nonce as in header
"\n" +
timestamp // same timestamp as in header
)
All fields are UTF-8 encoded and concatenated with newlines before hashing.
Replay prevention: The nonce is unique per request. The control plane keeps a short-lived nonce cache (5 minutes). A replayed request with the same nonce is rejected.
Timestamp binding: Requests more than 30 seconds old are rejected. This prevents replays even if the nonce cache doesn’t have the nonce.
Body integrity: The body hash prevents anyone from modifying the request body in transit.
Method + path binding: The signature covers the full
endpoint, not just the body. A valid signature for a
GET /heartbeat cannot be replayed as a
POST /jobs.
When the control plane receives a request:
X-Infernet-Auth headertimestamp is within ±30 seconds of current
timenonce hasn’t been seen recentlynode_id from the request body or URL)If any step fails, the request is rejected with HTTP 401.
Note that client developers use bearer tokens from the dashboard — not secp256k1 keys. The secp256k1 auth is only for node-to-control-plane communication. Clients authenticate with standard bearer tokens because:
If you want your application to have Nostr-style auth, that’s possible but requires a custom integration — contact the team.
The control plane cannot impersonate a node. Even if an attacker gains full control of the control plane database, they cannot forge a signed request from any node because they don’t have the private keys.
Compromising the control plane doesn’t steal earnings. CPRs are signed by node keys. Fake CPRs without valid node signatures are rejected by the on-chain payment contract.
Node operators control their own identity. The public key is the node’s canonical identity on the network. Rotating to a new keypair requires re-registration (new node ID), but the operator’s history and reputation can be migrated if the old key signs a migration message.
The private key is stored in ~/.infernet/keys/node.key
with file permissions 600 (owner read/write only). On
Linux, this means only the process running as the same user can read
it.
Best practices:
infernet user and run the daemon as that user.To back up securely:
# Encrypt with a passphrase before storing anywhere
gpg --symmetric --cipher-algo AES256 ~/.infernet/keys/node.key
# Stores as ~/.infernet/keys/node.key.gpg