Skip to main content
Keypairs are fundamental to Solana development. This guide shows you how to create and manage keypairs using the Sava SDK.

Generating New Keypairs

The simplest way to create a new keypair is to generate one randomly:
import software.sava.core.accounts.Signer;
import software.sava.core.accounts.PublicKey;

// Generate a random private key
byte[] privateKey = Signer.generatePrivateKeyBytes();

// Create a signer from the private key
Signer signer = Signer.createFromPrivateKey(privateKey);

// Access the public key
PublicKey publicKey = signer.publicKey();
System.out.println("Public Key: " + publicKey.toBase58());

Generating a Full Keypair

To generate both private and public keys together:
// Generate a complete keypair (64 bytes: 32 private + 32 public)
byte[] keyPair = Signer.generatePrivateKeyPairBytes();

// Create signer from the keypair
Signer signer = Signer.createFromKeyPair(keyPair);

Loading Keypairs from Seed

If you already have a private key (for example, from a wallet file), you can load it:
import software.sava.core.encoding.Base58;

// From Base58-encoded private key
String base58PrivateKey = "your-private-key-here";
byte[] privateKey = Base58.decode(base58PrivateKey);
Signer signer = Signer.createFromPrivateKey(privateKey);

// From a 64-byte keypair array
byte[] fullKeyPair = new byte[64]; // Load from file or other source
Signer signerFromKeyPair = Signer.createFromKeyPair(fullKeyPair);

Loading Public Keys

To work with public keys without signing capabilities:
import software.sava.core.accounts.PublicKey;

// From Base58 string
PublicKey publicKey = PublicKey.fromBase58Encoded(
  "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
);

// From Base64 string
PublicKey pubKeyBase64 = PublicKey.fromBase64Encoded(
  "base64-encoded-public-key"
);

// From byte array
byte[] publicKeyBytes = new byte[32];
PublicKey pubKeyFromBytes = PublicKey.createPubKey(publicKeyBytes);

Deriving Program-Derived Addresses (PDAs)

PDAs are deterministic addresses derived from seeds and a program ID. They’re commonly used for program-owned accounts.

Finding a PDA

import software.sava.core.accounts.PublicKey;
import software.sava.core.accounts.ProgramDerivedAddress;
import java.util.List;

// Define your program ID
PublicKey programId = PublicKey.fromBase58Encoded(
  "YourProgramId111111111111111111111111111111"
);

// Define seeds for the PDA
List<byte[]> seeds = List.of(
  "metadata".getBytes(),
  userPublicKey.toByteArray(),
  "config".getBytes()
);

// Find PDA with bump seed
ProgramDerivedAddress pda = PublicKey.findProgramAddress(seeds, programId);

System.out.println("PDA: " + pda.publicKey().toBase58());
System.out.println("Bump: " + pda.nonce());

Creating a Program Address

If you already know the bump seed:
import java.util.List;

List<byte[]> seeds = List.of(
  "vault".getBytes(),
  new byte[]{255} // bump seed
);

PublicKey programAddress = PublicKey.createProgramAddress(seeds, programId);

Creating Accounts with Seeds

For deterministic addresses based on a base key and seed:
PublicKey baseKey = PublicKey.fromBase58Encoded(
  "BaseKey111111111111111111111111111111111111"
);
String seed = "vault";
PublicKey programId = PublicKey.fromBase58Encoded(
  "Program111111111111111111111111111111111111"
);

PublicKey derivedAddress = PublicKey.createWithSeed(baseKey, seed, programId);
System.out.println("Derived Address: " + derivedAddress.toBase58());

Signing Messages

Once you have a signer, you can sign transactions and messages:
Signer signer = Signer.createFromPrivateKey(privateKey);

// Sign a message
byte[] message = "Hello, Solana!".getBytes();
byte[] signature = signer.sign(message);

System.out.println("Signature length: " + signature.length); // 64 bytes

Validating Keypairs

The SDK automatically validates keypairs when creating signers:
try {
  // This will throw an exception if the keypair is invalid
  Signer.validateKeyPair(privateKey, publicKey);
  System.out.println("Keypair is valid");
} catch (IllegalStateException e) {
  System.err.println("Invalid keypair: " + e.getMessage());
}

Best Practices

Never hardcode private keys in your source code. Always load them from secure storage or environment variables.
  1. Secure Storage: Store private keys in secure locations (environment variables, key vaults, hardware wallets)
  2. Validation: Always validate keypairs when loading from external sources
  3. Memory Management: Clear sensitive key material from memory when no longer needed
  4. PDA Seeds: Use consistent seed patterns for PDAs to ensure addresses are reproducible
  5. Bump Seeds: Cache bump seeds to avoid recalculating PDAs

Thread Safety

For concurrent signing operations, create dedicated signers:
Signer baseSigner = Signer.createFromPrivateKey(privateKey);

// Create a dedicated signer for thread-safe operations
Signer dedicatedSigner = baseSigner.createDedicatedSigner();

Next Steps

Token Accounts

Work with SPL token accounts

Transactions

Learn how to build and sign transactions

Build docs developers (and LLMs) love