Skip to content

Keystore & Signatures

The Ashen keystore provides secure, encrypted storage for ed25519 signing keys. All key material is encrypted at rest using XChaCha20-Poly1305 with a password-derived key (Argon2id). There is no plaintext metadata—the entire payload requires the password to access.

Terminal window
# Initialize a new keystore
ashen keystore init
# Generate a new key
ashen keystore add --label my-validator
# List keys
ashen keystore list
# Export public address
ashen keystore export --label my-validator

Ashen uses ed25519 for all transaction signatures and validator network keys.

PropertyValue
AlgorithmEd25519 (RFC 8032)
Private Key Size32 bytes
Public Key Size32 bytes
Signature Size64 bytes
Security Level~128 bits

ed25519 provides:

  • Fast signing and verification — optimized for high-throughput transaction processing
  • Deterministic signatures — same message + key always produces the same signature
  • No side-channel leaks — constant-time operations prevent timing attacks
  • Small keys and signatures — efficient for network transmission and storage

The keystore file is encrypted using XChaCha20-Poly1305 (AEAD):

PropertyValue
CipherXChaCha20-Poly1305
Key Size256 bits
Nonce Size192 bits (24 bytes)
Auth Tag128 bits

Why XChaCha20-Poly1305:

  • Extended nonce — 192-bit nonces allow safe random generation without collision risk
  • AEAD — Authenticated Encryption with Associated Data prevents tampering
  • No weak keys — Unlike AES, ChaCha20 has no weak key classes
  • Constant-time — Immune to cache-timing attacks

Passwords are stretched into encryption keys using Argon2id, the winner of the Password Hashing Competition:

ParameterDefault ValueDescription
AlgorithmArgon2idHybrid of Argon2i and Argon2d
Memory Cost64 MB (m_cost=65536)Memory required for hashing
Time Cost3 iterationsSequential hash iterations
Parallelism2 threadsParallel computation lanes
Salt32 bytes (random)Per-keystore salt
Output32 bytesDerived encryption key

Why Argon2id:

  • Memory-hard — Requires 64 MB of RAM, making GPU/ASIC attacks expensive
  • Side-channel resistant — Argon2id hybrid mode resists timing attacks
  • Tunable — Parameters scale with hardware improvements
  • Fresh salt — Each keystore has a unique random salt

All sensitive key material is automatically zeroed from memory when:

  • The UnlockedKeystore is dropped
  • Secret key bytes go out of scope
  • The derived encryption key is released
// Internal implementation
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
struct SecretBytes(Vec<u8>);
impl Drop for DerivedKey {
fn drop(&mut self) {
self.key.zeroize(); // Overwrite with zeros
}
}

This prevents secrets from lingering in memory where they could be extracted via:

  • Core dumps
  • Memory scanners
  • Cold boot attacks
  • Swap file analysis

On Unix systems, keystore files are written with mode 0600 (owner read/write only):

Terminal window
$ ls -la ~/.local/share/ashen/keystore/keystore.json
-rw------- 1 user user 1234 Jan 22 10:00 keystore.json

Additional file safety measures:

  • Atomic writes — Changes are written to a temp file, then atomically renamed
  • File locking — Exclusive locks prevent concurrent corruption
  • Parent directory creation — Directories are created with safe defaults

The keystore is stored as JSON with the following structure:

{
"version": 1,
"kdf": {
"algorithm": "argon2id",
"salt_b64": "<base64-encoded-32-byte-salt>",
"m_cost": 65536,
"t_cost": 3,
"p_cost": 2
},
"nonce": "<base64-encoded-24-byte-nonce>",
"ciphertext": "<base64-encoded-encrypted-payload>"
}

The encrypted payload contains:

struct KeystorePayload {
version: u8, // Payload version (currently 1)
keys: Vec<StoredKey>, // All stored keys
}
struct StoredKey {
handle: [u8; 16], // Random key identifier
key_type: KeyType, // Ed25519 or BlsMinSig
label: String, // User-assigned label
created_at: u64, // Unix timestamp
secret: Vec<u8>, // Encrypted private key
public_key: Vec<u8>, // Derived public key
}

TypeEnumSizeUse Case
ed25519KeyType::Ed2551932 bytesTransaction signing, validator network keys
BLSKeyType::BlsMinSig32 bytesThreshold signatures (DKG shares)

Used for:

  • Transaction signatures — All transactions are signed with ed25519
  • Validator network identity — P2P authentication between validators
  • Account addresses — Derived from the public key

BLS12-381 keys are used for threshold cryptography:

  • Threshold signatures — 2f+1 validators sign blocks together
  • DKG shares — Distributed Key Generation produces per-validator shares
  • Sealed transactions — Threshold decryption for MEV protection

BLS keys are typically generated via DKG, not stored directly in the keystore.


