author_rotateKeys - JSON-RPC Method
Descriptionā
Generates a new set of session keys and returns them as a hex-encoded string. This JSON-RPC method is essential for validators to rotate their keys periodically for security. The generated keys are stored in the node's keystore and can be used to set on-chain session keys.
Parametersā
This method does not require any parameters.
Returnsā
Field | Type | Description |
---|---|---|
result | string | Hex-encoded concatenated public keys for all session key types |
Request Exampleā
{
"jsonrpc": "2.0",
"method": "author_rotateKeys",
"params": [],
"id": 1
}
Response Exampleā
{
"jsonrpc": "2.0",
"result": "0x9257c7a88f94f858a6f477743b4180f0c9471b2b1a2b0c1e1af9d7c2e2c3d4e5f6789abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456",
"id": 1
}
Code Examplesā
JavaScriptā
const rotateSessionKeys = async () => {
const response = await fetch('https://api-polkadot.n.dwellir.com', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'author_rotateKeys',
params: [],
id: 1
})
});
const data = await response.json();
return data.result;
};
// Rotate keys and prepare for on-chain update
const setupValidator = async () => {
console.log('Generating new session keys...');
const newKeys = await rotateSessionKeys();
console.log('New session keys generated:');
console.log(newKeys);
console.log('\nā ļø IMPORTANT: Save these keys securely!');
console.log('You need to submit these keys on-chain using:');
console.log('1. session.setKeys() extrinsic');
console.log('2. Include the keys and proof parameter');
return newKeys;
};
// Example: Full validator key rotation flow
const rotateValidatorKeys = async () => {
try {
// Step 1: Generate new keys
const sessionKeys = await rotateSessionKeys();
// Step 2: Parse the concatenated keys
// Keys are concatenated in order: grandpa, babe, im_online, authority_discovery
const keyLength = 32; // Each key is 32 bytes (64 hex chars)
const keys = {
grandpa: sessionKeys.slice(2, 66),
babe: sessionKeys.slice(66, 130),
imOnline: sessionKeys.slice(130, 194),
authorityDiscovery: sessionKeys.slice(194, 258)
};
console.log('Parsed session keys:');
console.log('GRANDPA:', keys.grandpa);
console.log('BABE:', keys.babe);
console.log('ImOnline:', keys.imOnline);
console.log('Authority Discovery:', keys.authorityDiscovery);
return {
raw: sessionKeys,
parsed: keys
};
} catch (error) {
console.error('Failed to rotate keys:', error);
throw error;
}
};
Pythonā
import requests
import json
from datetime import datetime
class ValidatorKeyManager:
def __init__(self, rpc_url: str, api_key: str):
self.rpc_url = rpc_url
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def rotate_keys(self) -> str:
"""Generate new session keys"""
payload = {
"jsonrpc": "2.0",
"method": "author_rotateKeys",
"params": [],
"id": 1
}
response = requests.post(
self.rpc_url,
headers=self.headers,
data=json.dumps(payload)
)
if response.status_code != 200:
raise Exception(f"RPC call failed: {response.text}")
result = response.json()
if "error" in result:
raise Exception(f"RPC error: {result['error']}")
return result["result"]
def parse_session_keys(self, keys_hex: str) -> dict:
"""Parse concatenated session keys"""
# Remove 0x prefix
keys_hex = keys_hex[2:] if keys_hex.startswith("0x") else keys_hex
# Each key is 32 bytes (64 hex characters)
key_length = 64
keys = {
"grandpa": "0x" + keys_hex[0:key_length],
"babe": "0x" + keys_hex[key_length:key_length*2],
"im_online": "0x" + keys_hex[key_length*2:key_length*3],
"authority_discovery": "0x" + keys_hex[key_length*3:key_length*4]
}
return keys
def backup_keys(self, keys: str, backup_path: str = None):
"""Backup session keys to file"""
timestamp = datetime.now().isoformat()
backup_data = {
"timestamp": timestamp,
"session_keys": keys,
"parsed": self.parse_session_keys(keys)
}
if backup_path:
with open(backup_path, 'w') as f:
json.dump(backup_data, f, indent=2)
print(f"Keys backed up to: {backup_path}")
return backup_data
def rotate_and_backup(self, backup_path: str = None):
"""Complete key rotation with backup"""
print("Rotating validator session keys...")
# Generate new keys
new_keys = self.rotate_keys()
print(f"ā
New session keys generated")
print(f"Keys: {new_keys[:20]}...{new_keys[-20:]}")
# Parse keys
parsed = self.parse_session_keys(new_keys)
print("\nParsed keys:")
for key_type, key_value in parsed.items():
print(f" {key_type}: {key_value[:10]}...{key_value[-10:]}")
# Backup if requested
if backup_path:
self.backup_keys(new_keys, backup_path)
print("\nā ļø Next steps:")
print("1. Save these keys securely")
print("2. Submit session.setKeys() extrinsic on-chain")
print("3. Wait for the next era for keys to become active")
return new_keys
# Usage
manager = ValidatorKeyManager("https://api-polkadot.n.dwellir.com", "YOUR_API_KEY")
# Rotate keys with backup
new_keys = manager.rotate_and_backup("validator_keys_backup.json")
TypeScript (@polkadot/api)ā
import { ApiPromise, WsProvider } from '@polkadot/api';
import { Keyring } from '@polkadot/keyring';
import fs from 'fs';
interface SessionKeys {
grandpa: string;
babe: string;
imOnline: string;
paraValidator: string;
paraAssignment: string;
authorityDiscovery: string;
}
class ValidatorKeyRotation {
private api: ApiPromise;
async connect(wsUrl: string) {
const provider = new WsProvider(wsUrl);
this.api = await ApiPromise.create({ provider });
}
async rotateKeys(): Promise<string> {
// Generate new session keys
const newKeys = await this.api.rpc.author.rotateKeys();
return newKeys.toHex();
}
parseSessionKeys(keysHex: string): SessionKeys {
// Remove 0x prefix
const keys = keysHex.slice(2);
// Each key is 32 bytes (64 hex chars)
// Order depends on runtime configuration
const keySize = 64;
let offset = 0;
return {
grandpa: '0x' + keys.slice(offset, offset += keySize),
babe: '0x' + keys.slice(offset, offset += keySize),
imOnline: '0x' + keys.slice(offset, offset += keySize),
paraValidator: '0x' + keys.slice(offset, offset += keySize),
paraAssignment: '0x' + keys.slice(offset, offset += keySize),
authorityDiscovery: '0x' + keys.slice(offset, offset += keySize)
};
}
async submitKeysOnChain(
sessionKeys: string,
validatorAccount: string,
proof: Uint8Array = new Uint8Array()
) {
const keyring = new Keyring({ type: 'sr25519' });
const validator = keyring.addFromUri(validatorAccount);
// Create the setKeys transaction
const tx = this.api.tx.session.setKeys(sessionKeys, proof);
// Sign and send
const hash = await tx.signAndSend(validator, ({ status, events }) => {
if (status.isInBlock) {
console.log(`Transaction included in block ${status.asInBlock}`);
// Check for errors
events.forEach(({ event }) => {
if (this.api.events.system.ExtrinsicFailed.is(event)) {
console.error('Transaction failed');
} else if (this.api.events.system.ExtrinsicSuccess.is(event)) {
console.log('ā
Session keys updated successfully');
}
});
}
});
return hash.toHex();
}
async fullKeyRotation(validatorSeed?: string) {
console.log('Starting key rotation process...');
// Step 1: Generate new keys
const newKeys = await this.rotateKeys();
console.log('ā
New session keys generated');
// Step 2: Parse keys
const parsed = this.parseSessionKeys(newKeys);
console.log('Session keys parsed:');
Object.entries(parsed).forEach(([type, key]) => {
console.log(` ${type}: ${key.slice(0, 10)}...`);
});
// Step 3: Backup keys
const backup = {
timestamp: new Date().toISOString(),
sessionKeys: newKeys,
parsed: parsed
};
fs.writeFileSync(
`session_keys_${Date.now()}.json`,
JSON.stringify(backup, null, 2)
);
console.log('ā
Keys backed up to file');
// Step 4: Submit on-chain if validator seed provided
if (validatorSeed) {
console.log('Submitting keys on-chain...');
const txHash = await this.submitKeysOnChain(newKeys, validatorSeed);
console.log(`ā
Transaction submitted: ${txHash}`);
} else {
console.log('\nš Manual submission required:');
console.log(`Session Keys: ${newKeys}`);
console.log('Submit using session.setKeys() extrinsic');
}
return newKeys;
}
async disconnect() {
await this.api.disconnect();
}
}
// Usage
async function rotateValidatorKeys() {
const rotator = new ValidatorKeyRotation();
await rotator.connect('wss://api-polkadot.n.dwellir.com');
try {
// Option 1: Just generate keys (manual submission)
const keys = await rotator.fullKeyRotation();
// Option 2: Generate and submit (requires validator seed)
// const keys = await rotator.fullKeyRotation('//Alice');
} finally {
await rotator.disconnect();
}
}
rotateValidatorKeys().catch(console.error);
Security Best Practicesā
- Regular Rotation: Rotate keys periodically (e.g., monthly)
- Secure Storage: Never expose session keys in logs or public repos
- Backup Keys: Always backup keys before rotation
- Verify Generation: Ensure keys are properly generated before submission
- Monitor Activation: Track when new keys become active
Session Key Typesā
Key Type | Purpose | Algorithm |
---|---|---|
GRANDPA | Finality voting | Ed25519 |
BABE | Block production | Sr25519 |
ImOnline | Liveness proof | Sr25519 |
ParaValidator | Parachain validation | Sr25519 |
ParaAssignment | Parachain assignment | Sr25519 |
AuthorityDiscovery | Peer discovery | Sr25519 |
Use Casesā
- Validator Setup: Initial validator configuration
- Key Rotation: Periodic security key updates
- Key Recovery: Generate new keys after compromise
- Multi-node Setup: Configure backup validator nodes
- Security Audits: Regular key rotation policy
Notesā
- Keys are generated and stored in the node's keystore
- New keys must be submitted on-chain via
session.setKeys()
- Key changes take effect in the next era/session
- Keep generated keys secure and backed up
- Node must have
--validator
flag to generate keys
Related Methodsā
author_submitExtrinsic
- Submit transactionsauthor_pendingExtrinsics
- View pending transactionssystem_properties
- Get chain properties