Session Keys
Session keys are cryptographic keys that validators use to participate in consensus: producing blocks and voting on finality.
This guide explains how to generate, manage, and rotate session keys securely.
What Are Session Keys?
Overview
Session keys are hot keys stored on your validator server. They are used by the node software to:
AURA keys (sr25519):
└─ Sign blocks during your validator turn
GRANDPA keys (ed25519):
└─ Vote on chain finality
Key properties:
- 🔥 Hot keys = stored on server (not cold storage)
- 🔄 Rotatable = can be changed without losing validator status
- 🚫 Not for funds = cannot access your validator rewards account
- 🤖 Automatic = node uses them automatically (no manual signing)
Session Keys vs Account Keys
| Property | Session Keys | Account Keys |
|---|---|---|
| Purpose | Consensus (block signing, voting) | Funds, governance |
| Storage | Server (hot) | Cold wallet, hardware wallet |
| Security | Medium (server compromise = lost) | High (offline storage) |
| Rotation | Easy (rotate anytime) | Difficult (lose access = lose funds) |
| Example | AURA, GRANDPA keys | Validator rewards account |
Analogy:
Session keys = Office keys (access during work hours, can be changed)
Account keys = Safe deposit box key (holds valuables, never changes)
Key Types in Pilier
Pilier validators use two types of session keys:
1. AURA Keys (Block Production)
Algorithm: sr25519 (Schnorrkel)
Purpose: Sign blocks during validator's turn
Used by: pallet-aura (consensus)
Rotation: Every session (~1 hour on testnet)
How it works:
Validator's turn (slot 123):
├─ Node checks: "Do I have AURA key?"
├─ Yes → Sign block with AURA private key
├─ Broadcast signed block to network
└─ Other validators verify signature (AURA public key)
2. GRANDPA Keys (Finality)
Algorithm: ed25519 (EdDSA)
Purpose: Vote on which chain to finalize
Used by: pallet-grandpa (finality gadget)
Rotation: Less frequent (can keep same key for months)
How it works:
Finality round:
├─ Validator votes: "I believe chain ending at block #456 is correct"
├─ Sign vote with GRANDPA private key
├─ Broadcast to other validators
└─ If 2/3+ validators agree → block finalized
Generating Session Keys
Method 1: Automatic Generation (Recommended)
Easiest method: Node generates keys automatically via RPC.
# Ensure node is running
sudo systemctl status pilier
# Generate keys via RPC
curl -H "Content-Type: application/json" \
-d '{"id":1, "jsonrpc":"2.0", "method": "author_rotateKeys", "params":[]}' \
http://127.0.0.1:9933/
# Response (example):
{
"jsonrpc": "2.0",
"result": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"id": 1
}
What this does:
- Node generates AURA key (sr25519)
- Node generates GRANDPA key (ed25519)
- Saves keys to keystore:
/var/lib/pilier/chains/pilier_testnet/keystore/ - Returns concatenated public keys (hex-encoded)
Save this result! You'll need it to register as validator.
Method 2: Manual Generation (Advanced)
For offline key generation or custom workflows.
Step 1: Generate AURA Key
# Generate sr25519 key for AURA
pilier-node key generate --scheme sr25519 --output-type json
# Output:
{
"secretPhrase": "word1 word2 word3 ... word12",
"secretSeed": "0xabcd...",
"publicKey": "0x1234...",
"accountId": "5Gx8R...",
"ss58Address": "5Gx8R..."
}
Save the secret phrase in a password manager (1Password, Bitwarden)!
Step 2: Generate GRANDPA Key
# Generate ed25519 key for GRANDPA
pilier-node key generate --scheme ed25519 --output-type json
# Output:
{
"secretPhrase": "word1 word2 word3 ... word12",
"secretSeed": "0xdef...",
"publicKey": "0x5678...",
"accountId": "5Hy9K...",
"ss58Address": "5Hy9K..."
}
Save this secret phrase separately from AURA phrase!
Step 3: Insert Keys into Keystore
# Insert AURA key
pilier-node key insert \
--base-path /var/lib/pilier \
--chain /etc/pilier/testnet.json \
--scheme sr25519 \
--suri "your-aura-secret-phrase-here" \
--key-type aura
# Insert GRANDPA key
pilier-node key insert \
--base-path /var/lib/pilier \
--chain /etc/pilier/testnet.json \
--scheme ed25519 \
--suri "your-grandpa-secret-phrase-here" \
--key-type gran
Important:
- Use the secret phrase, not the hex seed
- Wrap phrase in quotes if it contains spaces
--key-type auraand--key-type granare critical (must match)
Step 4: Verify Keys Loaded
# Check keystore directory
ls -la /var/lib/pilier/chains/pilier_testnet/keystore/
# Should see two files:
# 61757261<hex-public-key> ← AURA key (6175 7261 = "aura" in hex)
# 6772616e<hex-public-key> ← GRANDPA key (6772 616e = "gran" in hex)
File naming:
Format: <key-type-hex><public-key-hex>
Example:
61757261d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
├─ 61757261 = "aura" (key type)
└─ d43593c7... = public key (32 bytes)
Method 3: Using Subkey (Offline Generation)
For maximum security: generate keys on an air-gapped machine.
Install Subkey
# Option A: From source (requires Rust)
cargo install --force subkey --git https://github.com/paritytech/polkadot-sdk
# Option B: Download binary (Polkadot releases)
wget https://github.com/paritytech/polkadot/releases/download/v1.0.0/subkey-linux-amd64
chmod +x subkey-linux-amd64
sudo mv subkey-linux-amd64 /usr/local/bin/subkey
Generate Keys Offline
# Generate AURA key (sr25519)
subkey generate --scheme sr25519
# Output:
Secret phrase: word1 word2 ... word12
Network ID: substrate
Secret seed: 0xabcd...
Public key: 0x1234...
Account ID: 0x1234...
SS58 Address: 5Gx8R...
# Generate GRANDPA key (ed25519)
subkey generate --scheme ed25519
# Output similar to above
Security best practice:
- Generate keys on offline computer
- Write secret phrases on paper (not digital)
- Transfer only public keys to validator server (via USB or manual typing)
- Insert keys using
pilier-node key insert(as in Method 2, Step 3)
Key Storage & Security
Keystore Location
Default path: /var/lib/pilier/chains/<chain-id>/keystore/
Example (testnet):
/var/lib/pilier/chains/pilier_testnet/keystore/
├── 61757261d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
└── 6772616e88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee
File contents:
- Each file contains the secret key (not secret phrase!)
- Hex-encoded, 32-64 bytes depending on key type
- Highly sensitive - anyone with these files can impersonate your validator
File Permissions
Critical security step:
# Set correct ownership
sudo chown -R pilier:pilier /var/lib/pilier/chains/pilier_testnet/keystore/
# Restrict permissions (read-only for pilier user, no one else)
sudo chmod 600 /var/lib/pilier/chains/pilier_testnet/keystore/*
# Verify
ls -la /var/lib/pilier/chains/pilier_testnet/keystore/
# Should show: -rw------- pilier pilier (owner read/write only)
DO NOT:
- Copy keystore files over unencrypted channels (FTP, HTTP)
- Share keystore in GitHub, Slack, email
- Store keystore in cloud storage (Dropbox, Google Drive) without encryption
- Leave world-readable permissions (chmod 777, 644, etc.)
Backup Session Keys
Yes, you should backup session keys!
Why:
- If server fails, you can restore validator quickly
- Rotating keys requires downtime (~1 hour), backup avoids this
How to backup:
# Create encrypted backup
BACKUP_DIR="/secure-backup/pilier-keys-$(date +%Y%m%d)"
KEYSTORE="/var/lib/pilier/chains/pilier_testnet/keystore"
# Copy keystore
mkdir -p "$BACKUP_DIR"
cp -r "$KEYSTORE" "$BACKUP_DIR/"
# Encrypt with GPG
tar -czf "$BACKUP_DIR.tar.gz" "$BACKUP_DIR/"
gpg --symmetric --cipher-algo AES256 "$BACKUP_DIR.tar.gz"
# Result: pilier-keys-20260201.tar.gz.gpg
# Store in:
# - Hardware encrypted USB drive
# - Password manager (as file attachment)
# - Offline safe
# Clean up plaintext
rm -rf "$BACKUP_DIR" "$BACKUP_DIR.tar.gz"
Restore from backup:
# Decrypt
gpg --decrypt pilier-keys-20260201.tar.gz.gpg > pilier-keys-20260201.tar.gz
# Extract
tar -xzf pilier-keys-20260201.tar.gz
# Copy to keystore
sudo cp pilier-keys-20260201/keystore/* /var/lib/pilier/chains/pilier_testnet/keystore/
sudo chown pilier:pilier /var/lib/pilier/chains/pilier_testnet/keystore/*
sudo chmod 600 /var/lib/pilier/chains/pilier_testnet/keystore/*
# Restart node
sudo systemctl restart pilier
Registering Session Keys On-Chain
After generating keys, you must register them on-chain so the network knows which public keys belong to your validator.
Step 1: Get Session Keys Hex
If you used author_rotateKeys:
# You already have the hex string from the RPC response:
0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
If you generated manually:
# Extract public keys from keystore files
cd /var/lib/pilier/chains/pilier_testnet/keystore/
# AURA public key (file starting with 61757261)
AURA_FILE=$(ls 61757261*)
AURA_PUBKEY=${AURA_FILE:8} # Remove "61757261" prefix
# GRANDPA public key (file starting with 6772616e)
GRANDPA_FILE=$(ls 6772616e*)
GRANDPA_PUBKEY=${GRANDPA_FILE:8} # Remove "6772616e" prefix
# Concatenate (AURA + GRANDPA)
SESSION_KEYS="0x${AURA_PUBKEY}${GRANDPA_PUBKEY}"
echo $SESSION_KEYS
Step 2: Set Keys On-Chain (Polkadot.js Apps)
Using the web UI:
- Go to https://polkadot.js.org/apps
- Connect to Pilier:
- Click top-left network selector
- Custom endpoint:
wss://testnet-rpc.pilier.net(orws://127.0.0.1:9944if local)
- Navigate: Developer → Extrinsics
- Select account: Your validator rewards account (NOT session keys account)
- Select extrinsic:
session→setKeys - Parameters:
keys: Paste your session keys hex (0x1234...)proof:0x00(empty proof)
- Click Submit Transaction
- Sign with your validator account
- Wait for confirmation (block finalized)
Step 3: Set Keys On-Chain (CLI)
Using polkadot-js-api CLI:
# Install if not already installed
npm install -g @polkadot/api-cli
# Set session keys
polkadot-js-api \
--ws ws://127.0.0.1:9944 \
--seed "//Alice" \
tx.session.setKeys \
"0x1234567890abcdef..." \
"0x00"
# Replace:
# - ws://127.0.0.1:9944 → Your RPC endpoint
# - "//Alice" → Your validator account seed phrase (quoted!)
# - "0x1234..." → Your session keys hex
Step 4: Verify Keys Registered
# Query your account's next session keys
polkadot-js-api \
--ws wss://testnet-rpc.pilier.net \
query.session.nextKeys \
5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY
# Replace 5GNJq... with your validator account address
# Output (if keys registered):
{
aura: 0x1234...,
grandpa: 0x5678...
}
# If keys NOT registered:
null
Keys become active in the next session (~1 hour on testnet).
Key Rotation
Why Rotate Keys?
Rotate session keys when:
- 🔐 Security breach suspected (server compromised)
- 🔄 Regular rotation policy (every 6-12 months)
- 🖥️ Server migration (moving to new hardware)
- 🐛 Key corruption (keystore file damaged)
You do NOT need to rotate when:
- Updating node software (keys persist)
- Restarting node (keys loaded from keystore)
- Network upgrade (runtime changes don't affect keys)
Rotation Process
Step 1: Generate new keys
# Option A: Automatic (rotateKeys)
curl -H "Content-Type: application/json" \
-d '{"id":1, "jsonrpc":"2.0", "method": "author_rotateKeys", "params":[]}' \
http://127.0.0.1:9933/
# Option B: Manual (generate + insert, see above)
What happens:
- Node generates NEW keys
- Saves new keys to keystore (old keys remain for now)
- Returns new public keys
Step 2: Set new keys on-chain
# Same process as initial registration
# Use Polkadot.js Apps or CLI to call session.setKeys with new hex
Step 3: Wait for session change
Current session: Old keys still active
Next session: New keys become active (automatically)
Timeline:
├─ t=0: You submit setKeys transaction
├─ t=6s: Transaction included in block, finalized
├─ t=0-60min: Wait for next session
└─ t=60min: Session changes, new keys active
During this time: Validator continues using old keys. No downtime.
Step 4: Verify new keys active
# Check current session keys
polkadot-js-api \
--ws wss://testnet-rpc.pilier.net \
query.session.validators
# Your account should still be in the list
# Check which keys are being used (after session change)
# Monitor logs for "Using authority key: 0x..."
sudo journalctl -u pilier -f | grep "authority key"
Step 5: Clean up old keys (optional)
# After confirming new keys work for 24 hours:
# List keystore files
ls -la /var/lib/pilier/chains/pilier_testnet/keystore/
# Identify old keys (older timestamps)
# Delete old keys
sudo rm /var/lib/pilier/chains/pilier_testnet/keystore/61757261OLD_PUBLIC_KEY
sudo rm /var/lib/pilier/chains/pilier_testnet/keystore/6772616eOLD_PUBLIC_KEY
# Keep backups of old keys for 30 days (in case rollback needed)
Emergency Key Rotation
If your server is compromised:
Immediate actions:
- Stop the validator node
sudo systemctl stop pilier
- Generate new keys on a DIFFERENT server (or offline)
# Use subkey on air-gapped machine
subkey generate --scheme sr25519 # AURA
subkey generate --scheme ed25519 # GRANDPA
- Set new keys on-chain immediately
# Use Polkadot.js Apps from secure device (not compromised server)
# Call session.setKeys with new public keys
-
Provision new server (if old server compromised)
- Fresh OS installation
- Restore from clean backup
- Insert new session keys
- Start node
-
Monitor for misuse of old keys
- Check telemetry: Is another node using your old keys?
- Check block explorer: Double-signing events?
- Report to Pilier team if malicious activity detected
Expected downtime: 1-2 hours (until new keys active + new server synced)
Session Mechanics
What is a Session?
A session is a time period during which the validator set and their session keys remain constant.
Testnet sessions:
Duration: ~1 hour (configurable)
Blocks per session: ~600 (6-second blocks × 60 min)
Mainnet sessions:
Duration: ~1 hour (same)
Blocks per session: ~600
During a session:
- Validators use their registered session keys
- New validators cannot join mid-session
- Key changes take effect at next session boundary
Session Transition
What happens at session boundary:
Block #599 (end of session N):
├─ Validators using old keys
├─ Finality with old GRANDPA votes
Block #600 (start of session N+1):
├─ Node loads new session keys from chain state
├─ New validators activated (if approved)
├─ Old validators removed (if unapproved)
└─ All validators now use updated keys
Timeline:
t=3594s: Block #599 (last block of session)
t=3600s: Block #600 (first block of new session) ← Keys change here!
Querying Session Info
# Current session index
polkadot-js-api --ws wss://testnet-rpc.pilier.net query.session.currentIndex
# Output: 42 (current session number)
# Validators in current session
polkadot-js-api --ws wss://testnet-rpc.pilier.net query.session.validators
# Output: [ "5GNJq...", "5FHne...", "5FLSi..." ]
# Your queued keys (activated next session)
polkadot-js-api --ws wss://testnet-rpc.pilier.net query.session.queuedKeys
# Output: [ [ "5GNJq...", { aura: "0x...", grandpa: "0x..." } ], ... ]
Troubleshooting
Keys Not Loading
Symptom: Node starts but doesn't produce blocks.
Check logs:
sudo journalctl -u pilier -n 100 | grep -i key
# Look for:
# ✓ "Loaded keystore" → Good
# ✗ "No key found" → Problem
Solutions:
# 1. Verify keystore files exist
ls -la /var/lib/pilier/chains/pilier_testnet/keystore/
# Should see 2 files (AURA + GRANDPA)
# 2. Check file permissions
# Should be: -rw------- pilier pilier
sudo chown pilier:pilier /var/lib/pilier/chains/pilier_testnet/keystore/*
sudo chmod 600 /var/lib/pilier/chains/pilier_testnet/keystore/*
# 3. Re-insert keys
pilier-node key insert --base-path /var/lib/pilier --chain /etc/pilier/testnet.json --scheme sr25519 --suri "secret-phrase" --key-type aura
# 4. Restart node
sudo systemctl restart pilier
Wrong Keys Registered
Symptom: Set keys on-chain, but validator still not producing blocks after session change.
Diagnosis:
# Compare public keys in keystore vs on-chain
# From keystore:
cd /var/lib/pilier/chains/pilier_testnet/keystore/
ls 61757261* # AURA key file
# Extract public key from filename (everything after 61757261)
# From chain:
polkadot-js-api --ws wss://testnet-rpc.pilier.net \
query.session.nextKeys YOUR_VALIDATOR_ACCOUNT
# Do they match?
Solution if mismatch:
# Option A: Re-register correct keys
# Call session.setKeys again with correct hex
# Option B: Regenerate keys if unsure
curl -H "Content-Type: application/json" \
-d '{"id":1, "jsonrpc":"2.0", "method": "author_rotateKeys"}' \
http://127.0.0.1:9933/
# Then set keys on-chain with new result
Session Keys Lost
Symptom: Server crashed, keystore deleted.
Recovery:
If you have backup:
# Restore from encrypted backup (see "Backup Session Keys" section)
# Downtime: ~10 minutes
If you have secret phrases saved:
# Re-insert keys using secret phrases
pilier-node key insert ... --suri "saved-secret-phrase"
# Downtime: ~10 minutes
If you have NO backup:
# Must rotate keys (see "Key Rotation" section)
# Downtime: ~1 hour (until next session)
# Steps:
# 1. Generate new keys
# 2. Set on-chain
# 3. Wait for session change
Keystore File Corruption
Symptom: Node starts but crashes with "Failed to load keystore" error.
Check integrity:
# List files
ls -la /var/lib/pilier/chains/pilier_testnet/keystore/
# Check file sizes (should be 32-64 bytes)
du -b /var/lib/pilier/chains/pilier_testnet/keystore/*
# If 0 bytes or very large (>1 KB) → corrupted
Recovery:
# Delete corrupted files
sudo rm /var/lib/pilier/chains/pilier_testnet/keystore/*
# Restore from backup OR re-insert from secret phrases
# (see "Session Keys Lost" section above)
Security Best Practices
Do's ✅
- ✅ Generate keys on the validator server (or offline for max security)
- ✅ Backup keystore regularly (encrypted, offline storage)
- ✅ Save secret phrases (password manager, paper backup)
- ✅ Restrict keystore permissions (chmod 600, owner-only)
- ✅ Rotate keys periodically (every 6-12 months)
- ✅ Monitor for unauthorized use (telemetry, block explorer)
Don'ts ❌
- ❌ Never share secret phrases (via email, Slack, GitHub)
- ❌ Never commit keystore to Git (even private repos)
- ❌ Never use weak passwords for encrypted backups
- ❌ Never reuse keys across different networks (testnet ≠ mainnet)
- ❌ Never store keystore unencrypted in cloud (Dropbox, Drive)
- ❌ Never skip backups ("I'll do it later" = keys lost forever)
FAQ
Can I use the same keys on testnet and mainnet?
No! Always generate separate keys for testnet and mainnet.
Why: If testnet keys are compromised (less security), your mainnet validator remains safe.
What if someone steals my session keys?
Impact:
- ❌ They can impersonate your validator (produce blocks as you)
- ❌ You get slashed (if they cause equivocation - double-signing)
- ✅ They CANNOT steal your funds (session keys ≠ account keys)
Response:
- Immediately rotate keys (see "Emergency Key Rotation")
- Report to Pilier team (validators@pilier.net)
- Monitor for malicious blocks signed with old keys
How often should I rotate keys?
Recommended:
- Every 6 months: Routine security hygiene
- Immediately: If breach suspected
- After major incidents: Server compromised, keystore leaked
You do NOT need to rotate:
- After node software updates
- After runtime upgrades
- After restarting validator
Can I change keys without downtime?
Yes! Key rotation has zero downtime if done correctly:
- Generate new keys (old keys still active)
- Set new keys on-chain
- Wait for next session (old keys still working)
- Session changes → new keys active (seamless transition)
Total downtime: 0 seconds ✅
What if I lose my secret phrases?
If keystore files still exist:
- ✅ Node continues working (keys loaded from keystore)
- ⚠️ But you cannot restore if server crashes
If keystore files lost AND no secret phrases:
- ❌ Must rotate keys (downtime ~1 hour)
- ❌ No way to recover old keys
Lesson: Always backup both keystore files AND secret phrases!
Do session keys expire?
No, session keys do not expire. They remain valid until you rotate them.
However:
- Sessions themselves rotate (every ~1 hour)
- But your keys remain the same across sessions (until you change them)
Next Steps
After setting up session keys:
- Verify keys registered → Query
session.nextKeyson-chain - Wait for session change → Monitor logs, telemetry
- Confirm block production → Check "Imported #X" in logs
- Setup monitoring → Monitoring Guide
- Backup keys → Follow "Backup Session Keys" section above
For genesis validators:
For new validators:
Support
Session key issues?
- Forum: forum.pilier.net/validators
- Telegram: t.me/pilier_validators
- Email: validators@pilier.net
Document version: 1.0
Last updated: 2026-01-12