Initialize a new keystore file.

Terminal window
ashen keystore init
ashen keystore init --path ~/.ashen/custom-keystore.json

You will be prompted for a password. The keystore file is created with no keys.

List all keys in the keystore.

Terminal window
ashen keystore list

Output:

Handle Type Label Created Public Key
────────────────────────────────────────────────────────────────────────────────
a1b2c3d4e5f6... ed25519 my-validator 2026-01-22 10:00:00 0x493615aa...
f6e5d4c3b2a1... ed25519 backup-key 2026-01-20 14:30:00 0xdeadbeef...

Generate a new random key.

Terminal window
# Generate ed25519 key
ashen keystore add --label my-key
# Specify key type
ashen keystore add --label my-key --type ed25519

Import an existing secret key.

Terminal window
# Import from hex
ashen keystore add --label imported --secret 0x<64-hex-chars>
# Import from file
ashen keystore add --label imported --secret @./secret.key

Export a key’s public address.

Terminal window
ashen keystore export --label my-key
# Output: 0x493615aa1e16a24f618d3ab6dd93a9250ca76e19996e46493a372c5994862e8c

Remove a key from the keystore.

Terminal window
ashen keystore remove --label my-key

Keys can be referenced in CLI commands using several formats:

FormatExampleDescription
Hex0xabcd1234...Raw 64-character hex private key
Keystore labelkeystore:my-validatorReference by label
Keystore label (short)ks:my-validatorShort form
File path@./secret.keyRead from file
Environment$ASHEN_PRIVATE_KEYEnvironment variable
Terminal window
# Using hex directly (not recommended for production)
ashen call 0xCONTRACT transfer ... --key 0xabcd1234...
# Using keystore reference (recommended)
ashen call 0xCONTRACT transfer ... --key keystore:my-validator
ashen call 0xCONTRACT transfer ... --key ks:my-validator
# Using environment variable
export ASHEN_PRIVATE_KEY="keystore:my-validator"
ashen call 0xCONTRACT transfer ...
# Validator network key
node run --validator-network-key "keystore:validator-1"

MethodFlagSecurity
Interactive prompt(default)Most secure
Stdin--keystore-password-stdinGood for automation
File--keystore-password-file /pathModerate (secure the file)
Terminal window
# Interactive (recommended)
ashen keystore list
# From stdin (for scripts)
echo "my-password" | ashen keystore list --keystore-password-stdin
# From file
ashen keystore list --keystore-password-file ~/.ashen/password
Terminal window
ashen keystore change-password

This re-encrypts all keys with the new password. The old password is required.


  • Use a strong password — At least 16 characters, mixed case, numbers, symbols
  • Backup the keystore — Store encrypted backups in multiple locations
  • Use keystore references — Never put raw private keys in command lines or scripts
  • Restrict file permissions — Ensure 0600 permissions on keystore files
  • Use separate keys — Different keys for different purposes (validator, testing, etc.)
  • Don’t commit secrets — Never put keystore files or passwords in git
  • Don’t share passwords — Each operator should have their own credentials
  • Don’t use weak passwords — Short or dictionary passwords can be brute-forced
  • Don’t disable memory zeroization — The zeroize crate is there for a reason
  • Don’t store passwords in environment — Use stdin or file references instead

The keystore protects against:

ThreatProtection
File theftAES-256 equivalent encryption (XChaCha20)
Password brute forceArgon2id with 64MB memory cost
Memory scanningAutomatic zeroization on drop
Unauthorized file accessUnix 0600 permissions
Ciphertext tamperingPoly1305 authentication tag
Timing attacksConstant-time crypto operations
Nonce reuse192-bit random nonces

The keystore does NOT protect against:

  • Compromised host — If the machine is compromised while the keystore is unlocked
  • Weak passwords — A weak password can still be brute-forced offline
  • Physical access — Cold boot attacks while the keystore is in memory

use keystore::{Keystore, KeyType};
// Open existing keystore
let ks = Keystore::new("~/.local/share/ashen/keystore/keystore.json");
let mut unlocked = ks.open("my-password")?;
// List keys
for key in unlocked.list() {
println!("{}: {} ({})", key.label, key.handle, key.key_type);
}
// Add a new key
let handle = unlocked.add_key(KeyType::Ed25519, "new-key")?;
// Sign a message
let signature = unlocked.sign_ed25519(&handle, b"ashen", b"message")?;
// Save changes
unlocked.save()?;

PlatformKeystore Location
Linux~/.local/share/ashen/keystore/keystore.json
macOS~/Library/Application Support/xyz.ashen.ashen/keystore/keystore.json
Windows%APPDATA%\ashen\ashen\keystore\keystore.json

Override with --path or NODE_KEYSTORE_PATH environment variable.