Skip to main content

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​

FieldTypeDescription
resultstringHex-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​

  1. Regular Rotation: Rotate keys periodically (e.g., monthly)
  2. Secure Storage: Never expose session keys in logs or public repos
  3. Backup Keys: Always backup keys before rotation
  4. Verify Generation: Ensure keys are properly generated before submission
  5. Monitor Activation: Track when new keys become active

Session Key Types​

Key TypePurposeAlgorithm
GRANDPAFinality votingEd25519
BABEBlock productionSr25519
ImOnlineLiveness proofSr25519
ParaValidatorParachain validationSr25519
ParaAssignmentParachain assignmentSr25519
AuthorityDiscoveryPeer discoverySr25519

Use Cases​

  1. Validator Setup: Initial validator configuration
  2. Key Rotation: Periodic security key updates
  3. Key Recovery: Generate new keys after compromise
  4. Multi-node Setup: Configure backup validator nodes
  5. 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